首页
文章打赏记录
博客统计
说说
博客APP
更多
友情链接
时间轴
精美壁纸
留言板
扶贫计划
投稿
推荐
植物大战僵尸
网站优化
高考倒计时
烟花模拟器
忘心主页
在线PS
Search
1
QQ打开链接跳转浏览器代码
7,582 阅读
2
中国疫情和全球疫情实时地图
6,427 阅读
3
利用html写APP公告、更新的文章
5,296 阅读
4
QQ卡片实现红包强制进群代码
5,221 阅读
5
2021年一款好看的高考倒计时源码
4,535 阅读
新闻资讯
技术教程
闲言碎语
小曲配故事
话题探讨
源码
站外篇
杂乱分享
登录
/
注册
Search
标签搜索
Typecho
代码
源码
swap
热歌推荐
话题
api
小程序
web
机器人
引流
Linux
新冠实时疫情
一言
备份
红包卡片
代刷
宝塔
远程公告
APP
忘心
累计撰写
107
篇文章
累计收到
161
条评论
首页
栏目
新闻资讯
技术教程
闲言碎语
小曲配故事
话题探讨
源码
站外篇
杂乱分享
页面
文章打赏记录
博客统计
说说
博客APP
友情链接
时间轴
精美壁纸
留言板
扶贫计划
投稿
推荐
植物大战僵尸
网站优化
高考倒计时
烟花模拟器
忘心主页
在线PS
搜索到
36
篇与
的结果
2020-10-23
从面试角度学完 Kafka
Kafka 是一个优秀的分布式消息中间件,许多系统中都会使用到 Kafka 来做消息通信。对分布式消息系统的了解和使用几乎成为一个后台开发人员必备的技能。今天码哥字节就从常见的 Kafka 面试题入手,和大家聊聊 Kafka 的那些事儿。讲一讲分布式消息中间件问题 什么是分布式消息中间件? 消息中间件的作用是什么? 消息中间件的使用场景是什么? 消息中间件选型? 分布式消息是一种通信机制,和 RPC、HTTP、RMI 等不一样,消息中间件采用分布式中间代理的方式进行通信。如图所示,采用了消息中间件之后,上游业务系统发送消息,先存储在消息中间件,然后由消息中间件将消息分发到对应的业务模块应用(分布式生产者 - 消费者模式)。这种异步的方式,减少了服务之间的耦合程度。定义消息中间件: 利用高效可靠的消息传递机制进行平台无关的数据交流 基于数据通信,来进行分布式系统的集成 通过提供消息传递和消息排队模型,可以在分布式环境下扩展进程间的通信 在系统架构中引用额外的组件,必然提高系统的架构复杂度和运维的难度,那么在系统中使用分布式消息中间件有什么优势呢?消息中间件在系统中起的作用又是什么呢? 解耦 冗余(存储) 扩展性 削峰 可恢复性 顺序保证 缓冲 异步通信 面试时,面试官经常会关心面试者对开源组件的选型能力,这既可以考验面试者知识的广度,也可以考验面试者对某类系统的知识的认识深度,而且也可以看出面试者对系统整体把握和系统架构设计的能力。开源分布式消息系统有很多,不同的消息系统的特性也不一样,选择怎样的消息系统,不仅需要对各消息系统有一定的了解,也需要对自身系统需求有清晰的认识。下面是常见的几种分布式消息系统的对比:答案关键字 什么是分布式消息中间件?通信,队列,分布式,生产消费者模式。 消息中间件的作用是什么? 解耦、峰值处理、异步通信、缓冲。 消息中间件的使用场景是什么? 异步通信,消息存储处理。 消息中间件选型?语言,协议、HA、数据可靠性、性能、事务、生态、简易、推拉模式。 Kafka 基本概念和架构问题 简单讲下 Kafka 的架构? Kafka 是推模式还是拉模式,推拉的区别是什么? Kafka 如何广播消息? Kafka 的消息是否是有序的? Kafka 是否支持读写分离? Kafka 如何保证数据高可用? Kafka 中 zookeeper 的作用? 是否支持事务? 分区数是否可以减少? Kafka 架构中的一般概念: Producer:生产者,也就是发送消息的一方。生产者负责创建消息,然后将其发送到 Kafka。 Consumer:消费者,也就是接受消息的一方。消费者连接到 Kafka 上并接收消息,进而进行相应的业务逻辑处理。 Consumer Group:一个消费者组可以包含一个或多个消费者。使用多分区 + 多消费者方式可以极大提高数据下游的处理速度,同一消费组中的消费者不会重复消费消息,同样的,不同消费组中的消费者消息消息时互不影响。Kafka 就是通过消费组的方式来实现消息 P2P 模式和广播模式。 Broker:服务代理节点。Broker 是 Kafka 的服务节点,即 Kafka 的服务器。 Topic:Kafka 中的消息以 Topic 为单位进行划分,生产者将消息发送到特定的 Topic,而消费者负责订阅 Topic 的消息并进行消费。 Partition:Topic 是一个逻辑的概念,它可以细分为多个分区,每个分区只属于单个主题。同一个主题下不同分区包含的消息是不同的,分区在存储层面可以看作一个可追加的日志(Log)文件,消息在被追加到分区日志文件的时候都会分配一个特定的偏移量(offset)。 Offset:offset 是消息在分区中的唯一标识,Kafka 通过它来保证消息在分区内的顺序性,不过 offset 并不跨越分区,也就是说,Kafka 保证的是分区有序性而不是主题有序性。 Replication:副本,是 Kafka 保证数据高可用的方式,Kafka 同一 Partition 的数据可以在多 Broker 上存在多个副本,通常只有主副本对外提供读写服务,当主副本所在 broker 崩溃或发生网络一场,Kafka 会在 Controller 的管理下会重新选择新的 Leader 副本对外提供读写服务。 Record: 实际写入 Kafka 中并可以被读取的消息记录。每个 record 包含了 key、value 和 timestamp。 Kafka Topic Partitions LayoutKafka 将 Topic 进行分区,分区可以并发读写。Kafka Consumer Offsetzookeeper Broker 注册:Broker 是分布式部署并且之间相互独立,Zookeeper 用来管理注册到集群的所有 Broker 节点。 Topic 注册: 在 Kafka 中,同一个 Topic 的消息会被分成多个分区并将其分布在多个 Broker 上,这些分区信息及与 Broker 的对应关系也都是由 Zookeeper 在维护 生产者负载均衡:由于同一个 Topic 消息会被分区并将其分布在多个 Broker 上,因此,生产者需要将消息合理地发送到这些分布式的 Broker 上。 消费者负载均衡:与生产者类似,Kafka 中的消费者同样需要进行负载均衡来实现多个消费者合理地从对应的 Broker 服务器上接收消息,每个消费者分组包含若干消费者,每条消息都只会发送给分组中的一个消费者,不同的消费者分组消费自己特定的 Topic 下面的消息,互不干扰。 答案关键字 简单讲下 Kafka 的架构? Producer、Consumer、Consumer Group、Topic、Partition Kafka 是推模式还是拉模式,推拉的区别是什么? Kafka Producer 向 Broker 发送消息使用 Push 模式,Consumer 消费采用的 Pull 模式。拉取模式,让 consumer 自己管理 offset,可以提供读取性能 Kafka 如何广播消息? Consumer group Kafka 的消息是否是有序的? Topic 级别无序,Partition 有序 Kafka 是否支持读写分离? 不支持,只有 Leader 对外提供读写服务 Kafka 如何保证数据高可用? 副本,ack,HW Kafka 中 zookeeper 的作用? 集群管理,元数据管理 是否支持事务? 0.11 后支持事务,可以实现”exactly once“ 分区数是否可以减少? 不可以,会丢失数据 Kafka 使用问题 Kafka 有哪些命令行工具?你用过哪些? Kafka Producer 的执行过程? Kafka Producer 有哪些常见配置? 如何让 Kafka 的消息有序? Producer 如何保证数据发送不丢失? 如何提升 Producer 的性能? 如果同一 group 下 consumer 的数量大于 part 的数量,kafka 如何处理? Kafka Consumer 是否是线程安全的? 讲一下你使用 Kafka Consumer 消费消息时的线程模型,为何如此设计? Kafka Consumer 的常见配置? Consumer 什么时候会被踢出集群? 当有 Consumer 加入或退出时,Kafka 会作何反应? 什么是 Rebalance,何时会发生 Rebalance? 命令行工具Kafka 的命令行工具在 Kafka 包的/bin目录下,主要包括服务和集群管理脚本,配置脚本,信息查看脚本,Topic 脚本,客户端脚本等。 kafka-configs.sh: 配置管理脚本 kafka-console-consumer.sh: kafka 消费者控制台 kafka-console-producer.sh: kafka 生产者控制台 kafka-consumer-groups.sh: kafka 消费者组相关信息 kafka-delete-records.sh: 删除低水位的日志文件 kafka-log-dirs.sh:kafka 消息日志目录信息 kafka-mirror-maker.sh: 不同数据中心 kafka 集群复制工具 kafka-preferred-replica-election.sh: 触发 preferred replica 选举 kafka-producer-perf-test.sh:kafka 生产者性能测试脚本 kafka-reassign-partitions.sh: 分区重分配脚本 kafka-replica-verification.sh: 复制进度验证脚本 kafka-server-start.sh: 启动 kafka 服务 kafka-server-stop.sh: 停止 kafka 服务 kafka-topics.sh:topic 管理脚本 kafka-verifiable-consumer.sh: 可检验的 kafka 消费者 kafka-verifiable-producer.sh: 可检验的 kafka 生产者 zookeeper-server-start.sh: 启动 zk 服务 zookeeper-server-stop.sh: 停止 zk 服务 zookeeper-shell.sh:zk 客户端 我们通常可以使用kafka-console-consumer.sh和kafka-console-producer.sh脚本来测试 Kafka 生产和消费,kafka-consumer-groups.sh可以查看和管理集群中的 Topic,kafka-topics.sh通常用于查看 Kafka 的消费组情况。Kafka ProducerKafka producer 的正常生产逻辑包含以下几个步骤: 配置生产者客户端参数常见生产者实例。 构建待发送的消息。 发送消息。 关闭生产者实例。 Producer 发送消息的过程如下图所示,需要经过拦截器,序列化器和分区器,最终由累加器批量发送至 Broker。Kafka Producer 需要以下必要参数: bootstrap.server: 指定 Kafka 的 Broker 的地址 key.serializer: key 序列化器 value.serializer: value 序列化器 常见参数: batch.num.messages 默认值:200,每次批量消息的数量,只对 asyc 起作用。 request.required.acks 默认值:0,0 表示 producer 毋须等待 leader 的确认,1 代表需要 leader 确认写入它的本地 log 并立即确认,-1 代表所有的备份都完成后确认。 只对 async 模式起作用,这个参数的调整是数据不丢失和发送效率的 tradeoff,如果对数据丢失不敏感而在乎效率的场景可以考虑设置为 0,这样可以大大提高 producer 发送数据的效率。 request.timeout.ms 默认值:10000,确认超时时间。 partitioner.class 默认值:kafka.producer.DefaultPartitioner,必须实现 kafka.producer.Partitioner,根据 Key 提供一个分区策略。有时候我们需要相同类型的消息必须顺序处理,这样我们就必须自定义分配策略,从而将相同类型的数据分配到同一个分区中。 producer.type 默认值:sync,指定消息发送是同步还是异步。异步 asyc 成批发送用 kafka.producer.AyncProducer, 同步 sync 用 kafka.producer.SyncProducer。同步和异步发送也会影响消息生产的效率。 compression.topic 默认值:none,消息压缩,默认不压缩。其余压缩方式还有,"gzip"、"snappy"和"lz4"。对消息的压缩可以极大地减少网络传输量、降低网络 IO,从而提高整体性能。 compressed.topics 默认值:null,在设置了压缩的情况下,可以指定特定的 topic 压缩,未指定则全部压缩。 message.send.max.retries 默认值:3,消息发送最大尝试次数。 retry.backoff.ms 默认值:300,每次尝试增加的额外的间隔时间。 topic.metadata.refresh.interval.ms 默认值:600000,定期的获取元数据的时间。当分区丢失,leader 不可用时 producer 也会主动获取元数据,如果为 0,则每次发送完消息就获取元数据,不推荐。如果为负值,则只有在失败的情况下获取元数据。 queue.buffering.max.ms 默认值:5000,在 producer queue 的缓存的数据最大时间,仅仅 for asyc。 queue.buffering.max.message 默认值:10000,producer 缓存的消息的最大数量,仅仅 for asyc。 queue.enqueue.timeout.ms 默认值:-1,0 当 queue 满时丢掉,负值是 queue 满时 block, 正值是 queue 满时 block 相应的时间,仅仅 for asyc。 Kafka ConsumerKafka 有消费组的概念,每个消费者只能消费所分配到的分区的消息,每一个分区只能被一个消费组中的一个消费者所消费,所以同一个消费组中消费者的数量如果超过了分区的数量,将会出现有些消费者分配不到消费的分区。消费组与消费者关系如下图所示:Kafka Consumer Client 消费消息通常包含以下步骤: 配置客户端,创建消费者 订阅主题 拉去消息并消费 提交消费位移 关闭消费者实例 因为 Kafka 的 Consumer 客户端是线程不安全的,为了保证线程安全,并提升消费性能,可以在 Consumer 端采用类似 Reactor 的线程模型来消费数据。Kafka consumer 参数 bootstrap.servers: 连接 broker 地址,host:port 格式。 group.id: 消费者隶属的消费组。 key.deserializer: 与生产者的key.serializer对应,key 的反序列化方式。 value.deserializer: 与生产者的value.serializer对应,value 的反序列化方式。 session.timeout.ms: coordinator 检测失败的时间。默认 10s 该参数是 Consumer Group 主动检测 (组内成员 comsummer) 崩溃的时间间隔,类似于心跳过期时间。 auto.offset.reset: 该属性指定了消费者在读取一个没有偏移量后者偏移量无效(消费者长时间失效当前的偏移量已经过时并且被删除了)的分区的情况下,应该作何处理,默认值是 latest,也就是从最新记录读取数据(消费者启动之后生成的记录),另一个值是 earliest,意思是在偏移量无效的情况下,消费者从起始位置开始读取数据。 enable.auto.commit: 否自动提交位移,如果为false,则需要在程序中手动提交位移。对于精确到一次的语义,最好手动提交位移 fetch.max.bytes: 单次拉取数据的最大字节数量 max.poll.records: 单次 poll 调用返回的最大消息数,如果处理逻辑很轻量,可以适当提高该值。 但是max.poll.records条数据需要在在 session.timeout.ms 这个时间内处理完 。默认值为 500 request.timeout.ms: 一次请求响应的最长等待时间。如果在超时时间内未得到响应,kafka 要么重发这条消息,要么超过重试次数的情况下直接置为失败。 Kafka Rebalancerebalance 本质上是一种协议,规定了一个 consumer group 下的所有 consumer 如何达成一致来分配订阅 topic 的每个分区。比如某个 group 下有 20 个 consumer,它订阅了一个具有 100 个分区的 topic。正常情况下,Kafka 平均会为每个 consumer 分配 5 个分区。这个分配的过程就叫 rebalance。什么时候 rebalance?这也是经常被提及的一个问题。rebalance 的触发条件有三种: 组成员发生变更(新 consumer 加入组、已有 consumer 主动离开组或已有 consumer 崩溃了——这两者的区别后面会谈到) 订阅主题数发生变更 订阅主题的分区数发生变更 如何进行组内分区分配?Kafka 默认提供了两种分配策略:Range 和 Round-Robin。当然 Kafka 采用了可插拔式的分配策略,你可以创建自己的分配器以实现不同的分配策略。答案关键字 Kafka 有哪些命令行工具?你用过哪些?/bin目录,管理 kafka 集群、管理 topic、生产和消费 kafka Kafka Producer 的执行过程?拦截器,序列化器,分区器和累加器 Kafka Producer 有哪些常见配置?broker 配置,ack 配置,网络和发送参数,压缩参数,ack 参数 如何让 Kafka 的消息有序?Kafka 在 Topic 级别本身是无序的,只有 partition 上才有序,所以为了保证处理顺序,可以自定义分区器,将需顺序处理的数据发送到同一个 partition Producer 如何保证数据发送不丢失?ack 机制,重试机制 如何提升 Producer 的性能?批量,异步,压缩 如果同一 group 下 consumer 的数量大于 part 的数量,kafka 如何处理?多余的 Part 将处于无用状态,不消费数据 Kafka Consumer 是否是线程安全的?不安全,单线程消费,多线程处理 讲一下你使用 Kafka Consumer 消费消息时的线程模型,为何如此设计?拉取和处理分离 Kafka Consumer 的常见配置?broker, 网络和拉取参数,心跳参数 Consumer 什么时候会被踢出集群?奔溃,网络异常,处理时间过长提交位移超时 当有 Consumer 加入或退出时,Kafka 会作何反应?进行 Rebalance 什么是 Rebalance,何时会发生 Rebalance?topic 变化,consumer 变化 高可用和性能问题 Kafka 如何保证高可用? Kafka 的交付语义? Replic 的作用? 什么事 AR,ISR? Leader 和 Flower 是什么? Kafka 中的 HW、LEO、LSO、LW 等分别代表什么? Kafka 为保证优越的性能做了哪些处理? 分区与副本在分布式数据系统中,通常使用分区来提高系统的处理能力,通过副本来保证数据的高可用性。多分区意味着并发处理的能力,这多个副本中,只有一个是 leader,而其他的都是 follower 副本。仅有 leader 副本可以对外提供服务。 多个 follower 副本通常存放在和 leader 副本不同的 broker 中。通过这样的机制实现了高可用,当某台机器挂掉后,其他 follower 副本也能迅速”转正“,开始对外提供服务。为什么 follower 副本不提供读服务?这个问题本质上是对性能和一致性的取舍。试想一下,如果 follower 副本也对外提供服务那会怎么样呢?首先,性能是肯定会有所提升的。但同时,会出现一系列问题。类似数据库事务中的幻读,脏读。 比如你现在写入一条数据到 kafka 主题 a,消费者 b 从主题 a 消费数据,却发现消费不到,因为消费者 b 去读取的那个分区副本中,最新消息还没写入。而这个时候,另一个消费者 c 却可以消费到最新那条数据,因为它消费了 leader 副本。Kafka 通过 WH 和 Offset 的管理来决定 Consumer 可以消费哪些数据,已经当前写入的数据。只有 Leader 可以对外提供读服务,那如何选举 Leaderkafka 会将与 leader 副本保持同步的副本放到 ISR 副本集合中。当然,leader 副本是一直存在于 ISR 副本集合中的,在某些特殊情况下,ISR 副本中甚至只有 leader 一个副本。 当 leader 挂掉时,kakfa 通过 zookeeper 感知到这一情况,在 ISR 副本中选取新的副本成为 leader,对外提供服务。 但这样还有一个问题,前面提到过,有可能 ISR 副本集合中,只有 leader,当 leader 副本挂掉后,ISR 集合就为空,这时候怎么办呢?这时候如果设置 unclean.leader.election.enable 参数为 true,那么 kafka 会在非同步,也就是不在 ISR 副本集合中的副本中,选取出副本成为 leader。副本的存在就会出现副本同步问题Kafka 在所有分配的副本 (AR) 中维护一个可用的副本列表 (ISR),Producer 向 Broker 发送消息时会根据ack配置来确定需要等待几个副本已经同步了消息才相应成功,Broker 内部会ReplicaManager服务来管理 flower 与 leader 之间的数据同步。性能优化 partition 并发 顺序读写磁盘 page cache:按页读写 预读:Kafka 会将将要消费的消息提前读入内存 高性能序列化(二进制) 内存映射 无锁 offset 管理:提高并发能力 Java NIO 模型 批量:批量读写 压缩:消息压缩,存储压缩,减小网络和 IO 开销 Partition 并发一方面,由于不同 Partition 可位于不同机器,因此可以充分利用集群优势,实现机器间的并行处理。另一方面,由于 Partition 在物理上对应一个文件夹,即使多个 Partition 位于同一个节点,也可通过配置让同一节点上的不同 Partition 置于不同的 disk drive 上,从而实现磁盘间的并行处理,充分发挥多磁盘的优势。顺序读写Kafka 每一个 partition 目录下的文件被平均切割成大小相等(默认一个文件是 500 兆,可以手动去设置)的数据文件,每一个数据文件都被称为一个段(segment file), 每个 segment 都采用 append 的方式追加数据。答案关键字 Kafka 如何保证高可用? 通过副本来保证数据的高可用,producer ack、重试、自动 Leader 选举,Consumer 自平衡 Kafka 的交付语义? 交付语义一般有at least once、at most once和exactly once。kafka 通过 ack 的配置来实现前两种。 Replic 的作用? 实现数据的高可用 什么是 AR,ISR? AR:Assigned Replicas。AR 是主题被创建后,分区创建时被分配的副本集合,副本个 数由副本因子决定。ISR:In-Sync Replicas。Kafka 中特别重要的概念,指代的是 AR 中那些与 Leader 保 持同步的副本集合。在 AR 中的副本可能不在 ISR 中,但 Leader 副本天然就包含在 ISR 中。关于 ISR,还有一个常见的面试题目是如何判断副本是否应该属于 ISR。目前的判断 依据是:Follower 副本的 LEO 落后 Leader LEO 的时间,是否超过了 Broker 端参数 replica.lag.time.max.ms 值。如果超过了,副本就会被从 ISR 中移除。 Leader 和 Flower 是什么? Kafka 中的 HW 代表什么? 高水位值 (High watermark)。这是控制消费者可读取消息范围的重要字段。一 个普通消费者只能“看到”Leader 副本上介于 Log Start Offset 和 HW(不含)之间的 所有消息。水位以上的消息是对消费者不可见的。 Kafka 为保证优越的性能做了哪些处理? partition 并发、顺序读写磁盘、page cache 压缩、高性能序列化(二进制)、内存映射 无锁 offset 管理、Java NIO 模型 本文并没有深入 Kafka 的实现细节和源码分析,但 Kafka 确实是一个 优秀的开源系统,很多优雅的架构设计和源码设计都值得我们学习,十分建议感兴趣的同学更加深入的去了解一下这个开源系统,对于自身架构设计能力,编码能力,性能优化都会有很大的帮助。推荐阅读以下几篇文章阅读量与读者反馈都很好,推荐大家阅读: 数据库系统设计概述 不可不知的软件架构模式 Tomcat 架构原理解析到架构设计借鉴 Tomcat 高并发之道原理拆解与性能调优
2020年10月23日
707 阅读
0 评论
0 点赞
2020-10-23
手写一个HTTP框架:两个类实现基本的IoC功能
jsoncat: 仿 Spring Boot 但不同于 Spring Boot 的一个轻量级的 HTTP 框架国庆节的时候,我就已经把 jsoncat 的 IoC 功能给写了,具体可以看这篇文章《手写“SpringBoot”近况:IoC模块已经完成》 。今天这篇文章就来简单分享一下自己写 IoC 的思路与具体的代码实现。IoC (Inverse of Control:控制反转) 和 AOP(Aspect-Oriented Programming:面向切面编程) 可以说是 Spring 框架提供的最核心的两个功能。但凡是了解过 Spring 的小伙伴,那肯定对这个两个概念非常非常了解。不了解的小伙伴,可以查看《面试被问了几百遍的 IoC 和 AOP ,还在傻傻搞不清楚?》这篇通俗易懂的文章。考虑到这篇文章要手写 Spring 框架的 IoC 功能,所以,我这里还是简单介绍一下 IoC 。如果你不太清楚 IoC 这个概念,一定要搞懂之后再看后面具体的代码实现环节。IoC 介绍IoC(Inverse of Control:控制反转)是一种设计思想,也就是 将原本在程序中手动创建对象的控制权交由Spring框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。IoC 容器IoC 容器是用来实现 IoC 的载体,被管理的对象就被存放在IoC容器中。IoC 容器在 Spring 中实际上就是个Map(key,value),Map 中存放了各种被管理的对象。IoC 解决了什么问题将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。IoC 和 DI 别再傻傻分不清楚IoC(Inverse of Control:控制反转)是一种设计思想 或者说是某种模式。这个设计思想就是 将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种被管理的对象。IoC 最常见以及最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI)。并且,老马(Martin Fowler)在一篇文章中提到将 IoC 改名为 DI,原文如下,原文地址:https://martinfowler.com/articles/injection.html 。IoC实现思路注意 :以下思路未涉及解决循环依赖的问题!开始代码实现之前,我们先简单聊聊实现 IoC 的思路,搞清楚了思路之后,实现起来就非常简单了。 扫描指定包下的特定注解比如@Component标记的类,并将这些类保存起来。 遍历所有被特定注解比如@Component标记的类,然后将这些类通过反射实例化并通过一个 Map 保存起来,Map 的 key 为类名,value为类对象。 再一次遍历所有被特定注解比如@Component标记的类,并获取类中所有的字段,如果类被 @Autowired 注解标记的话,就进行第 4 步。 通过字段名 key,从bean容器中获取对应的对象 value。 判断获取到的对象是否为接口。如果是接口的话,需要获取接口对应的实现类,然后再将指定的实现类的实例化对象通过反射赋值给指定对象。如果不是接口的话,就直接将获取到的对象通过反射赋值给指定对象。 IoC 实现核心代码核心注解@Autowired :注解对象@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { } @Component :声明对象被IoC容器管理 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Component { String name() default ""; } @Qualifier: 指定注入的bean(当接口有多个实现类的时候需要使用)@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Qualifier { String value() default ""; } 工具类简单封装一个反射工具类。工具类包含3个后面会用到的方法: scanAnnotatedClass() :扫描指定包下的被指定注解标记的类(使用Reflections这个反射框架一行代码即可解决扫描获取指定注解的类)。 newInstance() : 传入 Class 即可返回 Class 对应的对象。 setField() :为对象的指定字段赋值。 @Slf4j public class ReflectionUtil { /** * scan the classes marked by the specified annotation in the specified package * * @param packageName specified package name * @param annotation specified annotation * @return the classes marked by the specified annotation in the specified package */ public static Set> annotatedClass = reflections.getTypesAnnotatedWith(annotation, true); log.info("The number of class Annotated with @RestController :[{}]", annotatedClass.size()); return annotatedClass; } /** * create object instance through class * * @param cls target class * @return object created by the target class */ public static Object newInstance(Class cls) { Object instance = null; try { instance = cls.newInstance(); } catch (InstantiationException | IllegalAccessException e) { log.error("new instance failed", e); } return instance; } /** * set the value of a field in the object * * @param obj target object * @param field target field * @param value the value assigned to the field */ public static void setField(Object obj, Field field, Object value) { field.setAccessible(true); try { field.set(obj, value); } catch (IllegalAccessException e) { log.error("set field failed", e); e.printStackTrace(); } } } 根据实现思路写代码注意 :以下代码未涉及解决循环依赖的问题!以下是 IoC 实现的核心代码,完整代码地址:https://github.com/Snailclimb/jsoncat 。1.扫描指定包下的特定注解比如@Component标记的类,并将这些类保存起来。扫描指定注解@RestController和@Component并保存起来:public class ClassFactory { public static final Map>> CLASSES = new ConcurrentHashMap(); //1.扫描指定包下的特定注解比如`@Component`标记的类,并将这些类保存起来 public static void loadClass(String packageName) { Set> componentSets = ReflectionUtil.scanAnnotatedClass(packageName, Component.class); CLASSES.put(RestController.class, restControllerSets); CLASSES.put(Component.class, componentSets); } } 2.遍历所有被特定注解比如@Component标记的类,然后将这些类通过反射实例化并通过一个 Map 保存起来,Map 的 key 为类名,value为类对象。public final class BeanFactory { public static final Map BEANS = new ConcurrentHashMap(128); public static void loadBeans() { // 2.遍历所有被特定注解比如 @Component 标记的类,然后将这些类通过反射实例化并通过一个 Map 保存起来,Map 的 key 为类名,value为类对象 ClassFactory.CLASSES.forEach((annotation, classes) -> { if (annotation == Component.class) { //将bean实例化, 并放入bean容器中 for (Class aClass : classes) { Component component = aClass.getAnnotation(Component.class); String beanName = "".equals(component.name()) ? aClass.getName() : component.name(); Object obj = ReflectionUtil.newInstance(aClass); BEANS.put(beanName, obj); } } if (annotation == RestController.class) { for (Class aClass : classes) { Object obj = ReflectionUtil.newInstance(aClass); BEANS.put(aClass.getName(), obj); } } }); } } 3.再一次遍历所有被特定注解比如@Component标记的类,并获取类中所有的字段,如果类被 @Autowired 注解标记的话,就进行第 4 步。public class DependencyInjection { public static void dependencyInjection(String packageName) { Map beans = BeanFactory.BEANS; if (beans.size() == 0) return; //3.再一次遍历所有被特定注解比如 @Component 标记的类,并获取类中所有的字段,如果类被 `@Autowired` 注解标记的话,就进行第 4 步。 // 3.1.遍历bean容器中的所有对象 beans.values().forEach(bean -> { // 3.2.获取对象所属的类声明的所有字段/属性 Field[] beanFields = bean.getClass().getDeclaredFields(); if (beanFields.length == 0) return; //3.3.遍历对象所属的类声明的所有字段/属性 for (Field beanField : beanFields) { //3.4.判断字段是否被 @Autowired 注解标记 if (beanField.isAnnotationPresent(Autowired.class)) { //4.通过字段名 key,从bean容器中获取对应的对象 value。 //4.1.字段对应的类型 Class beanFieldClass = beanField.getType(); //4.2.字段对应的类名 String beanName = beanFieldClass.getName(); if (beanFieldClass.isAnnotationPresent(Component.class)) { Component component = beanFieldClass.getAnnotation(Component.class); beanName = "".equals(component.name()) ? beanFieldClass.getName() : component.name(); } //4.3.从bean容器中获取对应的对象 Object beanFieldInstance = beans.get(beanName); //5.判断获取到的对象是否为接口。如果是接口的话,需要获取接口对应的实现类,然后再将指定的实现类的实例化对象通过反射赋值给指定对象。如果不是接口的话,就直接将获取到的对象通过反射赋值给指定对象。 if (beanFieldClass.isInterface()) { //如果是接口,获取接口对应的实现类 Set aClass = subClasses.iterator().next(); beanFieldInstance = ReflectionUtil.newInstance(aClass); } //实现类多与一个的话,根据 Qualifier 注解的值获取 if (subClasses.size() > 1) { Class aClass = subClasses.iterator().next(); Qualifier qualifier = beanField.getDeclaredAnnotation(Qualifier.class); beanName = qualifier == null ? aClass.getName() : qualifier.value(); beanFieldInstance = beans.get(beanName); } } // 如果最后获取到的字段对象为null,就抛出异常 if (beanFieldInstance == null) { throw new CanNotDetermineTargetBeanException("can not determine target bean"); } //通过反射设置指定对象中的指定字段的值 ReflectionUtil.setField(bean, beanField, beanFieldInstance); } } }); } /** * 获取接口对应的实现类 */ @SuppressWarnings("unchecked") public static Set
2020年10月23日
702 阅读
0 评论
0 点赞
2020-10-22
Kubernetes K8S之存储ConfigMap详解
K8S之存储ConfigMap概述与说明,并详解常用ConfigMap示例 主机配置规划 服务器名称(hostname) 系统版本 配置 内网IP 外网IP(模拟) k8s-master CentOS7.7 2C/4G/20G 172.16.1.110 10.0.0.110 k8s-node01 CentOS7.7 2C/4G/20G 172.16.1.111 10.0.0.111 k8s-node02 CentOS7.7 2C/4G/20G 172.16.1.112 10.0.0.112 ConfigMap概述ConfigMap 是一种 API 对象,用来将非机密性的数据保存到健值对中。使用时可以用作环境变量、命令行参数或者存储卷中的配置文件。ConfigMap 将环境配置信息和容器镜像解耦,便于应用配置的修改。当你需要储存机密信息时可以使用 Secret 对象。备注:ConfigMap 并不提供保密或者加密功能。如果你想存储的数据是机密的,请使用 Secret;或者使用其他第三方工具来保证数据的私密性,而不是用 ConfigMap。 ConfigMap创建方式通过目录创建配置文件目录1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# ll /root/k8s_practice/storage/configmap # 配置文件存在哪个目录下4 total 8 5 -rw-r--r-- 1 root root 159 Jun 7 14:52game.properties6 -rw-r--r-- 1 root root 83 Jun 7 14:53ui.properties7 [root@k8s-master storage]#8 [root@k8s-master storage]# cat configmap/game.properties # 涉及文件19 enemies=aliens10 lives=3 11 enemies.cheat=true 12 enemies.cheat.level=noGoodRotten13 secret.code.passphrase=UUDDLRLRBABAs14 secret.code.allowed=true 15 secret.code.lives=30 16 17 [root@k8s-master storage]#18 [root@k8s-master storage]# cat configmap/ui.properties # 涉及文件219 color.good=purple20 color.bad=yellow21 allow.textmode=true 22 how.nice.to.look=fairlyNice 创建ConfigMap并查看状态1 [root@k8s-master storage]# kubectl create configmap game-config --from-file=/root/k8s_practice/storage/configmap2 configmap/game-config created3 [root@k8s-master storage]#4 [root@k8s-master storage]# kubectl get configmap5 NAME DATA AGE6 game-config 2 14s 查看ConfigMap有哪些数据1 [root@k8s-master storage]# kubectl get configmap -o yaml ##### 查看方式12 apiVersion: v13 items:4 -apiVersion: v15 data:6 game.properties: |+ ##### 本段最后有一行空格,+表示保留字符串行末尾的换行7 enemies=aliens8 lives=3 9 enemies.cheat=true 10 enemies.cheat.level=noGoodRotten11 secret.code.passphrase=UUDDLRLRBABAs12 secret.code.allowed=true 13 secret.code.lives=30 14 15 ui.properties: | 16 color.good=purple17 color.bad=yellow18 allow.textmode=true 19 how.nice.to.look=fairlyNice20 kind: ConfigMap21 metadata:22 creationTimestamp: "2020-06-07T06:57:28Z" 23 name: game-config24 namespace: default25 resourceVersion: "889177" 26 selfLink: /api/v1/namespaces/default/configmaps/game-config27 uid: 6952ac85-ded0-4c5e-89fd-b0c6f0546ecf28 kind: List29 metadata:30 resourceVersion: "" 31 selfLink: "" 32 [root@k8s-master storage]#33 [root@k8s-master storage]# kubectl describe configmap game-config ##### 查看方式234 Name: game-config35 Namespace: default36 Labels: 37 Annotations: 38 39 Data40 ==== 41 game.properties:42 ---- 43 enemies=aliens44 lives=3 45 enemies.cheat=true 46 enemies.cheat.level=noGoodRotten47 secret.code.passphrase=UUDDLRLRBABAs48 secret.code.allowed=true 49 secret.code.lives=30 50 51 52 ui.properties:53 ---- 54 color.good=purple55 color.bad=yellow56 allow.textmode=true 57 how.nice.to.look=fairlyNice58 59 Events: 通过文件创建配置文件位置1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# cat /root/k8s_practice/storage/configmap/game.properties4 enemies=aliens5 lives=3 6 enemies.cheat=true 7 enemies.cheat.level=noGoodRotten8 secret.code.passphrase=UUDDLRLRBABAs9 secret.code.allowed=true 10 secret.code.lives=30 创建ConfigMap并查看状态1 [root@k8s-master storage]# kubectl create configmap game-config-2 --from-file=/root/k8s_practice/storage/configmap/game.properties2 configmap/game-config-2created3 [root@k8s-master storage]#4 [root@k8s-master storage]# kubectl get configmap game-config-2 5 NAME DATA AGE6 game-config-2 1 29s 查看ConfigMap有哪些数据1 [root@k8s-master storage]# kubectl get configmap game-config-2 -o yaml ##### 查看方式12 apiVersion: v13 data:4 game.properties: |+ ##### 本段最后有一行空格,+表示保留字符串行末尾的换行5 enemies=aliens6 lives=3 7 enemies.cheat=true 8 enemies.cheat.level=noGoodRotten9 secret.code.passphrase=UUDDLRLRBABAs10 secret.code.allowed=true 11 secret.code.lives=30 12 13 kind: ConfigMap14 metadata:15 creationTimestamp: "2020-06-07T07:05:47Z" 16 name: game-config-2 17 namespace: default18 resourceVersion: "890437" 19 selfLink: /api/v1/namespaces/default/configmaps/game-config-2 20 uid: 02d99802-c23f-45ad-b4e1-dea9bcb166d821 [root@k8s-master storage]#22 [root@k8s-master storage]# kubectl describe configmap game-config-2##### 查看方式223 Name: game-config-2 24 Namespace: default25 Labels: 26 Annotations: 27 28 Data29 ==== 30 game.properties:31 ---- 32 enemies=aliens33 lives=3 34 enemies.cheat=true 35 enemies.cheat.level=noGoodRotten36 secret.code.passphrase=UUDDLRLRBABAs37 secret.code.allowed=true 38 secret.code.lives=30 39 40 41 Events: 通过命令行创建创建ConfigMap并查看状态1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# kubectl create configmap special-config --from-literal=special.how=very --from-literal="special.type=charm" 4 configmap/special-config created5 [root@k8s-master storage]#6 [root@k8s-master storage]# kubectl get configmap special-config7 NAME DATA AGE8 special-config 2 23s 查看ConfigMap有哪些数据1 [root@k8s-master storage]# kubectl get configmap special-config -o yaml ##### 查看方式12 apiVersion: v13 data:4 special.how: very5 special.type: charm6 kind: ConfigMap7 metadata:8 creationTimestamp: "2020-06-07T09:32:04Z" 9 name: special-config10 namespace: default11 resourceVersion: "912702" 12 selfLink: /api/v1/namespaces/default/configmaps/special-config13 uid: 76698e78-1380-4826-b5ac-d9c81f746eac14 [root@k8s-master storage]#15 [root@k8s-master storage]# kubectl describe configmap special-config ##### 查看方式216 Name: special-config17 Namespace: default18 Labels: 19 Annotations: 20 21 Data22 ==== 23 special.how:24 ---- 25 very26 special.type:27 ---- 28 charm29 Events: 通过yaml文件创建yaml文件1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# catconfigmap.yaml4 apiVersion: v15 kind: ConfigMap6 metadata:7 name: configmap-demo8 data:9 # 类属性键;每一个键都映射到一个简单的值10 player_initial_lives: "3" 11 ui_properties_file_name: 'user-interface.properties' 12 #13 # 类文件键14 game.properties: | 15 enemy.types=aliens,monsters16 player.maximum-lives=5 17 user-interface.properties: | 18 color.good=purple19 color.bad=yellow20 allow.textmode=true 创建ConfigMap并查看状态1 [root@k8s-master storage]# kubectl apply -f configmap.yaml2 configmap/configmap-demo created3 [root@k8s-master storage]# kubectl get configmap configmap-demo4 NAME DATA AGE5 configmap-demo 4 2m59s 查看ConfigMap有哪些数据1 [root@k8s-master storage]# kubectl get configmap configmap-demo -o yaml ##### 查看方式12 apiVersion: v13 data:4 game.properties: | 5 enemy.types=aliens,monsters6 player.maximum-lives=5 7 player_initial_lives: "3" 8 ui_properties_file_name: user-interface.properties9 user-interface.properties: | 10 color.good=purple11 color.bad=yellow12 allow.textmode=true 13 kind: ConfigMap14 metadata:15 annotations:16 kubectl.kubernetes.io/last-applied-configuration: | 17 {"apiVersion":"v1","data":{"game.properties":"enemy.types=aliens,monsters\nplayer.maximum-lives=5\n","player_initial_lives":"3","ui_properties_file_name":"user-interface.properties","user-interface.properties":"color.good=purple\ncolor.bad=yellow\nallow.textmode=true\n"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"configmap-demo","namespace":"default"}}18 creationTimestamp: "2020-06-07T11:36:46Z" 19 name: configmap-demo20 namespace: default21 resourceVersion: "931685" 22 selfLink: /api/v1/namespaces/default/configmaps/configmap-demo23 uid: fdad7000-87bd-4b72-be98-40dd8fe6400a24 [root@k8s-master storage]#25 [root@k8s-master storage]#26 [root@k8s-master storage]# kubectl describe configmap configmap-demo ##### 查看方式227 Name: configmap-demo28 Namespace: default29 Labels: 30 Annotations: kubectl.kubernetes.io/last-applied-configuration:31 {"apiVersion":"v1","data":{"game.properties":"enemy.types=aliens,monsters\nplayer.maximum-lives=5\n","player_initial_lives":"3","ui_proper... 32 33 Data34 ==== 35 game.properties:36 ---- 37 enemy.types=aliens,monsters38 player.maximum-lives=5 39 40 player_initial_lives:41 ---- 42 3 43 ui_properties_file_name:44 ---- 45 user-interface.properties46 user-interface.properties:47 ---- 48 color.good=purple49 color.bad=yellow50 allow.textmode=true 51 52 Events: Pod中使用ConfigMap如何在Pod中使用上述的ConfigMap信息。当前存在的ConfigMap1 [root@k8s-master storage]# kubectl get configmap2 NAME DATA AGE3 configmap-demo 430m4 game-config 25h9m5 game-config-2 15h1m6 special-config 2 5m48s 使用ConfigMap来替代环境变量yaml文件1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# catpod_configmap_env.yaml4 apiVersion: v15 kind: Pod6 metadata:7 name: pod-configmap-env 8 spec:9 containers:10 -name: myapp11 image: registry.cn-beijing.aliyuncs.com/google_registry/myapp:v112 command: ["/bin/sh", "-c", "env"]13 ### 引用方式114 env:15 -name: SPECAIL_HOW_KEY16 valueFrom:17 configMapKeyRef:18 name: special-config ### 这个name的值来自 ConfigMap19 key: special.how ### 这个key的值为需要取值的键20 -name: SPECAIL_TPYE_KEY21 valueFrom:22 configMapKeyRef:23 name: special-config24 key: special.type25 ### 引用方式226 envFrom:27 -configMapRef:28 name: game-config-2### 这个name的值来自 ConfigMap29 restartPolicy: Never 启动pod并查看状态1 [root@k8s-master storage]# kubectl apply -f pod_configmap_env.yaml2 pod/pod-configmap-envcreated3 [root@k8s-master storage]#4 [root@k8s-master storage]# kubectl get pod -o wide5 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES6 pod-configmap-env 0/1 Completed 0 6s 10.244.2.147 k8s-node02 查看打印日志1 [root@k8s-master storage]# kubectl logs pod-configmap-env 2 MYAPP_SVC_PORT_80_TCP_ADDR=10.98.57.156 3 KUBERNETES_SERVICE_PORT=443 4 KUBERNETES_PORT=tcp://10.96.0.1:443 5 MYAPP_SVC_PORT_80_TCP_PORT=80 6 HOSTNAME=pod-configmap-env 7 SHLVL=1 8 MYAPP_SVC_PORT_80_TCP_PROTO=tcp9 HOME=/root10 SPECAIL_HOW_KEY=very ### 来自ConfigMap11 game.properties=enemies=aliens ### 来自ConfigMap12 lives=3### 来自ConfigMap13 enemies.cheat=true### 来自ConfigMap14 enemies.cheat.level=noGoodRotten ### 来自ConfigMap15 secret.code.passphrase=UUDDLRLRBABAs ### 来自ConfigMap16 secret.code.allowed=true### 来自ConfigMap17 secret.code.lives=30### 来自ConfigMap18 19 20 SPECAIL_TPYE_KEY=charm ### 来自ConfigMap21 MYAPP_SVC_PORT_80_TCP=tcp://10.98.57.156:80 22 NGINX_VERSION=1.12.2 23 KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1 24 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin25 KUBERNETES_PORT_443_TCP_PORT=443 26 KUBERNETES_PORT_443_TCP_PROTO=tcp27 MYAPP_SVC_SERVICE_HOST=10.98.57.156 28 KUBERNETES_SERVICE_PORT_HTTPS=443 29 KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443 30 PWD=/ 31 KUBERNETES_SERVICE_HOST=10.96.0.1 32 MYAPP_SVC_SERVICE_PORT=80 33 MYAPP_SVC_PORT=tcp://10.98.57.156:80 使用ConfigMap设置命令行参数yaml文件1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# catpod_configmap_cmd.yaml4 apiVersion: v15 kind: Pod6 metadata:7 name: pod-configmap-cmd8 spec:9 containers:10 -name: myapp11 image: registry.cn-beijing.aliyuncs.com/google_registry/myapp:v112 command: ["/bin/sh", "-c", "echo \"===$(SPECAIL_HOW_KEY)===$(SPECAIL_TPYE_KEY)===\""]13 env:14 -name: SPECAIL_HOW_KEY15 valueFrom:16 configMapKeyRef:17 name: special-config18 key: special.how19 -name: SPECAIL_TPYE_KEY20 valueFrom:21 configMapKeyRef:22 name: special-config23 key: special.type24 restartPolicy: Never 启动pod并查看状态1 [root@k8s-master storage]# kubectl apply -f pod_configmap_cmd.yaml2 pod/pod-configmap-cmd created3 [root@k8s-master storage]#4 [root@k8s-master storage]# kubectl get pod -o wide5 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES6 pod-configmap-cmd 0/1 Completed 0 5s 10.244.4.125 k8s-node01 查看打印日志1 [root@k8s-master storage]# kubectl logs pod-configmap-cmd2 ===very===charm=== 通过数据卷插件使用ConfigMap【推荐】在数据卷里面使用ConfigMap,最基本的就是将文件填入数据卷,在这个文件中,键就是文件名【第一层级的键】,键值就是文件内容。yaml文件1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# catpod_configmap_volume.yaml4 apiVersion: v15 kind: Pod6 metadata:7 name: pod-configmap-volume8 spec:9 containers:10 -name: myapp11 image: registry.cn-beijing.aliyuncs.com/google_registry/myapp:v112 #command: ["/bin/sh", "-c", "ls -l /etc/config/"]13 command: ["/bin/sh", "-c", "sleep 600"]14 volumeMounts:15 - name: config-volume16 mountPath: /etc/config17 volumes:18 - name: config-volume19 configMap:20 name: configmap-demo21 restartPolicy: Never 启动pod并查看状态1 [root@k8s-master storage]# kubectl apply -f pod_configmap_volume.yaml2 pod/pod-configmap-volume created3 [root@k8s-master storage]#4 [root@k8s-master storage]# kubectl get pod -o wide5 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES6 pod-configmap-volume 1/1 Running 0 5s 10.244.2.153 k8s-node02 进入pod并查看1 [root@k8s-master storage]# kubectl exec -it pod-configmap-volume sh 2 / # ls /etc/config3 game.properties player_initial_lives ui_properties_file_name user-interface.properties4 /#5 /#6 /#7 / # cat /etc/config/player_initial_lives8 3/#9 /#10 /#11 / # cat /etc/config/ui_properties_file_name12 user-interface.properties/#13 /#14 /#15 / # cat /etc/config/game.properties16 enemy.types=aliens,monsters17 player.maximum-lives=5 18 /#19 /#20 / # cat /etc/config/user-interface.properties21 color.good=purple22 color.bad=yellow23 allow.textmode=true ConfigMap热更新准备工作yaml文件1 [root@k8s-master storage]# pwd 2 /root/k8s_practice/storage3 [root@k8s-master storage]# catpod_configmap_hot.yaml4 apiVersion: v15 kind: ConfigMap6 metadata:7 name: log-config8 namespace: default9 data:10 log_level: INFO11 --- 12 apiVersion: apps/v113 kind: Deployment14 metadata:15 name: myapp-deploy16 namespace: default17 spec:18 replicas: 2 19 selector:20 matchLabels:21 app: myapp22 release: v123 template:24 metadata:25 labels:26 app: myapp27 release: v128 env: test29 spec:30 containers:31 -name: myapp32 image: registry.cn-beijing.aliyuncs.com/google_registry/myapp:v133 imagePullPolicy: IfNotPresent34 ports:35 - containerPort: 80 36 volumeMounts:37 - name: config-volume38 mountPath: /etc/config39 volumes:40 - name: config-volume41 configMap:42 name: log-config 应用yaml文件并查看状态1 [root@k8s-master storage]# kubectl apply -f pod_configmap_hot.yaml2 configmap/log-config created3 deployment.apps/myapp-deploy created4 [root@k8s-master storage]#5 [root@k8s-master storage]# kubectl get configmap log-config6 NAME DATA AGE7 log-config 121s8 [root@k8s-master storage]#9 [root@k8s-master storage]# kubectl get pod -o wide10 NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES11 myapp-deploy-58ff9c997-drhwk 1/1 Running 0 30s 10.244.2.154 k8s-node02 12 myapp-deploy-58ff9c997-n68j2 1/1 Running 0 30s 10.244.4.126 k8s-node01 查看ConfigMap信息1 [root@k8s-master storage]# kubectl get configmap log-config -o yaml2 apiVersion: v13 data:4 log_level: INFO5 kind: ConfigMap6 metadata:7 annotations:8 kubectl.kubernetes.io/last-applied-configuration: | 9 {"apiVersion":"v1","data":{"log_level":"INFO"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"log-config","namespace":"default"}}10 creationTimestamp: "2020-06-07T16:08:11Z" 11 name: log-config12 namespace: default13 resourceVersion: "971348" 14 selfLink: /api/v1/namespaces/default/configmaps/log-config15 uid: 7e78e1d7-12de-4601-9915-cefbc96ca305 查看pod中的ConfigMap信息1 [root@k8s-master storage]# kubectl exec -it myapp-deploy-58ff9c997-drhwk -- cat /etc/config/log_level2 INFO 热更新修改ConfigMap1 [root@k8s-master storage]# kubectl edit configmap log-config ### 将 INFO 改为了 DEBUG2 # Please edit the object below. Lines beginning with a '#'will be ignored,3 # and an empty file will abort the edit. If an error occurs while saving this filewill be4 # reopened with the relevant failures.5 #6 apiVersion: v17 data:8 log_level: DEBUG9 kind: ConfigMap10 metadata:11 annotations:12 kubectl.kubernetes.io/last-applied-configuration: | 13 {"apiVersion":"v1","data":{"log_level":"DEBUG"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"log-config","namespace":"default"}}14 creationTimestamp: "2020-06-07T16:08:11Z" 15 name: log-config16 namespace: default17 resourceVersion: "971348" 18 selfLink: /api/v1/namespaces/default/configmaps/log-config19 uid: 7e78e1d7-12de-4601-9915-cefbc96ca305 查看ConfigMap信息1 [root@k8s-master storage]# kubectl get configmap log-config -o yaml2 apiVersion: v13 data:4 log_level: DEBUG5 kind: ConfigMap6 metadata:7 annotations:8 kubectl.kubernetes.io/last-applied-configuration: | 9 {"apiVersion":"v1","data":{"log_level":"DEBUG"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"log-config","namespace":"default"}}10 creationTimestamp: "2020-06-07T16:08:11Z" 11 name: log-config12 namespace: default13 resourceVersion: "972893" 14 selfLink: /api/v1/namespaces/default/configmaps/log-config15 uid: 7e78e1d7-12de-4601-9915-cefbc96ca305 稍后10秒左右,再次查看pod中的ConfigMap信息1 [root@k8s-master storage]# kubectl exec -it myapp-deploy-58ff9c997-drhwk -- cat /etc/config/log_level2 DEBUG由此可见,完成了一次热更新 相关阅读1、YAML 语言教程与使用案例2、Kubernetes K8S之通过yaml创建pod与pod文件常用字段详解3、Kubernetes K8S之存储Secret详解 ———END———如果觉得不错就关注下呗 (-^O^-) !
2020年10月22日
699 阅读
0 评论
0 点赞
2020-10-22
C# 范型约束 new() 你必须要知道的事
C# 范型约束 new() 你必须要知道的事注意:本文不会讲范型如何使用,关于范型的概念和范型约束的使用请移步谷歌。本文要讲的是关于范型约束无参构造函数 new 的一些底层细节和注意事项。写这篇文章的原因也是因为看到 github 上,以及其他地方看到的代码都是那么写的,而我一查相关的资料,发现鲜有人提到这方面的细节,所以才有了此文。这里我先直接抛出一段代码,请大家看下这段代码有什么问题?或者说能说出什么问题?public static T CreateInstance() where T: new() => new T(); 先不要想这种写法的合理性(实际上很多人都会诸如此类的这么写,无非就是中间多了一些业务处理,最后还是会 return new T())。先想一下,然后在看下面的分析。假设这样的问题出现在面试上,其实能有很多要考的点。首先是范型约束的底层细节如果说我们不知道范型底下到底做了什么操作,我们也不用急,我们可以用 ILSpy 来看查看一下,代码片段如下:.method public hidebysig static !!T CreateInstance () cil managed { // Method begins at RVA 0x2053 // Code size 6 (0x6) .maxstack 8 IL_0000: call !!0 [System.Private.CoreLib]System.Activator::CreateInstance() IL_0005: ret } // end of method C::CreateInstance 没有 ILSpy 的同学可以移步这里在线查看在 IL_0000 就能明显看出范型约束 new() 的底层实现是通过反射来实现的。至于 System.Activator.CreateInstance 方法实现我在这里就不提了。只知道这里用的是它就足够了。不知道大家看到这里有没有觉得一丝惊讶,我当时是有被惊到的,因为我的第一想法就是觉得这么简单肯定是直接调用无参 .ctor,居然是用到的反射。毕竟编译器拥有在编译器就能识别具体的范型类了。现在可以马后炮的讲:正因为是编译器只有在编译期才确定具体范型类型,所以编译器无法事先知道要直接调用哪些无参构造函数类,所以才用到了反射。如果本文仅仅只是这样,那我肯定没有勇气写下这片文章的。因为其实已经有人早在 04 年园子里就提到了这一点。但是我查到的资料也就止步于此。试想一下 ,如果你的框架中有些方法用到了无参构造函数范型约束,并且处于调用的热路径上,其实这样性能是大打折扣的,因为反射 Activator.CreateInstance 性能肯定是远远不如直接调用无参构造函数的。那么有没有什么方法能够在使用范型约束这个特征的同时,又不会让编译器去用反射呢?答案肯定是有的,这点我想喜欢动手实验肯定早就知道了。其实我们可以用到委托来初始化类。范型约束 return new T() 的优化——委托如果大家对这点都知道的话,可以略过本节(在这里鼓励大家可以写出来造福大家呀,对于这点那些不知道的人(我)要花很长时间才弄清楚 -_-)。让我们把上面的例子改成如下方式:public static Func InstanceFactory => () => new Bar(); 对于委托的底层相信大家还是都知道的,底层是通过生成一个类 C,在这个类中直接实例化类 Bar。下面我只贴出关键的代码片段.method public hidebysig specialname static class [System.Private.CoreLib]System.Func`1 get_InstanceFactory () cil managed { // Method begins at RVA 0x205a // Code size 32 (0x20) .maxstack 8 IL_0000: ldsfld class [System.Private.CoreLib]System.Func`1 C/'c'::'9__3_0' IL_0005: dup IL_0006: brtrue.s IL_001f IL_0008: pop IL_0009: ldsfld class C/'c' C/'c'::'9' IL_000e: ldftn instance class Bar C/'c'::'b__3_0'() IL_0014: newobj instance void class [System.Private.CoreLib]System.Func`1::.ctor(object, native int) IL_0019: dup IL_001a: stsfld class [System.Private.CoreLib]System.Func`1 C/'c'::'9__3_0' IL_001f: ret } // end of method C::get_InstanceFactory .method assembly hidebysig instance class Bar 'b__3_0' () cil managed { // Method begins at RVA 0x2090 // Code size 6 (0x6) .maxstack 8 IL_0000: newobj instance void Bar::.ctor() IL_0005: ret } // end of method 'c'::'b__3_0' 同样我们可以通过 ILSpy 或者 在线查看示例 查看委托生成的代码。这里可以明显看出是不存在反射调用的,IL_000e 处直接调用编译器生成的类 C 的方法 b__3_0 ,在这个方法中就会直接调用类 Bar 的构造函数。所以性能上绝对要比上种写法要高得多。看到这里可能大家又有新问题了,众所周知,委托要在初始化时就要确定表达式。所以与此处的范型动态调用是冲突的。的确没错,委托必须要在初始化表达式时就要确定类型。但是我们现在已经知道了委托是能够避免让编译器不用反射的,剩下的只是解决动态表达式的问题,毫无疑问表达式树该登场了。范型约束 return new T() 的优化——表达式树对于这部分已经知道的同学可以跳过本节。把委托改造成表达式树那是非常简单的,我们可以不假思索的写出下面代码:private static readonly Expression ctorExpression = () => new T(); public static T CreateInstance() where T : new() { var func = ctorExpression.Compile(); return func(); } 到这里其实就有点”旧酒装新瓶“的意思了。不过有点要注意的是,如果单纯只是表达式树的优化,从执行效率上来看肯定是不如委托来的快,毕竟表达式树多了一层构造表达式然后编译成委托的过程。优化也是有的,再继续往下讲就有点“偏题”了。因为往后其实就是对委托,对表达式树的性能优化问题。跟范型约束倒没关系了总结其实如果面试真的有问到这个问题的话,其实考的就是对范型约束 new() 底层的一个熟悉程度,然后转而从反射的点来思考问题的优化方案。因为这可以散发出很多问题,比如性能优化,从直接返回 new T() 到委托,因为委托无法做到动态变化,所以想到了表达式树。那么我们继而也能举一反三的知道,如果要继续优化的话,在构造表达式树时,我们可以用缓存来节省每次调用方法的构造表达式树的时间(DI 的 CallSite 实现细节就是如此)。如果我们生思熟虑之后还要选择继续优化,那么我们还可以从表达式树转到动态生成代码这一领域,通过编写 IL 代码来生成表达式树,进而缓存下来达到近乎直接调用的性能。这也是为什么我花了很长时间弄清楚这个的原因。最后关于代码代码地址在:https://github.com/MarsonShine/Books/tree/master/WHPerformanceDotNet/src/GenericOptimization注意:我上传这一版是下方第一个文章给出的例子的整理之后的版本。文中有很多代码我都没贴出来,一是觉得意义不大,重要的是思考过程和实践过程,还占文章篇幅。二是还是想让不知道这些的同学能自己动手编码自己的版本,最后才看与那些大牛写的版本的差距在哪,这样才会更有收获。参考资料 https://devblogs.microsoft.com/premier-developer/dissecting-the-new-constraint-in-c-a-perfect-example-of-a-leaky-abstraction/ https://alexandrnikitin.github.io/blog/dotnet-generics-under-the-hood/ https://www.microsoft.com/en-us/research/wp-content/uploads/2001/01/designandimplementationofgenerics.pdf 《编写高性能.NET代码》
2020年10月22日
676 阅读
0 评论
0 点赞
2020-10-21
蒲公英 · JELLY技术周刊 Vol.25 · Webpack 5 正式发布,你学废了么
蒲公英 · JELLY技术周刊 Vol.25阔别两年,Webpack 5 正式发布了,不仅清理掉很多冗余的功能,同样也为我们带来了很多新鲜的能力,不论是默认开启的持久缓存,还是反病毒保护,亦或者被其作者之一称为 JS 架构变革者的Module Federation。虽然不知道你有没有心动呢,但现在却正是时候上车 Webpack 5 体验一把装备升级。登高远眺天高地迥,觉宇宙之无穷基础技术看完这篇文章,我奶奶都懂了https的原理文章由浅入深展示了对称加密、非对称加密、数字证书、CA机构、数字签名等在 https 协议过程中所扮演的角色,语言通俗易懂,虽然拉着奶奶一起看,奶奶表示看不懂,但还是值得推荐!工程化阔别两年,webpack 5 正式发布自从 2018 年 2 月,Webpack 4 发布以来,Webpack 就暂时没有更进一步的重大更新,为了保持 API 的一致性,旧的架构没有做太多改变,遗留了很多的包袱。阔别 2 年多后,2020 年 10 月 10 日,Webpack 5 正式发布,并带来了诸多重大的变更,将会使前端工程师的构建效率与质量大为提升。Webpack 5 的 Release Note 非常长,本文尝试摘出最简练的信息。图形编程AR 核心概念及技术解析这是谷歌推出的 ARCore 开发入门课程系列中的第二节课,介绍了 AR 的六大理念,以及 ARCore 实现这六大理念的思路与技术解决方案。文章开头提到的上节课《解密 AR 增强现实》的内容也同样值得一读。RTF: 用 React 来写 three.js如何优雅地使用 React 写 three.js,本篇文章提供了入门教程。文中罗列了选择 R3F(react-three-fiber)的理由:组件化场景的开发模式可读性极高且便于复用,内置了很多相当有用的辅助函数,暴露了足够多的 hooks 以便开发者灵活扩展,提前预处理画布尺寸,不限制 three.js 的使用版本,对二次渲染的算法做了优化。人工智能AI 聊天机器人——京东·数据科学实验室A Survey on Dialogue Systems: Recent Advances and New Frontiers.(智能对话系统调查:前沿与进展)虽然也是2018年的,全文引用了124篇论文,是一篇综合全面的介绍对话系统的文章,现在看来仍具有参考意义。论文解读:基于动态词表的对话生成研究虽然是18年的文章,但通过本文主要可以了解聊天机器人的分类以及实现思路,至于本文的核心内容,用动态词典替换静态词典的实现思路以及优化词预测损失和回复生成损失方法,则适合更深入研究的读者。工具推介历经十年岁月磨砺,这个网页视频播放器中的王牌终于开源了!ckplayer 是一款用于网页上播放视频的软件,它有着高度自定义化的特点;而且作为一个老牌的网页视频播放器,使用起来只需几个简单的文件即可插入视频。而在最近,该播放器作者在更新了 X2 版本后,又将 ckplayer 正式开源给各位开发者,本文将带大家简单介绍该播放器的特点以及使用和安装方式。沧海拾遗沧海拾遗,积跬步以至千里Nginx 配置 HTTPS 服务器了解了那些关于 HTTPS 的基础,在实际的项目中该如何应用呢?本文讲解了如何使用 Nginx 来配置 HTTPS 服务器,还不快用起来,给自己的小网站按上象征安全认证的「绿锁」。Webpack原理浅析Webpack 5 正式发布了,当我们抽丝剥茧的去学习其中的内容时,还是需要先对 Webpack 有更为深刻的了解,这篇 Webpack 原理浅析,就是很好的教程,文章以实现一个简单的打包工具为路线图,逐一解析了 Webpack 中的各项能力,如果将它放在收藏夹吃灰就太可惜了。「蒲公英」期刊,每周更新,我们专注于挖掘「基础技术、工程化、跨端框架技术、图形编程、服务端开发、桌面开发、人工智能、设计哲学、前端框架」等多个大方向的业界热点,并加以专业的解读;不仅如此,我们还会推介精选凹凸技术文章,向大家呈现团队内的研究技术方向。抬头仰望,蒲公英的种子会生根发芽,如夏花绚烂;格物致知,我们登高远眺、沧海拾遗,以求积硅步而至千里。蒲公英 · JELLY技术周刊贡献指南欢迎关注凹凸实验室博客:aotu.io或者关注凹凸实验室公众号(AOTULabs),不定时推送文章:
2020年10月21日
681 阅读
0 评论
0 点赞
2020-10-21
如何提升前端基建的效能价值?
写在前面上一篇如何衡量工具平台的效能价值?推导出了一种度量模型,通过具体的数据指标来衡量效能价值,让内部工具/平台的价值也能看得见、说得清那么,对于正在做或者将要做的工具平台,如何进一步提升其效能价值呢? 一.效能价值有哪些影响因素?首先,工具的关键目标是解决实际问题:工具总是为解决问题而生的选定目标问题之后,接着通过工具化、平台化等自动/半自动的手段来尝试解决,并通过效率和体验两方面的提升体现出解决方案的效能价值:效能价值 = 效率价值 * 体验因子 进一步细化:工具效率 = 问题规模 / 操作时间 工具效率 = (不用该工具解决所需的)时间成本 / (用该工具解决所需的)时间成本 工具体验 = 易用程度 * 稳定程度 因此,工具的效能价值取决于 4 个因素: 问题规模 操作时间 易用性 稳定性 提升工具效能就是想办法增大分子、减小分母,即提升问题规模、易用性、稳定性,降低操作时间 二.如何提升问题规模?对于选定的目标问题,其规模通常是固定的,所以关键在于如何选择目标价值最高的问题:问题的目标价值 = 目标用户量 * 需求频率 * 单次的价值 多数情况下,我们倾向于选择目标用户量更大的问题,因为解决一个普遍存在的问题要比解决只有小部分用户才会遇到的特殊问题更有意义然而,需求频率与单价对目标价值的影响却不那么显而易见:其中: 首选高频高价:非常难得的需求,如果有,优先满足 不做低频低价:此类需求不值得做 高频低价、低频高价并重:大多数需求都是这两类,选择也都集中在这里 在高频低价与低频高价之间,产品经理的一般策略是:高频抓用户,低频做利润也就是说,前期先通过满足高频低价的需求获得大量用户,中后期再将低频高价的需求考虑进来:先利用高频低价的需求抓用户,因为高频场景和用户互动的机会多,而低价的轻决策场景可以降低用户进入门槛,容易拉新、引流;再用低频高价的需求做利润,因为单价高了,可以切分的蛋糕才大。之所以采取这样的先后次序,是因为必须有海量用户做基础,低频需求的总量才足够大。 三.如何降低操作时间?当然,如果有明显的待优化项,应该尽快去做,先把工具自身的效率提升到相当高的水准,减少用户等待工具运转完成的时间但如果工具本身在耗时上已经没有太大的优化空间,此时就需要将目光从局部的工具中移出来,放眼全局考虑整体优化: 面向过程的视角:流程上,能否减少一些中间环节,简化工作流 面向对象的视角:模式上,能否减少参与其中的相关角色,减少人与工具、工具与工具、工具与人之间的交互,减少一些中间产物 流程上,甚至协作模式上的变革通常有机会颠覆先前解决问题的关键路径,绕过既有工具的效率瓶颈,从而大幅降低操作时间 四.如何提升易用性?工具型产品的第一要义是用户会用,让用户至少会用,才能体现产品的价值易用性要求产品功能尽可能地符合用户心智(至少要保证核心功能的易用性),简化交互,降低用户上手使用的学习成本:从用户心智向产品功能做映射,极致的易用是符合直觉,上手即用那么,首先要明确用户心智,做法非常简单:告诉用户,这个工具能给你解决什么具体问题。接着(在产品功能不那么符合直觉的阶段)先教会用户怎么用,功能引导、新手教程/视频、帮助文档等都是不错的方法,旨在提升易用性,让用户先用起来。同时根据用户真实反馈不断优化使用体验,缩小产品功能与用户心智之间的差距,使之最终符合直觉: 心智负担小(学习成本低) 交互友好 UI 美观 核心功能流程顺畅 除了让产品功能向用户心智靠拢外,还有一种非常规思路是培养用户心智(即改变用户直觉,使之符合产品功能),多出现在颠覆式创新的场景,必须改变用户根深蒂固的直觉才能真正提高效率 五.如何提升稳定性?从用户心智向产品性能做映射,极致的稳定是完全信任,从不怀疑工具会出问题与易用性相比,稳定性是客观而明确的,单从技术角度就能在很大程度上确保稳定性,例如: 降低 crash 率:持续关注 top 崩溃,及时修复影响范围较大的 减少 bug 数:持续观察 bug 增长趋势,快速迭代修复,收敛功能性问题 减少操作失败次数:记录失败操作,分析改善常见误操作,同时反向丰富功能 其中,值得注意是记录失败操作,以搜索功能为例,失败操作包括: 搜索服务出错 搜索无结果 搜索结果与预期不符(结果没有帮助) 从技术上看,后两类并不属于操作失败,但同样值得关注,因为无结果的搜索通常意味着语义化/模糊搜索功能不够完善,或者相关内容有缺失,这些信息对于丰富产品功能很有帮助。同理,不符合用户预期的搜索结果也是一种有价值的负反馈,有助于发现问题,改善用户使用体验 六.如何提升用户量?当工具的效率和体验都达标后,最关键的问题是如何提升用户量,放大工具的价值与其它产品相比,工具型产品的难点在于: 可替代性强 用户不知道(有工具可以用) 用户粘性差,容易流失 强的不可替代性是决定性因素,作为唯一选项自然不必考虑用户量的问题,例如小程序开发者工具如果不具备强的不可替代性,就要通过其它手段来增加用户的替换成本,常用的策略有场景化运营、社区运营、内容运营等 场景化运营 将工具与使用场景紧密关联起来,培养用户的使用习惯:做工具型产品一定要时刻追问用户在什么样的场景下会想到打开你的产品,这个具体场景就是一切运营的基础围绕一个核心场景,充分满足关键需求,成为该场景下的最优解决方案,从而解决用户不知道的问题另一方面,场景化的温馨提示有助于提升产品的温度,让用户感受到人性关怀,而不只是冷冰冰的工具 社区运营加强产品与用户,以及用户与用户的联系,建立社区是提高用户粘性的有效手段,例如: 运营一个群组:将冰冷的工具做成能够交流的“活人”,拉近产品与用户的距离 增加社交功能:用户订阅产品更新,用户之间关注、评论、点赞等,增加用户的参与感和归属感 通过群组将产品的变化告知用户,这种持续的频繁正向反馈能够激发用户反馈问题的积极性,增强产品与用户的联系社交化听起来与供内部使用的工具平台有些距离,实际上并不遥远。以前端工程为例,像公共组件/代码片段、Code Review、新手教程/API 文档等都可以有简单的社交功能(点赞、评论),看似细小,却有助于提升用户的参与度 内容运营与社区一样,内容也是一种场景延伸,将工具产出的内容也作为工具的一部分,例如: WPS 与稻壳儿模版 Git 与 Gist 工具引导用户输出附加价值,从而提升工具的整体价值(工具 + 共享内容)。另一方面,用户将产生的内容分享给其它用户,也有助于提升自身的影响力,互相促进 参考资料 再理解“高频打低频”,对也不对 工具类产品应该怎样运营? 工具类产品到底该怎么运营?附案例
2020年10月21日
665 阅读
0 评论
0 点赞
1
2
3
...
6