我是在解决分布式事务的一致性问题时了解到RabbitMQ的,当时主要是要基于RabbitMQ来实现咱们分布式系统之间对有事务可靠性要求的系统间通讯的。关于分布式事务一致性问题及其常见的解决方案,能够看我另外一篇博客。提到RabbitMQ,不难想到的几个关键字:消息中间件、消息队列。而消息队列不禁让我想到,当时在大学学习操做系统这门课,消息队列不难想到生产者消费者模式。(PS:操做系统这门课程真的很好也很重要,其中的一些思想在我工做的很长一段一时间内给了我很大帮助和启发,给我提供了许多解决问题的思路。强烈建议每个程序员都去学一学操做系统!)html
消息中间件也能够称消息队列,是指用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通讯来进行分布式系统的集成。经过提供消息传递和消息队列模型,能够在分布式环境下扩展进程的通讯。当下主流的消息中间件有RabbitMQ、Kafka、ActiveMQ、RocketMQ等。其能在不一样平台之间进行通讯,经常使用来屏蔽各类平台协议之间的特性,实现应用程序之间的协同。其优势在于可以在客户端和服务器之间进行同步和异步的链接,而且在任什么时候刻均可以将消息进行传送和转发。是分布式系统中很是重要的组件,主要用来解决应用耦合、异步通讯、流量削峰等问题。程序员
消息中间件几大主要做用以下:数据库
P2P模式包含三个角色:消息队列(Queue),发送者(Sender),接收者(Receiver)。每一个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。json
P2P的特色:数组
Pub/Sub模式包含三个角色主题(Topic),发布者(Publisher),订阅者(Subscriber) 。多个发布者将消息发送到Topic,系统将这些消息传递给多个订阅者。安全
Pub/Sub的特色服务器
Kafka是LinkedIn开源的分布式发布-订阅消息系统,目前归属于Apache定级项目。Kafka主要特色是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输。0.8版本开始支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求,适合产生大量数据的互联网服务的数据收集业务。架构
RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。AMQP协议更多用在企业系统内,对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。并发
RocketMQ是阿里开源的消息中间件,它是纯Java开发,具备高吞吐量、高可用性、适合大规模分布式系统应用的特色。RocketMQ思路起源于Kafka,但并非Kafka的一个Copy,它对消息的可靠传输及事务性作了优化,目前在阿里集团被普遍应用于交易、充值、流计算、消息推送、日志流式处理、binglog分发等场景。app
RabbitMQ比Kafka可靠,kafka更适合IO高吞吐的处理,通常应用在大数据日志处理或对实时性(少许延迟),可靠性(少许丢数据)要求稍低的场景使用,好比ELK日志收集。
RabbitMQ是流行的开源消息队列系统。RabbitMQ是AMQP(高级消息队列协议)的标准实现。支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX,持久化。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。是使用Erlang编写的一个开源的消息队列,自己支持不少的协议:AMQP,XMPP, SMTP, STOMP,也正是如此,使的它变的很是重量级,更适合于企业级的开发。同时实现了一个Broker构架,这意味着消息在发送给客户端时先在中心队列排队。对路由(Routing),负载均衡(Load balance)或者数据持久化都有很好的支持。其主要特色以下:
RabbitMQ从总体上来看是一个典型的生产者消费者模型,主要负责接收、存储和转发消息。其总体模型架构以下图所示:
咱们先来看一个RabbitMQ的运转流程,稍后会对这个流程中所涉及到的一些概念进行详细的解释。
生产者:
(1)生产者链接到RabbitMQ Broker,创建一个链接( Connection)开启一个信道(Channel)
(2)生产者声明一个交换器,并设置相关属性,好比交换机类型、是否持久化等
(3)生产者声明一个队列井设置相关属性,好比是否排他、是否持久化、是否自动删除等
(4)生产者经过路由键将交换器和队列绑定起来
(5)生产者发送消息至RabbitMQ Broker,其中包含路由键、交换器等信息。
(6)相应的交换器根据接收到的路由键查找相匹配的队列。
(7)若是找到,则将从生产者发送过来的消息存入相应的队列中。
(8)若是没有找到,则根据生产者配置的属性选择丢弃仍是回退给生产者
(9)关闭信道。
(10)关闭链接。'
消费者:
(1)消费者链接到RabbitMQ Broker ,创建一个链接(Connection),开启一个信道(Channel) 。
(2)消费者向RabbitMQ Broker 请求消费相应队列中的消息,可能会设置相应的回调函数,
(3)等待RabbitMQ Broker 回应并投递相应队列中的消息,消费者接收消息。
(4)消费者确认(ack) 接收到的消息。
(5)RabbitMQ 从队列中删除相应己经被确认的消息。
(6)关闭信道。
(7)关闭链接。
这里咱们主要讨论两个问题:
为什么要有信道?
主要缘由仍是在于TCP链接的"昂贵"性。不管是生产者仍是消费者,都须要和RabbitMQ Broker 创建链接,这个链接就是一条TCP 链接。而操做系统对于TCP链接的建立于销毁是很是昂贵的开销。假设消费者要消费消息,并根据服务需求合理调度线程,若只进行TCP链接,那么当高并发的时候,每秒可能都有成千上万的TCP链接,不只仅是对TCP链接的浪费,也很快会超过操做系统每秒所能创建链接的数量。若是能在一条TCP链接上操做,又能保证各个线程之间的私密性就完美了,因而信道的概念出现了。
信道为什么?
信道是创建在Connection 之上的虚拟链接。当应用程序与Rabbit Broker创建TCP链接的时候,客户端紧接着能够建立一个AMQP 信道(Channel) ,每一个信道都会被指派一个惟一的D。RabbitMQ 处理的每条AMQP 指令都是经过信道完成的。信道就像电缆里的光纤束。一条电缆内含有许多光纤束,容许全部的链接经过多条光线束进行传输和接收。
关于生产者消费者咱们须要了解几个概念:
从RabbitMQ的运转流程咱们能够知道生产者的消息是发布到交换器上的。而消费者则是从队列上获取消息的。那么消息究竟是如何从交换器到队列的呢?咱们先具体了解一下这几个概念。
Queue:队列,是RabbitMQ的内部对象,用于存储消息。RabbitMQ中消息只能存储在队列中。生产者投递消息到队列,消费者从队列中获取消息并消费。多个消费者能够订阅同一个队列,这时队列中的消息会被平均分摊(轮询)给多个消费者进行消费,而不是每一个消费者都收到全部的消息进行消费。(注意:RabbitMQ不支持队列层面的广播消费,若是须要广播消费,能够采用一个交换器经过路由Key绑定多个队列,由多个消费者来订阅这些队列的方式。)
Exchange:交换器。在RabbitMQ中,生产者并不是直接将消息投递到队列中。真实状况是,生产者将消息发送到Exchange(交换器),由交换器将消息路由到一个或多个队列中。若是路由不到,或返回给生产者,或直接丢弃,或作其它处理。
RoutingKey:路由Key。生产者将消息发送给交换器的时候,通常会指定一个RoutingKey,用来指定这个消息的路由规则。这个路由Key须要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。在交换器类型和绑定键固定的状况下,生产者能够在发送消息给交换器时经过指定RoutingKey来决定消息流向哪里。
Binding:RabbitMQ经过绑定将交换器和队列关联起来,在绑定的时候通常会指定一个绑定键,这样RabbitMQ就能够指定如何正确的路由到队列了。
从这里咱们能够看到在RabbitMQ中交换器和队列实际上能够是一对多,也能够是多对多关系。交换器和队列就像咱们关系数据库中的两张表。他们同归BindingKey作关联(多对多关系表)。在咱们投递消息时,能够经过Exchange和RoutingKey(对应BindingKey)就能够找到相对应的队列。
RabbitMQ主要有四种类型的交换器:
fanout:扇形交换器,它会把发送到该交换器的消息路由到全部与该交换器绑定的队列中。若是使用扇形交换器,则不会匹配路由Key。
direct:direct交换器,会把消息路由到RoutingKey与BindingKey彻底匹配的队列中。
topic:彻底匹配BindingKey和RoutingKey的direct交换器 有些时候并不能知足实际业务的需求。topic 类型的交换器在匹配规则上进行了扩展,它与direct 类型的交换器类似,也是将消息路由到BindingKey 和RoutingKey 相匹配的队
列中,但这里的匹配规则有些不一样,它约定:
如图:
· 路由键为" apple.rabbit.client" 的消息会同时路由到Queuel 和Queue2;
· 路由键为" orange.mq.client" 的消息只会路由到Queue2 中:
· 路由键为" apple.mq.demo" 的消息只会路由到Queue2 中:
· 路由键为" banana.rabbit.demo" 的消息只会路由到Queuel 中:
· 路由键为" apple.orange.banana" 的消息将会被丢弃或者返回给生产者由于它没有匹配任何路由键。
了解了上面的概念,咱们再来思考消息是如何从交换器到队列的。首先Rabbit在接收到消息时,会解析消息的标签从而获得消息的交换器与路由key信息。而后根据交换器的类型、路由key以及该交换器和队列的绑定关系来决定消息最终投递到哪一个队列里面。