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

请问有必要用专门的消息队列吗? all in sqldb 是否可行?

  •  1
     
  •   Need4more · 14 天前 · 2951 次点击

    首先是 message 表:

    字段名 类型及描述
    id 主键
    status 消息状态(待处理,进行中,成功,失败)
    try_count 消费者重试次数
    lock_expires 锁过期时间
    created 创建时间
    data 消息数据

    然后消费端轮询:

    select *
    from message
    where status in ('PENDING', 'STARTED', 'FAILED')
    and try_count < max_tries
    and lock_expires < now()
    order by created
    limit 1 for update
    skip locked;
    

    解释下 sql:

    1. 状态筛选 (status IN ('PENDING', 'STARTED', 'FAILED')) 只选取未开始或已开始但未完成的任务(可能因崩溃需重启)。
    2. 重试保护 (try_count < max_tries) 确保任务重试次数未超过允许的最大值(避免无限重试失败任务)。
    3. 锁过期检查 (lock_expires < NOW()) 任务被处理时会加锁(设置未来过期时间),此条件筛选锁已过期的任务(说明此前处理进程异常退出)。
    4. 排序与限量 (ORDER BY created LIMIT 1) 按创建时间排序后取最旧的一条任务,实现公平调度。
    5. 并发控制 (FOR UPDATE SKIP LOCKED)
      • FOR UPDATE:锁定所选行,防止其他进程修改。
      • SKIP LOCKED:跳过已被其他进程锁定的行,避免阻塞等待,提升并发效率。
      • 此语法在 postgresql 、mysql(8.0)均支持。

    利用关系数据库持久化消息,支持索引,可以灵活检索,关键是无需额外引入组件,请问这种方案是否可行呢?

    31 条回复    2025-07-05 22:31:24 +08:00
    chesha1
        1
    chesha1  
       14 天前
    为啥不用 redis ,redis 做消息队列不是比 sqldb 更成熟的方案吗?
    nulIptr
        2
    nulIptr  
       14 天前
    看你侧重哪部分功能,简单少量数据的场景随便怎么搞都行,无需引入额外组件也确实是一个优点,古时候在金蝶做某个项目的时候真是你这种用法,除了数据库是 sqlserver 。
    不过消息队列主要还是数据量大的时候为了高吞吐量,就要花点心思了。
    julyclyde
        3
    julyclyde  
       14 天前
    举个例子:
    一个消息需要被
    不定量的消费者
    分别读取

    就这个“不定量”,你用关系型数据库就不好搞吧
    Need4more
        4
    Need4more  
    OP
       14 天前
    @julyclyde 再增加一个订阅表即可
    sub_id:消费者标识
    last_msg_id:最近一次消费的消息
    status: 消费状态

    每增加一个消费者就在这个表添加条记录
    Need4more
        5
    Need4more  
    OP
       14 天前
    @chesha1 redis 不支持持久化
    Need4more
        6
    Need4more  
    OP
       14 天前
    @nulIptr 是的,当你的系统达到那么大规模的时候,再招个运维也不是啥问题了。在此之前,我倾向于用最少的组件交付业务。
    HENQIGUAI
        7
    HENQIGUAI  
       14 天前
    不是很理解为什么说 Redis 不支持持久化
    Need4more
        8
    Need4more  
    OP
       14 天前
    @HENQIGUAI
    redis 的持久化保证没有数据库强,同时 pub/sub 不可靠
    codingKingKong
        9
    codingKingKong  
       14 天前   ❤️ 1
    1.首先看规模, 如果数据量不大, 资源竞争不严重, 我同意其他人的观点, 怎么搞都行
    2.然后还是看规模, 如果都上到 sql 上, 发现性能能接受, 那其实就是合适的方案
    3.如果流量再高些, 请考虑 redis, 你可以不用它的 push, pop 这些来实现, 但是它能提供比高并发下 mysql 更好的性能, 毕竟它是做缓存用的
    4.如果还有些特殊需求, 请考虑上专业的延迟队列, 例如延迟消费, 自动重试, 精确消费, 至多至少消费语义等, 这些不是 sqldb 不能实现, 而是你需要自己实现, 你需要考虑性价比
    最后, 是否有必须要这个问题需要你自己考虑, 不同场景下答案不一样
    ratazzi
        10
    ratazzi  
       14 天前
    可行啊,Rails 8 的 Solid Queue 就是这样的

    https://dev.37signals.com/solid-queue-v1-0/
    neilq
        11
    neilq  
       14 天前
    恭喜你发现了 `本地消息表`

    完全可以这么做
    neilq
        12
    neilq  
       14 天前
    另针对 redis 消息队列补充一句,现在 redis 5.0 以上不推荐使用 pub/sub ,pub/sub 确实不能持久化。

    现在 redis 消息队列推荐使用 redis streams 。不过需要自己实现清理消息的机制。

    redis 服务器压力没那么大的情况下不需要考虑持久化稳定性,尤其在你`系统没那么大规模`的情况下。
    zepc007
        13
    zepc007  
       13 天前
    All In Postgres
    banmuyutian
        14
    banmuyutian  
       13 天前
    可以呀,我司在用的 solace 就是这么实现的
    moyupai
        15
    moyupai  
       13 天前
    完全可以,但完全没必要,
    量不大,用啥都行
    量大,你要想想数据库怎么扩喽
    8355
        16
    8355  
       13 天前
    数据库性能难道不是比队列或者 redis 更贵吗。。。
    这到底是轻量化需求还是受环境限制?
    mightofcode
        17
    mightofcode  
       13 天前
    业务量小 实时性要求低
    其实没啥问题
    bbao
        18
    bbao  
       13 天前
    redis 从来不推荐你做消息队列使用,sql 做消息队列, 内部系统随便你;

    toC 系统,HR 这个人不合适,我们启动招聘吧。
    cookii
        19
    cookii  
       13 天前
    多一个消息队列多一个维护成本,优先在已有的技术栈上进行业务实现。性能扛不住了再换到消息中间件。
    唯一注意的是代码做好抽象,以便未来切换。
    z1829909
        20
    z1829909  
       13 天前
    消费端从数据库获取消息 + 标记消息状态 不是原子性的, 会导致一个消息同时被多个地方执行吧
    z1829909
        21
    z1829909  
       13 天前
    超时时间也不是很合理, 因为对于不同的任务, 能忍受的长度不一样, 感觉还是约定一个心跳间隔, 只要消息还在被处理, 就定时更新这条记录某个字段的时间. 这样当这个字段超过心跳间隔没有更新, 视为处理的进程已经无了.
    Need4more
        22
    Need4more  
    OP
       13 天前
    select for update 会锁住记录,确保只会被单个地方消费,skip locked 确保不会阻塞消费者
    @z1829909
    z1829909
        23
    z1829909  
       13 天前
    @Need4more GET, 那你更新 status 和 select 是在一个事务里对么
    kaffka
        24
    kaffka  
       13 天前
    可以考虑添加成熟的 PG mq 拓展
    spritecn
        25
    spritecn  
       13 天前
    没 redis leftPop+rightPush 好用,还有 3/5/10/15 秒后重试你这个不太好实现,fro update 如果消息处理时间稍长,一直占着连接是个问题
    fionasit007
        26
    fionasit007  
       13 天前
    @HENQIGUAI #7 用的阿里云 redis ,六七年的数据都还在,不过因为数据量太大不知道有没有丢数据的情况
    spritecn
        27
    spritecn  
       13 天前
    @fionasit007 阿里 1G 一年 500 块,我好像 10 分之 1 都用不到
    fionasit007
        28
    fionasit007  
       13 天前
    @spritecn #27 个人的话我是肯定不用,挺贵的,不过这样的云服务用着就是省心,阿里云 redis 反正这么多次事故好像都幸免于难
    mightybruce
        29
    mightybruce  
       13 天前
    如果就是简单的消息队列需要落盘, 不如采用 redis + rocksdb 持久化的项目 kvrocks
    https://github.com/apache/kvrocks

    https://kvrocks.apache.org/docs/supported-commands/ 支持 redis 消息操作。
    darrenxyli
        30
    darrenxyli  
       12 天前
    我们现在就是这么干的,做过压测,瓶颈全在数据库能够支持的 QPS 上面
    Need4more
        31
    Need4more  
    OP
       12 天前
    @darrenxyli 性能能抗住吗?
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5310 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 01:24 · PVG 09:24 · LAX 18:24 · JFK 21:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.