0%

消息队列设计精要

消息队列设计精要

文章引用:美团技术团队博客

实现消息队列的基本功能

  • 解决的核心问题

    • 解耦
    • 最终一致性
    • 广播
    • 错峰与流控 - “通用漏斗”
  • RPC通信协议 - 避免重复造轮子,使用现有RPC框架(thrift,dubbo等)

  • 高可用

  • 服务端承载消息对接的能力

  • 存储子系统选择

    理论上,从速度来看,文件系统>分布式KV(持久化)>分布式文件系统>数据库,而可靠性却截然相反。还是要从支持的业务场景出发作出最合理的选择,如果你们的消息队列是用来支持支付/交易等对可靠性要求非常高,但对性能和量的要求没有这么高,而且没有时间精力专门做文件存储系统的研究,DB是最好的选择。 但是DB受制于IOPS,如果要求单broker 5位数以上的QPS性能,基于文件的存储是比较好的解决方案。整体上可以采用数据文件+索引文件的方式处理。 分布式KV(如MongoDB,HBase)等,或者持久化的Redis,由于其编程接口较友好,性能也比较可观,如果在可靠性要求不是那么高的场景,也不失为一个不错的选择。

  • 消费关系解析

  • 队列高级特性设计

    • 可靠投递(最终一致性)

      1、producer往broker发送消息之前,需要做一次落地。

      2、请求到server后,server确保数据落地后再告诉客户端发送成功。

      3、支持广播的消息队列需要对每个待发送的endpoint,持久化一个发送状态,直到所有endpoint状态都OK才可删除消息。

    • 消费确认

    • 重复消息和顺序消息

      • 如何鉴别消息重复,并幂等的处理重复消息

      • 一个消息队列如何尽量减少重复消息的投递

      • 版本号

        • 对发送方必须要求消息带业务版本号
        • 下游必须存储消息的版本号,对于要严格保证顺序的
      • 状态机

      • 重复消息

        • broker记录MessageId,直到投递成功后清除,重复的ID到来不做处理,这样只要发送者在清除周期内能够感知到消息投递成功,就基本不会在server端产生重复消息。
        • 对于server投递到consumer的消息,由于不确定对端是在处理过程中还是消息发送丢失的情况下,有必要记录下投递的IP地址。决定重发之前询问这个IP,消息处理成功了吗?如果询问无果,再重发。
    • 事务

      • 两阶段提交,分布式事务。
        • 分布式事务存在的最大问题是成本太高,两阶段提交协议,对于仲裁down机或者单点故障,几乎是一个无解的黑洞
      • 本地事务,本地落地,补偿发送
        • 配置较为复杂,“绑架”业务方,必须本地数据库实例提供一个库表。
        • 对于消息延迟高敏感的业务不适用。
    • 性能相关

      • 异步/同步
        • 服务端使用异步最大的好处:解放了线程和I/O(I/O合并和线程创建数量减少)
      • 批量
        • 减少无谓的请求头,如果你每个请求只有几字节,而头却有几十字节,无疑效率非常低下。
        • 减少回复的ack包个数。把请求合并后,ack包数量必然减少,确认和重发的成本就会降低。
    • push/pull模式

      • 慢消费 - push模型致命伤,容易造成消息堆积。对于建立索引等慢消费,消息量有限且到来的速度不均匀的情况,pull模式比较合适
      • 消息延迟与忙等
        • 解决方案
          • 从短时间开始指数级增长等待。开始等5ms,然后10ms,然后20ms知道消息到来回到5ms。但是并不能完全解决延迟问题。
          • rocketMq优化做法 - 长轮询,平衡推拉模型的各自缺点。消费者如果尝试拉取失败,不是直接return,而是把连接挂在那里wait,服务端如果有新的消息到来,把连接notify起来,这也是不错的思路。但海量的长连接block对系统的开销还是不容小觑的,还是要合理的评估时间间隔,给wait加一个时间上限比较好
      • 顺序消息
        • pull模式较push模式容易做到全局顺序消息,主要因为push模式满消费是瓶颈,且全局顺序容易出现慢消费和消息堆积。