V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Lullaby
V2EX  ›  Java

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

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

    最近用 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 redis 中的 messageListener 方法,大致看了下他的代码,他好像是用的线程池“轮询”的?是吗?
    3 、使用一个线程或者线程池在跑监听(类似于循环跑“轮询”),其他的监听队列注册到这个线程或者线程池上面去,当前者发现消息了,就告知后者,类似于 Actor 模式这种的。

    求指导。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2053 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 16:16 · PVG 00:16 · LAX 08:16 · JFK 11:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.