kafka架构
kafka拓扑结构说明
- producer
消息生产者,发布消息到kafka集群的终端或服务 - broker
kafka集群中包含的服务器,代理人,消费转发服务 - topic
每条发布到kafka集群的消息所属的类别,即kafka是面向topic的 - partition
partition是物理上的概念,每个topic包含一个或多个partition,kafka分配的单位是partition - consumer
从kafka集群中消费消息的终端或服务 - consumer group
high-level consumer API中,每个consumer都属于一个consumer group,每条消息只能被consumer group中的一个consumer消费,但可以被多个consumer group的consumer消费,即组间数据是共享的,组内数据是竞争的 - replica
partition的副本,保障partition的高可用 - leader
replica中的一个角色,producer和consumer只跟leader交互 - follower
replica中的一个角色,从leader中复制数据 - controller
kafka集群中的其中一个服务器,用来进行leader election以及各种failover - zookeeper
作为分布式协调服务,kafka通过zookeeper来存储集群的元数据信息
topic与partition说明
上图是topic主题的剖析图
上图是生产者与消费者读写模式
- topic
每条发布到kafka集群的消息都有一个类别,这个类别被称为topic主题 - partition
partition是物理上的概念,每个topic包含一个或多个partition - topic在逻辑上可以被认为是一个queue,每条消息都必须指定它的topic,可以简单理解为必须指明把这条消息放进哪个queue里
- 为了使得kafka的吞吐率可以线性提高,物理上把topic分成一个或多个partition,每个partition在物理上对应一个文件夹,该文件夹存储了这个partition的所有消息和索引文件,如下所示:
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 testtopic-0
cd testtopic-0查看所有文件
-rw-r--r--. 1 root root 10485760 Jan 26 00:57 00000000000000000000.index
-rw-r--r--. 1 root root 213 Jan 26 01:19 00000000000000000000.log
-rw-r--r--. 1 root root 10485756 Jan 26 00:57 00000000000000000000.timeindex
-rw-r--r--. 1 root root 8 Jan 26 00:57 leader-epoch-checkpoint
- 每条消息都被append到该partition中,属于顺序写磁盘,这样效率非常高
- 对于传统的mq而言,一般会删除已经被消费的消息,而kafka集群会保留所有的消息,无论有没有被消费。当然,因为磁盘限制,不可能永久保留所有数据,实际上也没有必要,因此kafka提供了两种删除策略来删除旧数据。一是基于时间,二是基于partition文件大小。可在server.properties中设置,如下所示:
#可被删除的日志文件的最小年龄,单位:小时
log.retention.hours=168
#日志段文件的最大大小。当达到这个大小时,将创建一个新的日志段,单位:字节
log.segment.bytes=1073741824
#检查日志段以查看是否可以根据保留策略删除它们的间隔,单位:毫秒
log.retention.check.interval.ms=300000
#如果设置了log.cleaner.enable = true,则将启用清理程序,然后可以标记单个日志以进行日志压缩
log.cleaner.enable=false
kafka消息流处理
示意图:
- producer先从zookeeper的
/brokers/topics/testtopic/partitions/0/state
节点找到该partition的leader - producer将消息发送给该leader
- leader将消息写入本地log文件
- followers从leader pull消息,写入本地log后向leader发送ACK
- leader收到所有ISR(In-Sync Replicas机制)中的replica的ACK后,增加HW(high watermark,最后commit的offset)并向producer发送ACK
什么是ISR?
- leader会维护一个与其基本保持同步的Replica列表,该列表称为ISR(in-sync Replica),每个Partition都会有一个ISR,而且是由leader动态维护
- 如果一个flower比一个leader落后太多,或者超过一定时间未发起数据复制请求,则leader将其重ISR中移除
- 当ISR中所有Replica都向Leader发送ACK时,leader才commit
- 比如:有三个副本 ,编号是① ② ③ ,其中② 是leader ① ③是follower。假设在数据同步过程中,①跟上leader,但是③出现故障或没有及时同步,则① ②是一个ISR,而③不是ISR成员。后期在leader选举时,会用到ISR机制,会优先从ISR中选择leader
kafka高可用机制
概述:
- 同一个partition可能会有多个replica,对应server.properties配置项中的default.replication.factor=N
- 没有replica的情况下,一旦broker宕机,其上所有partition的数据都不可被消费,同时producer也不能再将数据存于其上的partition
- 引入replication之后,同一个partition可能会有多个replica,而这是需要在这些replica之间选出一个leader,producer和consumer只与这个leader交互,其他replica作为follower从leader中复制数据
leader failover:
- 当partition对应的leader宕机时,需要从follower中选举出新的leader。在选举新的leader时,一个基本的原则是,新的leader必须拥有旧的leader commit过的所有消息
- 由写入流程可知ISR里面的所有replica都跟上了leader,只有ISR里面的成员才能选举为leader。对于f+1个replica,一个partition可以在容忍f个replica失效的情况下保证消息不丢失。比如:一个分区有5个副本,挂了4个,剩一个副本,依然可以工作(注意:kafka选举不同于zookeeper,用的不是过半选举)
当所有replica都不工作时,有两种可行的方案:
- 等待ISR中的任一个replica活过来,并选举它为leader。这样可保证数据不丢失,但时间可能相对较长
- 选举第一个活过来的replica(不一定是ISR成员)作为leader。这样无法保证数据不丢失,但相对不可用的时间较短,kafka默认使用该方案。
kafka offset机制
kafka消费者的offset存储机制,consumer在从broker读取消息后,可以选择commit,该操作会在kafka中保存该consumer在当前partition中读取的消息的offset。该consumer下一次再读取当前partition时会从下一条开始读取,通过这一特性可以保证同一消费者从kafka中不会重复消费数据。
执行:sh kafka-console-consumer.sh --bootstrap-server node01:9092 --topic testtopic --from-beginning
然后,进入kafka-logs目录查看,会发现很多个目录,这是因为kafka默认会生成50个_consumer_offsets的目录,用于存储消费者消费的offset位置。如下所示:
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-0
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-10
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-12
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-14
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-16
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-18
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-2
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-20
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-22
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-24
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-26
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-28
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-30
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-32
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-34
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-36
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-38
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-4
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-40
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-42
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-44
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-46
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-48
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-6
drwxr-xr-x. 2 root root 4096 Jan 26 00:57 __consumer_offsets-8
注意:如果有多个broker节点,offsets目录会平均分布到多个节点上,kafka会使用以下计算公式计算该消费者group位移保存在_consumer_offsets的哪个目录上:Math.abs(groupID.hashCode())%50