跑到第250条时消费者崩了,对账下来500条里少了一条

跑到第250条时消费者崩了,对账下来500条里少了一条
张文保 更新 26 分钟阅读 4,752 阅读
本文目录
  1. 一条消息消失的时候,系统里发生了什么?
  2. 消息还没进队列就丢了,算谁的?
  3. 为什么规范里写的是至少一次,而不是正好一次?
  4. 把取出改成两步,能救回那一条吗?
  5. 可靠性的标价是多少?
  6. 处理中列表越积越多,谁来收拾?
  7. 重复投递躲不掉,那怎么让它不出事?
  8. 幂等键该用什么当标识?
  9. 多个消费者一起跑,顺序会乱成什么样?
  10. 幂等键的有效期一过会怎样?
  11. 失败了要重试几次,间隔怎么排?
  12. 什么样的消息该被判死刑?
  13. 队列自己躺下了怎么办?
  14. 死信进了以后呢?
  15. 上线换版本的时候,队列里那些旧消息怎么办?
  16. 这一整套在独立站上要落到哪几个动作?
  17. 常见问题解答
  18. 权威参考资料
摘要:保哥往队列里推了500条消息,让消费者跑到第250条时强制中断,然后对账。取出即删的写法算下来是:已处理249条、队列里还剩250条、差额1条,那条既没处理完也不在队列里,它消失了。换成取出的同时转存到另一个列表,同样在第250条中断,对账变成249加250加1,差额为0,而且能直接指出卡住的是哪一条。这不是实现质量问题——某云厂商的文档原话是标准队列保证至少一次投递、可能重复、可能乱序,某开源代理的文档也直说朴素队列在消费者崩溃时会丢消息。本文用一台真实服务器上的六组对账数据,把丢消息、重复消费、处理中积压、重试风暴、死信这五件事逐个拆开,并给出每一件的判据和代价:可靠投递让吞吐从9343条每秒掉到4420条每秒,慢2.11倍就是可靠性的标价

上一篇聊的是要不要上队列,用的是时间账:把一个900毫秒的第三方调用挪出请求路径,响应从23.9毫秒变923.9毫秒的问题就没了。那篇的结论是大多数站没到需要队列的程度。

这一篇假设你已经上了。上了之后会遇到的第一个真问题不是性能,是你不确定消息到底送到没有

这个不确定感很难靠读文档消除,因为文档会告诉你有确认机制、有重试、有死信,但不会告诉你不用它们会丢多少。所以保哥干脆在那台服务器上把每一种失败都跑了一遍,用对账的方式看差额。

为什么坚持用对账而不是看日志?因为日志只记录发生过的事,而丢消息的本质是某件事没发生,且没人记录它没发生。一条消息在消费者内存里随进程消失时,日志上不会留任何痕迹——发送方记了发送成功,队列记了投递成功,消费者的那行处理完成压根没打出来,而没打出来的日志是不会有人去找的。

对账则不同。总数是已知的,已处理、队列剩余、处理中三个数是可查的,四个数一相加,差额藏不住。这套方法在排查服务器问题时同样好使:先找一个能闭合的等式,再看哪一项对不上。下面每一节都会给出这样一个等式。

一条消息消失的时候,系统里发生了什么?

先看最朴素的写法:生产者往列表里推,消费者从另一端取,取到就算拿走了。

实验设计很简单:推500条,消费者处理到第250条时强制中断——模拟进程被杀、机器断电、或者代码抛了个没接住的异常。然后数三个数。

项目条数
已经处理完的249
队列里还剩的250
合计499
差额1

那一条去哪了?它被取出来了,所以不在队列里;但处理还没做完,所以也没生效。它在消费者的内存里,随着进程一起没了。

这不是某个实现的缺陷。官方文档写得比我直白,说这种形式的队列并不可靠,消息可能丢失,举的例子正是消费者刚收到消息、还没来得及处理就崩了。也就是说,这个丢法是设计上就存在的,不是你写错了。

500条丢1条,比例是0.2%,听起来不大。但换个说法:每崩一次,丢一条。这跟消息总量无关,跟崩溃次数有关。一个跑了半年的消费者,重启过几十次,就是几十条订单邮件没发出去,而你完全不知道是哪几笔。

消息还没进队列就丢了,算谁的?

上面那500条是已经进了队列的。还有一段更早的路容易被忽略:生产者发出去了,但队列没收到。

