共计 3612 个字符,预计需要花费 10 分钟才能阅读完成。
咱们在应用 mq
的时候,就会很天然思考一个问题:怎么保证数据不失落?
当初 austin
接入层是把音讯发到 mq
,下发逻辑层从mq
生产数据,随后调用 对应渠道接口 来下发音讯。
音讯推送平台🔥推送下发【邮件】【短信】【微信服务号】【微信小程序】【企业微信】【钉钉】等音讯类型。
- https://gitee.com/zhongfucheng/austin/
- https://github.com/ZhongFuCheng3y/austin
音讯抛弃个别咱们思考的是 生产端,于是重点看的是下发逻辑层。
(因为对于 mq
应用方来说:生产端只有配置 mq
相干的参数,在调用下发时有回调重试机制。那就足够了,生产端能做的货色的确不多)
目前为止,下发逻辑层 (生产端) 应用的是 主动提交 offset策略。只有生产端存在零碎重启或者过程被 kill
掉,那就会有丢音讯的状况。
spring.kafka.consumer.enable-auto-commit=true
以后下发逻辑层 (生产端) 有可能放大了这个抛弃音讯的问题,因为当初是生产到 mq
数据后,会把音讯给到线程池去解决。线程池会指定一个阻塞队列,那队列数量越大,可能由重启所抛弃的音讯就越多。
这里我的策略是:当利用重启的时候,零碎里的线程池是 优雅敞开 的(尽可能期待一段时间,等阻塞队列里没有音讯了,再敞开线程池)。
但回到问题的实质上,只有生产端是主动提交 offset 策略,就肯定会有丢音讯的问题。所以要做到生产端的音讯不丢,咱们就要设置为手动提交 offset,这个是必要条件。
有没有必要保障不丢
在探讨具体的技术实现计划之前,咱们来看看在业务上有没有必要保障音讯不丢。我刚接触到音讯推送平台的时候,过后那个交接的哥们通知我和我学长:音讯少发比多发要好。
1、重要的音讯用户很可能会 手动重试触发。
austin
是一个发送各类渠道音讯的平台,从我的教训来说,这外面最重要的是 短信渠道 。通过austin
下发很可能是登陆验证码,银行卡提现验证码,这类音讯从全局上看是最重要的。
而其余渠道,例如 push 告诉栏的告诉音讯,微信渠道的营销音讯,这种音讯即使用户没收到,也不会对用户带来很大的应用体验问题。这种音讯或者对绝大数用户都是无感知的(少发几条,用户可能更乐意)。
咱们先假如用户的某一次银行卡提现的验证码 恰好因为咱们重启零碎而抛弃 。这时候,绝大数用户可能狐疑本人的信号问题, 会持续操作,从新发送一次。
(因为客服常常找我排查这种问题,每次都能看到有好几条下发记录。当然了,能到技术的,99% 的问题都不是由零碎重启失落音讯导致的,更多可能是用户的客户端自身的确就存在问题)
2、音讯是 有时效性 的。比方验证码这种短信个别就 5min 的时效性,因为零碎的问题,你超过这个工夫给用户发送,对用户的体验是十分差的。
3、音讯推送平台是有 全链路追踪 的,是能够晓得下发的音讯有没有到达到用户手上,至多都能够晓得在咱们的零碎外部执行过程中有没有丢。如果这条音讯真的那么重要,那能够 独自为抛弃的音讯独自做重发解决,这些性能在音讯推送平台都是反对的。
这个问题我以前的共事也跟我探讨过,就是把下面的内容给我隔壁的老哥听的,他说:你就尽扯淡吧,到面试的时候人家可不认你,丢了就是丢了,其余都是借口。
我说:没事,要是不认的话,就把咱们解决订单那一套给他讲讲嘛,反正解决的思路都是一样的。
不过啊,广告订单逻辑解决又绝对没那么简单,广告订单最初是以 入数据库 作为规范的,又能够承受肯定的提早,只有能保障解决完就行了。
要想 client 端生产数据不能丢,必定是不能应用 autoCommit 的,所以必须是手动提交的。
<span style=”color:#000080″>候选者</span>:咱们这边是这样实现的:
<span style=”color:#000080″>候选者</span>:一、从 Kafka 拉取音讯(一次批量拉取 500 条,这里次要看配置)时
<span style=”color:#000080″>候选者</span>:二、为每条拉取的音讯调配一个 msgId(递增)
<span style=”color:#000080″>候选者</span>:三、将 msgId 存入内存队列(sortSet)中
<span style=”color:#000080″>候选者</span>:四、应用 Map 存储 msgId 与 msg(有 offset 相干的信息)的映射关系,通过 msgId 用来获取相干元信息
<span style=”color:#000080″>候选者</span>:五、当业务解决完音讯后,ack 时,获取以后解决的音讯 msgId,而后从 sortSet 删除该 msgId(此时代表曾经解决过了)
<span style=”color:#000080″>候选者</span>:六、接着与 sortSet 队列(本地内存队列)的首部第一个 Id 比拟(其实就是最小的 msgId),如果以后 msgId<=sort Set 第一个 ID,则提交以后 offset
<span style=”color:#000080″>候选者</span>:七、零碎即使挂了,在下次重启时就会从 sortSet 队首的音讯开始拉取,实现至多解决一次语义
<span style=”color:#000080″>候选者</span>:八、会有大量的音讯反复,但只有上游做好幂等就 OK 了。
<span style=”color:#ab4642″>面试官</span>:嗯,你也提到了幂等,你们这业务怎么实现幂等性的呢?
<span style=”color:#000080″>候选者</span>:嗯,还是以解决订单音讯为例好了。
<span style=”color:#000080″>候选者</span>:幂等 Key 咱们由订单编号 + 订单状态所组成(一笔订单的状态只会解决一次)
<span style=”color:#000080″>候选者</span>:在解决之前,咱们首先会去查 Redis 是否存在该 Key,如果存在,则阐明咱们曾经解决过了,间接丢掉
<span style=”color:#000080″>候选者</span>:如果 Redis 没解决过,则持续往下解决,最终的逻辑是将解决过的数据插入到业务 DB 上,再到最初把幂等 Key 插入到 Redis 上
<span style=”color:#000080″>候选者</span>:显然,单纯通过 Redis 是无奈保障幂等的(:
<span style=”color:#000080″>候选者</span>:所以,Redis 其实只是一个「前置」解决,最终的幂等性是依赖数据库的惟一 Key 来保障的(惟一 Key 实际上也是订单编号 + 状态)
<span style=”color:#000080″>候选者</span>:总的来说,就是通过 Redis 做前置解决,DB 惟一索引做最终保障来实现幂等性的
保障 austin 数据不丢须要做什么?
保证数据不丢简略来说,就是咱们要在生产端手动 ack offset
,不能再用主动提交策略了。这样当咱们零碎重启时,kafka
会主动从未 ack
的offset
中拉取。
如果要实现音讯推送平台不丢音讯的话,有几个问题是须要思考的:
1、音讯少发比多发要好,那么要实现音讯不丢,就 必须要在零碎内实现幂等 。因为当初的音讯不丢,个别都是基于【至多一次] 生产语义去做的。
2、那实现幂等的逻辑是在调用渠道下发接口前,还是渠道下发接口后?
如果做在下发接口前,那是不是会有可能第一次下发记录写入了,但理论调用下发接口却失败了,前面的重试都被幂等解决掉了。
如果做在下发接口后,那是不是会有可能调用调用下发接口胜利了,但写入幂等解决的音讯失败了,前面的重试就会导致音讯多发
3、音讯是有时效性的,那 如果重试的解决工夫过长,那是不是要思考把这条音讯给抛弃掉,不再重试了。
4、重试的音讯不应该影响到失常音讯的下发,他得作为一种弥补的机制,而非主流程。
略微细想下技术实现,应该不太好搞,还有很多细节的中央得关注到。比方业务上的:应该是不须要所有的渠道的所有类型音讯都得实现音讯不丢吧?当初的设计是谋求高性能的 ,能在短时间内下发批量的音讯。而 如果做到所有音讯不丢,必定会影响到下发的速率。
什么时候入手?
1、对于这个性能吧,有用必定是有用,但 这性能又没那么急。
2、我 估摸对现有代码改变还是蛮大 的,当初我还没想好该怎么实现比拟好,也始终没下手。
3、最近工作的事挺多的,没那么有空。
论断:先看看想要这个性能的人多不多,不多就鸽一会。
都看到这了,如果按下面的理由,我不实现这个性能,你认不认可?
音讯推送平台🔥推送下发【邮件】【短信】【微信服务号】【微信小程序】【企业微信】【钉钉】等音讯类型。
- https://gitee.com/zhongfucheng/austin/
- https://github.com/ZhongFuCheng3y/austin