背景介绍:
过去的Kafka的一起故障,虽然这起规则没有引起业务上损失,但是故障后的复盘值得深思。故障表现出来的现象和真实原因相差甚远。(不要根据现象就轻易下结论)
----------------------------------------------------------------------
故障现象:
应用是基于tomcat的,应用通过部署系统发布到灰度环境后,测试没有问题后,接着应用运维将代码包部署到其中一个机房(承载流量较小),QA同学再次测试新功能,发现新功能异常(未发现新功能) 。QA同学将相关信息反馈给开发同学后,开发同学觉得应用运维并没有发布新代码,或者线上运行的是老的代码。因此问题从开发转到运维侧,应用运维观察应用日志和代码的MD5, 没有发现什么问题,开发侧,运维侧僵持地坚持自己的判断。于是介入进去,定位问题到底出现在哪个环节。多年职业经验的直觉告诉我,这个问题不简单,我们看到仅仅非常表面的现象(例如:森林地表落叶层下的盘根错节根本就看不到)。
----------------------------------------------------------------------
原因分析:
于是收集各方的反馈的信息:
1.1 运维侧:A机房灰度分组没有问题,机房A的其他的分组有问题,灰度分组的war包和其他分组的war包的MD5一致。应用日志无异常报错 。
1.2 测试QA:A机房灰度分组下代码新功能可以使用,A机房其他分组新功能无法使用。
1.3 开发侧:同样代码在不同的分组表现不一样,所以是运维侧代码发布问题,环境问题导致 。(从现象来看,确实如此,无言以对)
由于新功能不能使用并不影响先前的功能,并且A机房的流量有限, 因此没有着急回滚。
最开始怀疑进程没有重启,观察进程启动的时间后,排除掉,接着怀疑tomcat的缓存,strace 跟踪后,排除掉,对比灰度分组和正常分组下的tomcat启动日志,逐行对比后,并没有发现明显差异。开发同学反馈:之前发布出现过一次,让应用运维重启后就解决了。通过发布任务的时间追溯,发现在XX月11日,就开始存在这种现象(这种现象维持了一段时间)。开始查看11日以及之前的应用日志,发现9日存在大量异常的日志,跟kafka相关。跟开发同学沟通后,新功能跟kafka的某个topic相关(消费者需要消费该topic)。到此,问题范围已经大大缩小了。kafka的大部分topic都是正常,仅部分topic工作异常。
联系公有云kafka的研发,将kafka相关日志拿了过来,仔细分析kafka日志和应用端的日志后发现:
kafka 在9日凌晨 发生网络分区,broker3以前是controller, broker1和broker2在一个分区,broker3在另外的一个分区,此刻broker1被选举为controller, ISR(1,2), broker3 此刻无感知。zookeeper seesion timeout为4000ms (zk tick time 为:2000ms)。几分钟后,broker3在未恢复的情况,再次发生broker1网络故障,10秒后故障恢复。(典型的连续故障场景,一般的分布式组件是扛不住的)。联系公有云后,9日凌晨有网络变更,但他们评估不影响到业务方,因此没有告知使用方 (无力吐槽)。
事故的时间起点可以大体上对得上,但还需要深入分析。
----------------------------------------------------------------------
Kafka的消费过程深入分析:
消费者要消费消息,必须依赖Coordinator。(Coordinator 用于管理 Consumer Group 中各个成员,负责消费 offset 位移管理和 Consumer Rebalance)。
Consumer 在消费时必须先确认 Consumer Group 对应的 Coordinator,随后才能 join Group,获取对应的 topic partition 进行消费。
那如何确定 Consumer Group 的 Coordinator 呢?分2个阶段:
第一阶段:
一个 Consumer Group 对应一个__consumers_offsets topic的分区,首先先计算 Consumer Group 对应的__consumers_offsets 的分区,计算公式如下:
代码语言:javascript复制__consumers_offsets partition# = Math.abs(groupId.hashCode() % groupMetadataTopicPartitionCount
其中 groupMetadataTopicPartitionCount 由 offsets.topic.num.partitions 指定。
第二阶段:
阶段一 中计算的该 partition 的 leader 所在的 broker 就是被选定的 Coordinator。该案例中,连续故障的情况下,造成__consumers_offsets的部分分区ISR(3,1,2)变成ISR(2),从而Coordinator无法选出Leader,这就直接导致了消费卡死。
回到故障表现出来现象中:
由于程序端的consumer 每次启动都会生成一个新的消费者group, 从而hash到__consumers_offsets的不同分区(50个分区), 只要不要落到异常分区( 17个分区无法选出leader), 程序端就可以正常消费,新功能就正常, 因此重新部署代码都会导致应用重启,应用初始化会使用新的consumer group, 小概率性地分配到__consumers_offsets的无Leader分区的Coordinator, 因此会出现灰度分组和正常分组 一个正常,一个异常。因此从表象上看,重启可以大概率地解决问题。
----------------------------------------------------------------------
事后总结:
1. 出现异常,为什么业务日志/应用日志无任何报错?
这个里面分2个阶段:
第一阶段:
在网络分区的时候,应用端确实产生异常的日志, 但由于日志规范推动乏力(运维侧不能依靠exception, error 类型的关键字产生告警),运维侧只能将已知的错误日志纳入监控,例如kafka属于新增错误类型, 所以即使有异常日志但没有告警。
第二阶段:
应用程序的consummer连接到Coordinator, coordinator无法选出leader, 但应用层未捕获到任何异常,导致问题无法发现(kafka sdk每个开发团队使用都不一样)。
2. 为何不启用APM?
由于采用的开源pinpoint解决方案,存在较大的性能损耗,核心链路并没有开启。
3. 为什么2周后才发现这个问题?
由于Kafka使用是公有云Pass服务,使用的监控是pass提供的监控监控,pass层kafka无该层面的监控,pass端提供的kafka监控无任何异常, 导致使用方无法窥测。
----------------------------------------------------------------------
总体上来看,故障由多个因素共同影响下爆发出来。虽然故障影响较小,但反馈的问题不少:
运维侧:
1.公有云Pass层Kafka监控粒度较为粗糙,需要使用方补齐底层监控或者自建。
开发侧:
1.日志标准化建设:(日志等级/格式标准化 统一标准)
2.组件的接入层标准化接入 (公司层面统一标准),禁止自动创建消费者组
流程层面:
1.完善第三方变更通知机制
2.应用运维在确保操作无误后,发现异常情况需要记录反馈。
标准化的建设是所有技术人员逐步规范化的过程(不分开发,运维), 否则技术债务到了一定阶段会反噬业务。