网络抖一下、队列所在的进程刚好在重启、或者连接池里那条连接其实已经断了只是还没被发现——这些情况下发送方的代码不会报错,它只是把字节写进了操作系统的发送缓冲区,然后就返回了。你的日志里会记一条发送成功,而队列里什么都没有。

某开源代理的文档把这件事讲得很清楚:按标准协议,保证消息不丢的唯一办法原本是用事务,而事务太重,于是引入了一套确认机制——队列收到并落好之后回一个确认给生产者,生产者拿到确认才算数。同一份文档还补了一句边界:如果节点在消息写入磁盘之前就失效,即使标记为持久化的消息也可能丢。

换句话说,投递保证是一条链子,从生产者到队列、从队列到消费者、消费者处理完再回确认,任何一环没有确认,那一环就是漏的。本文后面测的都是第二段和第三段,但第一段同样要配上,否则前面做得再细也是从中间开始的。

落地上就一句话:生产端要开确认,并且确认失败时不能只写日志了事,得有兜底——最常见的做法是先把消息写进本地的一张表,确认成功再删。这样队列没收到时,那张表里还留着。

为什么规范里写的是至少一次,而不是正好一次?

很多人第一次接触队列时会有个朴素期待:既然是专门做消息传递的组件,那它应该保证每条消息不多不少处理一次。

行业的答案是做不到,或者说代价高到不值得。某云厂商的标准队列文档原话是:"Standard queues ensure at-least-once message delivery, but due to the highly distributed architecture, more than one copy of a message might be delivered, and messages may occasionally arrive out of order."

翻译过来是三句话:保证至少一次;可能收到多份;可能顺序乱。三句都是承诺,第二句和第三句是承诺你会遇到,不是承诺不会。

某开源消息代理的文档说得更具体,它直接给出了应对方式:"consumers must be prepared to handle redeliveries and otherwise be implemented with idempotence in mind."——消费者必须做好重复投递的准备,实现时要考虑幂等。同一份文档还提到重复投递的消息会带一个布尔属性标记,让你知道这不是第一次送。

所以正确的心态不是想办法让它不重复,是假定它一定会重复,然后让重复不产生后果。这两条路的工作量差着一个数量级。

为什么正好一次这么难?根子在于确认这个动作本身也会失败。消费者处理完了要告诉队列这条搞定了,这个告知走网络,网络会丢包。队列没收到确认,只能假定消费者没做完,于是重投。而消费者那边其实已经做完了。队列和消费者之间永远差着一个不可能同时提交的边界。

把取出改成两步,能救回那一条吗?

能。改法是取出消息的同时,原子地把它塞进另一个叫处理中的列表,处理完了再从那个列表里删掉。

同样的实验,同样在第250条中断:

项目取出即删取出转存
已经处理完的249249
队列里还剩的250250
处理中列表里的1
合计499500
差额10

不但数对上了,还能直接看到卡住的是哪一条——查一下处理中列表,里面躺着的就是它。实测那条是编号249的任务,跟中断位置完全吻合。

关键在于原子。取出和转存必须是同一个操作,不能先取出再写入。如果拆成两步,两步之间崩溃,消息还是会消失,只是概率变小了——从必然变成偶然,反而更难查。

可靠性的标价是多少?

天下没有白给的保证。同样3000条消息,两种写法的出队速率:

写法耗时出队速率会不会丢
取出即删321.1毫秒9343条/秒崩溃时丢
取出转存再删678.7毫秒4420条/秒不丢

2.11倍。原因很直白:一次操作变成两次,往返次数翻倍。

这个价该不该付,要看你的量。4420条每秒对绝大多数独立站是远远够用的,一天能处理3.8亿条。除非你确实撞到了吞吐天花板,否则这2.11倍是这篇文章里最值得花的钱。为了省一倍吞吐去接受随机丢单,属于捡了芝麻。

处理中列表越积越多,谁来收拾?

上一节留了个尾巴:那条消息保住了,但它现在躺在处理中列表里,没人管它。

保哥把崩溃重复了三次,每次都在第5条上中断:

第几次崩溃队列剩余处理中列表
第1次251
第2次202
第3次153

三次崩溃之后,处理中列表里躺着三条,实测查出来是编号14、9、4的三个任务。它们不在队列里,所以永远不会被再次取出;也没处理完,所以业务上就是没发生。比丢了更糟——丢了至少数字对不上能发现,卡在这里数字是对的,只是永远不动。

