1、业务场景介绍
数据同步的业务流程简化如下:
通过数据同步组件,通过订阅MySQL数据库的binlog日志,将订单数据先写入到kafka集群,然后下游系统根据不同的业务处理逻辑,订阅该主题,进行对应的下游业务处理。
某一天,我们通过业务监控监测出某一段时间菜鸟这边订单异常,几乎下降到0,持续半个小时左右后订单流量突增,是往常的10倍,此时Kafka集群各个节点的流量将网卡打满,达到了10G,如下图所示:
但从消息的监控平台发现,节点的写入延迟在如此大流量的冲击下保持的还不错,如下图所示:
为啥部分集群在双十一流量冲击下不堪一击,仅仅过去2个月,我们的集群表现为哈突然这么优秀了呢?
得意于从生产故障|Kafka ISR频繁伸缩引发性能急剧下降这个故障中吸取了经验教训,从而对集群的参数进行了优化,其中最重要的几个优化参数如下:
- replica.lag.time.max.ms 从默认的10s调整为60s。
- num.replica.fetchers 从默认的1调整为10。
这个两个参数为何具有这么大魔力呢?
2、这次为什么“扛住”了呢?
首先这里再来简单回顾一下笔者在双十一遇到故障的原因:频繁发生ISR收缩与扩张。
ISR收缩与扩张过程:
- 当Follower副本从落后Leader副本的数据超过replica.lag.time.max.ms设置的值,默认为10s后,该Follower副本就会从ISR集合中被移除,此时ISR集合中的副本减少,俗称“收缩”。
- 副本从ISR集合中移除后,会继续向Leader副本同步数据,当落后的数据小于10s后,该副本又会重新加入到ISR集合中,此时此时ISR集合中的副本增加,俗称“扩张”。
但Kafka在处理ISR收缩与扩张时,在更新高水位线时会加写锁,而Kafka Broker在处理消息写入、处理消息读取时更新Kafka高水位线,需要申请leaderIsrUpdateLock的读锁,ISR的伸缩、扩张与消息读取、消息消费,从节点从主节点复制这几个动作是互斥的,并发急剧下降,造成集群吞吐直线下降,从而影响Kafka集群的可用性。
故核心关键是要避免ISR的频繁发生ISR收缩与扩张,关键之关键在于频繁二字。
在大流量急剧冲击Kafka集群时,并没有频繁发生ISR,是不是副本之间数据没有延迟,这个我相信不难想到,网卡流量都打满了,要想确保Leader与Follower副本之间的数据没有延迟,还是比较困难的。
从kafka-manager系统上去观察对应topic的分区情况,发现所有分区的ISR集合中都只包含Leader,其他副本全部被剔除,但由于调高了replica.lag.time.max.ms的值,使得副本落后后,在网卡打满的情况下,想要跟上Leader节点就没这么容易,所以只出现了ISR集合的剔除(每一个分区只剩下leader本身),并没有反复出现,共消息的写入并未收到大的影响,集群健康度良好。
当然部分对kafka比较了解的读者可能会问,ISR集合中只有Leader一个节点,还能写入消息?
在这里不得不和大家脑补一下Kafka中一个非常重要的参数:acks,可选值:
- 0 表示生产者不关系该条消息在 broker 端的处理结果,只要调用 KafkaProducer 的 send 方法返回后即认为成功,显然这种方式是最不安全的,因为 Broker 端可能压根都没有收到该条消息或存储失败。
- all 或 -1 表示消息不仅需要 Leader 节点已存储该消息,并且要求其副本(准确的来说是 ISR 中的节点)全部存储才认为已提交,才向客户端返回提交成功。这是最严格的持久化保障,当然性能也最低。
- 1 表示消息只需要写入 Leader 节点后就可以向客户端返回提交成功。
首先,在我们的场景中,acks设置的是1,只要Leader节点写入成功即可,但又有人会问,这样可能会造成消息丢失,那为什么我们还在如此重要的场景(订单场景)使用acks为1呢?
因为就算集群由于断电等异常因素造成了数据丢失,这部分数据我们能够非常容易的恢复,只需要将数据同步组件回溯一下,重新解析Binlog,将数据重新写入到kafka集群即可。
这里再强调一下acks设置为all时,只需要ISR集合中的副本写入成功即可,既然选择了all,说明对数据丢失的容忍度极低,此时需要考虑如何保证不丢失消息?
只要ISR集合中的副本写入成功就成功,这取决于ISR副本中的个数,更直白一点就是如果ISR集合中只有Leader节点,那该机制不是退化为acks=1了吗?
故Kafka还提供了另外一个topic级别的参数min.insync.replicas,用来控制当acks为all时,能够往该topic写入消息必备条件:ISR集合中的最小副本数,该参数默认为1,在topic的副本数为3的情况下,通常建议将该值设置为2,表示容忍一个副本不在ISR,还能成功写入,因为数据至少会存储2份。