RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件,也称为面向消息的中间件,是一个在AMQP基础上完整的,可复用的企业消息系统。它可以用于大型软件系统各个模块之间的高效通信,支持高并发,支持可扩展。
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计,它定义了以下这些特性:
消息方向
消息队列
消息路由
可靠性
安全性
RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
基本概念
在开始之前,我们先了解一下有关MQ的基本概念,为后续demo做一些必要的铺垫
- Broker:消息对俄服务器的实体,它是一个人中间件应用,负责接收消息生产者的消息,然后将这些消发送到消息接收者或者其他的Broker。消息
Queue:消息队列,消息通过发送和路由之后最终达到的地方,到达Queue的消息及进入了逻辑上等待消息消费的状态,每个消息都会被发送到一个或者多个队列,它是RabbitMQ的内部对象,用于存储消息,用下图表示:
RabbitMQ中的消息都只能存储在Queue中,生产者(下图中的P)生产消息并最终投递到Queue中,消费者(下图中的C)可以从Queue中获取消息并消费。
多个消费者可以订阅同一个Queue,这时Queue中的消息会被平均分摊给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,
从这里我们看到生产者将消息投递到Queue中,实际上这在RabbitMQ中这种事情永远都不会发生。实际的情况是,生产者将消息发送到Exchange(交换器),由Exchange将消息路由到一个或多个Queue中(或者丢弃)。下面我们讲解有关Exchange的内容Exchange:消息交换机,是消息第一个达到的地方,消息通过它指定的路由规则,下发到不同的消息队列中区。如下图所示,实际在使用MQ的时候都是生产者将消息发送到Exchange(交换器),由Exchange将消息路由到一个或多个Queue中(或者丢弃)。
Exchange是按照什么逻辑将消息路由到Queue的?这就需要介绍Binding相关的知识了Binding:绑定,他的作用是把Exchange和Queue按照路由规则绑定起来,也就是Exchange和Queue之间的虚拟连接。
Binding key:在绑定(Binding)Exchange与Queue的同时,一般会指定一个binding key;消费者将消息发送给Exchange时,一般会指定一个routing key;当binding key与routing key相匹配时,消息将会被路由到对应的Queue中。这个将在Exchange Types章节会列举实际的例子加以说明。
在绑定多个Queue到同一个Exchange的时候,这些Binding允许使用相同的binding key。
binding key 并不是在所有情况下都生效,它依赖于Exchange Type,比如fanout类型的Exchange就会无视binding key,而是将消息路由到所有绑定到该Exchange的Queue。Routing key:路由关键字,Exchange根据这个关键字进行消息投递。
生产者在将消息发送给Exchange的时候,一般会指定一个routing key,来指定这个消息的路由规则,而这个routing key需要与Exchange Type及binding key联合使用才能最终生效。
在Exchange Type与binding key固定的情况下(在正常使用时一般这些内容都是固定配置好的),我们的生产者就可以在发送消息给Exchange时,通过指定routing key来决定消息流向哪里。
RabbitMQ为routing key设定的长度限制为255 bytes。Connection:连接,代表生产者、消费者、Broker之间进行通讯的物理网络。
- Channel:消息通道,用于连接生产者和消费者的逻辑结构。在客户端的每个连接里,可以建立多个channel,每个channel代表一个会话任务,通过channel可以隔离同一个连接中的不同交互内容。
- Producer:消息生产着,制造消息发送消息的程序
- Consumer:消息消费者,接收消息并处理的程序
消息投递过程
消息投递到多队列的整个过程大致如下:
- 客户端连接到消息队列服务器,打开一个channel
- 客户端声明一个Exchange,并设置相关属性
- 客户端声明一个Queue,并设置相关属性
- 客户端使用Routing key,在Exchange和Queue之间建立好绑定关系
- 客户端投递消息到Exchange
- Exchange接收到消息后,根据消息的Key和已经设置横扫的Binding,进行消息路由,将消息投递到一个或者多个Queue中
Exchange有三种类型:
Direct交换机:完全根据Key进行匹配,它会把消息路由到那些binding key与routing key完全匹配的Queue中:
以上图的配置为例,我们以routingKey=”error”发送消息到Exchange,则消息会路由到Queue1(amqp.gen-S9b…,这是由RabbitMQ自动生成的Queue名称)和Queue2(amqp.gen-Agl…);如果我们以routingKey=”info”或routingKey=”warning”来发送消息,则消息只会路由到Queue2。如果我们以其他routingKey发送消息,则消息不会路由到这两个Queue中。Topic交换机:对key进行模式匹配后进行投递,可以使用符号#匹配一个或者多个词,符号*匹配正好一个词。
它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但这里的匹配规则有些不同,它约定:- routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
- binding key与routing key一样也是句点号“. ”分隔的字符串
- binding key中可以存在两种特殊字符“”与“#”,用于做模糊匹配,其中“”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)
以上图中的配置为例,routingKey=”quick.orange.rabbit”的消息会同时路由到Q1与Q2,routingKey=”lazy.orange.fox”的消息会路由到Q1与Q2,routingKey=”lazy.brown.fox”的消息会路由到Q2,routingKey=”lazy.pink.rabbit”的消息会路由到Q2(只会投递给Q2一次,虽然这个routingKey与Q2的两个bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息将会被丢弃,因为它们没有匹配任何bindingKey。
- Fanout交换机:不需要任何的Key他采用广播的方式,一个消息进来时,投递到与改交换机绑定的所有队列
快速入门
新建一个Spring Boot工程在pom.xml中引入如下依赖内容,其中spring-boot-starter-amqp用于支持RabbitMQ。
parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置好Mq相关的连接属性:
spring.application.name=rabbitmq-hello
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.password: "guest",
spring.rabbitmq.username: "guest",
创建消息生产者Sender。通过注入AmqpTemplate接口的实例来实现消息的发送
@Component
public class Message {
@Autowired
RabbitMessagingTemplate rabbitSendTemplate;
public void send(String... params) throws BusinessAccessException {
rabbitSendTemplate.convertAndSend("demo.listener.exchange", "demo.listener.key",params);
}
}
创建消息消费者Receiver:
@Component
public class Listener {
@RabbitListener(bindings = @QueueBinding(
// 1.value:队列名,2.durable:是否长期有效,3.false:是否自动删除
value = @Queue(value = "demo.listener.key", durable = "true", autoDelete = "false"),
// 1.value交换器名称,2.durable:是否长期有效,3.type:类型是topic
exchange = @Exchange(value = "demo.listener.exchange", durable = "true"),
// test2.send:路由的名称,ProducerConfig 里面 绑定的路由名称(xxxx.to(exchange).with("test2.send")))
key = "demo.listener.key"))
public void receive(String... params) {
System.out.println("Receiver : " + params);
}
}