参考:
https://blog.csdn.net/u013256816/article/details/81123600
消费方式
consumer 采用 pull(拉)模式从 broker 中读取数据。
push(推)模式很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成 consumer 来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而 pull 模式则可以根据 consumer 的消费能力以适当的速率消费消息。
pull 模式不足之处是,如果 kafka 没有数据,消费者可能会陷入循环中,一直返回空数据。针对这一点,Kafka 的消费者在消费数据时会传入一个时长参数 timeout,如果当前没有数据可供消费,consumer 会等待一段时间之后再返回,这段时长即为 timeout。
分区分配策略
一个 consumer group 中有多个 consumer,一个 topic 有多个 partition,所以必然会涉及到 partition 的分配问题,即确定那个 partition 由哪个 consumer 来消费。
Kafka 有两种分配策略,一是 RoundRobin,一是 Range。
轮询RoundRobin
这种分区策略只适合消费组中的所有消费都订阅某些主题,把所有主题看成是一个整体,进行排序和轮询。
如下图,消费者A订阅了topicA(TA),消费者B订阅了topicB(TB),topicA由若干分区(TA0,TA1,TA2)组成,topicB由若干分区(TB0,TB1,TB2)组成,则消费者A有可能接收到自己没订阅的topicB的分区TB0、TB1,消费者B有可能接收到自己没订阅的topicA的分区TA1、TA2。
注意,一个消费者是一个线程。
以上是比较理想的情况,以下是不理想的情况。
比如我们依然有3个消费者(C0,C1,C2),他们合在一起订阅了 3 个主题:T0、T1 和 T2(C0订阅的是主题T0,消费者C1订阅的是主T0和T1,消费者C2订阅的是主题T0、T1和T2),这 3 个主题分别有 1、2、3 个分区(即:T0有1个分区(p0),T1有2个分区(p0、p1),T2有3个分区(p0、p1、p2)),即整个消费者所订阅的所有分区可以标识为 T0p0、T1p0、T1p1、T2p0、T2p1、T2p2。此时如果使用RoundRobin分配策略,得到的分区分配结果如下:
这时候显然分配是不均匀的,因此在使用RoundRobin分配策略时,为了保证得均匀的分区分配结果,需要满足两个条件:
同一个消费者组里的每个消费者订阅的主题必须相同;
同一个消费者组里面的所有消费者的num.streams必须相等。不相等会导致消费者消费的分区更加不均衡。
如果无法满足,那最好不要使用RoundRobin分配策略。
问题:
RoundRobin模式下,一个topic有10个分区,现在消费者组里面起了10个线程(其实就是10个消费者),问一个消费者能否拿到多个分区的数据?
答:不能。在轮询规则下,一个消费者只能拿到一个分区的数据。
分区分配策略之Range
这是kafka消费者默认的消费分区策略。分区是针对单个主题,并不是吧所有主题看成一个整体。
按照Kafka默认的消费逻辑设定,一个分区只能被同一个消费组(ConsumerGroup)内的一个消费者消费。假设目前某消费组内只有一个消费者C0,订阅了一个topic,这个topic包含7个分区,也就是说这个消费者C0订阅了7个分区,参考下图
满足的规律是
假设n=分区数(该主题的分区数)/消费者数量(所有订阅该主题的num.stream),m=分区数%消费者数量,那么前m个消费者每个分配n+1个分区,后面的(消费者数量-m)个消费者每个分配n个分区。
举例:
假设有个名为T1的主题,包含了7个分区,它有两个消费者(C0和C1),其中C0的num.streams(消费者线程) = 1,C1的num.streams = 2。排序后的分区是:0,1,2,3,4,5,6;消费者线程排序后是:C0-0,C1-0,C1-1;一共有7个分区,3个消费者线程,进行计算7/3=2…1,商为2余数为1,则每个消费者线程消费2个分区,并且前面1个消费者线程多消费一个分区,结果会是这样的:
但是一般在实际生产环境下,会有多个主题,我们假设有3个主题(T1,T2,T3),都有7个分区,两个消费者都订阅了T1,T2,T3主题,那么按照咱们上面这种Range分配策略分配后的消费结果如下,range策略是针对每一个主题的,那个消费者订阅了这个主题,就采用上面的规律
具体过程
T1主题: 7/3=2----1,那么前一个消费线程就应该是2+1=3个分区,分别是T1P0,T1P1,T1P2,后面两个消费线程分别是2个分区,分别是T1P3,T1P4和T1P5,T1P6,这个range就是针对一个主题的策略
T2,T3主题就以此类推。。。
我们可以发现,在这种情况下,C0-0消费线程要多消费3个分区,这显然是不合理的,其实这就是Range分区分配策略的缺点。可以明显的看到这样的分配并不均匀,如果将类似的情形扩大,有可能会出现部分消费者过载的情况。对此我们再来看下另一种RoundRobinAssignor策略的分配效果如何。
offset 的维护
由于 consumer 在消费过程中可能会出现断电宕机等故障,consumer 恢复后,需要从故障前的位置的继续消费,所以 consumer 需要实时记录自己消费到了哪个offset,以便故障恢复后继续消费。
Kafka 0.9版本之前,consumer 默认将 offset 保存在 Zookeeper 中,从0.9版本开始,consumer 默认将 offset 保存在 Kafka 一个内置的 topic 中,该 topic 为__consumer_offsets。
Kafka高效读写数据
顺序写磁盘
Kafka 的 producer 生产数据,要写入到 log 文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到 600M/s,而随机写只有 100K/s。这与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。
零复制技术
不适用User Space,而是使用Kernel Space直接读写文件。
Kafka 事务
Kafka 从 0.11 版本开始引入了事务支持。事务可以保证 Kafka 在 Exactly Once 语义的基础上,生产和消费可以跨分区和会话,要么全部成功,要么全部失败。
Producer 事务
为了实现跨分区跨会话的事务,需要引入一个全局唯一的 Transaction ID,并将 Producer获得的 PID 和 Transaction ID 绑定。这样当 Producer 重启后就可以通过正在进行的 TransactionID 获得原来的 PID。
为了管理 Transaction,Kafka 引入了一个新的组件 Transaction Coordinator。Producer 就是通过和 Transaction Coordinator 交互获得 Transaction ID 对应的任务状态。TransactionCoordinator 还负责将事务所有写入 Kafka 的一个内部 Topic,这样即使整个服务重启,由于
事务状态得到保存,进行中的事务状态可以得到恢复,从而继续进行。
Consumer 事务
上述事务机制主要是从 Producer 方面考虑,对于 Consumer 而言,事务的保证就会相对较弱,尤其时无法保证 Commit 的信息被精确消费。这是由于 Consumer 可以通过 offset 访问任意信息,而且不同的 Segment File 生命周期不同,同一事务的消息可能会出现重启后被
删除的情况。