1. RabbitMQ概述
RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。
AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。
1.1消息模型
所有 MQ 产品从模型抽象上来说都是一样的过程:
消费者(consumer)订阅某个队列,生产者(producer)创建消息,然后发布到队列(queue)中,最后将消息发送到监听的消费者:
1.2RabbitMQ 基本概念
上面介绍过 RabbitMQ 是 AMQP 协议的一个开源实现,所以其内部实际上也是 AMQP 中的基本概念:
- Message 消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等
- Publisher 消息的生产者,也是一个向交换器发布消息的客户端应用程序
- Exchange 交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列
- Binding 绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表
- Queue 消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走
- Connection 网络连接,比如一个TCP连接
- Channel 信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接
- Consumer 消息的消费者,表示一个从消息队列中取得消息的客户端应用程序
- Virtual Host 虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 /
- Broker 表示消息队列服务器实体
2. Spring集成RabbitMQ demo
Spring AMQP 是基于 Spring 框架的 AMQP 消息解决方案,提供模板化的发送和接收消息的抽象层,提供基于消息驱动的 POJO 的消息监听等,很大方便我们使用RabbitMQ程序的相关开发。
2.1 Maven结构
2.2 pom依赖
在pom.xml里添加一下jar包依赖
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>1.7.4.RELEASE</version>
</dependency>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.0.2</version>
</dependency>
2.3 连接配置
根据rabbitmq的部署情况,修改host 、 username 、 password 、 port (默认为5672)、 virtual-host (默认为“/”),保证程序连上 rabbitmq-server 服务。
单个连接:
<rabbit:connection-factory id="connectionFactory"
host="127.0.0.1" port="5672" username="ychai" password="ychai"
virtual-host="/" />
集群连接:
<rabbit:connection-factory id="connectionFactory"
addresses="192.168.101.1:5672,192.168.101.2:5672"
username="ychai" password="ychai" virtual-host="/" />
2.4 Rabbit Admin
RabbitAdmin 是 Spring AMQP 封装的一个对象,实现配置化自动创建队列,交换器
<!-- 当前声明的exchange和queue会在服务器上自动生成 -->
<rabbit:admin id="connectAdmin" connection-factory="connectionFactory" />
2.5 Queue队列
- durable :是否持久化
- exclusive : 仅创建者可以使用的私有队列,断开后自动删除
- auto-delete : 当所有消费客户端连接断开后,是否自动删除队列
<!-- durable:是否持久化; exclusive: 仅创建者可以使用的私有队列,断开后自动删除; auto_delete: 当所有消费客户端连接断开后,是否自动删除队列 -->
<rabbit:queue id="queue1" name="queue_one" durable="true"
exclusive="false" auto-delete="true">
</rabbit:queue>
<rabbit:queue id="queue2" name="queue_two" durable="true"
exclusive="false" auto-delete="true">
</rabbit:queue>
<rabbit:queue id="queue3" name="queue_three" durable="true"
exclusive="false" auto-delete="true">
</rabbit:queue>
2.6 生产端
Spring AMQP 提供了一个发送和接收消息的操作模板类 AmqpTemplate , AmqpTemplate 它定义包含了发送和接收消息等的一些基本的操作功能, RabbitTemplate 是 AmqpTemplate 的一个实现
<!-- 定义rabbit template用于数据的接收和发送 -->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory" />
此时 appcontext-rabbitmq.xml 里面的 amqpTemplate 已经被spring容器管理,故可以使用 @Autowired 自动注入。
@RestController
public class Producer
{
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/sendMessage")
public String sendMessages() throws Exception {
String message = "hello,德玛西亚永不退缩!"+System.currentTimeMillis();
rabbitTemplate.convertAndSend("queue1",message);
return "rabbitmq发送成功";
}
}
2.7 消费端
org.springframework.amqp.core.MessageListener 是 Spring AMQP 异步消息投递的监听器接口,它只有一个方法 onMessage ,用于处理消息队列推送来的消息,获得的消息在 message.getBody() 中(消费端监听到的消息为byte[]型,发送端发的消息类型为string型)
消费端 MessageListener 需要和队列 Queue 进行绑定才能接受被分发的消息,下图中为消费端1绑定了消息队列queue_one和queue_two,为消费端2绑定了消息队列queue_three。
<!-- 消费者 -->
<bean id="remindChannelListener" class="com.epoint.rocketmq.spring.Consumer" />
<!-- 7其他监听器 -->
<rabbit:listener-container
connection-factory="connectionFactory" acknowledge="manual"
concurrency="20" prefetch="1">
<rabbit:listener queues="queue1" ref="remindChannelListener" />
</rabbit:listener-container>
标签对应的 bean 对象类型是固定的,例如 rabbit:listener-container 标签对应的 ListenerContainer 是 SimpleMessageListenerContainer 类
- prefetch 是指单一消费者最多能消费的 unacked messages 数目
- concurrency 设置的是对每个 listener 在初始化的时候设置的并发消费者的个数
对应的消费者:
public class Consumer implements ChannelAwareMessageListener
{
public void onMessage(Message message,Channel channel) throws IOException {
try{
System.out.println("consumer--:"+message.getMessageProperties()+":"+new String(message.getBody()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}catch(Exception e){
e.printStackTrace();//TODO 业务处理
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
}
}
}
2.8 Exchange
交换机,用来接收生产者发送的消息并将这些消息路由给服务器中的队列,主要介绍三种 Direct exchange 、 Fanout exchange 、 Topic exchange 。
Direct exchange
要求消息中的路由键 routing key 如果和 Binding 中的 binding key 一致, 交换器就将消息发到对应的队列中,它是完全匹配、单播的模式
<!-- rabbit:direct-exchange:定义exchange模式为direct,意思就是消息与一个特定的路由键完全匹配,才会转发。
rabbit:binding:设置消息queue匹配的key -->
<!-- 发布订阅定向路由 -->
<rabbit:direct-exchange name="my_exchange_direct"
durable="true" auto-delete="true" id="my_exchange_one">
<rabbit:bindings>
<rabbit:binding queue="queue1" key="hello" />
<rabbit:binding queue="queue2" key="hello2" />
<rabbit:binding queue="queue3" key="hello3" />
</rabbit:bindings>
</rabbit:direct-exchange>
Fanout exchange
每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上, fanout 类型转发消息是最快的
<!-- 广播 分发消息到所有绑定的队列 -->
<rabbit:fanout-exchange name="my_exchange_fanout"
durable="true" auto-delete="true" id="my_exchange_fanout">
<rabbit:bindings>
<rabbit:binding queue="queue1" />
<rabbit:binding queue="queue2" />
<rabbit:binding queue="queue3" />
</rabbit:bindings>
</rabbit:fanout-exchange>
Topic exchange
将路由键和某模式进行匹配,此时队列需要绑定要一个模式上。符号“#”匹配一个或多个词,符号“”匹配一个词,因此“hello.#”能够匹配到“hello.a.a”,但是“hello.” 只会匹配到“hello.a”。
<!-- 主题 模糊路由 -->
<rabbit:topic-exchange name="my_exchange_topic"
durable="true" auto-delete="true" id="my_exchange_topic">
<rabbit:bindings>
<rabbit:binding queue="queue1" pattern="hello.*" />
<rabbit:binding queue="queue2" pattern="hello.*.*" />
<rabbit:binding queue="queue3" pattern="hello.#" />
</rabbit:bindings>
</rabbit:topic-exchange>
2.9死信交换机
如图:在定义业务队列的时候,要考虑指定一个死信交换机,死信交换机可以和任何一个普通的队列进行绑定,然后在业务队列出现死信的时候就会将数据发送到死信队列
消息变成死信有一下几种情况:
- 消息被拒绝(basic.reject/ basic.nack)并且requeue=false
- 消息TTL过期(消息过期时间)
- 队列达到最大长度(队列满了,无法再添加数据到mq中)
定义死信队列和死信交换机:
<!-- dead letter queue -->
<rabbit:queue id="dead_letter_queue" name="dead_letter_queue">
</rabbit:queue>
<!-- dead letter exchange -->
<rabbit:direct-exchange name="my_exchange_dead_letter"
durable="true" auto-delete="true" id="my_exchange_dead_letter">
<rabbit:bindings>
<rabbit:binding queue="dead_letter_queue" key="dead-letter-key"/>
</rabbit:bindings>
</rabbit:direct-exchange>
绑定死信交换机:
<rabbit:queue id="queue1" name="queue_one" durable="true"
exclusive="false" auto-delete="true">
<rabbit:queue-arguments>
<entry key="x-dead-letter-exchange" value="my_exchange_dead_letter" />
<!-- 指定一个 routing key 用于 DLX 分发消息,如果没有指定,那么消息本身使用的 routing key 将被使用 -->
<entry key="x-dead-letter-routing-key" value="dead-letter-key" />
</rabbit:queue-arguments>
</rabbit:queue>