跑到第250条时消费者崩了,对账下来500条里少了一条
本文目录
摘要:保哥往队列里推了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条中断:
| 项目 | 取出即删 | 取出转存 |
|---|---|---|
| 已经处理完的 | 249 | 249 |
| 队列里还剩的 | 250 | 250 |
| 处理中列表里的 | — | 1 |
| 合计 | 499 | 500 |
| 差额 | 1 | 0 |
不但数对上了,还能直接看到卡住的是哪一条——查一下处理中列表,里面躺着的就是它。实测那条是编号249的任务,跟中断位置完全吻合。
关键在于原子。取出和转存必须是同一个操作,不能先取出再写入。如果拆成两步,两步之间崩溃,消息还是会消失,只是概率变小了——从必然变成偶然,反而更难查。
可靠性的标价是多少?
天下没有白给的保证。同样3000条消息,两种写法的出队速率:
| 写法 | 耗时 | 出队速率 | 会不会丢 |
|---|---|---|---|
| 取出即删 | 321.1毫秒 | 9343条/秒 | 崩溃时丢 |
| 取出转存再删 | 678.7毫秒 | 4420条/秒 | 不丢 |
慢2.11倍。原因很直白:一次操作变成两次,往返次数翻倍。
这个价该不该付,要看你的量。4420条每秒对绝大多数独立站是远远够用的,一天能处理3.8亿条。除非你确实撞到了吞吐天花板,否则这2.11倍是这篇文章里最值得花的钱。为了省一倍吞吐去接受随机丢单,属于捡了芝麻。
处理中列表越积越多,谁来收拾?
上一节留了个尾巴:那条消息保住了,但它现在躺在处理中列表里,没人管它。
保哥把崩溃重复了三次,每次都在第5条上中断:
| 第几次崩溃 | 队列剩余 | 处理中列表 |
|---|---|---|
| 第1次 | 25 | 1 |
| 第2次 | 20 | 2 |
| 第3次 | 15 | 3 |
三次崩溃之后,处理中列表里躺着三条,实测查出来是编号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次 | 5 | 930毫秒 | 1秒内砸5次 |
| 指数退避重试7次 | 7 | 127秒 | 摊到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