剖析Linkedln遭遇的Kafka“危机故障”

Apache Kafka在LinkedIn是作为各种数据管道和异步消息的后端被使用的。除了LinkedIn,Netflix和Microsoft也是Kafka的重量级使用者(Four Comma Club,每天万亿级别的消息量)。

虽然Kafka有着极其稳定的架构,但是在每天万亿级别消息量的大规模下也会偶尔出现有趣的bug。本文深度分析了过去几年在LinkedIn所遭遇的重大“危机故障”。在这里阐述Kafka的“重大”bug产生的根本原因(多种bug、不正常的客户端行为和监控不当多种因素相互作用导致的)是“后见之明”(有点像“马后炮”的意思),但分享出来可以给广大同仁以借鉴。

本文将讲述如何探测、研究和修复这些问题,并总结出Kafka的一些特色以供将来消除或者减缓类似的bug。

1.Offset Rewinds

Kafka的broker分配到每条消息的偏移量都是单调增加的,它会追加到每个topic的分区日志。Kafka的consumer分发消费任务(“fetch”请求)时会指定从哪个偏移位置(offset)开始消费。当一个“fetch”请求所指的偏移量(offset)越界,将会收到OffsetOutOfRange错误回复。

然后,consumer根据auto.offset.reset配置参数自动把偏移量重置为最小偏有效移量(the earliest valid offset)或者最新有效偏移量(the latest valid offset),见下图所标示。这种偏移量的重置对线上应用有重大影响——重置为最小偏移量会引起重复消费数据;重置为最新偏移量意味着有丢失消息数据的潜在可能(偏移量重置到下次“fetch”请求之间到达到消息)。 

Kafka的consumer会周期性地checkpoint每个topic分区消费者的偏移量(i.e. 位置信息)。当consumer因某种情况重启后,consumer能从最后一次checkpoint重新消费。

比如,如果consumer失败了,consumer会从最近一次checkpoint重新消费;或者如果更多的分区加入到topic,跨消费实例的分区分布发生变化,这时consumer也会从最近一次checkpoint重新消费。在Kafka 0.8.2中引入consumer的偏移量管理(offset management)。早在2015年早期,Kafka的consumer偏移量管理仍然还是个新功能,而且作者也会偶尔出现偏移量重置的现象。解决这个难题是比较棘手的,也从而导致其他bug的修复,包含偏移量管理、日志压缩和监控。

2.如何发现Offset Rewinds

Kafka的broker并未维护客户端的错误度量(比如,offset reset),但是有几个指标会出现,比如,当consumer失败后重置为最小偏移量时会有偏移量“倒回”。服务端最明显的指标是:消息输出量非常大,而相应的输入量并未增加,如下图所示: 

客户端最明显的指标是consumer延迟突然增加,在这个故障中,作者注意到Kafka跨集群同步(mirror maker,是Kafka内置的跨集群同步工具)延迟监控上出现“毛刺”。 

延迟监控有点棘手,因为并不是所有的延迟“毛刺”都会引起警报。延迟趋势监控是相当重要的,你可以使用LinkedIn提供的延迟监控服务Burrow(Kafka消费延迟监测)。

3.第一次“危机故障”

CRT (Change Request Tracker)[]是LinkedIn持续集成发布平台,用来监控构建和发布服务,并发出邮件。在七月初,许多开发者(包括作者在内)反应收到重复的CRT邮件。与之相关的原因是包含发布事件的Kafka topic复制管道出现偏移量重置。

偏移量重置(offset reset)的一个典型的原因是未知的leader选举。当一个分区的leader broker失败,而其它复制副本也并未跟上,那就会发生这种情况。一个未同步的复制不得不接管leader,同时导致日志截断。这意味着,正在读取日志尾部的consumer发生突然越界的错误。最好的做法是监控集群的未知leader选举率。但是在七月份这次故障中并未看到任何未知leader选举。 

然而,consumer的日志很明显的显示好几个分区有偏移量重置的情况。为了debug这个故障,我们需要懂得consumer偏移量管理是如何工作的。

4.Consumer偏移量管理

Consumer通过发送OffsetCommitRequest请求到指定broker(偏移量管理者)提交偏移量。这个请求中包含一系列分区以及在这些分区中的消费位置(偏移量)。偏移量管理者会追加键值(key-value)形式的消息到一个指定的topic(__consumer_offsets)。key是由consumerGroup-topic-partition组成的,而value是偏移量,如下图所示。内存中也会维护一份最近的记录,为了在指定key的情况下能快速的给出OffsetFetchRequests而不用扫描全部偏移量topic日志。如果偏移量管理者因某种原因失败,新的broker将会成为偏移量管理者并且通过扫描偏移量topic来重新生成偏移量缓存。在下面的例子中,mirror-maker消费topic(PageViewEvent)到分区0的位置321。 

偏移量topic是会日志压缩的,这意味着旧的冗余项最终会被清除。因为有些瞬时consumer(比如,控制台consumer)来来回回的,偏移量管理者会周期地扫描偏移量缓存,并移除死亡consumer group的偏移量项(比如,在配置指定周期内未进行更新)。这个过程也会在偏移量topic日志中追加一个公告来通知下次进行日志压缩的时候就从持久化的日志中移除。 

