前面给大家讲解了RabbitMQ的环境搭建以及工作模式和一些基本的用法,今天就来给大家讨论一下RabbitMQ的高级用法。在讨论之前先给大家讲解一下SpringBoot整合RabbitMQ,后面的示例代码我们也使用SpringBoot了,不再使用main方法来演示了。
首先创建一个空的项目,在空的项目中创建模块。如下图所示:
创建完成之后我们首先创建一个module ,如下图所示:
然后填写项目名以及左边等信息,首先我们创建消息生产者,创建完成之后项目结构如下:
接着就是大家熟悉的 加依赖,写yml,写启动类三连了。pom.xml文件如下图所示:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<groupId>org.wcan.rabbitmq</groupId>
<artifactId>rabbitmq-boot-producer</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
配置文件中主要配置rabbitmq的主机信息:
server:
port: 9001
spring:
rabbitmq:
host: 192.168.137.93
username: wcan
password: wcan123
virtual-host: /wcan
port: 5672
最后启动类:
@SpringBootApplication
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class);
}
}
接着我们需要创建一个配置类,代码如下:
@Configuration
public class RabbitMQConfig {
//交换机名称
public static final String ITEM_TOPIC_EXCHANGE = "item_topic_exchange";
//队列名称
public static final String ITEM_QUEUE = "item_queue";
//声明交换机
@Bean("itemTopicExchange")
public Exchange topicExchange(){
return ExchangeBuilder.topicExchange(ITEM_TOPIC_EXCHANGE).durable(true).build();
}
//声明队列
@Bean("itemQueue")
public Queue itemQueue(){
return QueueBuilder.durable(ITEM_QUEUE).build();
}
//绑定队列和交换机
@Bean
public Binding itemQueueExchange(@Qualifier("itemQueue") Queue queue,
@Qualifier("itemTopicExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("item.#").noargs();
}
}
最后我们再来编写一个controller,通过浏览器来发消息:
@RestController
public class SendMsgController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/sendmsg")
public String sendMsg(@RequestParam String msg, @RequestParam String key){
rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE ,key ,msg);
return "发送消息成功!";
}
}
好了,生产者的程序已经搭建完毕了。下面我们 搭建消息消费者的服务。同样的新建一个module,然后就是加依赖,写yml,写启动类三连了。依赖是一样的,yml也是一样的所以这里就不再贴出重复的内容了项目结构如下:
与生产者不同的这里我们需要编写一个简单的消息监听器,用来监听队列中的消息 ,代码如下所示:
@Component
public class MyListener {
@RabbitListener(queues = "item_queue")
public void myListener1(String message){
System.out.println("消费者接收到的消息为:" + message);
}
}
好了,下面我们就可以启动这两个项目了。启动之后我们可以通过浏览器或者postman等工具开进行测试。
我们使用浏览器像消息生产者发送get请求,最终请求中的msg被消息消费者获取。
好了,下面就进入今天的主题了,我们来研究一下RabbitMQ的高级特性以及用法。首先我们来聊聊消息的TTL( Time To Live),也就是消息的生存时间,不过我们通常更习惯叫消息过期时间。也就是TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;过了之后消息将自动被删除 在rabbitmq中有两种办法来实现,第一种是给队列设置TTL,第二种是给消息设置TTL。那么你可能会有疑问,如果我们同时设置了消息TTL和队列TTL ,那么消息过期的时间取决于队列设置的还是消息设置的呢??? 关于这个问题大家在看完本次的教程之后可以自己写个demo试试就知道了。
好了,it's time coding !! ! 我们来看看代码的实现吧,首先我们在上面的消费者工程中新增一个配置文件spring-rabbitmq.xml
该配置文件中内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--定义过期队列及其属性,不存在则自动创建-->
<rabbit:queue id="my_ttl_queue" name="my_ttl_queue" auto-declare="true">
<rabbit:queue-arguments>
<entry key="x-message-ttl" value-type="long" value="6000"/>
</rabbit:queue-arguments>
</rabbit:queue>
</beans>
上述配置中定义了一个队列 my_ttl_queue 该队列中的消息生存时间是6S,到了6s之后消息就会被删除(转发给死信交换机)。
下面我们来编写一个test类,该类的代码如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class ProducerTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void ttlQueueTest(){
//路由键与队列同名
rabbitTemplate.convertAndSend("my_ttl_queue", "发送到过期队列my_ttl_queue,6秒内不消费则不能再被消费。");
}
}
我们运行以下这个test方方法,然后去看看管理台中 my_ttl_queue 这个队列中的消息
我们发现过一会儿my_ttl_queue这个队列中的消息就不见了。这种方式就是设置队列的属性来给消息设置过期时间。下面我们来看看直接给消息设置过期时间的实现方式,我们在 ProducerTest 这个类中新增一个测试方法,代码如下:
@Test
public void ttlMessageTest(){
MessageProperties messageProperties = new MessageProperties();
//设置消息的过期时间,3秒
messageProperties.setExpiration("3000");
Message message = new Message("测试过期消息,3秒钟过期".getBytes(), messageProperties);
//路由键与队列同名
rabbitTemplate.convertAndSend("my_ttl_queue", message);
}
同样的我们运行之后,看mq的管理台,发现3秒钟之后消息又不见了。好了上面分别演示了在RabbitMQ设置消息的TTL的两种方案。
接下来我们就来讨论下一个主题 死信队列。在RabbitMQ中会有一种交换机叫做Dead-Letter-Exchange(私信交换机)。顾名思义和死信交换机绑定的队列就被称之为死信队列。那么什么情况下消息会被投入到死信交换机呢?主要有以下三种情况:
上面我们演示的消息的TTL的时候,当超过了消息的存活期的时候该消息就变成了dead message 也就是死信,我们看不到过期的消息了其实就是他被投入到了死信交换机。
我们需要注意的是 DLX其实也是一个正常的交换机,和一般的交换机没有区别,它能在任何的队列上被指定,实际上就是设置某一个队列的属性。当这个队列中存在死信时,Rabbitmq就会自动地将这个消息重新发布到设置的DLX上去,进而被路由到另一个队列,即死信队列。使用死信队列只需要在定义队列的时候设置队列参数 x-dead-letter-exchange
指定交换机即可。下面来看具体的编码实现:
首先在配置文件中加入以下配置信息
<!--定义定向交换机中的持久化死信队列,不存在则自动创建-->
<rabbit:queue id="my_dlx_queue" name="my_dlx_queue" auto-declare="true"/>
<!--定义广播类型交换机;并绑定上述两个队列-->
<rabbit:direct-exchange id="my_dlx_exchange" name="my_dlx_exchange" auto-declare="true">
<rabbit:bindings>
<!--绑定路由键my_ttl_dlx、my_max_dlx,可以将过期的消息转移到my_dlx_queue队列-->
<rabbit:binding key="my_ttl_dlx" queue="my_dlx_queue"/>
<rabbit:binding key="my_max_dlx" queue="my_dlx_queue"/>
</rabbit:bindings>
</rabbit:direct-exchange>
上述配置主要是定义一个死信队列,然后又将两个队列绑定到一个广播类型的交换机上。接下来我们继续在配置文件中定义过期队列以及限制长度的队列。分别来演示三种死信的效果
<!--定义过期队列及其属性,不存在则自动创建-->
<rabbit:queue id="my_ttl_dlx_queue" name="my_ttl_dlx_queue" auto-declare="true">
<rabbit:queue-arguments>
<!--投递到该队列的消息如果没有消费都将在6秒之后被投递到死信交换机-->
<entry key="x-message-ttl" value-type="long" value="6000"/>
<!--设置当消息过期后投递到对应的死信交换机-->
<entry key="x-dead-letter-exchange" value="my_dlx_exchange"/>
</rabbit:queue-arguments>
</rabbit:queue>
<!--定义限制长度的队列及其属性,不存在则自动创建-->
<rabbit:queue id="my_max_dlx_queue" name="my_max_dlx_queue" auto-declare="true">
<rabbit:queue-arguments>
<!--投递到该队列的消息最多2个消息,如果超过则最早的消息被删除投递到死信交换机-->
<entry key="x-max-length" value-type="long" value="2"/>
<!--设置当消息过期后投递到对应的死信交换机-->
<entry key="x-dead-letter-exchange" value="my_dlx_exchange"/>
</rabbit:queue-arguments>
</rabbit:queue>
<!--定义定向交换机 根据不同的路由key投递消息-->
<rabbit:direct-exchange id="my_normal_exchange" name="my_normal_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding key="my_ttl_dlx" queue="my_ttl_dlx_queue"/>
<rabbit:binding key="my_max_dlx" queue="my_max_dlx_queue"/>
</rabbit:bindings>
</rabbit:direct-exchange>
接着我们在测试类中新增一个测试方法:
@Test
public void dlxTTLMessageTest(){
rabbitTemplate.convertAndSend("my_normal_exchange", "my_ttl_dlx", "测试过期消息;6秒过期后会被投递到死信交换机");
}
我们先把测试方法运行起来,再打开控制台看效果:
我们可以发现,6s之后消息就从 my_ttl_dlx_queue中投入到了my_dlx_queue中了,同样的我们继续演示一下队列达到最大长度的情况,我们新增测试方法:
@Test
public void dlxMaxMessageTest(){
rabbitTemplate.convertAndSend("my_normal_exchange", "my_max_dlx",
"队列my_max_dlx_queue的最大长度为2;消息超过后会被投递到死信交换机;这是第1个消息");
rabbitTemplate.convertAndSend("my_normal_exchange", "my_max_dlx",
"队列my_max_dlx_queue的最大长度为2;消息超过后会被投递到死信交换机;这是第2个消息");
rabbitTemplate.convertAndSend("my_normal_exchange", "my_max_dlx",
"队列my_max_dlx_queue的最大长度为2;消息超过后会被投递到死信交换机;这是第3个消息");
}
我们发现第一条消息被挤到死信队列中了,这就是队列达到最大长度的情况下产生死信的效果
好了,上面就是给大家介绍的死信队列。其实在众多的MQ产品中话题最多的莫过于延迟队列,很遗憾,在RabbitMQ中没有延迟队列这种概念,但是我们可以实现和延迟队列一样的功能,而实现的手段就是上面给大家介绍的TTL+死信队列。来看下面这张图:
上图中,我们使用消息的TTL+死信队列就可以实现消息的延迟接收。相关的代码实现相信大家结合前面的死信队列的示例,自己也能够实现了。这里我主要给大家说说延迟队列的应用场景。
- 支付场景;如果在用户下单之后的几十分钟内没有支付成功;那么这个支付的订单算是支付失败,要进行支付失败的异常处理(将库存加回去),这时候可以通过使用延迟队列来处理
- 在系统中如有需要在指定的某个时间之后执行的任务都可以通过延迟队列处理
接下来就要给大家介绍很重要的一个知识点----消息确认机制,该机制是为了确认并且保证消息被送达。同样的在RabbitMQ中 提供了两种方式:发布确认和事务。两者不可同时使用,在channel为事务时,不可引入确认模式;同样channel为确认模式下,不可使用事务。