0. 我想玩玩小块儿地…
1. 分区 Paritition
为了实现扩展性,一个非常大的Topic可以分布到多个Broker上,一个Topic可以分为多个Partition,每个Partition是一个有序的队列(分区有序,不能保证全局有序)
1.1 分区底层关系
Topic是逻辑上的概念,Partition是物理上的概念,每个Partition对应着一个log文件,该log文件中存储的就是producer生产的数据
1.1.1 分区数据的持久化
- Kafka文件存储也是通过本地落盘的方式存储的,主要是通过相应的log与index等文件保存具体的消息文件
逻辑上,log由多个分片(segment)构成 - 1个分片(segment)由.index、.log文件构成(分片和索引的机制快速定位大文件——二分查找)
.index文件存储的消息的offset+真实的起始偏移量
.log中存放的是真实的数据。
1.2 分区 与 生产/消费
- Producer生产的数据会被不断的追加到该log文件的末端,且每条数据都有自己的offset
- consumer组中的每个consumer,都会实时记录自己消费到了哪个offset,以便出错恢复的时候,可以从上次的位置继续消费。
1.3 分区内部结构
1.3.1 Leader
每个分区多个副本的主角色,生产者发送数据的对象,以及消费者消费数据的对象都是Leader。
1.3.2 Follower
每个分区多个副本的从角色,实时的从Leader中同步数据,保持和Leader数据的同步,Leader发生故障的时候,某个Follower会成为新的Leader。
1.4 分区分配规则
1.4.1 消息产出时的分区分配规则
- 由消息的生产者指定分区
- 上述条件不存在时,若消息有Key,则取 key的hash对分区总数 取模
- 上述条件不存在时,第一次调用时产生一个随机数,后续调用时在这个随机上++,然后将其对分区数取模(round-robin算法)
1.4.2 消息被消费时的分区分配规则
1.4.2.1 先解释一波为什么消费方式:Pull
-
kafka消费消息采用pull的方式(非push)的好处:
不同消费者的消费速率可能不同
push的速率由broker来决定 -
kafka缓解的pull所带来的空轮询的性能损耗:
消费者支持传入一个timeout
1.4.2.2 进入正题
一个cusomer group中如何决定某条消息由哪个实例消费:
- round robin
做法
这里并未使用随机数,而是按照分区的字典值 对 分区、消费者实例 排序
循环遍历,遇到自己订阅的topic则消费,否则向下轮询
坏处
订阅多的消费者实例,压力大,总的说就是不均衡
- range
做法:
topicX-PartitionY按先topic后partition的方式分配
如果多出1个则分配给字典值靠前的 消费者实例
坏处
如果topic多的话,字典值靠前的 消费者实例 可能压力大
2. 数据可靠性
2.1 客户端提交消费
消费者每次调用poll()方法,它总是返回由生产者写入Kafka但没有被消费者读取过的记录,我们因此可以知道哪些消息是被群组里的哪个消费者读取的。Kafka不会像JMS队列那样需要得到消费者的确认,消费者使用broker里的MetaData来追踪消息在分区里的位置(offset)
提交方式:自动提交、手动提交(又分为 同步、异步)
2.1.1 自动提交
定时
2.1.2 手动提交
2.1.2.1 同步方式
略
2.1.2.2 异步方式
相比 同步 多了一个 回调方法
但二者都可能出现 漏消费、重复消费(由rebalance引起):
提交offset后消费,有可能造成数据的漏消费,而先消费再提交offset,有可能会造成数据的重复消费
2.1.3 rebalance
2.1.3.1 什么是 rebalance
-
当有新的消费者加入消费者组、已有的消费者退出消费者组或者订阅的主体分区发生了变化,会触发分区的重新分配操作,重新分配的过程称为Rebalance。
-
消费者发生Rebalace之后,每个消费者消费的分区就会发生变化,因此消费者需要先获取到重新分配到的分区,并且定位到每个分区最近提交的offset位置继续消费。(High Water高水位)
2.1.3.2 如何解决 rebalance 问题
- rebalance发生前回调 -> 先将offset提交
- rebalance发生之后回调 -> 找到最新的offset位置继续消费即可
2.1.4 消费者保存offset的方式
-
Kafka0.9版本之前,offset存储在zookeeper中,0.9版本及之后的版本,默认将offset存储在Kafka的一个内置的topic中(_consumer_offsets),以便故障恢复后继续消费。除此之外,Kafka还可以选择自定义存储offset数据。offse的维护相当繁琐,因为需要考虑到消费者的rebalance过程
-
消费者会向_consumer_offset的特殊主题发送消息,消息里包含每个分区的offset。如果消费者一直处于运行状态,offset就没什么用处,用处体现在发生rebalance的时候
2.2 生产者
2.2.1 生产者动态ISR(in-sync replica set,副本集同步策略)
“动态” 指的是:
follower长时间没有向leader同步数据,则将其从ISR中踢出
2.2.2 生产者的ack
对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没有必要等到ISR中所有的follower全部接受成功。
kafka 提供了 三种级别的 ack
0
producer不等待broker的ack,这一操作提供了最低的延迟,broker接收到还没有写入磁盘就已经返回,当broker故障时有可能丢失数据
1
producer等待broker的ack,partition的leader落盘成功后返回ack,如果在follower同步成功之前leader故障,那么将丢失数据。(只是leader落盘)
-1
producer等待broker的ack,partition的leader和ISR的follower全部落盘成功才返回ack,但是如果在follower同步完成后,broker发送ack之前,如果leader发生故障,会造成数据重复。(这里的数据重复是因为没有收到,所以继续重发导致的数据重复)
3. 数据一致性
3.1 副本集中leader与followers的一致性
3.1.1 引入的概念
- LEO(Log End Offset):每个副本末端的偏移量
- HW(High Watermark):高水位,指代消费者能见到的最大的offset,ISR队列中最小的LEO。
3.1.2 如果解决follower和leader故障
- follower故障:
- 踢出ISR
- follower恢复
- follower截掉故障前HW后面的部分
- follower重新同步leader
- follower追上leader后,再次加入到ISR
- leader故障:
- ISR的followers中重新选举出一个新的leader
- 其余follower将各自log中HW后面的部分截掉
- 截掉后,重新向新的leader同步
这些个做法只能保证副本间的数据一致性,无法保证数据 不漏 或 不重
3.1.3 ExactlyOnce:解决不漏、不重的问题
At least Once:生产者ack=-1
at most once:生产者ack=0
3.1.3.1 具体实现方案
借组 kafka-v0.11引入 幂等性:
- 需要手动开启
- 将去重的操作放到了上游做(给生产者分配一个PID,对PDI&Partition&SeqNumber去重)
- 配合 at least once的语义,可以做到 exactly once
4. 高性能读写
-
Kafka的producer生产数据,需要写入到log文件中,写的过程是追加到文件末端,顺序写的方式
-
底层借助 零拷贝技术
5. Zookeeper的作用
Kafka集群中有一个broker会被选举为Controller,Controller的工作管理是依赖于zookeeper的:
- 负责管理集群broker的上下线
- 所有topic的分区副本分配和leader的选举等工作。
6. 事务
kafka从0.11版本开始引入了事务支持,事务可以保证Kafka在Exactly Once语义的基础上,生产和消费可以跨分区的会话,要么全部成功,要么全部失败。
6.1 Producer事务
为了按跨分区跨会话的事务,需要引入一个全局唯一的Transaction ID,并将Producer获得的PID(可以理解为Producer ID)和Transaction ID进行绑定,这样当Producer重启之后就可以通过正在进行的Transaction ID获得原来的PID。
为了管理Transaction,Kafka引入了一个新的组件Transaction Coordinator,Producer就是通过有和Transaction Coordinator交互获得Transaction ID对应的任务状态,Transaction Coordinator还负责将事务信息写入内部的一个Topic中,这样即使整个服务重启,由于事务状态得到保存,进行中的事务状态可以恢复,从而继续进行。
也会保存在内置的topic上…
6.2 Consumer事务
对于Consumer而言,事务的保证相比Producer相对较弱,尤其是无法保证Commit的信息被精确消费,这是由于Consumer可以通过offset访问任意信息,而且不同的Segment File声明周期不同,同一事务的消息可能会出现重启后被删除的情况。
7. API生产者流程
Kafka的Producer发送消息采用的是异步发送的方式,在消息发送的过程中,设计到了两个线程main线程和Sender线程,以及一个线程共享变量RecordAccumulator,main线程将消息发送给RecordAccumulator,Sender线程不断从RecordAccumulator中拉取消息发送到Kafka broker中。
就是一个buff: