目录
三. kfk的架构深入
3.4 kfk的消费者
主要研究消费者的消费方式 和 消费者组的分区分配策略
3.4.1 消费者的消费方式
- consumer采用pull拉取的方式从broker中拉取数据.
push, broker推送数据的方式很难适应不同的消费者, 因为不同消费者的消费速率不同. - pull模式的缺点是如果kfk中没有数据, 消费者可能陷入循环, 一直返回空数据.
- 针对这一点, kfk的消费者在消费数据时会传入一个时长参数
timeout
, 如果当前没有数据可供消费, consumer会等待一段时间之后再返回, 这段时长即为timeout
.
- 针对这一点, kfk的消费者在消费数据时会传入一个时长参数
3.4.2 分区分配策略
一个consumer group中有多个consumer, 一个topic有多个partition, 所以必然会涉及到partition的分配问题, 即确定哪个partition由哪个consumer来消费.
同一个消费者组中的不同消费者, 能同时消费同一个topic, 不能同时消费同一个partition.
kfk有两种消费者分区分配策略: RoundRobin 和 Range(默认).
RoundRobin是按照组来划分的, Range是按照主题来划分的.
3.4.2.1 RoundRobin轮询
- 1个topic, 7个partition
- 2个topic, 每个topic有3个partition
-
-
假设消费者组同时订阅了两个topic(如上图). 那么在kfk轮询的策略中, 会把topic1和topic2中所有的partition看做一个整体, 根据每个partition的哈希值, 将这6个partition排序, 然后再轮询分配给consumer group中的两个consumer.
-
在API中, 每个partition都会被封装成一个
TopicAndPartition
对象, 使用的是这个对象的哈希值. -
这样做的
优点
是consumer group中每个consumer 分配的partition的数量的差值最多差1个, 因为是轮询嘛. 即保证了每个consumer分配到的partition数量尽可能相同. -
这样做的
缺点
是假如同一个consumer group中的consumer-a订阅了topic1和topic2, consumer-b订阅了topic2和topic3, 那么轮询策略会把topic1 topic2 topic3中所有的partition看做一个整体, 轮询分配到consumer-a和consumer-b中, 这样的话consumer-a可能分配到topic3中的partition, consumer-b可能分配到topic1中的partition. 如下图. -
-
如果consumer-a消费topic1, consumer-b消费topic2, 那么按照轮询的分配策略, 会将topic1 topic2中的所有partition看做一个整体轮询分配到每个consumer中, 那么consumer-a可能分配到topic2中的partition, consumer-b可能分配到topic1中的partition. 如下图.
-
-
所以使用轮询的分区分配策略的前提是: 必须保证consumer group中consumer订阅的topic是相同的(可以是多个).
-
正是由于有上述的问题, 因此kfk默认的分区分配策略是Range.
-
3.4.2.2 Range范围(默认)
- 这种策略按照
topic
分, 而不是按照partition
分. - 1个topic, 7个partition
-
-
按照
7/3
将partition分配到每个consumer中. -
这种策略的
缺点
是有的consumer分配的partition太多, 有的consumer分配的partition太少. 即consumer group中每个consumer分配的partition数量不均匀.
-
- 2个topic, 每个topic有3个partition
-
-
随着订阅的topic越多, 不同consumer中分配的partition数差距将越大.
-
- 结论
- RoundRobin策略优点是能让consumer group中每个consumer尽量分配到相同数量的partition, 缺点是当consumer group中consumer订阅的topic不同时会出问题, 因为所有的topic会被看做一个整体.
- Range策略的优点是RoundRobin策略的缺点, Range策略的缺点是RoundRobin策略的优点.
- 两种策略按照实际业务场景选择, 默认Range.
3.4.2.3 什么时候会用到分区分配策略
分区策略在API中就是一个方法. 这个方法何时被调用? 即何时会触发策略的执行? 即策略的触发时机是什么时候?
- 当consumer group中
消费者个数变化时
就会重新触发分区策略. 即使消费者数量增加到比分区数量还多时, 仍然会触发分区策略.
3.4.3 offset的维护
由于consumer在消费过程中可能会出现断电宕机等故障, consumer恢复后, 需要从故障前的位置继续消费, 所以需要consumer实时记录自己消费到了哪个offset, 以便故障恢复后继续消费.
kfk0.9前偏移量保存在zk, kfk0.9开始偏移量保存在kfk.
从kfk0.9开始, consumer默认将offset保存在kfk一个内置的topic中, 该topic为
_consumer_offsets
.
-
offset由3个部分唯一确定:
consumer group + topic + partition
. offset是按照consumer group保存的而不是按照consumer保存的, 这样的好处就是如果一个consumer挂了, offset不至于丢失. 如果consumer-a保存了10条msg, 这是consumer group中新加consumer-b(此时触发分区策略), 这就能保证consumer-b能从第11条msg接着消费. -
查看zk中维护的offset
-
创建一个topic名为topic100, 2个partition, 每个partition都有2个副本
/opt/kafka/kafka/bin/kafka-topics.sh --create --topic topic100 --zookeeper localhost:2181 --partitions 2 --replication-factor 2
-
启动一个生产者
/opt/kafka/kafka/bin/kafka-console-producer.sh --broker-list localhost:9092 --topic topic100
-
启动一个消费者. 使用zk存储offset.
/opt/kafka/kafka/bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic topic100
-
生产者输入 “hello”
-
进入zk
# 进入zk /opt/zookeeper/zookeeper/bin/zkCli.sh # 展示根节点. # 根节点下面的内容, 除了zookeeper, 其他都是和kfk相关的. 其中的controller是kfk中的控制节点, 哪个broker先抢到资源, 哪个broker就是controller, 一般先启动的broker会成为controller. controller的作用和其他broker的作用相同, 只不过controller可以和zk进行交互. # brokers下面有ids,topics,seqid. 其中ids中记录了kfk的broker-id. 只要是注册到ids中的, 都是kfk集群中的一个节点. kfk并没有组织集群, 而是zk帮kfk组织了集群; /brokers/topics记录了kfk中所有的topic. # /consumers中存储了消费者向zk写的数据. 如下的"console-consumer-68063", 这实际上是消费者组, 以控制台的形式启动消费者, 会被默认分配到一个组, 组名就是"console-consumer-随机数". 如果使用API, 必须手动指定消费者组名, 不会自动创建. # 消费者组中有offset, offset中有topic, topic中有分区, 获取分区中值就能得到offset的值. [zk: localhost:2181(CONNECTED) 0] ls / [cluster, controller_epoch, controller, brokers, zookeeper, admin, isr_change_notification, consumers, latest_producer_id_block, config] [zk: localhost:2181(CONNECTED) 3] ls /consumers [console-consumer-68063, console-consumer-39547, console-consumer-47083] [zk: localhost:2181(CONNECTED) 4] ls /consumers/console-consumer-68063 [ids, owners, offsets] [zk: localhost:2181(CONNECTED) 8] ls /consumers/console-consumer-39547/offsets [topic100] [zk: localhost:2181(CONNECTED) 9] ls /consumers/console-consumer-39547/offsets/topic100 [0, 1] [zk: localhost:2181(CONNECTED) 10] get /consumers/console-consumer-39547/offsets/topic100/0 0 # 消息偏移量 cZxid = 0x649 ctime = Sat Dec 05 16:39:13 CST 2020 mZxid = 0x649 mtime = Sat Dec 05 16:39:13 CST 2020 pZxid = 0x649 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 1 numChildren = 0
-
-
查看kfk本地维护的offset
-
由于kfk中存储offset的主题是kfk内置的主题
_consumer_offsets
, 因此我们需要改一些配置才能消费这个topic中的数据.# 修改consumer.properties. 不除外内置的topics. exclude.internal.topis=false
-
读取offset
- kfk0.11之前的版本
- 略
- kfk0.11开始的版本
- 略
- kfk0.11之前的版本
-
3.4.4 消费者组案例
-
测试同一个消费者组中的消费者, 同一时刻只能有一个消费者消费.
-
使用控制台的consumer时, 每次启动一个consumer都会分配到一个新的consumer group中, 组名随机分配. 我们可以通过修改
consumer.properties
文件, 将group.id
修改为任意, 那么启动任意个控制台的consumer就都会在同一个consumer group中了. group.id就是组名.
3.5 kfk高效读写数据的原因
研究集群版kfk中单个节点的读写效率为什么比其他mq效率高; 研究单台kfk的读写效率为什么比单台其他mq的效率高.
-
集群读写高效原因
- kfk是分布式的, 能并发读写.
- 顺序写磁盘. 顺序写磁盘省去了大量磁头寻址的时间.
- 零拷贝技术.
-
单节点读写高效原因
- 顺序写磁盘. 顺序写磁盘省去了大量磁头寻址的时间.
- 零拷贝技术.
-
不用零拷贝
-
使用零拷贝
3.6 zk在kfk中的作用
- kfk集群中有一个broker会被选举为controller, 负责
管理集群broker
的上下线, 所有topic的分区副本分配
和leader
选举等工作. broker被选为controller的策略是抢占资源, 谁先抢到谁就是controller, 一般最先启动的broker将成为controller. - controller的管理工作都是依赖于zk的.
- leader选举流程:
3.7 Range策略再分析
- 上述使用的策略是Range
使用轮询的话, 看消费者组, 看整个组订阅了哪些topic, 然后将这些topic中的所有partition作为一个整体 进行轮询
. 所以上述的情况不能使用轮询策略, 否则consumer-A可能会分配到topic2的partition.
3.8 kfk事务
3.8.1 producer事务
- 保证跨分区跨会话级别的精准一次性.
3.8.2 consumer事务
- 保证消费者消费的数据是只消费一次的. (这一块很少聊, 关键还是生产者那边的事务)