消息队列笔记

来源:互联网 发布:龙应台安德烈现状知乎 编辑:程序博客网 时间:2024/06/05 14:21
  • 常见消息队列:ActiveMQ、RabbitMQ、Kafka,阿里巴巴自主开发的Notify、MetaQ、RocketMQ等。
  • 常用于解决:业务解耦/最终一致性/广播/错峰流控
  • 如果下游有很多业务方关系你的系统发出的通知的时候 ,果断的使用消息队列吧
  • 业界的一些为最终一致性而生的消息队列如notify(阿里)、QMQ(去哪儿),设计初衷是为了交易系统中的高可靠通知。一种实现方案是通过补偿机制, 比如在客户端像队列的服务端发送一条消息时,将消息存入本地数据库,与真正的业务逻辑形成一个本地事务,等到真正的讲消息发送到队列服务端,事务才算完成,否则失败回滚。
  • 不是所有的系统都要求最终一致性或者可靠投递,任何基础组件要服务于业务场景。
  • 单播、广播功能是消息队列的基本功能。
  • 消息队列需要做的事情
  • 基于消息的系统模型,一般需要broker即队列服务端,但是像Akka,ZeroMQ是没有服务端的。
  • 设计消息队列的整体思路是先build一个整体的数据流,例如producer发送给broker,broker发送给consumer,consumer回复消费确认,broker删除/备份消息等。
    利用RPC将数据流串起来。然后考虑RPC的高可用性,尽量做到无状态,方便水平扩展。
    之后考虑如何承载消息堆积,然后在合适的时机投递消息,而处理堆积的最佳方式,就是存储,存储的选型需要综合考虑性能/可靠性和开发维护成本等诸多因素。
    为了实现广播功能,我们必须要维护消费关系,可以利用zk/config server等保存消费关系。
    在完成了上述几个功能后,消息队列基本就实现了。
  • 一般业务方需要将业务搞成幂等的,因为一般需要对消息进行失败重发。
  • 从速度来看,文件系统>分布式KV(持久化)>分布式文件系统>数据库,而可靠性却截然相反,DB受制于IOPS,如果要求单broker 5位数以上的QPS性能,基于文件的存储是比较好的解决方案。整体上可以采用数据文件+索引文件的方式处理。
  • 顺序消息能否100%满足呢? 答案是可以,但条件更为苛刻:
    1.允许消息丢失。
    2.从发送方到服务方到接受者都是单点单线程。
    所以绝对的顺序消息基本上是不能实现的,当然在METAQ/Kafka等pull模型的消息队列中,单线程生产/消费,排除消息丢失,也是一种顺序消息的解决方案。
    一个主流消息队列的设计范式里,应该是不丢消息的前提下,尽量减少重复消息,不保证消息的投递顺序。
  • 重复消息两种解决方案:
    1.版本号
    如果每个消息自带一个版本号,每次只接受比当前版本号大的消息,如果到来的顺序是21,则先把2存起来,待1到来后,先处理1,再处理2,这样重复性和顺序性要求就都达到了。但是该方案要求业务方发送消息带业务版本号且下游要存储版本号。且需要等待,成本高,效率低。
    2.状态机
    业务方只需要自己维护一个状态机,定义各种状态的流转关系。例如,”下线”状态只允许接收”上线”消息,“上线”状态只能接收“下线消息”如果上线收到上线消息,或者下线收到下线消息,在消息不丢失和上游业务正确的前提下。要么是消息发重了,要么是顺序到达反了。这时消费者只需要把“我不能处理这个消息”告诉投递者,要求投递者过一段时间重发即可。而且重发一定要有次数限制,比如5次,避免死循环,就解决了。
    但是在消费方维护这么多状态,就涉及到一个消费方的消息落地/多机间的同步消费状态问题,复杂度指数级上升,而且只能解决部分问题。

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

  • 事务
    分布式事务存在的最大问题是成本太高,对于交易密集型或者I/O密集型的应用,没有办法承受这么高的网络延迟,系统复杂性。
    本地事务:
    比如在客户端像队列的服务端发送一条消息时,将消息存入本地数据库,与真正的业务逻辑形成一个本地事务,等到真正的讲消息发送到队列服务端,事务才算完成,否则失败回滚。

    本地事务是业务落地和消息落地的事务,而不是业务落地和RPC成功的事务。这里很多人容易混淆,如果是后者,无疑是事务嵌套RPC,是大忌,会有长事务死锁等各种风险。

    本地事务存在两个最大的使用障碍:
    配置较为复杂,“绑架”业务方,必须本地数据库实例提供一个库表。
    对于消息延迟高敏感的业务不适用。

  • PUSH OR PULL
    慢消费是push模型最大的致命伤,致命的是broker给consumer推送一堆consumer繁忙无法处理那么多消息,consumer不是reject就是error,然后来回踢皮球。
    对于pull模式,consumer可以按需消费,不用担心自己处理不了的消息来骚扰自己。
    所以对于建立索引等慢消费,消息量有限且到来的速度不均匀的情况,pull模式比较合适。

  • PULL模型中的消息延迟问题
    因为消费方无法准确地决定何时去拉取最新的消息,会造成消息处理延迟。
    业界较成熟的做法是从短时间开始(不会对broker有太大负担),然后指数级增长等待。比如开始等5ms,然后10ms,然后20ms,然后40ms……直到有消息到来,然后再回到5ms。
    在阿里的RocketMq里,有一种优化的做法-长轮询,基本思路是:消费者如果尝试拉取失败,不是直接return,而是把连接挂在那里wait,服务端如果有新的消息到来,把连接notify起来,并设置一个超时时间。

- 摘抄自http://tech.meituan.com/mq-design.html

0 0