MQ 的优点
为什么要使用 MQ 🎉 ?
- 解耦: 服务之间错综复杂的调用,子系统间耦合性太大的问题,有一个服务出现问题就会影响整个业务
- 异步: 同步调用时间过长,整个子服务都是串行化执行,一个一个按顺序执行。在没有服务之间没有联系的情况下,串行化的执行效率低下,客户体验感差。采用异步调用,整个子服务都是并行化执行,整体效率高,直接返回结果,客户体验感好。
- 消峰: 当突然出现请求峰值时,系统不稳定,可能出现宕机的情况,使用 MQ 后,能起到消峰的作用。
Kafka 纵向对比其他 MQ
Kafka 优点:
高性能: 单机测试能达到 100w tps;
低延时: 生产者和消费者的延时都很低,e2e 的延时在正常的 cluster 中也很低;
可用性高: replicate + isr + 选举机制保证;
工具链成熟: 监控 运维 管理 方案齐全;
生态成熟: 大数据场景必不可少 Kafka stream。
Kafaka 缺点:
无法弹性扩容: 对 partition 的读写都在 partition leader 所在的 broker,如果该 broker 压力过大,也无法通过新增 broker 来解决问题;
扩容成本高: 集群中新增的 broker 只会处理新 topic,如果要分担老 topic-partition 的压力,需要手动迁移 partition,这时会占用大量集群带宽;
消费者新加入和退出会造成整个消费组 rebalance: 导致数据重复消费,影响消费速度,增加 e2e 延迟;
partition 过多会使得性能显著下降: ZK 压力大,broker 上 partition 过多让磁盘顺序写几乎退化成随机写。
Kafka 消费模式
一对一
点对点的通信,即一个发送一个接收
消息被消费之后则删除 ,Queue 支持多个消费者,但对于一条消息而言,只有一个消费者可以消费,即一条消息只能被一个消费者消费。
一对多
个消息发送到消息队列,消费者根据消息队列的订阅拉取消息消费
这种模式也称为发布/订阅模式,即利用 Topic 存储消息,消息生产者将消息发布到 Topic 中,同时有多个消费者订阅此 topic,消费者可以从中消费消息,注意发布到 Topic 中的消息会被多个消费者消费, 消费者消费数据之后,数据不会被清除 ,Kafka 会默认保留一段时间,然后再删除。
Kafka 的基础架构
Producer: 消息的生产者,向 Kafka 中发布消息的角色;
**Consumer:**消息的消费者,从 Kafak 中拉去消息的客户端;
**ConsumerGroup:**消费者组,一组中存在多个消费者,消费者消费 Broker 中当前 Topic 的不同 Partition 中的消息,消费者组之间互不影响,所有的消费者都属于某个消费者组,消费者组时逻辑上的一个订阅者,某一个 Partition 中的消息只能够一个消费者组中的一个消费组所消费;
**Broker:**经纪人,一台 Kafak 服务器就是一个 Broker,一个集群时由多个 Broker 组成,一个 Broker 可以容纳多个 Topic;
**Topic:**主题,可以理解一个队列,生产者和消费者是面向以一个 Topic;
**Partition:**分区,为了实现扩展性,一个非长大的 Topic 可以分布到多个 Broker 上,一个 Topic 可以有多个 Partition,每个 Partition 是一个有序的队列(分区有序,不能保证全局有序;
**Replica:**副本 Replication,为保证集群中某个节点发生故障,节点上的 Partition 数据不丢失,Kafka 可以正常的工作,Kafka 提供了副本机制,一个 Topic 的每个分区有若干个副本,一个 Leader 和多个 Follower;
Leader: 每个 Partition 多个 Replication 的主角色,生产者发送数据的对象,以及消费者消费数据的对象都是 Leader;
**Follower:**每个 Partition 多个 Replication 的从角色,实时的从 Leader 同步数据,保持和 Leader 数据的同步,Leader 发生故障的时候,某个 Follower 会成为新的 Leader。
Kafka 的工作流程
Kafka 中消息是以 topic 进行分类的,Producer 生产消息,Consumer 消费消息,都是面向 topic 的。
Topic 是逻辑上的概念,Partition 是物理上的概念,每个 Partition 对应着一个 log 文本,该 log 文件中存储的就是 Producer 生产的数据,topic=N*partition;partition=log
Producer 生产的数据会被不断的追加到该 log 文件的末端,且每条数据都有自己的 offset,ConsumerGroup 中的每个 Consumer,都会实时记录自己消费到了那个 offset,以便出错恢复的时候,可以从上次的位置继续消费。流程:Producer => Topic(Log with offset)=> Consumer
.
文件存储
Kafka 文件存储也是通过本地落盘的方式存储的,主要是通过相应的 log 与 index 等文件保存具体的消息文件。
生产者不断的向 log 文件追加消息文件,为了防止 log 文件过大导致定位效率低下,Kafka 的 log 文件以 1G 为一个分界点,当 .log
文件大小超过 1G 的时候,此时会创建一个新的 .log
文件,同时为了快速定位大文件中消息位置,Kafka 采取了分片和索引的机制来加速定位。
在 kafka 的存储 log 的地方,即文件的地方,会存在消费的偏移量以及具体的分区信息,分区信息的话主要包括 .index
和 .log
文件组成,
分区目的是为了备份,所以同一个分区存储在不同的 broker 上,即当 third-2
存在当前机器 kafka01
上,实际上再 kafka03
中也有这个分区的文件(副本),分区中包含副本,即一个分区可以设置多个副本,副本中有一个是 leader,其余为 follower。
如果 .log
文件超出大小,则会产生新的 .log
文件。如下所示
00000000000000000000.log
00000000000000170410.index
00000000000000170410.log
00000000000000239430.index
00000000000000239430.log
此时如何快速定位数据,步骤:
.index
文件存储的消息的 offset
+ 真实的起始偏移量。.log
中存放的是真实的数据。
- 首先通过二分查找.index 文件到查找到当前消息具体的偏移,如上图所示,查找为 2,发现第二个文件为 6,则定位到一个文件中。
- 然后通过第一个.index 文件通过 seek 定位元素的位置 3,定位到之后获取起始偏移量 + 当前文件大小=总的偏移量。
- 获取到总的偏移量之后,直接定位到.log 文件即可快速获得当前消息大小。
生产者分区策略
分区的原因
- 方便在集群中扩展 :每个 partition 通过调整以适应它所在的机器,而一个 Topic 又可以有多个 partition 组成,因此整个集群可以适应适合的数据
- 可以提高并发 :以 Partition 为单位进行读写。类似于多路。
分区的原则
- 指明 partition(这里的指明是指第几个分区)的情况下,直接将指明的值作为 partition 的值
没有指明 partition 的情况下,但是存在值 key,此时将 key 的 hash 值与 topic 的 partition 总数进行取余得到 partition 值 - 值与 partition 均无的情况下,第一次调用时随机生成一个整数,后面每次调用在这个整数上自增,将这个值与 topic 可用的 partition 总数取余得到 partition 值,即 round-robin 算法。
生产者 ISR
为保证 producer 发送的数据能够可靠的发送到指定的 topic 中,topic 的每个 partition 收到 producer 发送的数据后,都需要向 producer 发送 ack acknowledgement
,如果 producer 收到 ack 就会进行下一轮的发送, 否则重新发送数据 。
发送 ack 的时机
确保有 follower 与 leader 同步完成,leader 在发送 ack,这样可以保证在 leader 挂掉之后,follower 中可以选出新的 leader(主要是确保 follower 中数据不丢失)
follower 同步完成多少才发送 ack
- 半数以上的 follower 同步完成,即可发送 ack
- 全部的 follower 同步完成,才可以发送 ack
副本数据同步策略
半数 follower 同步完成即发送 ack
优点是延迟低
缺点是选举新的 leader 的时候,容忍 n 台节点的故障,需要 2n+1 个副本(因为需要半数同意,所以故障的时候,能够选举的前提是剩下的副本超过半数),容错率为 1/2。
全部 follower 同步完成完成发送 ack
优点是容错率搞,选举新的 leader 的时候,容忍 n 台节点的故障只需要 n+1 个副本即可,因为只需要剩下的一个人同意即可发送 ack 了
缺点是延迟高,因为需要全部副本同步完成才可
kafka 选择的是第二种,因为在容错率上面更加有优势,同时对于分区的数据而言,每个分区都有大量的数据,第一种方案会造成大量数据的冗余。虽然第二种网络延迟较高,但是网络延迟对于 Kafka 的影响较小。
ISR(同步副本集)
猜想
采用了第二种方案进行同步 ack 之后,如果 leader 收到数据,所有的 follower 开始同步数据,但有一个 follower 因为某种故障,迟迟不能够与 leader 进行同步,那么 leader 就要一直等待下去,直到它同步完成,才可以发送 ack,此时需要如何解决这个问题呢?
解决
leader 中维护了一个动态的 ISR(in-sync replica set),即与 leader 保持同步的 follower 集合,当 ISR 中的 follower 完成数据的同步之后,给 leader 发送 ack,如果 follower 长时间没有向 leader 同步数据,则该 follower 将从 ISR 中被踢出,该之间阈值由 replica.lag.time.max.ms 参数设定。当 leader 发生故障之后,会从 ISR 中选举出新的 leader。
生产者 ack 机制
对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没有必要等到 ISR 中所有的 follower 全部接受成功。
Kafka 为用户提供了三种可靠性级别,用户根据可靠性和延迟的要求进行权衡选择不同的配置。
ack 参数配置
0
:producer 不等待 broker 的 ack,这一操作提供了最低的延迟,broker 接收到还没有写入磁盘就已经返回,当 broker 故障时有可能丢失数据1
:producer 等待 broker 的 ack,partition 的 leader 落盘成功后返回 ack,如果在 follower 同步成功之前 leader 故障,那么将丢失数据。( 只是 leader 落盘 )
-1(all):producer 等待 broker 的 ack,partition 的 leader 和 ISR 的 follower 全部落盘成功才返回 ack,但是如果在 follower 同步完成后,broker 发送 ack 之前,如果 leader 发生故障,会造成数据重复。(这里的数据重复是因为没有收到,所以继续重发导致的数据重复)
producer返ack,0无落盘直接返,1只leader落盘然后返,-1全部落盘然后返
数据一致性问题
- LEO(Log End Offset) :每个副本最后的一个 offset
- HW(High Watermark) :高水位,指代消费者能见到的最大的 offset,ISR 队列中最小的 LEO。
- follower 故障和 leader 故障
- **follower 故障:**follower 发生故障后会被临时提出 ISR,等待该 follower 恢复后,follower 会读取本地磁盘记录的上次的 HW,并将 log 文件高于 HW 的部分截取掉,从 HW 开始向 leader 进行同步,等待该 follower 的 LEO 大于等于该 partition 的 HW,即 follower 追上 leader 之后,就可以重新加入 ISR 了。
- **leader 故障:**leader 发生故障之后,会从 ISR 中选出一个新的 leader,为了保证多个副本之间的数据的一致性,其余的 follower 会先将各自的 log 文件高于 HW 的部分截掉,然后从新的 leader 中同步数据。
这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复
这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于