解法是加一个回收动作:定期把处理中列表里停留超过阈值的条目倒回队列。实测跑一次回收,三条全部捞回,队列从15条恢复到18条,一条不少。

这个回收动作的关键参数是阈值,也就是等多久算超时。太短会把正在正常处理的长任务误判成卡死,导致同一条被两个消费者同时处理;太长则故障恢复慢。实践中的取法是取单条处理耗时的九十九分位,再乘以三。用分位数而不是平均值,理由和上一篇量响应时间时一样,平均值会被长尾骗。

取这个分位数有个前提:你得先知道单条处理到底要多久。很多团队卡在这一步,因为消费者里根本没埋耗时统计。排查服务器变慢那篇里的思路可以直接搬过来——先有数,再谈阈值,否则设多少都是拍脑袋。保哥的建议是消费者每处理完一条就记一行耗时,攒一周再回头定阈值,这一周里先用一个明显偏大的值兜着。

顺带一提,成熟的消息代理把这套内建了。某开源代理的文档原话是,使用手动确认时,任何未被确认的投递会在对应通道或连接关闭时自动重新入队。这正是上面手写那套回收逻辑的托管版——你不用自己写超时判断,连接一断它就还回去了。内存库那边的流式结构也提供了类似的待确认清单,比手搓两个列表要省心。

重复投递躲不掉,那怎么让它不出事?

把丢消息堵上之后,问题就换了个方向:现在消息不会少,但会多。

回收机制本身就是重复的来源。一条消息因为确认丢失被判成超时,倒回队列重投,而它其实已经处理完了。保哥测了一遍最极端的情形——同一条消息连续三轮都是处理完但没来得及确认:

  • 不做任何防护:这条消息被处理了3次
  • 加一个幂等键:真正执行1次,另外2次被挡掉

幂等键的做法就一行:处理之前先尝试写一个以消息标识命名的键,条件是仅当不存在时才写。写成功说明是第一次,往下走;写失败说明有人做过了,直接跳过。

更贴近真实的一组数字是这样跑的:1000条消息,人为制造1%的确认丢失概率。

指标数值
消息条数1000
第一轮后卡在处理中列表15
回收重投后的总投递次数1015
被幂等键挡掉的次数15
真正执行的次数1000

投递次数比消息条数多了1.5%,而执行次数不多不少正好等于消息条数。这就是至少一次加幂等等于效果上的正好一次——这条等式是整个可靠投递体系的落脚点,值得记牢。

幂等键该用什么当标识?

这一步最容易做错。有人拿队列自动生成的消息编号当标识,那是没用的——重投时会生成新编号,两次投递在幂等键眼里是两条不同的消息。

标识必须来自业务,而且要和你想防的那个动作一一对应:

业务动作该用什么当标识不该用什么
发订单确认邮件订单号加邮件类型只用订单号,会挡掉发货通知
扣库存订单号加行项目编号商品编号,同一商品多单会漏扣
记一笔账业务流水号时间戳,两笔同秒的会互相挡
推送收录接口网址加当天日期只用网址,明天就推不了了

最后一行那个例子很典型。如果幂等键只用网址,那这个网址一辈子只能推一次,改了内容想重推都推不动。加上日期这个维度,幂等的粒度就变成了每天一次,既防了重复又不挡正常需求。幂等不是越严越好,是要跟业务上真正的重复定义对齐。

多个消费者一起跑,顺序会乱成什么样?

幂等解决的是同一条消息被处理多次。还有一类问题是不同消息之间的先后关系被打乱了。

场景很好想:同一个订单先后产生了创建、支付、发货三条消息。两个消费者并发取消息,一个拿到创建一个拿到支付,如果创建那条恰好慢一点,支付先处理完了——数据库里就出现了一笔付了款但订单还不存在的记录。

前面引的那份云厂商文档已经预告了这件事:消息可能乱序到达,而且它只承诺尽最大努力保持顺序。并发消费和保序天生冲突,一个消费者变两个,吞吐翻倍,顺序就没了。

能用的解法有三种,代价依次上升:

解法怎么做代价
让消息自带顺序消息里带状态和版本号,处理时校验业务代码要写状态机
按键分区同一订单的消息固定进同一个分区并发度受分区数限制
干脆不并发单消费者串行处理吞吐降到单机上限

实践中第一种用得最多,因为它不牺牲并发度。做法是消费者在处理支付消息时先检查订单是否存在,不存在就把这条消息延后重投——反正有重试机制,延后几秒它自己会回来。把保序问题转成重试问题,是这三种里最省事的一条路。

