应用消息队列能够对系统进行解耦,流量削峰,在分布式系统设计中,消息队列是重要的组件之一。数据库
在开发中应用过ActiveMQ,kafka等mq,不过对消息队列背后的实现原理关注很少,其实了解消息队列背后的实现特别重要,编程
好比对一致性等实现的关注,能够帮助咱们在开发中避免踩坑,规避问题的出现。这篇文章简单探讨下当设计和实现一个消息队列时,咱们须要关心哪些地方。并发
一个传统意义上的消息队列,须要支持消息的发送,接受和消息暂存的功能。负载均衡
在实际应用中,对消息队列的要求远不止于此,在不一样的业务场景中,须要消息队列提供如顺序消息,消息可靠性,消息持久化等需求。异步
从消息可否会被即时接受和处理的角度,能够把消息传递的方式分为两种。分布式
一种是即时消息通信,也就是说消息从发送者一端发出后当即就能够达到接收者一端;oop
另外一种方式称为延迟消息通信,即消息从某一端发出后,首先进入一个容器进行临时存储,当达到某种条件后,再由这个容器发送给另外一端。性能
延迟消息通信的容器实现就是消息队列。ui
消息队列须要支持消息的发送,消息暂存,和消息的异步消费,spa
除了基本功能之外,消息队列在某些特殊的场景还须要支持事务,消息重试等功能。
为了实现消息队列的基础功能,即消息的传输,存储和消费,
须要从如下几个维度去进行设计:
消息既是信息的载体,消息发送者须要知道如何构造消息,消息接收者须要知道如何解析消息,它们须要按照一种统一的格式描述消息,这种统一的格式称之为消息协议。没有格式的消息是没有意义的。
传统的通讯协议标准有XMPP和AMQP协议等,如今更多的消息队列从性能的角度出发使用本身设计实现的通讯协议。
AMQP 是 Advanced Message Queuing Protocol,即高级消息队列协议。AMQP不是一个具体的消息队列实现,而 是一个标准化的消息中间件协议。目标是让不一样语言,不一样系统的应用互相通讯,并提供一个简单统一的模型和编程接口。 目前主流的ActiveMQ和RabbitMQ都支持AMQP协议。
JMS是Java平台的一部分,是一种应用于异步消息传递的标准API,JMS能够容许不一样应用、不一样模块之间实现可靠、异步数据通讯。
在JMS中,支持两种消息模型,点对点(Point-to-point)和发布-订阅(Publish and subscribe), 这两种模式分别对应于JMS中的两种消息目标(Message Destination):队列及主题(queue/topic)。
Kafka的Producer、Broker和Consumer之间采用的是一套自行设计的基于TCP层的协议。Kafka的这套协议彻底是为了Kafka自身的业务需求而定制的,而非要实现一套相似于Protocol Buffer的通用协议。
消息队列经常保存在链表结构中,拥有权限的进程能够向消息队列中写入或读取消息。
对于分布式系统,消息存储的选择有如下几种:
从速度上内存显然是最快的,对于容许消息丢失,消息堆积能力要求不高的场景(例如日志),内存会是比较好的选择。关系型数据库则是最简单的实现可靠存储的方案,很适合用在可靠性要求很高,最终一致性的场景(例如交易消息)。
对于不须要100%保证数据完整性的场景,要求性能和消息堆积的场景,hbase也是一个很好的选择,典型的好比 kafka的消息落地可使用hadoop。
消息队列须要支持点对点和发布/订阅模式的消费模型, 消费端的消费进度也须要记录,典型的如消费端重连的处理,参考Kafka对每一个Consumer提供一个偏移量的支持。
另外消息队列选择Pull仍是Push模型进行实现也很是重要。在消费端,ActiveMQ使用PUSH模型,而Kafka使用PULL模型,二者各有利弊。对于PUSH,broker很难控制数据发送给不一样消费者的速度,而PULL能够由消费者本身控制,可是PULL模型可能形成消费者在没有消息的状况下盲等,这种状况下能够经过long polling机制缓解。对于几乎每时每刻都有消息传递的流式系统,使用Pull模型更合适。
消息队列中消息的有序性直接依赖与存储的选择,而且和存储的分布式部署以及消费端的并发状况密切相关。
消息的有序可使用存储的顺序性来支持,好比Kafka,在一个partition上是一段连续的存储,能够保证这一段连续的消息有序。
使用Redis能够实现一个简单的消息队列,保证生产端和消费端都是单线程的生产和消费,由于底层数据机构有序,就能够实现消息的有序。
消息投递的可靠性涉及到分布式数据一致性的话题,好比如何保证不丢数据,消息的幂等此类的问题。
RabbitMQ的设计是,当从队列当中取出一个消息的时候,RabbitMQ须要应用显式地回馈说已经获取到了该消息。若是一段时间内不回馈,RabbitMQ会将该消息从新分配给另一个绑定在该队列上的消费者。另外一种状况是消费者断开链接,可是获取到的消息没有回馈,则RabbitMQ一样从新分配。
投递的可靠性须要消费端和生产端一些约定的规则进行约束,保证投递的可靠性,确定会影响性能,须要一些额外的工做来记录消息的状态等。
消息确认机制能够给消息一致性提供支持,包括发送端的确认和消费端的确认,AMQP 协议自己使用的是事务机制进行消息确认,可是事务机制性能较差,而且容易发生阻塞。
Kafka应用的是ACK机制,RabbitMQ也设计了单独的消息确认机制。
消息队列支持不一样的投递语义,以Kafka为例,提供三种不一样的语义:
相似的有阿里巴巴的MQ中间件,发送普通消息有三种实现方式:可靠同步发送、可靠异步发送、单向(Oneway)发送。