注册

3个bug导致Kafka消息丢失,我人麻了

近期修复了几个线上问题,其中一个问题让我惊讶不已,发个Kafka消息居然出现了三个bug!我给jym细数下这三个bug


发送MQ消息居然加了超时熔断


在封装的发送消息工具方法中竟然添加了Hystrix熔断策略,超过100毫秒就会被视为超时。而熔断策略则是在QPS超过20且失败率大于5%时触发熔断。这意味着当QPS=20时,只要有一条消息发送超时,整个系统就会熔断,无法继续发送MQ消息。
hystrix.command.default.circuitBreaker.errorThresholdPercentage=5


HystrixCommand(
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "100"),
@HystrixProperty(name = "execution.timeout.enabled", value = "true")})
public void doSendMessage(Message message){
// 发送消息
}

之前系统一直运行正常,直到最近系统请求量上升才触发了这个bug。现在已经找不到是谁配置了这个过于激进的熔断策略了。真的非常气人!


一般情况下,发送MQ消息不会失败。但是在服务刚启动且未预热时,可能会有少量请求超过100毫秒,被Hystrix判断为失败。而恰好当时QPS超过了20,导致触发了熔断。


为什么发送MQ消息还需要加入熔断机制呢? 我很不理解啊


MQ(消息队列)本身就是用来削峰填谷的,可以支持非常高的并发量。无论是低峰期还是高峰期,只要给MQ发送端添加熔断机制都会导致数据严重不一致!我真的不太明白,为什么要在发送MQ消息时加入熔断机制。


另外,为什么要设定这么激进的熔断策略呢?仅有5%的失败率就导致服务100%不可用,这是哪个天才的逻辑呢?至少在失败率超过30%且QPS超过200的情况下,才需要考虑使用熔断机制吧。在QPS为20的情况下,即使100%的请求都失败了,也不会拖垮应用服务,更何况只是区区5%的失败率呢。


这是典型的为了熔断而熔断!把熔断变成政治正确的事情。不加熔断反而变成异类,会被人瞧不起!


吞掉了异常


虽然添加熔断策略,会导致发送MQ失败抛出熔断异常,但是上层代码考虑了消息发送失败的情况。流程中包含分布式重试方案,但是排查问题时我才发现,重试策略居然没有生效!这是什么原因?


在一番排查后我发现,发送MQ的代码 吞掉了异常信息,没有向上抛出!


去掉无用的业务逻辑后,我把代码粘贴到下面。


try{
doSendMessage(msg);
}catch(Exception e){
log.error("发送MQ异常:{}", msg, e);
//发送失败MQ消息到公司故障群!
}

消息发送异常后,仅仅在系统打印了ERROR日志,并将失败消息发送到了公司的IM群里。然而,这样的处理方式根本无法让上层方法意识到消息发送失败的情况,更别提察觉到由于熔断而导致的发送失败了。在熔断场景下,消息根本没有被发送给MQ,而是直接失败。因此,可以确定消息一定丢失了。


面试时我们经常会被问到”如何保证消息不丢“。大家能够滔滔不绝地说出七八个策略来确保消息的可靠性。然而当写起代码时,为什么会犯下如此低级的错误呢?


仅仅打印ERROR日志就能解决问题吗?将故障消息上报到公司的群里就有人关注吗?考虑到公司每天各种群里都会涌现成千上万条消息,谁能保证一定有人会关注到!国庆节放假八天,会有人关注公司故障群的消息吗?


很多人在处理异常时习惯性的吞掉异常,害怕把异常抛给上游处理。系统应该处理Rpc调用失败、MQ发送失败的场景,不应该吞掉异常,而是应该重试!一般流程都会有整体的分布式重试机制,出问题不怕、出异常也不怕,只要把问题抛出,由上游发起重试即可。


悄咪咪的把异常吞掉,不是处理问题的办法!


于是我只能从日志中心捞日志,然后把消息手动发送到MQ中。我真的想问,这代码是人写的吗?


服务关闭期间,生产者先于消费者关闭,导致消息发送失败


出问题的系统流程是 先消费TopicA ,然后发送消息到Topic B。但是服务实例关闭期间,发送TopicB消息时,报错 producer has closed。为什么消费者还未关闭,生产者先关闭呢?


这个问题属于服务优雅发布范畴,一般情况下都应该首先关闭消费者,切断系统流量入口,然后再关闭生产者实例。


经过排查,发现问题的原因是生产者实例注册了shutdown hook钩子程序。也就是说,只要进程收到Kill信息,生产者就会启动关闭流程。这解释了为什么会出现这个问题。


针对这个问题,我修改了策略,删除了生产者注册shutdown hook钩子的逻辑。确保消费者先关闭!生产者后关闭。


总结


如果有人问我:消息发送失败的可能原因,我是肯定想不到会有这三个原因的。也是涨见识了。


很多人滔滔不绝的谈着 消息不丢不重,背后写的代码却让人不忍直视!


作者:他是程序员
来源:juejin.cn/post/7288228582692929547

0 个评论

要回复文章请先登录注册