5.调试故障

下面是偏移量重置时间周围的消费日志摘要:  

在偏移量发生重置之前出现了几个consumer的rebalance。Rebalance一般发生在consumer离开或者加入consumer group,或者新的topic或分区变成可以消费的情况。

在rebalance期间,consumer依次经过:停止消费数据;提交它们的偏移量;跨group重新分配分区;从新所属的分区获取偏移量;重新消费数据。在前面的打印日志中,initOffset所在行会指出consumer将会从哪个位置开始消费。前两个rebalance从有效的偏移量开始重新消费,而第三个rebalance试图从明显是旧偏移量消费。当调试这些故障时,最好能在消费者控制台使用以下命令dump偏移量的topic(因为日志压缩会随时发生): 

记住在配置中设置exclude.internal.topics值为false,因为偏移量topic是一个内部的topic。

进一步深入的研究下偏移量topic的dump发生了什么: 

无效偏移量(6811737)被checkpoint是在这次故障一个月之前。但是最近也有有效的checkpoint返回了偏移量。那为什么偏移量管理者会返回一个很长时间的旧偏移量?

偏移量管理者打印的日志清晰的指出:在加载偏移量到偏移量缓存中时,偏移量管理者发生了移动,随之发生了问题。 

这是偏移量topic消息的格式发生改变时的一个bug。在缓存中加入过期的偏移量之后,偏移量管理者会短暂移动。这也是为什么偏移量获取会返回过期的偏移量。

6.重复邮件的缘由

在这个故障中,mirror maker设置的auto.offset.reset为“latest”,按理来说应该不会出现重复消费数据。所以需要进一步的看看包含发布事件的topic到底发生了什么? 

虽然这个topic展示了雷同的错误——i.e.,在早期的rebalance之后偏移量“fetch”到当前的偏移量,而最近的rebalance之后偏移量“fetch”到了过期的偏移量,虽然过期的偏移量是一个月之前的,但它仍然是有效的。

最开始大家对这个问题比较吃惊,因为作者在Kafka集群设置的topic保留时间为四天。最后发现,发布记录的topic数据量太小,没有触发基于数据量大小的阈值。基于时间的保留依赖于最后的修改时间,但是在小数据量topic时无效。

偏移量管理bug最近修复了。

7.第二次故障

这次的故障发生在数据发布管道中:Hadoop的push-jobs发送数据到CORP环境的(比如,非生产环境)Kafka中,然后镜像到PROD来为线上服务来消费。作者收到了延迟警报,mirror maker因为某种原因卡住了。许多重复的消息发送到下游,也就意味着出现偏移量“倒回”。 

consumer打印的日志显示偏移量“fetch”返回 -1 (这意味着没有偏移量被checkpoint)。换句话说,偏移量管理者丢失了以前的一些checkpoint偏移量: 

偏移量管理者打印的日志提供了一些帮助: 

偏移量管理者发生移动,偏移量缓存加载了17分钟。偏移量topic是非常小的,正常只需要几秒钟就可以处理完。这很明显的说明日志压缩过程因为某种原因发生中断,造成未checkpoint的偏移量topic一直在增长。

这种情况发生不会导致已经checkpoint的偏移量信息丢失。在加载过程中偏移量“fetch”收到错误代码意味着加载过程有问题,consumer只是重试偏移量“fetch”。在这个故障中,偏移量“fetch”实际返回的意思是“没有有效的偏移被找到”。

这证明了日志压缩过程时间太久,有些旧偏移量仍然在偏移量topic中。偏移量缓存加载过程会把这些旧偏移量加载进缓存。然而它自己认为在日志里有更多最近的偏移量最终会覆盖旧偏移量项,所以没啥问题。

问题就在这里,在长时间的偏移量加载中旧偏移量的清除任务被杀掉,并且在清除掉旧偏移量项后会在日志尾部添加通知。偏移量加载过程继续加载最近的偏移量到缓存中,但是只有当它发现追加的通知才会移除这些旧偏移量。这也旧解释了为什么偏移量总是丢失。

8.结论

基于Kafka的偏移量管理是Kafka consumer默认的偏移量管理机制。很好的理解偏移量管理是如何工作的对使用Kafka是非常有用的。

简单总结如下: 

Consumer延迟警报对监控consumer健康和探测偏移量“倒回”是非常必要的。Burrow会很好的帮助你做好每个consumer的延迟监控服务。激活监控日志压缩度量也是非常重要的,特别是max-dirty-ratio。

其它的度量offset-cache-size, commit-rate和group-count sensors也会对偏移量管理有帮助。当出现偏移量“倒回”,就要对topic(__consumer_offsets)进行dump。你也需要检查未知leader选举以及偏移量管理者和consumer打印日志

文章标签: Kafka 消息 LinkedIn


关注微信公众号“架构说”,加入Q群微群,让架构师带你飞︿( ̄︶ ̄)︿。


原文链接: 阅读原文
免责申明: 架构说任何转载的文章都会明确标注原文链接。如有侵权,请与本站联系。
转载说明: 架构说原创文章转载时请务必注明文章作者、链接和来源。