第二种适合量大且顺序要求硬的场景,代价是并发上限被分区数卡死。有个常见误区是以为加消费者就能加吞吐,实际上分区数才是天花板——十个分区挂二十个消费者,有十个是闲着的。

幂等键的有效期一过会怎样?

这是个很多人没想过的边界,保哥专门测了一次,用的是2秒有效期:

  • 第1次:执行
  • 第2次,紧接着重来:被挡掉
  • 第3次,等键过期之后再来:又执行了

结论一句话:幂等窗口只有有效期那么长。这不是缺陷,是必须做的取舍——键不设过期就会无限堆积,实测1000个幂等键大约占100KB,看着不多,但一年下来就是另一回事了。

所以有效期怎么定?规则是要盖住最长的一条重投链路。把这几个时间加起来:处理中列表的回收阈值、最大重试次数乘以最大退避间隔、再加一段富余。如果你的回收阈值是5分钟、最多重试5次、退避最长到64秒,那有效期至少要10分钟以上。设成60秒的话,一条重试到第4次的消息回来时幂等键已经没了,它会被当成新消息重新执行一遍。

另一个坑更隐蔽:幂等键和业务数据必须放在同一个可靠性等级上。上一篇查过那台机器的内存库配置,追加写关闭、快照策略为空、六天没落盘、内存打满按最近最少使用淘汰。把幂等键放在这种实例上,等于给防重复上了一道随时会消失的锁。内存一紧张,键被淘汰,重复就长驱直入了。要么开持久化,要么把幂等键写进业务库——很多团队的做法是直接在业务表上建一个唯一索引,让数据库替你挡,这招粗暴但从不失手。

失败了要重试几次,间隔怎么排?

前面处理的都是消费者自己出问题。还有一大类是下游出问题——邮件服务挂了、支付网关超时、仓储接口返回500。

这时候重试是对的,但重试的排法很有讲究。保哥拿一个必然失败的地址测了固定间隔的写法:

策略请求数总耗时对下游的压力
固定间隔重试5次5930毫秒1秒内砸5次
指数退避重试7次7127秒摊到2分钟里

请求数差不多,甚至退避还多两次,但压力完全不是一回事。

把消费者数量算进来才是真相。假设你有100个消费者,下游刚好趴下,每个消费者手里都有失败的任务在重试:固定间隔的写法会在1秒内往一个已经趴下的服务上砸500个请求。它本来可能只是短暂过载,喘口气就能起来,结果被你的重试按在地上起不来了。这个场景有个很贴切的说法叫重试风暴,它的特点是故障范围随着你的系统规模成正比放大——消费者越多,砸得越狠。

指数退避的排法是每失败一次把间隔翻倍:1秒、2秒、4秒、8秒、16秒、32秒、64秒,累计127秒。给下游留出了恢复窗口。

还有一个细节叫抖动,必须加。如果100个消费者在同一时刻开始退避,它们的第1秒、第2秒、第4秒会完美对齐,退避的效果就打了对折——压力从连续变成了脉冲,峰值没降。做法是在每次间隔上乘一个随机系数,比如0.5到1.5之间,把这100个消费者的重试时刻打散开。

本站在主动推送那篇里也遇到过同类问题:接口有配额限制,一失败就猛重试,配额消耗得比正常推送还快。用定时任务串运维动作时同理,失败重跑一定要带退避,否则一个卡住的备份能把整台机器的负载顶上去。

什么样的消息该被判死刑?

退避解决的是下游临时故障。但有一类失败重试多少次都没用——消息体本身就是坏的,比如里面那个订单号根本不存在。

这类消息如果一直放回队列,就会变成一个永动机:取出、失败、放回、再取出。它不会消失,会持续占用消费能力,队列长度看起来还挺正常,实际上消费者一直在原地打转。

解法是死信:给每条消息记一个失败计数,超过上限就转到一个专门的列表里,不再参与正常流转。保哥跑了一遍完整流程,队列里放一条必然失败的和一条正常的:

轮次消息结果
第1轮坏消息第1次失败,放回队列
第2轮正常消息处理成功
第3轮坏消息第2次失败,放回队列
第4轮坏消息第3次失败,转入死信

最终队列0条、处理中0条、死信1条。注意第2轮——坏消息在重试的间隙里没有挡住正常消息,这正是要的效果。

