接上文:《架构设计:系统间通信(28)——Kafka及场景应用(中1)》
4-3、复制功能
我们在上文中已经讨论了Kafka使用分区的概念存储消息,一个topic可以有多个分区它们分布在整个Kafka集群的多个Broker服务节点中,并且一条消息只会按照消息生产者的要求进入topic的某一个分区。那么问题来了:如果某个分区中的消息在被消费端Pull之前,承载该分区的Broker服务节点就因为各种异常原因崩溃了,那么在这个Broker重新启动前,消费者就无法收到消息了。
为了解决这个问题,Apache Kafka在V 0.8+版本中加入了复制功能:让topic下的每一个分区存储到多个Broker服务节点上,并由Zookeeper统一管理它们的状态。
请注意Kafka中Partition(分区)和replication(复制)是两个完全不同的概念,很多读者容易将这两个概念混淆——虽然它们都和“如何存储消息”这件事情有关:前者是说将若干条消息按照一定的规则分别存放在不同的区域,一条消息只存入一个区域(且Topic下多个分区可以存在于同一个Broker上);后者是说,为了保证消息在被消费前不会丢失,需要将某一个区域中的消息集合复制出多个副本(同一个分区的多个副本不能存放在同一个Broker上)。
Kafka将分区的多个副本分为两种角色:Leader和Follower,Leader Broker是主要服务节点,消息只会从消息生产者发送给Leader Broker,消息消费者也只会从Leader Broker中Pull消息。Follower Broker为副本服务节点,正常情况下不会公布给生产者或者消费者直接进行操作。Follower Broker服务节点将会主动从Leader Broker上Pull消息。
在这种工作机制下,Follower和Leader的消息复制过程由于Follower服务节点的性能、压力、网络等原因,它们和Leader服务节点会有一个消息差异性。当这个差异性扩大到一定的范围,Leader节点就会认为这个Follower节点再也跟不上自己的节奏,导致的结果就是Leader节点会将这个Follower节点移出“待同步副本集”ISR(in-sync replicas),不再关注这个Follower节点的同步问题。
只有当ISR中所有分区副本全部完成了某一条消息的同步过程,这条消息才算真正完成了“记录”操作。只有这样的消息才会发送给消息消费者。至于这个真正完成“记录”操作的通知是否能返回给消息生产者,完全取决于消息生产者采用的acks模式(后文会讲到)。
现在我们可以回过头看看上文中4-1-3-5小节给出的“查看Topic状态”命令以及命令结果:
# 脚本命令范例
kafka-topics.sh --describe --zookeeper 192.168.61.139:2181 --topic my_topic2
# 显示的结果
Topic:my_topic2 PartitionCount:4 ReplicationFactor:2 Configs:
Topic: my_topic2 Partition: 0 Leader: 2 Replicas: 2,1 Isr: 2,1
Topic: my_topic2 Partition: 1 Leader: 1 Replicas: 1,2 Isr: 1,2
Topic: my_topic2 Partition: 2 Leader: 2 Replicas: 2,1 Isr: 2,1
Topic: my_topic2 Partition: 3 Leader: 1 Replicas: 1,2 Isr: 1,2
以上命令行用于显示指定topic名称的基本状态信息。Partition表示分区号,Replicas表示所有副本的所在位置的Broker.id信息,Isr表示当前状态正常可以进行消息复制的副本所在位置的Broker.id信息。
那么从命令结果来看,名叫“my_topic2”的topic一共有4个数据分区,每一个分区有两个副本。其中:0号分区的Leader Broker服务节点的id为2,0号分区的两个副本分别在id为2和id为1的Broker服务节点上,且id为2和id为1的Broker上的副本状态都是正常的;同理,1号分区的Leader Broker服务节点的id为1,1号分区的两个副本分别在id为2和id为1的Broker服务节点上,且id为2和id为1的Broker上的副本状态都是正常的。。。
4-4、Kafka原理:生产者
请注意之前我们给出的Kafka集群方案的示意图,在图中消息生产者并没有连接到zookeeper协调服务,而是直接和多个Kafka Server Brokers建立了连接。和其他种类的消息队列的设计不同,在整个Kafka方案中消息生产者(Producer)会有很多重要规则的决定权,例如:
消费生产者(Producer)可以决定向指定的Topic的哪一个分区(Partition)发送消息。而不是由Broker来决定。
消息生产者(Producer)可以决定消息达到Kafka Broker后,Producer对消息的一致性关注到什么样的级别,又或者根本不关心消息在Broker上的一致性问题。
消息生产者(Producer)可以决定是以同步方式(sync)还是异步方式(aSync)向Broker Server List发送消息。
在异步方式下,消费生产者(Producer)还可以决定以什么样的间隔(周期)向Broker Server List发送消息。
随机选定Broker Server List中某一个服务节点,读取当前Topic下的分区和复制表信息,并保存在本地Pool中的工作也是由消息生产者(Producer)主动完成。
另外,Kafka中的消息生产者没有类似ActiveMQ中那样的事务机制(可参见文章《架构设计:系统间通信(23)——提高ActiveMQ工作性能(中)》)。这样的设计和Kafka主要的业务场景有关——用来收集各种操作日志。这样的场景对消息的可靠性要求并不高:漏掉一两条日志并不影响后端大数据平台对日志数据的分析结果;而且这样的设计大量简化了Broker的设计结构:它不需要像ActiveMQ那样专门为达成传输但还未进行commit的消息专门创建存储区域“transaction store”,并在进行了commit或者rollback操作后进行标记。这种处理机制是Apache Kafka高效性能的又一种保障。
Kafka中的多个消息生产者(Producer)并不需要ZooKeeper服务中的任何信息为它们协调发送过程,因为没有什么可协调的。生产者唯一需要知道的Topic有多少个分区以及每个分区,分别存在于哪些Broker上的信息都是来源于对某一个Broker的直接查询。所以Kafka集群中只剩下了Broker和Consumer需要进行协调(这个问题会在后文中进行详细讨论)。
这是分布式系统建设思想中一个重要的原则——不可滥用协调装置:完成同一件工作时,协调N个参与角色要比协调N-1个参与角色耗费更多的时间和性能;所以,只协调需要