一、Rabbit核心组成部分
1、核心概念
Server:又称Broker ,接受客户端的连接,实现AMQP实体服务。 安装rabbitmq-server。
Connection:连接,应用程序与Broker的网络连接 TCP/IP/ 三次握手和四次挥手
Channel:网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道,客户端可以建立对各Channel,每个Channel代表一个会话任务。
Message :消息:服务与应用程序之间传送的数据,由Properties和body组成,Properties可是对消息进行修饰,比如消息的优先级,延迟等高级特性,Body则就是消息体的内容。
Virtual Host 虚拟地址,用于进行逻辑隔离,最上层的消息路由,一个虚拟主机理由可以有若干个Exhange和Queueu,同一个虚拟主机里面不能有相同名字的Exchange
Exchange:交换机,接受消息,根据路由键发送消息到绑定的队列。(不具备消息存储的能力)
Bindings:Exchange和Queue之间的虚拟连接,binding中可以保护多个routing key.
Routing key:是一个路由规则,虚拟机可以用它来确定如何路由一个特定消息。
Queue:队列:也成为Message Queue,消息队列,保存消息并将它们转发给消费者。
2、RabbitMQ的运行流程
3、RabbitMQ支持消息的模式
3.1 simple模式
- 简单模式 Simple
3.2 fanout模式
- 发布订阅模式 fanout
- 类型:fanout
- 特点:fanout—发布与订阅模式,是一种广播机制,它是没有路由key的模式。
3.3 direct模式
- 路由模式 direct
- 类型:direct
- 特点:Direct模式是fanout模式上的一种叠加,增加了路由RoutingKey的模式。
RabbitMQ中默认交换机就是direct模式
3.4 topic模式
- 主题模式 Topic
- 类型:topic
- 特点:Topic模式是direct模式上的一种叠加,增加了模糊路由RoutingKey的模式。
3.5 work模式
- 工作模式 Work
当有多个消费者时,我们的消息会被哪个消费者消费呢,我们又该如何均衡消费者消费信息的多少呢?
主要有两种模式:
1、轮询模式的分发:一个消费者一条,按均分配;
2、公平分发:根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;按劳分配;
3.6 headers模式
- 参数模式
二、代码讲解各种模式
1、Java整合RabbitMQ
1.1、direct模式
以Direct模式为例 (这里的交换机还有队列式通过代码生成的,当然也可以通过15672
端口界面手动创建)
Java的依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.10.0</version>
</dependency>
定义生产者
package com.zhang.rabbitmq.all;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer {
public static void main(String[] args) {
// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip:port
// 1:创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2:设置连接属性
connectionFactory.setHost("120.79.155.9");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3:创建连接Connection
connection = connectionFactory.newConnection("生产者");
// 4:通过连接获取Channel
channel = connection.createChannel();
// 5:准备消息内容
String message = "你好,章卫军!!!";
// 6: 准备交换机
String exchangeName = "direct-new-exchange";
// 7:指定交换机的类型
String exchangeType = "direct";
// 8:声明(创建)交换机 true代表持久化
channel.exchangeDeclare(exchangeName, exchangeType, true);
// 9:声明(创建)队列
// * @params1: queue 队列的名称 @params2: durable 队列是否持久化
// @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
// @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
// @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
channel.queueDeclare("queue5",true, false, false, null);
channel.queueDeclare("queue6",true, false, false, null);
channel.queueDeclare("queue7",true, false, false, null);
// 10:交换机绑定队列
channel.queueBind("queue5",exchangeName, "order");
channel.queueBind("queue6",exchangeName, "order");
channel.queueBind("queue7",exchangeName, "course");
// 11:定义路由key
String routerKey = "order";
// 12:发送消息给中间件rabbitmq-server
channel.basicPublish(exchangeName, routerKey, null, message.getBytes());
System.out.println("消息发送成功!");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 7:先关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 8:再关闭连接
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
定义消费者
package com.zhang.rabbitmq.all;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
private static Runnable runnable = () -> {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("120.79.155.9");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
//获取队列的名称
final String queueName = Thread.currentThread().getName();
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息,如果队列不存在,则会创建
// 这里如果queue已经被创建过一次了,可以不需要定义
//channel.queueDeclare("queue1", false, false, false, null);
// 6: 定义接受消息的回调
Channel finalChannel = channel;
finalChannel.basicConsume(queueName, true, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println(queueName + ":开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("接收消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
};
public static void main(String[] args) {
// 启动三个线程去执行
new Thread(runnable, "queue5").start();
new Thread(runnable, "queue6").start();
new Thread(runnable, "queue7").start();
}
}
1.2、work模式
另外再看一下Work模式,work模式有两种,第一种是轮询模式,第二种是公平模式
生产者相同(交换机采用的是默认的(direct模式),队列是在
http://ip地址:15672/#/queues
中实现申明好的)
package com.zhang.rabbitmq.work.fair;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("120.79.155.9");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 6: 准备发送消息的内容
//===============================end topic模式==================================
for (int i = 1; i <= 20; i++) {
//消息的内容
String msg = "章卫军:" + i;
// 7: 发送消息给中间件rabbitmq-server
// @params1: 交换机exchange
// @params2: 队列名称/routingkey
// @params3: 属性配置
// @params4: 发送消息的内容
channel.basicPublish("", "queue1", null, msg.getBytes());
}
System.out.println("消息发送成功!");
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
结果:
执行完毕后可以看到生产者通过交换机往queue1里面发送了20条消息,接下来申明消费者进行消费。
1.2.1、轮询模式
消费者1(work1)
package com.zhang.rabbitmq.work.polling;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Work1 {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("120.79.155.9");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者-Work1");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息
// 这里如果queue已经被创建过一次了,可以不需要定义
//channel.queueDeclare("queue1", false, true, false, null);
// 6: 定义接受消息的回调
Channel finalChannel = channel;
// finalChannel.basicQos(1);
// @params2:true,为开启自动应答,但是一般不这么用,一般都设置为手动应答
finalChannel.basicConsume("queue1", true, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
try{
System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
Thread.sleep(200);
}catch(Exception ex){
ex.printStackTrace();
}
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println("Work1-开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
消费者2(work2)
package com.zhang.rabbitmq.work.polling;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Work2 {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("120.79.155.9");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者-Work2");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息 这里如果queue已经被创建过一次了,可以不需要定义
// channel.queueDeclare("queue1", false, false, false, null);
// 6: 定义接受消息的回调
Channel finalChannel = channel;
// finalChannel.basicQos(1);
finalChannel.basicConsume("queue1", true, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
try{
System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
Thread.sleep(2000);
}catch(Exception ex){
ex.printStackTrace();
}
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println("Work2-开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
比较消费者1和消费者2可以看到,消费者1接收一条消息需要花费200ms,但是消费者2收到一条信息需要花费2000ms,按照轮询模式的概念,那么20条信息,不管他们接收速度如何,都要保证一个消费者一条,按均分配,我们看结果是否如此
结果:
果然如此!!!!
1.2.2、公平模式
消费者1 (work1)
package com.zhang.rabbitmq.work.fair;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Work1 {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("120.79.155.9");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者-Work1");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息
// 这里如果queue已经被创建过一次了,可以不需要定义
//channel.queueDeclare("queue1", false, true, false, null);
// 6: 定义接受消息的回调
Channel finalChannel = channel;
// 同一时刻,服务器只会推送一条消息给消费者
finalChannel.basicQos(1);
// @params2:false,为手动应答,公平分发都要改成这种模式
finalChannel.basicConsume("queue1", false, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
try{
System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
Thread.sleep(200);
finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
}catch(Exception ex){
ex.printStackTrace();
}
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println("Work1-开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
消费者2 (work2)
package com.zhang.rabbitmq.work.fair;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Work2 {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("120.79.155.9");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者-Work2");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息
// 这里如果queue已经被创建过一次了,可以不需要定义
// channel.queueDeclare("queue1", false, false, false, null);
// 6: 定义接受消息的回调
Channel finalChannel = channel;
// 同一时刻,服务器只会推送一条消息给消费者
finalChannel.basicQos(1);
// @params2:false,为手动应答,公平分发都要改成这种模式
finalChannel.basicConsume("queue1", false, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
try{
System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
Thread.sleep(2000);
finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
}catch(Exception ex){
ex.printStackTrace();
}
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println("Work2-开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
消费者1接收一条消息需要花费200ms,但是消费者2收到一条信息需要花费2000ms,按照公平模式的概念,根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;按劳分配;那么20条信息,消费者1应该接收更多的信息,而消费者2应该接收较少的信息,我们看一下结果是否如此
果然如此!!!
1.2.3 轮询模式和公平模式的区别
比较公平模式和轮询模式在消费者代码的区别,我们可以发现
- 公平模式有
finalChannel.basicQos(1);
,而轮询模式没有,这个是保证同一时刻,服务器只会推送一条消息给消费者 - 公平模式的
finalChannel.basicConsume("queue1", false, new DeliverCallback(){...}, new CancelCallback(){...})
方法的第二个参数设置为false,但是轮询模式却设置为true,这个表示应答方式,设置为false表示为手动应答,设置为true表示为自动应答。一般情况下不会设置为自动应答。
2、SpringBoot整合RabbitMQ
2.1、direct模式范例讲解
1、实现目标
使用springboot完成rabbitmq的消费模式-direct
2、实现步骤
1:创建生产者工程:sspringboot-order-rabbitmq-producer
2:创建消费者工程:springboot-order-rabbitmq-consumer
3:引入spring-boot-rabbitmq的依赖
4:进行消息的分发和测试
5:查看和观察web控制台的状况
3、定义生产者
1)创建生产者工程
2)在pom.xml中引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3)在application.yml进行配置
# 服务端口
server:
port: 8080
# 配置rabbitmq服务
spring:
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 47.104.141.27
port: 5672
4)定义订单的生产者
package com.xuexiangban.rabbitmq.springbootrabbitmqfanoutproducer.service;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
// 1: 定义交换机
private String exchangeName = "direct_order_exchange";
// 2: 路由key
private String routeKey = "email";
public void makeOrder(Long userId, Long productId, int num) {
// 模拟用户下单
String orderNumer = UUID.randomUUID().toString();
System.out.println("用户 " + userId + ",订单编号是:" + orderNumer);
// 发送订单信息给RabbitMQ direct
rabbitTemplate.convertAndSend(exchangeName, routeKey, orderNumer);
}
}
4)通过配置类创建交换机和队列,并绑定关系
package com.zhang.rabbitmq.order.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DirectRabbitMqConfiguration {
//队列 起名:TestDirectQueue
@Bean
public Queue directemailQueue() {
return new Queue("email.direct.queue", true);
}
@Bean
public Queue directsmsQueue() {
return new Queue("sms.direct.queue", true);
}
@Bean
public Queue directweixinQueue() {
return new Queue("weixin.direct.queue", true);
}
//Direct交换机 起名:TestDirectExchange
@Bean
public DirectExchange directOrderExchange() {
// return new DirectExchange("TestDirectExchange",true,true);
return new DirectExchange("direct_order_exchange", true, false);
}
//绑定 将队列和交换机绑定, 并设置用于匹配键,其实前面makeOrder()将routeKey设置为空,这里去掉with语句,那就是fanout模式了
@Bean
public Binding bindingDirect4() {
return BindingBuilder.bind(directweixinQueue()).to(directOrderExchange()).with("direct");
}
@Bean
public Binding bindingDirect5() {
return BindingBuilder.bind(directsmsQueue()).to(directOrderExchange()).with("sms");
}
@Bean
public Binding bindingDirect6() {
return BindingBuilder.bind(directemailQueue()).to(directOrderExchange()).with("email");
}
}
5)进行测试
package com.zhang.rabbitmq.order;
import com.zhang.rabbitmq.order.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {
@Autowired
OrderService orderService;
@Test
public void contextLoads() throws Exception {
orderService.makeOrder(1L, 2L, 12);
}
}
6)结果,生产者已经通过交换机将消息发送给队列了,因为路由key是eamil,所以只有eamil.direct.queue
收到了消息
4、定义消费者
1)创建消费者工程
2)在pom.xml中引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3)在application.yml进行配置
# 服务端口,端口不能和生产者冲突
server:
port: 8081
# 配置rabbitmq服务
spring:
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 47.104.141.27
port: 5672
4)定义订单的消费者-邮件服务
邮件服务通过@RabbitListener(queues = {"email.direct.queue"})
来监听email.direct.queue
队列
package com.zhang.rabbitmq.consumer.service.direct;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@RabbitListener(queues = {"email.direct.queue"})
@Service
public class DirectEmailConsumer {
@RabbitHandler
public void receiveMessage(String message){
System.out.println("email direct ---接收到了订单信息是:--->"+message);
}
}
5)定义订单的消费者-SMS服务
package com.zhang.rabbitmq.consumer.service.direct;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@RabbitListener(queues = {"sms.direct.queue"})
@Service
public class DirectSMSConsumer {
@RabbitHandler
public void receiveMessage(String message){
System.out.println("sms direct ---接收到了订单信息是:--->"+message);
}
}
6)定义订单的消费者-微信服务
package com.zhang.rabbitmq.consumer.service.direct;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@RabbitListener(queues = {"weixin.direct.queue"})
@Service
public class DirectWeixinConsumer {
@RabbitHandler
public void receiveMessage(String message){
System.out.println("weixin direct ---接收到了订单信息是:--->"+message);
}
}
7)启动所有服务查看效果
email.direct.queue
队列内容被消费
而且只有邮件服务消费了,因为我们设置里路由key为eamil
2.2、SpringBoot注解创建交换机和队列,并绑定关系
创建交换机和队列,并绑定关系的方式有三种
1)通过http://ip地址:15672
的界面进行手动创建
2)通过@Configuration配置类进行创建,上面已经说过了
3)通过注解进行创建
例如创建一个topic模式的交换机,以及队列并绑定关系,这个注解我们一般在消费者端进行,因为消费者是和我们的队列是直接联系的,而且消费者一般先提出消费需求,生产者才去生产。
邮件服务
package com.zhang.rabbitmq.consumer.service.topic;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value="email.topic.queue",durable = "true", autoDelete = "false"),
exchange = @Exchange(value="topic-order-exchange",type = ExchangeTypes.TOPIC),
key = "#.email.#"
))
@Service
public class TopicEmailConsumer {
@RabbitHandler
public void receiveMessage(String message){
System.out.println("email topic ---接收到了订单信息是:--->"+message);
}
}
SMS服务
package com.zhang.rabbitmq.consumer.service.topic;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value="sms.topic.queue",durable = "true", autoDelete = "false"),
exchange = @Exchange(value="topic-order-exchange",type = ExchangeTypes.TOPIC),
key = "#.sms.#"
))
@Service
public class TopicSMSConsumer {
@RabbitHandler
public void receiveMessage(String message){
System.out.println("sms topic ---接收到了订单信息是:--->"+message);
}
}
微信服务
package com.zhang.rabbitmq.consumer.service.topic;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;
import javax.lang.model.type.ExecutableType;
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value="weixin.topic.queue",durable = "true", autoDelete = "false"),
exchange = @Exchange(value="topic-order-exchange",type = ExchangeTypes.TOPIC),
key = "com.#"
))
@Service
public class TopicWeixinConsumer {
@RabbitHandler
public void receiveMessage(String message){
System.out.println("weixin topic ---接收到了订单信息是:--->"+message);
}
}
三、死信队列
3.1 过期时间TTL
过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;过了之后消息将自动被删除。RabbitMQ可以对消息和队列设置TTL。目前有两种方法可以设置。
- 第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间。
- 第二种方法是对消息进行单独设置,每条消息TTL可以不同。
如果上述两种方法同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就称为dead message被投递到死信队列, 消费者将无法再收到该消息。
设置队列TTL
配置类中在创建队列的时候进行设置参数
package com.zhang.rabbitmq.order.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class TTLRabbitMqConfiguration {
@Bean
public DirectExchange ttlDirectOrderExchange() {
return new DirectExchange("ttl-direct-exchange", true, false);
}
@Bean
public Queue directTTLQueue() {
// 设置队列的过期时间
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl",5000); // 设置队列的过期时间
return new Queue("ttl.direct.queue", true,false,false,args);
}
@Bean
public Queue directTTLMessageQueue() {
return new Queue("ttl.message.direct.queue", true);
}
@Bean
public Binding bindingDirect() {
return BindingBuilder.bind(directTTLQueue()).to(ttlDirectOrderExchange()).with("ttl");
}
@Bean
public Binding ttlmsgBindings() {
return BindingBuilder.bind(directTTLMessageQueue()).to(ttlDirectOrderExchange()).with("ttlmessage");
}
}
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl",5000); // 设置队列的过期时间
5秒内如果消息没有被消费者接收,则送入死信队列。
参数 x-message-ttl
的值 必须是非负 32 位整数 (0 <= n <= 2^32-1) ,以毫秒为单位表示 TTL 的值。这样,值 6000 表示存在于 队列 中的当前 消息 将最多只存活 6 秒钟。
设置消息TTL
消息的过期时间;只需要在发送消息(可以发送到任何队列,不管该队列是否属于某个交换机)的时候设置过期时间即可。在测试类中编写如下方法发送消息并设置过期时间到队列:
public void makeOrderTTLMessage(Long userId, Long productId, int num) {
// 1: 定义交换机
String exchangeName = "ttl-direct-exchange";
// 2: 路由key
String routeKey = "ttlmessage";
// 模拟用户下单
String orderNumer = UUID.randomUUID().toString();
System.out.println("用户 " + userId + ",订单编号是:" + orderNumer);
// 给消息设置过期时间
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
// 这里就是字符串
message.getMessageProperties().setExpiration("5000");
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
// 发送订单信息给RabbitMQ
rabbitTemplate.convertAndSend(exchangeName, routeKey, orderNumer,messagePostProcessor);
}
expiration
字段以微秒为单位表示 TTL 值。且与 x-message-ttl
具有相同的约束条件。因为 expiration 字段必须为字符串类型,broker 将只会接受以字符串形式表达的数字。
当同时指定了 queue 和 message 的 TTL 值,则两者中较小的那个才会起作用。
3.2、死信队列
DLX,全称为Dead-Letter-Exchange , 可以称之为死信交换机,也有人称之为死信邮箱。当消息在一个队列中变成死信(dead message)之后,它能被重新发送到另一个交换机中,这个交换机就是DLX ,绑定DLX的队列就称之为死信队列。
消息变成死信,可能是由于以下的原因:
- 消息被拒绝
- 消息过期
- 队列达到最大长度
DLX也是一个正常的交换机,和一般的交换机没有区别,它能在任何的队列上被指定,实际上就是设置某一个队列的属性。当这个队列中存在死信时,Rabbitmq就会自动地将这个消息重新发布到设置的DLX上去,进而被路由到另一个队列,即死信队列。
要想使用死信队列,只需要在定义队列的时候设置队列参数 x-dead-letter-exchange
指定交换机即可。
设置死信交换机和私信队列,并绑定关系
package com.zhang.rabbitmq.order.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DeadRabbitMqConfiguration {
@Bean
public DirectExchange deadDirectOrderExchange() {
return new DirectExchange("dead-direct-exchange", true, false);
}
@Bean
public Queue deadQueue() {
return new Queue("dead.direct.queue", true);
}
@Bean
public Binding deadBinds() {
return BindingBuilder.bind(deadQueue()).to(deadDirectOrderExchange()).with("dead");
}
}
设置一个队列的过期策略(超时以及最大长度),并绑定死信交换机和设置路由key
package com.zhang.rabbitmq.order.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class TTLRabbitMqConfiguration {
@Bean
public DirectExchange ttlDirectOrderExchange() {
return new DirectExchange("ttl-direct-exchange", true, false);
}
@Bean
public Queue directTTLQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl",5000); // 设置队列的过期时间
args.put("x-max-length",5); // 设置队列的最大长度
args.put("x-dead-letter-exchange","dead-direct-exchange"); // 设置死信队列的交换机
args.put("x-dead-letter-routing-key","dead"); // 设置死信队列的路由key
return new Queue("ttl.direct.queue", true,false,false,args);
}
@Bean
public Binding bindingDirect() {
return BindingBuilder.bind(directTTLQueue()).to(ttlDirectOrderExchange()).with("ttl");
}
}