RabbitMQ是基于AMQP协议的消息中间件。AMQP中主要有两个组件:Exchange 和 Queue (在 AMQP 1.0 里还会有变动),如下图所示,绿色的 X 就是 Exchange ,红色的是 Queue ,这两者都在 Server 端,又称作 Broker ,这部分是 RabbitMQ 实现的,而蓝色的则是客户端,通常有 Producer 和 Consumer 两种类型:
Broker:简单来说就是消息队列服务器实体,接受客户端连接,实现AMQP消息队列和路由功能的进程。
vhost:虚拟主机,是一个虚拟概念,类似于权限控制组,用作不同用户的权限分离,一个broker里可以开设多个vhost,一个vhost里面可以有若干个Exchange和Queue,但是权限控制的最小粒度是vhost。
Exchange:消息交换机,接受生产者发送的消息,并根据Binding规则将消息路由给服务器中的队列。ExchangeType决定了Exchange路由消息的行为,例如在RabbitMQ中,ExchangeType有direct、Fanout和Topic三种,不同类型的Exchange路由的行为是不一样的。
Queue:消息队列载体,用于存储还未被消费者消费的消息,每个消息都会被投入到一个或多个队列。
Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来,Binding联系了Exchange与Message Queue。Exchange在与多个Message Queue发生Binding后会生成一张路由表,路由表中存储着Message Queue所需消息的限制条件即Binding Key。当Exchange收到Message时会解析其Header得到Routing Key,Exchange根据Routing Key与Exchange Type将Message路由到Message Queue。Binding Key由Consumer在Binding Exchange与Message Queue时指定,而Routing Key由Producer发送Message时指定,两者的匹配方式由Exchange Type决定。
Connection:连接,对于RabbitMQ而言,其实就是一个位于客户端和Broker之间的TCP连接。
Channel:信道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务,仅仅创建了客户端到Broker之间的连接后,客户端还是不能发送消息的。需要为每一个Connection创建Channel,AMQP协议规定只有通过Channel才能执行AMQP的命令。一个Connection可以包含多个Channel。之所以需要Channel,是因为TCP连接的建立和释放都是十分昂贵的,如果一个客户端每一个线程都需要与Broker交互,如果每一个线程都建立一个TCP连接,暂且不考虑TCP连接是否浪费,就算操作系统也无法承受每秒建立如此多的TCP连接。RabbitMQ建议客户端线程之间不要共用Channel,至少要保证共用Channel的线程发送消息必须是串行的,但是建议尽量共用Connection。
Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
producer:消息生产者,就是投递消息的程序。
consumer:消息消费者,就是接受消息的程序。
Message: 由Header和Body组成,Header是由生产者添加的各种属性的集合,包括Message是否被持久化、由哪个Message Queue接受、优先级是多少等。而Body是真正需要传输的APP数据。
Command:AMQP的命令,客户端通过Command完成与AMQP服务器的交互来实现自身的逻辑。例如在RabbitMQ中,客户端可以通过publish命令发送消息,txSelect开启一个事务,txCommit提交一个事务。
首先你必须连接到Rabbit才能发布和消费消息,那怎么连接和发送消息的呢?
你的应用程序和Rabbit Server之间会创建一个TCP连接,一旦TCP打开,并通过了认证,认证就是你试图连接Rabbit之前发送的Rabbit服务器连接信息和用户名和密码,有点像程序连接数据库,使用Java有两种连接认证的方式,后面代码会详细介绍,一旦认证通过你的应用程序和Rabbit就创建了一条AMQP信道(Channel)。
信道是创建在“真实”TCP上的虚拟连接,AMQP命令都是通过信道发送出去的,每个信道都会有一个唯一的ID,不论是发布消息,订阅队列或者介绍消息都是通过信道完成的。
为什么不通过TCP直接发送命令?
对于操作系统来说创建和销毁TCP会话是非常昂贵的开销,假设高峰期每秒有成千上万条连接,每个连接都要创建一条TCP会话,这就造成了TCP连接的巨大浪费,而且操作系统每秒能创建的TCP也是有限的,因此很快就会遇到系统瓶颈。
如果我们每个请求都使用一条TCP连接,既满足了性能的需要,又能确保每个连接的私密性,这就是引入信道概念的原因。
可靠性传输
这个特点可以说是消息中间件的立足之本,对于应用来说,只要成功把数据提交给消息中间件,那么关于数据可靠传输的问题就由消息中间件来负责。
不重复传输
不重复传播也就是断点续传的功能,特别适合网络不稳定的环境,节约网络资源。
异步性传输
异步性传输是指,接受信息双方不必同时在线,具有脱机能力和安全性。
消息驱动
接到消息后主动通知消息接收方。
支持事务
应用程序可以把一些数据更新组合成一个工作单元,这些更新通常是逻辑相关的,为了保障数据完整性,所有的更新必须同时成功或者同时失败)。
异步处理
场景说明:用户注册后,需要给用户发送邮件和短信,传统的做法有两种:
串行方式:将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。 这有一个问题是:发送邮件和短信并不是必须的,它只是一个通知,就算失败也并不影响注册,而串行的做法让客户端等待没有必要等待的逻辑。
并行方式:将注册信息写入数据库后,发送邮件的同时发送短信,以上三个任务完成后返回给客户端,并行的方式能提高处理的时间。
假设三个业务节点分别使用50ms,串行方式使用时间150ms,并行使用时间100ms。虽然并行已经提高了处理时间,但是前面说过邮件和短信对我正常的使用网站没有任何影响,客户端没有必要等着其发送完成才显示注册成功不应该等待这两个逻辑的执行。
消息队列:注册成功后,分别把发送邮件和短信的加入到消息队列,然后直接返回,让消费者读取消息去处理这两个逻辑。
引入消息队列后,用户的响应时间就等于写入数据库的时间+写入消息队列的时间(可以忽略不计)。
应用解耦
场景:电商系统中用户下单后订单系统需要通知库存系统,传统的做法是订单系统调用库存系统的接口。这种做法有一个缺陷:当库存系统出现故障时,订单就会失败(这样淘宝、京东将少赚好多好多钱),这就是订单系统和库存系统高耦合了。引入消息队列后,用户下单,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。库存系统订阅下单的消息,监听消息队列,获取下单消息后进行库存操作。
优势:就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失(马云、东哥这下高兴了)。
流量削峰
场景:流量削峰一般在秒杀活动中应用广泛,在秒杀活动中一般会因为流量过大,导致应用挂掉。为了解决这个问题,一般在应用前端加入消息队列。
作用:
日志处理
在大数据日志处理中可以使用RabbitMQ中间件接收日志系统收集的日志,使用不同的应用订阅消息,比如一个应用可以进行流计算,计算结果通过一些数据可视化工具来做实时展示,另一个应用对消息进行归档分类,然后保存到MySQL,我们就可以对这些日志做一些报表统计。