请教一个面试题: MQ 顺序消费时出错,怎么处理? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
dumbbell5kg
V2EX    程序员

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

  •  
  •   dumbbell5kg 191 天前 4906 次点击
    这是一个创建于 191 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

    下面是面试官的原话:

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

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

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

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

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

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

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

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

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

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

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

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

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



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

    2 、顺序消费
    rocketmq 客户端通过 申请 broke 锁保证一个消费者拉取消息、通过对消费队列加锁保证一个线程可以做消费。
    如果出异常,因为顺序消息重试次数默认-1 即一只重试,所以会阻塞队列。这种情况主动报警,并记入数据库,后续如果还有依赖的消息,直接标记为异常,同样记入数据库。
    julyclyde
        32
    julyclyde  
       191 天前
    @liprais
    sampeng
        33
    sampeng  
       191 天前
    这就是死区队列的用法啊。mq 不自带,aws 的 sqs 是可以开启死区队列。处理消息的时候发现有问题放入死区队列。另外有消费者单独处理死区队列按业务逻辑进行处理就好了,即不会堵后面的,后面的发现前置任务错误直接快速失败也和 redis 等东西配合也可以快速做到。mq 也可以做到,就是比较麻烦而且原子处理没研究过是不是要一些特殊处理,大概逻辑就是这样。
    strivezheng
        34
    strivezheng  
       190 天前
    加一个 redis 缓存池,所有的消息都要入池。如果 2 失败了,当消费到 3 时,根据依赖关系向前溯源(在缓存池中查数据),把关联的 123 都重新投递到消息队列进行消费。这样既保证了消息不会丢失,也能不阻塞
    ayogo
        35
    ayogo  
       190 天前
    @ytmsdy 他们的那套感觉像是纯粹的在原有数据上进行加减式更新,太奇怪了。对于依赖性强的数据,必须得在中间插一个校验和同步的数据条目,之前写过一个远程屏幕的方案就是用对画面进行分割,然后检查每个区域相比上一帧是否有更新,结果发现一旦出现丢包那么就会出现严重的糊屏,必须定期加同步帧来确保画面没问题。
    CoderChan
        36
    CoderChan  
       190 天前
    @barnetime 顺序问题 mq 本身有机制
    barnetime
        37
    barnetime  
       190 天前
    @CoderChan 如果需要严格顺序, mq 是不能保证的
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3148 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 10:51 PVG 18:51 LAX 03:51 JFK 06:51
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86