我的上家公司是做餐饮系统的,每天中午和晚上用餐高峰期,系统的并发量不容小觑。为了保险起见,公司规定各部门都要在吃饭的时间轮流值班,防止出现线上问题时能够及时处理。
我当时在后厨显示系统团队,该系统属于订单的下游业务。用户点完菜下单后,订单系统会通过发kafka消息给我们系统,系统读取消息后,做业务逻辑处理,持久化订单和菜品数据,然后展示到划菜客户端。这样厨师就知道哪个订单要做哪些菜,有些菜做好了,就可以通过该系统出菜。系统自动通知服务员上菜,如果服务员上完菜,修改菜品上菜状态,用户就知道哪些菜已经上了,哪些还没有上。这个系统可以大大提高后厨到用户的效率。
事实证明,这一切的关键是消息中间件:kafka,如果它有问题,将会直接影响到后厨显示系统的功能。
接下来,我跟大家一起聊聊使用kafka两年时间踩过哪些坑?
最近无意间获得一份BAT大厂大佬写的刷题笔记,一下子打通了我的任督二脉,越来越觉得算法没有想象中那么难了。
BAT大佬写的刷题笔记,让我offer拿到手软
顺序问题 1. 为什么要保证消息的顺序?刚开始我们系统的商户很少,为了快速实现功能,我们没想太多。既然是走消息中间件kafka通信,订单系统发消息时将订单详细数据放在消息体,我们后厨显示系统只要订阅topic,就能获取相关消息数据,然后处理自己的业务即可。
不过这套方案有个关键因素:要保证消息的顺序。
为什么呢?
订单有很多状态,比如:下单、支付、完成、撤销等,不可能下单的消息都没读取到,就先读取支付或撤销的消息吧,如果真的这样,数据不是会产生错乱?
好吧,看来保证消息顺序是有必要的。
2.如何保证消息顺序?我们都知道kafka的topic是无序的,但是一个topic包含多个partition,每个partition内部是有序的。
如此一来,思路就变得清晰了:只要保证生产者写消息时,按照一定的规则写到同一个partition,不同的消费者读不同的partition的消息,就能保证生产和消费者消息的顺序。
我们刚开始就是这么做的,同一个商户编号的消息写到同一个partition,topic中创建了4个partition,然后部署了4个消费者节点,构成消费者组,一个partition对应一个消费者节点。从理论上说,这套方案是能够保证消息顺序的。
一切规划得看似“天衣无缝”,我们就这样”顺利“上线了。
3.出现意外该功能上线了一段时间,刚开始还是比较正常的。
但是,好景不长,很快就收到用户投诉,说在划菜客户端有些订单和菜品一直看不到,无法划菜。
我定位到了原因,公司在那段时间网络经常不稳定,业务接口时不时报超时,业务请求时不时会连不上数据库。
这种情况对顺序消息的打击,可以说是毁灭性的。
为什么这么说?
假设订单系统发了:”下单“、”支付“、”完成“ 三条消息。
而”下单“消息由于网络原因我们系统处理失败了,而后面的两条消息的数据是无法入库的,因为只有”下单“消息的数据才是完整的数据,其他类型的消息只会更新状态。
加上,我们当时没有做失败重试机制,使得这个问题被放大了。问题变成:一旦”下单“消息的数据入库失败,用户就永远看不到这个订单和菜品了。
那么这个紧急的问题要如何解决呢?
4.解决过程最开始我们的想法是:在消费者处理消息时,如果处理失败了,立马重试3-5次。但如果有些请求要第6次才能成功怎么办?不可能一直重试呀,这种同步重试机制,会阻塞其他商户订单消息的读取。
显然用上面的这种同步重试机制在出现异常的情况,会严重影响消息消费者的消费速度,降低它的吞吐量。
如此看来,我们不得不用异步重试机制了。
如果用异步重试机制,处理失败的消息就得保存到重试表下来。
但有个新问题立马出现:只存一条消息如何保证顺序?
存一条消息的确无法保证顺序,假如:”下单“消息失败了,还没来得及异步重试。此时,”支付“消息被消费了,它肯定是不能被正常消费的。
此时,”支付“消息该一直等着,每隔一段时间判断一次,它前面的消息都有没有被消费?
如果真的这么做,会出现两个问题: