一种围绕kafka实现的通知消息重传机制的分析

背景:通信相关业务,在与B端系统(有多个B端)对接过程中,有一类通知消息需要及时准确的送达B端,用作计费、话统及其他数据分析,大致模型如下

一种围绕kafka实现的通知消息重传机制的分析_第1张图片

为什么需要重传?

在公网上,由于网络瞬时不稳定、双方业务系统瞬时中断等原因,经常会出现B端未成功接收到Consumer发送的通知,由于关联计费等重要场景,必须通过重传重新获取。

消息流向?

首先明确消息的流向,在一次通话业务触发后,业务系统产生一条通知,并由Producer生产至kafka中的指定Topic A中。

Consumer 是一个微服务的多个实例,即有多个Consumer进行消费(为了负荷分担及高并发),Consumer消费到某一条消息后,立即发送给B端,成功则已,不成功则触发重传。

发送到B端失败后, 如何重传?

这里有几种方式:

1、将这条消息重新扔回Topic A中,再次由Consumer进行消费。

2、将这条消息扔进失败重传的专有队列Topic B中,再次由Consumer进行消费。

3、将这条消息在Topic A中的partition、offset等信息记录到Topic Failure中,再由特定的Failure Consumer进行消费。

重传策略?

1、多次重传,总计失败6次后持久化到DB中,用于后续手动触发重传。

2、延时重传,如第一次失败后,第二次在1分钟后发出,第二次失败在4分钟后发出,第三次失败在9分钟后发出。

最初的实现?

将这条消息重新扔回Topic A中,再次由Consumer进行消费,毫无疑问的简单粗暴,且对其他消息会有影响,使得Topic A中的消息处理时间变得不可预测,实际业务开发过程中,只有在产品初期由于时间及人力原因的妥协下,勉强用了一阵,在后期商用过程中,问题及短板巨大,优点也只有代码量少了。 

一种围绕kafka实现的通知消息重传机制的分析_第2张图片

 

 

劣势:

1、使得消息的处理时间变得不可预测,极端情况下,两条正常消息中间可以插入很多的重传消息,使得正常消息处理停滞。

2、使得队列Topic A持久化占用磁盘过大,极易导致消息丢失。

3、Consumer实例CPU占用高,类似死循环的生产-消费。

4、在加入<延时重传>机制后,队列极易被未到重传时间的消息大比例占用,消息发送机制几乎崩溃。

重构(重写)优化思路:

首先分析痛点:

1、正常消息与重传消息糅合,且重传消息影响正常消息的处理。

2、重传消息占用大量持久化磁盘空间(一条消息大约1KB)。

3、<延时重传>的实现

针对痛点1:

将重传消息剥离Topic A,使其在Topic Failure中进行处理,Topic A扔按照 一次<生产-消费>进行,每条消息消费一次即完成。

一种围绕kafka实现的通知消息重传机制的分析_第3张图片

改造后,Topic A队列恢复正常,但Topic Failure的问题仍存在,痛点2、3并未解决。

针对痛点2:

由于重传消息与原始消息几乎完全一致,理论上只需要记录原始消息的topic、partition、offset即可取得,无需将消息拷贝再额外记录在Topic Failure中,于是即有下述改造:

一种围绕kafka实现的通知消息重传机制的分析_第4张图片

在Topic Failure中只记录原始消息的topic、partition以及offset,Failure Consumer消费到此消息后,通过一个独立的Seek Consumer在原Topic中取出原始信息,并进行处理,Topic Failure的持久化磁盘占用问题基本解决,痛点3仍未解决。

针对痛点3:

在多次失败场景较少时,可以采用重传取30s或1min为一个周期,Failure Consumer每隔一个周期消费一次,取出的消息若到达重传时间,则发送出去,否则再生产进入Topic Failure中。

此方案在较低失败场景下,通常可以满足重传需要,但 吃-吐 的方式难以让人舒服接受,于是有以下思考:

1、能否按照重传周期,拆分出几个相同重传时间间隔的队列?

2、在1的基础上,通过让Consumer线程睡眠的方式等待重传?

3、借助Redis实现定时重传?

针对第一点:理论上可以,但需求如果需要 “支持B端定制重传时间间隔”?过度的定制会更多的限制功能拓展。

针对第二点:线程睡眠需要考虑消息队列中,各消息是否严格按照时间轴排列,若出现队列第一条消息需要1小时后重传,而第二条需要1分钟后重传,则会带来很严重的阻塞问题。另,Failure Consumer需要同步配置,超出Failure Consumer消费最长周期而不消费,会导致Failure Consumer断连。

针对第三点:理论上可行,Failure Consumer所在实例订阅redis超时事件,在存在需要重传的消息时,将原始消息的信息(topic\partition\offset)记入redis,并设定超时时间为此次重传间隔时间即可,但由于当前需求不强烈,并未诉诸实践。

额外的问题或限制

1、B端需要定制重传次数、重传间隔

2、不同B端对我方发送的消息存在并发限制

本文旨在分享工作中有关kafka的使用,针对功能实现及kafka的使用如有疑问可在评论区讨论~

你可能感兴趣的:(kafka,java,分布式,消息队列)