目录
一、RabbitMQ入门
1、消息中间件RabbitMQ
2、RabbitMQ的安装部署配置
3、RabbitMQ的生产者消费者演示
二、交换机
1.交换机简介
2.直连交换机实操
创建一个主模块rabbitmq02
建立子模块rabbitmq-provider
建立子模块rabbitmq-consumer
生产者生产消息
消费者消费消息
3.主题交换机实操
生产者发送消息
消费者订阅消息
4.扇形交换机实操
生产者发送消息
消费者订阅消息
三、延迟队列
1.延迟队列的应用场景
2.延迟队列中的消息投递
3.延迟队列的消息消费
什么是MQ
消息队列(Message Queue,简称MQ),从字面意思上看,本质是个队列,FIFO先入先出,只不过队列中存放的内容是message而已作用:应用程序“对”应用程序的通信方法。
应用场景
主要解决异步处理、应用解耦、流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构
1.异步处理
用户注册后,需要发注册邮件和注册短信2.应用解耦
用户下单后,订单系统需要通知库存系统3.流量削锋(重点)
流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列
a、可以控制活动的人数
b、可以缓解短时间内高流量压垮应用
用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。
秒杀业务根据消息队列中的请求信息,再做后续处理4.日志处理
日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题
1.日志采集客户端,负责日志数据采集,定时写受写入Kafka队列
2.Kafka消息队列,负责日志数据的接收,存储和转发
3.日志处理应用:订阅并消费kafka队列中的日志数据
用户注册-串行方式
用户注册-并行方式
用户注册-消息队列
应用解耦(客户下单)
流量削锋(秒杀)
日志处理
MQ选型对比
停掉所有的容器
docker stop $(docker ps -aq)
查询镜像
docker search rabbitmq:management
获取镜像
docker pull rabbitmq:management
运行镜像
##方式一:默认guest用户,密码也是guest$ docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management
##方式二:设置用户名和密码
$ docker run -d \
--name my-rabbitmq \
-p 5672:5672 -p 15672:15672 \
-v /data:/var/lib/rabbitmq \
--hostname my-rabbitmq-host \
-e RABBITMQ_DEFAULT_VHOST=my_vhost \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin \
--restart=always \
rabbitmq:management
参数说明:
-d:后台运行容器
-name:指定容器名
-p:指定服务运行的端口(5672:应用访问端口;15672:控制台Web端口号)
-v:映射目录或文件,启动了一个数据卷容器,数据卷路径为:/var/lib/rabbitmq,再将此数据卷映射到住宿主机的/data目录
--hostname:主机名(RabbitMQ的一个重要注意事项是它根据所谓的 “节点名称” 存储数据,默认为主机名)
-e:指定环境变量;(
RABBITMQ_DEFAULT_VHOST:默认虚拟机名;
RABBITMQ_DEFAULT_USER:默认的用户名;
RABBITMQ_DEFAULT_PASS:默认用户名的密码)
--restart=always:当Docker重启时,容器能自动启动
rabbitmq:management:镜像名
注1:RABBITMQ_DEFAULT_VHOST=my_vhost,my_vhost名字请记好,在之后的编程中要用到,
如果启动时没指定,默认值为/#4.进入RabbitMQ管理平台进行相关操作
注1:容器启动后,可以通过docker logs 窗口ID/容器名字 查看日志
docker logs my-rabbitmq
注2:停止并删除所有容器
docker stop $(docker ps -aq) && docker rm $(docker ps -aq)
设置用户名和密码
RabbitMQ管理平台
后台地址:http://[宿主机IP]:15672
默认账号:guest/guest,用户也可以自己创建新的账号,例如:admin/admin
登录之后
添加新用户
授权 点击用户名
用idea新建一个项目
注意的两个点
一定是maven项目
添加pom依赖
org.springframework.boot
spring-boot-starter-amqp
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.7.2
com.cdl
rabbitmq01
0.0.1-SNAPSHOT
rabbitmq01
Demo project for Spring Boot
8
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-amqp
junit
junit
test
org.springframework.boot
spring-boot-maven-plugin
测试一下依赖
rabbitmq连接配置
server.port=8080
## rabbitmq config
spring.rabbitmq.host=192.168.199.144
spring.rabbitmq.port=5672
spring.rabbitmq.username=springboot
spring.rabbitmq.password=123456
## 与启动容器时虚拟主机名字一致~~~与启动容器时虚拟主机名字一致~~~与启动容器时虚拟主机名字一致~~~
spring.rabbitmq.virtual-host=my_vhost
写入
创建Rabbit配置类RabbitConfig
配置类主要用来配置队列、交换器、路由等高级信息
package com.cdl.rabbitmq01.config;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
@Bean
public Queue firstQueue() {
// 创建一个队列,名称为:first
return new Queue("first");
}
}
创建消息产生者类
package com.cdl.rabbitmq01.component;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Sender {
@Autowired
private AmqpTemplate rabbitTemplate;
public void send() {
rabbitTemplate.convertAndSend("first", "test rabbitmq message !!!");
}
}
创建测试类
package com.cdl.rabbitmq01;
import com.cdl.rabbitmq01.component.Sender;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class RabbitmqTest {
@Autowired
private Sender sender;
@Test
public void testRabbitmq() throws Exception {
sender.send();
}
}
运行三次测试类
创建消息消费者
package com.cdl.rabbitmq01.component;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "first")
public class Receiver {
@RabbitHandler
public void process(String msg) {
System.out.println("receive msg : " + msg);
}
}
讲send方法注掉
再运行改测试类
这个演示对应的就是应用解耦的那张图,当很多订单被写入时服务器宕机,此时库存系统的数据就会清零,怎么解决呢?我们可以在订阅的过程中设置限流,一次性处理定量的请求
Exchange(交换机)的作用
在RabbitMQ中,生产者发送消息不会直接将消息投递到队列中,而是先将消息投递到交换机中,在由交换机转发到具体的队列,
队列再将消息以推送或者拉取方式给消费者进行消费创建消息 路由键 pull/push
生产者------------>交换机------------>队列------------>消费者
RabbitMQ交换机的作用
Exchange(交换机)的类型
1.直连交换机:Direct Exchange
直连交换机是一种带路由功能的交换机,一个队列会和一个交换机绑定,除此之外再绑定一个routing_key,当消息被发送的时候,需要指定一个binding_key,这个消息被送达交换机的时候,就会被这个交换机送到指定的队列里面去。同样的一个binding_key也是支持应用到多个队列中的。这样当一个交换机绑定多个队列,就会被送到对应的队列去处理。
注1:什么是路由键
每个消息都有一个称为路由键(routing key)的属性,它其实就是一个简单的字符串注2:直连交换机适用场景
有优先级的任务,根据任务的优先级把消息发送到对应的队列,这样可以指派更多的资源去处理高优先级的队列。
2.主题交换机:Topic Exchange
直连交换机的缺点!
直连交换机的routing_key方案非常简单,如果我们希望一条消息发送给多个队列,那么这个交换机需要绑定上非常多的routing_key,
假设每个交换机上都绑定一堆的routing_key连接到各个队列上。那么消息的管理就会异常地困难。
所以RabbitMQ提供了一种主题交换机,发送到主题交换机上的消息需要携带指定规则的routing_key,
主题交换机会根据这个规则将数据发送到对应的(多个)队列上。主题交换机的routing_key需要有一定的规则,交换机和队列的binding_key需要采用*.#.*.....的格式,每个部分用.分开,其中
*表示一个单词
#表示任意数量(零个或多个)单词。示例:
队列Q1绑定键为 *.TT.*
队列Q2绑定键为TT.#如果一条消息携带的路由键为 A.TT.B,那么队列Q1将会收到
如果一条消息携带的路由键为TT.AA.BB,那么队列Q2将会收到
3.扇形交换机:Fanout Exchange
扇形交换机是最基本的交换机类型,它所能做的事情非常简单———广播消息。
扇形交换机会把能接收到的消息全部发送给绑定在自己身上的队列。因为广播不需要“思考”,
所以扇形交换机处理消息的速度也是所有的交换机类型里面最快的。这个交换机没有路由键概念,就算你绑了路由键也是无视的。
4.首部交换机:Headers exchange
5.默认交换机
实际上是一个由RabbitMQ预先声明好的名字为空字符串的直连交换机(direct exchange)。它有一个特殊的属性使得它对于
简单应用特别有用处:那就是每个新建队列(queue)都会自动绑定到默认交换机上,绑定的路由键(routing key)名称与队列名称相同。如:当你声明了一个名为”hello”的队列,RabbitMQ会自动将其绑定到默认交换机上,绑定(binding)的路由键名称也是为”hello”。
因此,当携带着名为”hello”的路由键的消息被发送到默认交换机的时候,此消息会被默认交换机路由至名为”hello”的队列中
类似amq.*的名称的交换机:
这些是RabbitMQ默认创建的交换机。这些队列名称被预留做RabbitMQ内部使用,不能被应用使用,否则抛出403 (ACCESS_REFUSED)错误6.Dead Letter Exchange(死信交换机)
在默认情况,如果消息在投递到交换机时,交换机发现此消息没有匹配的队列,则这个消息将被悄悄丢弃。
为了解决这个问题,RabbitMQ中有一种交换机叫死信交换机。当消费者不能处理接收到的消息时,将这个消息重新发布到另外一个队列中,
等待重试或者人工干预。这个过程中的exchange和queue就是所谓的”Dead Letter Exchange 和 Queue
直连交换机-单个绑定
直连交换机-多个绑定
交换机的属性
除交换机类型外,在声明交换机时还可以附带许多其他的属性,其中最重要的几个分别是:
Name:交换机名称
Durability:是否持久化。如果持久性,则RabbitMQ重启后,交换机还存在
Auto-delete:当所有与之绑定的消息队列都完成了对此交换机的使用后,删掉它
Arguments:扩展参数
打开虚拟机以及虚拟机的连接工具和我们的开发工具idea
要确保我们的rabbitMQ的后端能够连上
注意:主模块是一个maven项目 记得要修改maven的地址
创建完成后删除src 避免子模块的代码写错位置
生产者模块 这是一个spring项目
然后下一步就欧克了
消费者模块和生产者模块一样
创建子模块完成后,给子模块添加pom依赖
org.springframework.boot
spring-boot-starter-amqp
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
true
生产者模块的
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.7.6
com.cdl
rabbitmq-provider
0.0.1-SNAPSHOT
rabbitmq-provider
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-amqp
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-maven-plugin
消费者模块
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.7.6
com.cdl
rabbitmq-consumer
0.0.1-SNAPSHOT
rabbitmq-consumer
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-amqp
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-maven-plugin
主模块的不需要改变
配置类RabbitmqDirectConfig
package com.cdl.rabbitmqprovider.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;
/**
* @author cdl
* @site www.cdl.com
* @create 2022-12-21 17:29
*/
@Configuration
public class RabbitmqDirectConfig {
//直连交换机对应的队列
@Bean
public Queue directQueue(){
return new Queue("cdl-direct-Queue");
}
//直连交换机
@Bean
public DirectExchange directExchange(){
return new DirectExchange("cdl-direct-Exchange");
}
//直连交换机与对列的绑定关系
@Bean
public Binding directBinding(){
return BindingBuilder.bind(directQueue())
.to(directExchange())
.with("direct_routing_key");
}
}
SendMessageController
package com.cdl.rabbitmqprovider.controller;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
/**
* @author cdl
* @site www.cdl.com
* @create 2022-12-21 17:48
*/
@RestController
public class SendMessageController {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/sendDirect")
public Map sendDirect(String routingKey){
Map msg = new HashMap();
msg.put("msg","直连交换机 cdl-direct-Exchange 发送的消息");
msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
rabbitTemplate.convertAndSend("cdl-direct-Exchange",routingKey,msg);
Map res = new HashMap();
res.put("code",200);
res.put("msg","成功");
return res;
}
}
application.yml 消费者的是一样的除了端口
server:
port: 8080
spring:
rabbitmq:
host: 192.168.26.128
password: 123456
port: 5672
username: cdl
virtual-host: my_vhost
运行启动类
效果图:
可以看见 接收到了 当我们咋访问页面重复刷新时 接收的数量不会发生改变
改变端口
创建消息接收监听类 DirectReceiver
package com.cdl.rabbitmqconsumer.config;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@RabbitListener(queues = {"cdl-direct-Queue"})
public class DirectReceiver {
@RabbitHandler
public void handler(Map msg){
System.out.println(msg);
}
}
运行启动类
再查看
注1:新版jdk日期及格式化
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));注2:rabbitTemplate和amqpTemplate有什么关系
查看源码中会发现rabbitTemplate实现自amqpTemplate接口,两者使用起来并无区别,功能一致注3:不要@Configuration写成了@Configurable,这两个长得很像
@Configuration该注解是可以用来替代XML文件。
手动new出来的对象,正常情况下,Spring是无法依赖注入的,这个时候可以使用@Configurable注解
RabbitTopicConfig
package com.cdl.rabbitmqprovider.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author cdl
* @site www.cdl.com
* @create 2022-12-21 18:52
*/
@Configuration
public class RabbitTopicConfig {//主题交换机的配置类
//对列
@Bean
public Queue topicQueueA(){
return new Queue("cdl-topic-queue-a");
}
@Bean
public Queue topicQueueB(){
return new Queue("cdl-topic-queue-b");
}
@Bean
public Queue topicQueueC(){
return new Queue("cdl-topic-queue-c");
}
//交换机
@Bean
public TopicExchange topicExchange(){
return new TopicExchange("cdl-topic-Exchange");
}
//绑定关系
@Bean
public Binding binding1(){
return BindingBuilder.bind(topicQueueA())
.to(topicExchange())
.with("cdl.person.xx");
}
@Bean
public Binding binding2(){
return BindingBuilder.bind(topicQueueB())
.to(topicExchange())
.with("cdl.person.yy");
}
@Bean
public Binding binding3(){
return BindingBuilder.bind(topicQueueC())
.to(topicExchange())
.with("cdl.person.*");
}
}
SendMessageController 中添加一个方法
package com.cdl.rabbitmqprovider.controller;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
/**
* @author cdl
* @site www.cdl.com
* @create 2022-12-21 17:48
*/
@RestController
public class SendMessageController {
@Autowired
private RabbitTemplate rabbitTemplate;
//直连交换机推送消息
@RequestMapping("/sendDirect")
public Map sendDirect(String routingKey){
Map msg = new HashMap();
msg.put("msg","直连交换机 cdl-direct-Exchange 发送的消息");
msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
rabbitTemplate.convertAndSend("cdl-direct-Exchange",routingKey,msg);
Map res = new HashMap();
res.put("code",200);
res.put("msg","成功");
return res;
}
//主题交换机推送消息
@RequestMapping("/sendTopic")
public Map sendTopic(String routingKey){
Map msg = new HashMap();
msg.put("msg","主题交换机 cdl-topic-Exchange 发送的消息");
msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
rabbitTemplate.convertAndSend("cdl-topic-Exchange",routingKey,msg);
Map res = new HashMap();
res.put("code",200);
res.put("msg","成功");
return res;
}
}
测试一下
①
②
③
TopicReceiver
package com.cdl.rabbitmqconsumer.config;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author cdl
* @site www.cdl.com
* @create 2022-12-21 19:33
*/
@Component
public class TopicReceiver {
@RabbitListener(queues = {"cdl-topic-queue-a"})
@RabbitHandler
public void handler1(Map msg){
System.out.println("消费者订阅:cdl-topic-queue-a队列的消息为:"+msg);
}
@RabbitListener(queues = {"cdl-topic-queue-b"})
@RabbitHandler
public void handler2(Map msg){
System.out.println("消费者订阅:cdl-topic-queue-b队列的消息为:"+msg);
}
@RabbitListener(queues = {"cdl-topic-queue-c"})
@RabbitHandler
public void handler3(Map msg){
System.out.println("消费者订阅:cdl-topic-queue-c队列的消息为:"+msg);
}
}
运行启动类
RabbitmqFanoutConfig
package com.cdl.rabbitmqprovider.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author cdl
* @site www.cdl.com
* @create 2022-12-21 19:44
*/
@Configuration
public class RabbitmqFanoutConfig {//扇形交换机的配置类
//对列
@Bean
public Queue fanoutQueueA(){
return new Queue("cdl-fanout-queue-a");
}
@Bean
public Queue fanoutQueueB(){
return new Queue("cdl-fanout-queue-b");
}
@Bean
public Queue fanoutQueueC(){
return new Queue("cdl-fanout-queue-c");
}
//交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("cdl-fanout-Exchange");
}
//绑定关系
@Bean
public Binding bindingA(){
return BindingBuilder.bind(fanoutQueueA())
.to(fanoutExchange());
}
@Bean
public Binding bindingB(){
return BindingBuilder.bind(fanoutQueueB())
.to(fanoutExchange());
}
@Bean
public Binding bindingC(){
return BindingBuilder.bind(fanoutQueueC())
.to(fanoutExchange());
}
}
SendMessageController 中添加一个方法
package com.cdl.rabbitmqprovider.controller;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
/**
* @author cdl
* @site www.cdl.com
* @create 2022-12-21 17:48
*/
@RestController
public class SendMessageController {
@Autowired
private RabbitTemplate rabbitTemplate;
//直连交换机推送消息
@RequestMapping("/sendDirect")
public Map sendDirect(String routingKey){
Map msg = new HashMap();
msg.put("msg","直连交换机 cdl-direct-Exchange 发送的消息");
msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
rabbitTemplate.convertAndSend("cdl-direct-Exchange",routingKey,msg);
Map res = new HashMap();
res.put("code",200);
res.put("msg","成功");
return res;
}
//主题交换机推送消息
@RequestMapping("/sendTopic")
public Map sendTopic(String routingKey){
Map msg = new HashMap();
msg.put("msg","主题交换机 cdl-topic-Exchange 发送的消息");
msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
rabbitTemplate.convertAndSend("cdl-topic-Exchange",routingKey,msg);
Map res = new HashMap();
res.put("code",200);
res.put("msg","成功");
return res;
}
//扇形交换机推送消息
@RequestMapping("/sendFanout")
public Map sendFanout(){
Map msg = new HashMap();
msg.put("msg","扇形交换机 cdl-fanout-Exchange 发送的消息");
msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
rabbitTemplate.convertAndSend("cdl-fanout-Exchange",null,msg);
Map res = new HashMap();
res.put("code",200);
res.put("msg","成功");
return res;
}
}
运行启动类
再刷新一次
FanoutReceiver
package com.cdl.rabbitmqconsumer.config;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author cdl
* @site www.cdl.com
* @create 2022-12-21 20:02
*/
@Component
public class FanoutReceiver {
@RabbitListener(queues = {"cdl-fanout-queue-a"})
@RabbitHandler
public void handler1(Map msg){
System.out.println("消费者订阅:cdl-fanout-queue-a队列的消息为:"+msg);
}
@RabbitListener(queues = {"cdl-fanout-queue-b"})
@RabbitHandler
public void handler2(Map msg){
System.out.println("消费者订阅:cdl-fanout-queue-b队列的消息为:"+msg);
}
@RabbitListener(queues = {"cdl-fanout-queue-c"})
@RabbitHandler
public void handler3(Map msg){
System.out.println("消费者订阅:cdl-fanout-queue-c队列的消息为:"+msg);
}
}
1. 场景:“订单下单成功后,15分钟未支付自动取消”
1.传统处理超时订单
采取定时任务轮训数据库订单,并且批量处理。其弊端也是显而易见的;对服务器、数据库性会有很大的要求,
并且当处理大量订单起来会很力不从心,而且实时性也不是特别好。当然传统的手法还可以再优化一下,
即存入订单的时候就算出订单的过期时间插入数据库,设置定时任务查询数据库的时候就只需要查询过期了的订单,
然后再做其他的业务操作2.rabbitMQ延时队列方案
一台普通的rabbitmq服务器单队列容纳千万级别的消息还是没什么压力的,而且rabbitmq集群扩展支持的也是非常好的,
并且队列中的消息是可以进行持久化,即使我们重启或者宕机也能保证数据不丢失
死信队列产生流程
2. TTL和DLX
rabbitMQ中是没有延时队列的,也没有属性可以设置,只能通过死信交换机(DLX)和设置过期时间(TTL)结合起来实现延迟队列1.TTL
TTL是Time To Live的缩写, 也就是生存时间。
RabbitMq支持对消息和队列设置TTL,对消息这设置是在发送的时候指定,对队列设置是从消息入队列开始计算, 只要超过了队列的超时时间配置, 那么消息会自动清除。
如果两种方式一起使用消息的TTL和队列的TTL之间较小的为准,也就是消息5s过期,队列是10s,那么5s的生效。
默认是没有过期时间的,表示消息没有过期时间;如果设置为0,表示消息在投递到消费者的时候直接被消费,否则丢弃。设置消息的过期时间用 x-message-ttl 参数实现,单位毫秒。
设置队列的过期时间用 x-expires 参数,单位毫秒,注意,不能设置为0。消息:生产者 -> 交换机 消息在生产者制造消息的时候就开始计算了TTL TTL=5
队列:生产者 -> 交换机 -> 路由键 -> 队列 当消息送达到队列的时候才开始计算TTL TTL=10
2.DLX和死信队列
DLX即Dead-Letter-Exchange(死信交换机),它其实就是一个正常的交换机,能够与任何队列绑定。死信队列是指队列(正常)上的消息(过期)变成死信后,能够发送到另外一个交换机(DLX),然后被路由到一个队列上,
这个队列,就是死信队列成为死信一般有以下几种情况:
消息被拒绝(basic.reject or basic.nack)且带requeue=false参数
消息的TTL-存活时间已经过期
队列长度限制被超越(队列满)
注1:如果队列上存在死信, RabbitMq会将死信消息投递到设置的DLX上去 ,
注2:通过在队列里设置x-dead-letter-exchange参数来声明DLX,如果当前DLX是direct类型还要声明
x-dead-letter-routing-key参数来指定路由键,如果没有指定,则使用原队列的路由键
准备工作打开我们的虚拟机,连接工具,确保环境是没有问题的
我们先登录进去,之后再打开我们的idea
在生产者的项目中编写配置类RabbitmqDLXConfig
package com.cdl.rabbitmqprovider.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;
/**
* @author cdl
* @site www.cdl.com
* @create 2022-12-21 17:29
*/
@Configuration
public class RabbitmqDLXConfig {
//普通的
public final static String NORMAL_QUEUE = "normal_queue";
public final static String NORMAL_EXCHANGE = "normal_exchange";
public final static String NORMAL_ROUTING_KEY = "normal_routing_key";
//延迟的 delay
public final static String DLX_QUEUE = "dlx_queue";
public final static String DLX_EXCHANGE = "dlx_exchange";
public final static String DLX_ROUTING_KEY = "dlx_routing_key";
//普通交换机、队列、routing_key
@Bean
public Queue normalQueue(){
//在正常队列中要添加参数,2:25发送的消息 要在2:40发送到死信交换机中,再由死信交换机路由到死信队列
Map map = new HashMap();
map.put("x-message-ttl", 30000);//message在该队列queue的存活时间最大为10秒(10000)
map.put("x-dead-letter-exchange", DLX_EXCHANGE); //x-dead-letter-exchange参数是设置该队列的死信交换器(DLX)
map.put("x-dead-letter-routing-key", DLX_ROUTING_KEY);//x-dead-letter-routing-key参数是给这个DLX指定路由键
return new Queue(NORMAL_QUEUE,true,false,false,map);
}
@Bean
public DirectExchange normalDirectExchange(){
return new DirectExchange(NORMAL_EXCHANGE,true,false);
}
@Bean
public Binding normalBinding(){
return BindingBuilder.bind(normalQueue())
.to(normalDirectExchange())
.with(NORMAL_ROUTING_KEY);
}
//死信交换机、队列、routing_key
@Bean
public Queue dlxQueue(){
return new Queue(DLX_QUEUE);
}
@Bean
public DirectExchange dlxDirectExchange(){
return new DirectExchange(DLX_EXCHANGE);
}
@Bean
public Binding dlxBinding(){
return BindingBuilder.bind(dlxQueue())
.to(dlxDirectExchange())
.with(DLX_ROUTING_KEY);
}
}
SendMessageController 中添加方法
package com.cdl.rabbitmqprovider.controller;
import com.cdl.rabbitmqprovider.config.RabbitmqDLXConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
/**
* @author cdl
* @site www.cdl.com
* @create 2022-12-21 17:48
*/
@RestController
public class SendMessageController {
@Autowired
private RabbitTemplate rabbitTemplate;
//直连交换机推送消息
@RequestMapping("/sendDirect")
public Map sendDirect(String routingKey){
Map msg = new HashMap();
msg.put("msg","直连交换机 cdl-direct-Exchange 发送的消息");
msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
rabbitTemplate.convertAndSend("cdl-direct-Exchange",routingKey,msg);
Map res = new HashMap();
res.put("code",200);
res.put("msg","成功");
return res;
}
//主题交换机推送消息
@RequestMapping("/sendTopic")
public Map sendTopic(String routingKey){
Map msg = new HashMap();
msg.put("msg","主题交换机 cdl-topic-Exchange 发送的消息");
msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
rabbitTemplate.convertAndSend("cdl-topic-Exchange",routingKey,msg);
Map res = new HashMap();
res.put("code",200);
res.put("msg","成功");
return res;
}
//扇形交换机推送消息
@RequestMapping("/sendFanout")
public Map sendFanout(){
Map msg = new HashMap();
msg.put("msg","扇形交换机 cdl-fanout-Exchange 发送的消息");
msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
rabbitTemplate.convertAndSend("cdl-fanout-Exchange",null,msg);
Map res = new HashMap();
res.put("code",200);
res.put("msg","成功");
return res;
}
//死信交换机发送消息
@RequestMapping("/sendDLX")
public Map sendDLX(){
Map msg = new HashMap();
msg.put("msg","死信交换机 发送的消息");
msg.put("time",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
rabbitTemplate.convertAndSend(RabbitmqDLXConfig.NORMAL_EXCHANGE,RabbitmqDLXConfig.NORMAL_ROUTING_KEY,msg);
Map res = new HashMap();
res.put("code",200);
res.put("msg","成功");
return res;
}
}
运行启动类
再到第一张图的页面去刷新一次
再等大概30秒
在消费者中写消费的方法
package com.cdl.rabbitmqconsumer.config;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@RabbitListener(queues = {"dlx_queue"})
public class DLXReceiver {
@RabbitHandler
public void handler(Map msg){
//修改订单的状态的业务逻辑写在这
System.out.println("死信队列中接收到的消息:"+msg);
}
}
运行启动类
好啦 RabbitMQ的分享就到这里啦 下次分享的技术为微信小程序
喜欢就点个赞或者关注吧