如何保证消息只被消费一次? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Lullaby
V2EX    Java

如何保证消息只被消费一次?

  •  1
     
  •   Lullaby 2015-12-25 10:59:10 +08:00 14665 次点击
    这是一个创建于 3629 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近用 redis 的 pub/sub 做消息队列,开启多个 MQ 应用进程,由于使用的是发布订阅者模式,多个监听者(消费者)如何保证只有一个监听器接收到消息并消费?

    34 条回复    2015-12-26 11:04:20 +08:00
    evanmeng
        1
    evanmeng  
       2015-12-25 11:08:58 +08:00   1
    这不就是要做 Exactly Once 吗?虽然有很多绕过去的做法,但直接的回答就是一句:不能。
    surfire91
        2
    surfire91  
       2015-12-25 11:11:13 +08:00   1
    考虑过走队列吗?哪个消费者拿到了,哪个消费者处理。
    Lullaby
        3
    Lullaby  
    OP
       2015-12-25 11:13:21 +08:00
    @evanmeng 意思是只能 producers / consumers ?
    Lullaby
        4
    Lullaby  
    OP
       2015-12-25 11:15:04 +08:00
    @surfire91 就是走队列,只是借用了 redis 的 pub / sub ,生产消费者模式能保证只有一个消费者拿到并消费
    evanmeng
        5
    evanmeng  
       2015-12-25 11:19:10 +08:00
    在生产者和消费者都有可能当机的现实网络,要保证无数据丢失的 Exactly Once 是做不到的。
    常用的做法无非是:用一个 Zookeeper 之类的分布式锁记录已发送信息, pub 要 sub 发送确认,在多个 sub 一方去重。
    @Lullaby
    est
        6
    est  
       2015-12-25 11:21:21 +08:00   8
    @mathiasverraes

    There are only two hard problems in distributed systems:

    2. Exactly-once delivery
    1. Guaranteed order of messages
    2. Exactly-once delivery


    https://twitter.com/mathiasverraes/status/632260618599403520
    odirus
        7
    odirus  
       2015-12-25 11:21:57 +08:00   1
    无法保证的,建行当年花那么多钱都无法保证,还是需要去重,别去踩坑了。
    yangmls
        8
    yangmls  
       2015-12-25 11:25:30 +08:00   1
    为什么不用 redis 的 lpop ?
    Lullaby
        9
    Lullaby  
    OP
       2015-12-25 11:47:55 +08:00
    @evanmeng pub 要 sub 发送确认是什么意思?
    Lullaby
        10
    Lullaby  
    OP
       2015-12-25 11:48:43 +08:00
    @yangmls 看到支持 pub/sub 就直接扯来用了
    moe3000
        11
    moe3000  
       2015-12-25 11:57:45 +08:00   1
    不要用 pub/sub 用 list 操作
    xiamx
        12
    xiamx  
       2015-12-25 12:02:06 +08:00   1
    无法保证, CAP Theorem: https://en.wikipedia.org/wiki/CAP_theorem
    love
        13
    love  
       2015-12-25 12:02:16 +08:00   2
    只能绕过这个问题。

    比如以前我们的做法:
    队列向一个 consumer 发一个消息后,把这个消息设置为正在处理,并设置一个倒计时,时间到后 consumer 没有回发确认处理完毕的消息的话再放回队列以便给别的 consumer 再处理。
    xiamx
        14
    xiamx  
       2015-12-25 12:06:17 +08:00
    嗯 @love 说的是一个比较常见的折中方案了
    surfire91
        15
    surfire91  
       2015-12-25 12:12:37 +08:00
    @Lullaby 我懂了,你是用 pub/sub 来实现消息队列。 pub/sub 就没法保证只消费一次,还不如用 LIST 。
    xufang
        16
    xufang  
       2015-12-25 12:22:50 +08:00 via Android   1
    简单一点用数据库, sql 的 acid 特性可以保证。 单点问题可以通过主从来实现。

    复杂一些上 zk 或 etcd ,这个前面有人提到了。
    xufang
        17
    xufang  
       2015-12-25 12:48:27 +08:00
    见我 /t/233785 的回复,和这贴可以互相验证。
    zacard
        18
    zacard  
       2015-12-25 12:59:20 +08:00   1
    一个订阅者即可啊。
    SparkMan
        19
    SparkMan  
       2015-12-25 12:59:57 +08:00   1
    还是使用生产者消费者模式吧,否则就算加锁来保证,效率也大大降低了
    eimsteim
        20
    eimsteim  
       2015-12-25 14:00:59 +08:00   1
    不开启多个 MQ 应该能解决这个问题吧?如果单个 MQ 没有性能瓶颈的话,就不要开启多个了撒
    Lullaby
        21
    Lullaby  
    OP
       2015-12-25 14:11:35 +08:00
    @xufang
    贴里说的消息不安全,前排 @evanmeng 提到了: "在生产者和消费者都有可能宕机的现实网络,要保证无数据丢失是做不到的",所以需要用备用手段来保证消息通知到位,比如定时轮询几分钟之前的消息状态,如果消息过时且未被消费,则需要再 consume 一次
    不过各系统是否需要这样做都跟自己的业务相关
    defage
        22
    defage  
       2015-12-25 14:18:20 +08:00   1
    你这是订阅模式,跟 broadcast 一样, 如果在 rabbitmq 中就是 fanout

    要做的比较多的策略, 还是用 rabbitmq 这种消息队列服务把
    pokolovsky
        23
    pokolovsky  
       2015-12-25 14:41:42 +08:00   1
    量子信息能保证只能消费一次。
    ybdhjeak
        24
    ybdhjeak  
       2015-12-25 14:46:42 +08:00   1
    pub/sub 不合适用作队列,在没有订阅者的频道 pub 时会失败,如果要求强实时的话,加快 RPOP 的频率就行, redis 的操作都是原子的
    xujif
        25
    xujif  
       2015-12-25 14:51:51 +08:00   1
    这个适合用队列
    dingyaguang117
        26
    dingyaguang117  
       2015-12-25 14:52:33 +08:00 via iPhone   1
    使用姿势不对,应该用 list 的 bpop
    Lullaby
        27
    Lullaby  
    OP
       2015-12-25 15:05:23 +08:00
    @defage
    @pokolovsky
    @ybdhjeak
    我想问一下 如果要求队列保证 FIFO 怎么办 话说 order 对性能损伤很大
    haogefeifei
        28
    haogefeifei  
       2015-12-25 15:09:05 +08:00   1
    java 用 synchronized 可以搞定
    est
        29
    est  
       2015-12-25 15:52:17 +08:00
    @pokolovsky 没法保证的。首先你如何检测量子丢包?
    ybdhjeak
        30
    ybdhjeak  
       2015-12-25 23:58:23 +08:00   1
    @Lullaby rpush lpop 啊
    julyclyde
        31
    julyclyde  
       2015-12-26 09:55:22 +08:00   1
    @love 说的那种方法,可以通过简单的把 mq 替换为 beanstalkd 来实现
    不过还那句话:考虑到 sub 死的时候都喊不出来,这事不可能完全实现
    sbpcx
        32
    sbpcx  
       2015-12-26 10:10:17 +08:00   1
    最近也在撸,用的 list 。 pop 的话可以卡看 rbpop ,但是阻塞需要酌情考虑。
    Muninn
        33
    Muninn  
       2015-12-26 10:34:42 +08:00   1
    不用非得用 redis 啊 那个就只能订阅模式的
    用 rabbitmq 之类的专业队列
    或者如果是 python 项目 用 celery+redis 就好了
    sbpcx
        34
    sbpcx  
       2015-12-26 11:04:20 +08:00
    顺便 搭车一问: rbpop 是阻塞的,如果 queue 比较多,也有好多阻塞的,那会不会造成 redis 的链接过大呢?

    现在看到如何监听 redis 队列的时候,主要想到了三种想法:
    1 、用 rbpop ,阻塞监听,但是会不会碰到链接等待直至过大。
    2 、采用 spring data reis 中的 messageListener 方法,大致看了下他的代码,他好像是用的线程池“轮询”的?是吗?
    3 、使用一个线程或者线程池在跑监听(类似于循环跑“轮询”),其他的监听队列注册到这个线程或者线程池上面去,当前者发现消息了,就告知后者,类似于 Actor 模式这种的。

    求指导。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     902 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 21:37 PVG 05:37 LAX 13:37 JFK 16:37
    Do have faith in what you're doing.
    ubao msn 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