V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
dumbbell5kg
V2EX  ›  程序员

请教一个面试题: MQ 顺序消费时出错,怎么处理?

  •  
  •   dumbbell5kg · 1 天前 · 3572 次点击

    大致意思就是:MQ 顺序消费时,消息 2 依赖消息 1 ,消息 3 依赖消息 2 ,如果消息 2 出错了,后续还有几十万条消息,应该怎么处理?

    • 我给出的方案是后续的消息先记到表里,等消息 2 能成功消费了,再重放表里的记录。
    • 面试官回到:“它数量如果特别大的话,你要持久化能做下去吗?我们 MQ 的几十万条消息这边都在堵死,你发现出问题是两天前了,才会发现的报错,然后数据库整个都已经堵死在里面了。”

    下面是面试官的原话:

    • 假如我这个消息有顺序问题,前面一二三必须有依赖性,二依赖一,三依赖二,这时候如果前面的数据稍微出问题,能保证后面如果再读的话就发不出去了,因为比如说我有三个 update 语句,通过这种队列去消费了,然后你肯定要第一条消费完之后第二条消费对吧?不然第一条消费失败了,你直接拿第二条消费,还有第三条消费成功,那你怎么解决这个问题?
    • 他第一条消费失败了,你怎么能处理呢,就保证他不会消费第二条或者是直接放在那里。
    • 没成功的话就 hang 在那边,包括我们整个消费就不执行了,直接直接就业务就卡死了,只要你数据代码有问题,然后启动一下消费者,后面几万几十万进去都卡在那不动了。
    35 条回复    2025-04-03 15:10:32 +08:00
    poltao
        1
    poltao  
       1 天前
    根据他的回答来看用的不还是消息队列本身的机制嘛(加上业务消费出错直接报异常),没搞明白他问这个问题的意义是什么,这个面试题不好
    blackstack
        2
    blackstack  
       1 天前   ❤️ 11
    感觉他这个设计是有问题的,如果队列同时存在 N 个消费者,他又要怎么去控制消息消费的前后顺序??

    整个题目看起来是在一个错误的设计上,把一个问题复杂化,再寻找一个解决的方法来纠正整个错误。
    wangtian2020
        3
    wangtian2020  
       1 天前
    事务回滚不行吗
    meshell
        4
    meshell  
       1 天前
    和二楼的想法一样。
    blackstack
        5
    blackstack  
       1 天前
    如果他每次依赖的消息数量是固定的,比如固定 3 个,那消费者每次取 3 条消息,取出后就进行消息确认,再对消息内的数据进行处理。

    如果在处理的过程出现异常,将这三条存在异常的消息发到另外一个专门处理异常的队列,由另外一个消费者来处理这些异常数据。

    如果他连每次依赖的消息数量都不固定,我还是认为这个设计是存在问题的。
    lyxxxh2
        6
    lyxxxh2  
       1 天前
    [laravel - 任务链]( https://learnku.com/docs/laravel/10.x/queues/14873#f62992)
    框架自带的队列,一般都有任务链吧。
    SeaTac
        7
    SeaTac  
       1 天前 via iPhone
    别的不说 光是这个“发现出问题是两天前了”
    没有 alarm 么
    在我眼里能拖两天的问题说明不是啥严重的问题
    SeaTac
        8
    SeaTac  
       1 天前 via iPhone
    另外 mq 自带 dlq 吧 把 message 放进去呗
    loveaeen
        9
    loveaeen  
       1 天前
    tag 按照依赖进行消息区分,同类型打到一个分区里,减少队列堆积数。
    然后要不就发到死信做二次消费,还报错就转人工吧。
    ytmsdy
        10
    ytmsdy  
       1 天前
    同意二楼的观点,这个消息队列设计的就是有问题的。为什么要设计一个相互依赖的消息队列?
    为什么不在设计队列的时候,把一整个消费动作都整理成一个?反正一个出错了,整个事务都是要混滚的。
    Mithril
        11
    Mithril  
       1 天前   ❤️ 1
    @SeaTac 是的,几十万的消息堵了两天都没发现,说明这玩意压根就不重要,属于平时就没人看的东西。这种堵了就堵了,没出啥问题就删了完事。
    barnetime
        12
    barnetime  
       1 天前
    你不如问他 怎么保证消息的顺序性, 发送的时候时 123, 到 mq 变成了 213 (
    StephenCurryII
        13
    StephenCurryII  
       1 天前   ❤️ 2
    有些面试官问的就是他们开发中遇到的问题,但可能本身就是设计缺陷,却要应聘者找答案给他解决,这种解决,每个人的方式都不一样,我能给你解决鸡毛啊。
    Mithril
        14
    Mithril  
       1 天前
    @ytmsdy 这种设计感觉就像是在用 NoSQL 去做 RDBMS 的活。好好的关系型数据库的事务和回滚机制不用,非要自己在程序里面加一堆的锁来回绕。
    me1onsoda
        15
    me1onsoda  
       1 天前
    不理解为什么会 hang 住,默认已经实现按序消费,1 消费了,消费 2 出错发回队列,后续的消息也重新发回队列,还是顺序消费的
    abc950309
        16
    abc950309  
       1 天前
    主流 MQ 都有顺序保证机制的,比如 Kafka 的 Message Key 。如果消费侧配置的没问题,就可以确保同一 Message Key 下的消息消费是有序的。
    z1829909
        17
    z1829909  
       1 天前 via Android
    他这里设计有点拧巴,让消息之间有逻辑上的依赖可以,但是不代表有依赖就要等前置项消费完。
    可以接收到一个消息找个地方暂存,然后这条消息就 ack ,当最后一个消息到,校验下是否收齐了,然后 merge 。
    z1829909
        18
    z1829909  
       1 天前 via Android   ❤️ 1
    你的方案是对的,几十万消息根本就是特别低的量级,找个表存一下。
    换句话说,如果他们的量只有几十万,直接同步做算了,为啥要整成异步,结果还在用同步的思想硬整异步的逻辑。
    bronyakaka
        19
    bronyakaka  
       1 天前
    1 、重试
    2 、出错的消息标记为失败并入库,后续手动处理
    3 、如果是强依赖,那根本不能消费处理后面的消息,直接告警算了。弱依赖就先消费存着呗
    siweipancc
        20
    siweipancc  
       1 天前 via iPhone
    架构错误,为什么这么设计?
    prime2015
        21
    prime2015  
       1 天前
    这种是需要对方付咨询费的
    younger027
        22
    younger027  
       1 天前
    @loveaeen 哈哈哈 转人工实在是没绷住
    Scarb
        23
    Scarb  
       1 天前
    我觉得这种就不该用顺序消息,而是用事务。因为他这样顺序消费实际上只允许一个消费节点同步执行这些步骤,用消息来处理没什么意义。如果一定想用消息作为服务间通信,那就发一条消息,然后收到消息的服务用事务来执行这些步骤。

    用顺序消息的情况下怎么都不好。
    1. 重试——如果这个消息一直消费不成功一直重试,那会阻塞后面的业务消息消费
    2. 找个表存——如果 2 执行失败,3 实际上不应该执行。如果只把 2 存起来,执行 3 ,那执行结果不是你期望的。如果 2 和 3 都要存起来,那消费 3 的时候怎么去判断 2 失败了并且 3 依赖 2 呢?
    woodfizky
        24
    woodfizky  
       1 天前   ❤️ 1
    这就是他们实际业务遇到的问题吧哈哈,面试官自己头疼怎么解决呢拿来问你了。

    关键是消息队列还要考虑消息消费的顺序和依赖性,这就把问题复杂化了。
    怎么判断消息前后是否还有消息需要依赖?或者怎么做能够把依赖体现在消息内?
    这也是设计需要考虑的。

    如果非要这么干,那我个人倾向于,这些消息识别到后,用别的资源去处理,别干扰正常消息的消费。
    比如消费正常队列的时候,判断出来某些消息是有依赖顺序的,那就再推到专门的特殊队列里,或者干脆就直接按需存数据库。
    等最后一条消息推送进来,特殊队列的消费端程序能够判断没有后续依赖了,再整个链条一起拎出来处理。
    但是这样对延时有影响。不过看面试官的说法,这个等了两天才发现,也是有点离谱,应该要求不高。
    fyxtc
        25
    fyxtc  
       1 天前
    搁这跟我玩循环引用呢,是想让设计 gc 是吧

    消息队列首先是一个队列,队列能保证什么?顺序呗,把其他的任务分配到队列上增加复杂性就违反了 KISS
    事务:那我走?
    bk201
        26
    bk201  
       1 天前
    我的想法是第一个所有依赖的消息走一个 tag ,消费失败了一个后续全部进数据库或者 retry 队列里做重试。第二个改掉消息顺序依赖的方式,对相关的顺序操作走同一个消息做顺序执行,消息只做触发。
    yvyvyv
        27
    yvyvyv  
       1 天前
    认同#24 说的 "他们实际业务遇到这个问题了",可能是屎山要炸,还没想好怎么解决,直接将问题抛给面试的。
    三个消息需要有序执行,这本来就应该用同步单线程做的事放到一个事务中,这样也能避免多个消费者导致错序。
    也可以
    合并成一个消息,被消费者拿到后将消息交给逻辑 1 执行,执行后通过事件或者当前消费者服务器的 queue 交到 2 逻辑,同上交到 3 ,同时这个消息需要一个唯一标识,出错记录 db 中。如果 1 出错了也不会发送事件到 2-3 。人工也好,搞个 soc 预警也好。监控下错误日志即时排查呗
    ckdxc
        28
    ckdxc  
       23 小时 32 分钟前
    @Scarb
    @woodfizky
    我也没明白他那个依赖是整个队列都有顺序的依赖, 还是就 局部一些消息有依赖
    msg3 依赖 msg2, msg2 依赖 msg1, msg4 不依赖 msg3 了, 这样就类似分片了, 只需要做好分片管理, 接收分片 1 的时候创建分片任务(存内存, 或者数据库), 分片处理失败, 就回调一个失败回去呗, 让生产方重传整个分片, 或者通知到用户业务失败了

    如果真的是整个队列都是一条一条的有依赖关系, 那也只能是存起来, 有专门的程序处理, 或者人工处理

    突然想起来, 我这里也有个, 数据的增量同步, 中间有失败的, 真的就是阻塞后面的增量数据了,
    不过有个全同步机制, 可以按数据表级别, 同步到对端,
    有一个环境, 没人管, 积压了 100 多 G 数据在增量同步表里面, 不过是积压在生产者这边

    既然依赖关系这么强, 建议每条消息都增加处理标识, 要么生产者拉取一下结果, 失败了就不发 msg3 了, 要么消费者自己调回调, 具体看业务, 可能有些业务, 能定时核对, 保证正确, 有些就必须得人工介入处理
    qxmqh
        29
    qxmqh  
       23 小时 32 分钟前
    这很明显设计上有问题,我估计就是屎山,可能也不是他设计的,但是他遇到了,想问你,保不齐就要爆炸了。很大可能即使你入职了,第一个解决的问题就是这个问题。
    liprais
        30
    liprais  
       23 小时 30 分钟前
    这面试官自己都没搞懂就在这装了,别理他就行了,真做系统的哪有这么搞的
    X2S2
        31
    X2S2  
       23 小时 26 分钟前
    同意 27 楼的

    我猜测他的场景可能是消息重放,比如物流状态的变更。我怀疑是不是同一家面试的

    如果出现异常,将 异常数据及后面依赖的消息 直接或者指定重试次数后报警并写入数据库(可以把一组数据的标识 和 msgid ,消息内容等写入)。
    例如出问题的是 1 ,将 1 写入异常数据库,如果消费 2 的时候,判断异常数据库中是否有 1 ,有就先写入,不做消费。
    写入的数据,人工再处理。



    # 如果使用的是 rocketmq 的顺序消息
    1 、顺序发送
    理论上在集群环境,生产者不唯一,那么发送到 broke 队列里的消息顺序可能是乱序的。
    基于他的场景,应该 1 ,2 ,3 这种顺序大概率存在时间差,可能不用考虑乱序。
    如果要保证顺序发送,可能采用的是记录日志,然后使用定时任务或者 timer 来发送,并且分布式锁保证任务只有一个节点执行,保证发送者的唯一进而保证消息发送的顺序性。

    2 、顺序消费
    rocketmq 客户端通过 申请 broke 锁保证一个消费者拉取消息、通过对消费队列加锁保证一个线程可以做消费。
    如果出异常,因为顺序消息重试次数默认-1 即一只重试,所以会阻塞队列。这种情况主动报警,并记入数据库,后续如果还有依赖的消息,直接标记为异常,同样记入数据库。
    julyclyde
        32
    julyclyde  
       23 小时 24 分钟前
    @liprais
    sampeng
        33
    sampeng  
       23 小时 23 分钟前
    这就是死区队列的用法啊。mq 不自带,aws 的 sqs 是可以开启死区队列。处理消息的时候发现有问题放入死区队列。另外有消费者单独处理死区队列按业务逻辑进行处理就好了,即不会堵后面的,后面的发现前置任务错误直接快速失败也和 redis 等东西配合也可以快速做到。mq 也可以做到,就是比较麻烦而且原子处理没研究过是不是要一些特殊处理,大概逻辑就是这样。
    strivezheng
        34
    strivezheng  
       6 小时 18 分钟前
    加一个 redis 缓存池,所有的消息都要入池。如果 2 失败了,当消费到 3 时,根据依赖关系向前溯源(在缓存池中查数据),把关联的 123 都重新投递到消息队列进行消费。这样既保证了消息不会丢失,也能不阻塞
    ayogo
        35
    ayogo  
       45 分钟前
    @ytmsdy 他们的那套感觉像是纯粹的在原有数据上进行加减式更新,太奇怪了。对于依赖性强的数据,必须得在中间插一个校验和同步的数据条目,之前写过一个远程屏幕的方案就是用对画面进行分割,然后检查每个区域相比上一帧是否有更新,结果发现一旦出现丢包那么就会出现严重的糊屏,必须定期加同步帧来确保画面没问题。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5280 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 07:55 · PVG 15:55 · LAX 00:55 · JFK 03:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.