成熟的消息代理把这套做成了配置项。官方文档列出的转入死信的条件有四种:被消费者明确拒绝且不要求重新入队、消息自身的存活时间到期、队列长度超限被挤出、以及在特定队列类型上超过投递次数上限。

队列自己躺下了怎么办?

把生产端、消费端都堵严实之后,还剩最后一个漏点:队列这个组件本身。

保哥顺手查了那台机器上内存库的持久化配置,读数很能说明问题:追加写日志关闭,快照策略是空的,距离上一次落盘530938秒也就是6.14天,这期间累计108431次变更全在内存里,而内存打满时的淘汰策略是按最近最少使用删键。

如果有人在这个配置下把它当队列用,那前面所有的确认、回收、幂等都白做了:进程一重启,队列里没消费完的消息全部归零,而且不会有任何报错——重启是成功的,服务是正常的,只是那些消息不存在了。

更微妙的是淘汰策略那一条。它按最近最少使用删键,而队列里排在最后面、最久没被碰过的那些消息,恰恰是最容易被判定为最近最少使用的。内存一紧张,被删掉的正好是等得最久的那批。这个优先级刚好跟业务需求反着来。

所以选组件时至少要确认三件事:持久化开没开、内存策略会不会淘汰队列的键、以及队列的键和缓存的键有没有混在同一个实例里。第三条最容易中招,因为缓存实例通常是最早搭起来的,队列上得晚,顺手就用了同一个。本站写对象缓存那篇时量过这个实例的命中率和内存占用,当时的结论是给缓存用绰绰有余——但给队列用是另一套要求。

同一份文档还提到一个很容易踩的坑:死信可以配置成转发到另一个队列,而如果配置成环,消息会一直绕圈。官方的处理是检测到循环且整个环里没有出现过拒绝时,把消息丢掉。也就是说配错了不会报错,会静默丢消息——这类不报错的失败模式最难查,本站在文件监控那篇里也碰到过同一类问题。

死信进了以后呢?

死信列表最大的价值不是兜底,是它把不确定变成了确定。原来你不知道有没有消息处理失败,现在失败的都在一个地方摆着,可以数、可以看、可以告警。

配套要做三件事:死信条数一旦不为零就告警,因为正常情况下它应该长期是空的;每条死信要带上最后一次的错误原文,否则捞出来也不知道为什么死的;以及要有一个把死信重新放回队列的手动开关,修完代码之后能重放。

最后这一条很多团队会忘。修复了一个解析错误,结果发现死信里躺着的两千条消息没法重新处理,只能写一次性脚本。重放开关应该和死信机制一起做,不是等出事了再补。

上线换版本的时候,队列里那些旧消息怎么办?

这是所有队列系统迟早会撞上的一件事,而它很少被写进教程。

问题是这样的:你改了消息的字段结构,比如把收件人从一个字符串改成一个数组。新代码部署上去,队列里还躺着几百条老格式的消息。新的消费者拿到老消息,解析报错,进重试,重试三次进死信。一次正常的发版,制造了几百条死信。

根子在于队列是有状态的,而部署这个动作默认大家是无状态的。上一篇讲的那三个阶段里,任务表方案其实没这个问题——表里的行你可以先跑一条更新语句改掉,队列里的消息改不了。

能用的办法有三条:

  • 消息带版本号。每条消息里放一个版本字段,消费者按版本分支处理,老版本的分支保留一到两个发布周期再删。这是最稳的,代价是代码里会攒下一些兼容分支。
  • 只加不改。新增字段可以随便加,老消费者读不到会忽略;但不改已有字段的名字和类型,也不删。这条约束养成习惯之后,八成的兼容问题自动消失。
  • 发版前先排空。停止生产、等消费者把队列清空、再部署。最简单也最粗暴,只适合队列本来就空得快的场景。

保哥的建议是第二条当默认纪律,第一条当兜底。第三条别当常规手段——它意味着每次发版都要停一段时间的生产,而队列的初衷恰恰是解耦,为了发版重新把两端绑在一起,属于把优点用没了。

这一整套在独立站上要落到哪几个动作?

把上面五件事收敛成一张可执行的清单,按重要性排:

顺序动作解决什么不做的后果
1取出转存,别取出即删崩溃丢消息每崩一次丢一条,且无感
2关键动作加幂等键重复投递重复发邮件、重复扣库存
3处理中列表定期回收消息永久卡住数字对得上但业务没发生
4重试用指数退避加抖动重试风暴把临时故障拖成长时间故障
5加死信和条数告警坏消息占死消费能力队列看着正常,实际在空转

对独立站来说,前两条是必须的,第三到第五条可以随着量的增长逐步补。判断标准很实际:只要你的队列里跑着跟钱有关的动作,第2条就必须先做。重复发一封营销邮件是骚扰,重复扣一次库存是超卖,重复退一次款是真金白银。

还有一个容易被忽略的落点是可观测性。上一篇讲过队列长度这个数单看没用,这一篇可以补齐另外两个必看的数:处理中列表的长度死信的条数。前者长期不为零说明有消息卡住,后者不为零说明有消息彻底失败。这两个数和队列长度合起来,才算把队列的状态看全了。把消费者做成系统服务之后,进程存活也要一起监控——消费者死了而队列还在涨,是最常见的事故形态。

本站在邮件群发那篇里写过一个真实的反面案例:队列配好了,但没做幂等,一次重投把同一批订阅者的邮件发了两遍,投诉率直接把发信域名的信誉分打下来了。技术上那次投递是成功的,业务上是事故。

常见问题解答

问:小站有必要搞这么复杂吗?

看你队列里跑什么。如果只是生成缩略图、清缓存这类失败了重做一遍也没关系的事,取出即删就够,丢了下次再生成。如果跑的是发邮件、扣库存、对账这类有副作用的动作,那第1条和第2条必须做,加起来也就几十行代码。判断标准是问自己一句:这个动作重复做一次,会不会有人来投诉。

问:为什么不用事务把处理和确认包起来?

因为它们通常不在同一个系统里。业务数据在数据库,队列在另一个组件,两者之间没有共享事务。理论上有分布式事务这种东西,但代价高、依赖多、故障模式更复杂,绝大多数场景不值得。行业的主流答案就是至少一次加幂等,用一个便宜得多的机制达到等价效果。

问:处理中列表的回收阈值到底设多久?

取单条处理耗时的九十九分位再乘以三,这是个好起点。设太短的后果比设太长严重得多——一条正常处理中的长任务被误判成超时,倒回队列被另一个消费者取走,同一件事就有两个消费者在同时做。如果这时候幂等键还没做好,那就是两次扣款。所以拿不准的时候往长了设。

问:幂等键的有效期设多长合适?

必须盖住最长的重投链路。把回收阈值、最大重试次数乘以最大退避间隔加起来,再留一段富余。实测那组数据说明了后果:有效期2秒的键,过期之后同一条消息又执行了一次。如果你的退避最长排到64秒而幂等键只有60秒,那重试到最后一次的消息回来时,防线已经撤了。

问:可靠投递慢2.11倍,能不能只对重要消息用?

可以,而且是推荐做法。按消息类型分队列,发邮件、扣库存这类走可靠通道,生成缩略图、更新统计这类走快通道。这样既拿到了关键路径上的保证,又没有为不重要的消息付双倍成本。分队列还有个额外好处:一类消息堆积不会拖累另一类,缩略图积压一万条也不影响订单邮件按时发出去。

问:消息代理自带这些机制,还需要自己写吗?

确认、重试、死信这三样成熟的代理都自带,配置一下就行,不用自己实现。但幂等一定要自己做——没有任何组件能替你判断两条消息在业务上是不是同一件事,那个标识只能你自己给。这也是为什么前面那张标识对照表值得单独看一遍。

问:死信里的消息该保留多久?

比幂等键长得多,建议按周算。死信的用途是人工介入,而人工介入的响应速度取决于告警有没有人看、以及发现问题的那个人在不在。周五晚上进死信的消息,可能周一才有人处理,所以至少要能扛过一个周末。同时要给死信列表本身设长度上限并配告警,否则某次批量故障能塞进去几十万条,把存储撑爆——那时候你面对的就不是一个业务问题,是两个。

问:怎么验证这套机制真的生效了?

用本文的做法:造一批消息,故意在中间杀掉消费者,然后对账。已处理加队列剩余加处理中,三个数相加应该等于总数,差额必须是0。这个测试成本很低,但它是唯一能证明你的可靠性配置真的在工作的办法。没做过这个对账,你的可靠性配置就只是一份意图声明。

权威参考资料

分享到
标签
版权声明

本文标题:《跑到第250条时消费者崩了,对账下来500条里少了一条》

本文链接:https://zhangwenbao.com/message-queue-delivery-guarantee-retry-idempotent.html

版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交