RabbitMq学习记录
RabbitMQ 是用Erlang编写
MQ 的应用场景及为什么要使用 MQ
MQ(消息队列)应用:
1.异步处理(比异步线程更快,不需要等待结果)
场景说明:用户注册后,需要发注册邮件和注册短信
2.解耦:订单系统发消息,库存系统接收消息之后进行处理
场景说明:用户下单后,订单系统需要通知库存系统
3.流量控制(流量削峰):根据秒杀系统能力处理消息
场景说明:秒杀活动,流量过大,消息队列控制流量人数
-
使用 MQ 异步发送优惠券。
-
使用 MQ 异步发送短信。
-
使用 MQ 异步扣库存。
总之执行比较耗时的操作,都可以交给 MQ 异步实现。
- 跨系统数据传递
- 高并发的流量削峰
- 数据的分发和异步处理
- 大数据分析和传递
- 分布式事务
比如你有一个数据要进行迁移或者请求并发过多的时候,比如你有10W的并发请求下订单,我们可以在这些订单入库之前,我们可以吧订单请求堆积到消息队列中,让它稳健可靠的入库和执行。
消息中间件的核心组成部分
- 消息的协议 RabbitMQ 使用的是AMQP协议
- 消息的持久化机制
- 消息的分发策略
- 消息的高可用,高可靠
- 消息的容错机制
常见的消息中间件
ActiceMQ , RabbitMQ ,Kafka, RocketMQ
它是一种接收数据,接收请求,存储数据,发送数据等功能的技术服务。
MQ消息队列:负责数据的传输接收,存储和传递,所以性能要过于普通服务和技术
消息队列协议
所谓的协议是指:
- 计算机底层操作系统和应用程序通讯时共同遵守的一组约定,只有遵循共同的约定和规范,系统和底层操作系统之间才能互相交流
- 和一般的网络应用程序的不同它主要负责数据的接受和传递,所以性能比较高
- 协议对数据格式和计算机之间交换数据都必须严格遵守规范
网络协议的三要素
语法。语法是用户数据与控制信息的结构与格式,以及数据出现的顺序。
语义。语义是解释控制信息每个部分的意义,它规定了需要发出何种控制信息,以及完成的动作与做出什么样的响应。
时序。时序是对事件发生顺序的详细说明
通俗的讲: 比如我MQ发送一个信息,是一什么数据格式发送到队列中,然后每个部分的含义是什么,发送完毕以后的执行的动作,已经消费者消费信息的动作,消费完毕的响应结果和反馈是什么,然后按照对应的执行顺序进行处理。
协议的总结
协议:是在TCP/ip 协议基础之上构建的一种约定的规范和机制,他的主要目的可以让客户端(应用程序java,GO) 进行沟通和通讯。并且这种协议下规范必须具有持久性,高可用,高可靠的性能。
消息队列持久化
简单来说就是将数据存入磁盘,而不是存在内存中随服务器重启断开而小时,使数据能够永久保存
消息的分发策略
消息队列高可用高可靠
什么是高可用机制
所谓高可用:是指产品在规定的条件和规定的时间内处于可执行规定功能状态的能力
当业务量增加时,请求也过大,一台消息中间件服务器的会触及(CPU,内存,磁盘)的极限,一台消息服务器你已经无法满足业务的需求,所以消息中间件必须支持集群部署。来达到高可用的目的。
总结:
- 要么消息共享
- 要么消息同步
- 要么元数据共享
RabbitMQ 简单模式实现
生产者
public class Producer {
public static void main(String[] args) {
//所有的中间件技术都是基于TCP/IP协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp协议
//1:创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
Connection connection=null;
Channel channel=null;
try {
//2:创建连接Connection
connection = connectionFactory.newConnection("生产者");
//3:通过连接获取通道Channel
channel = connection.createChannel();
//4:通过通创建交换机,声明队列,绑定关系,路由Key,发送消息,和接收消息
String queueName = "queue1";
/**
* 队列的名称,
* 是否要持久化b=false,所谓持久化消息是否存盘,如果false 非持久化 true是持久化,非持久化会存盘嘛? 会存盘,但是会随着重启服务会丢失
* 排他性,是否是独占独立
* 是否自动删除,随着最后一个消费者消息完毕消息以后是否把队列自动删除
* 携带附属参数
*/
channel.queueDeclare(queueName,true,false,false,null);
//5:准备消息内容
String messqge = "你好啊 唐鸿";
//6:发送消息给队列queue
/**
* 交换机
* 队列,路由,key
* 消息的状态控制
* 消息主题
* 面试题:可以存在没有交换机的队列嘛? 不能,虽然很没有指定交换机但是一定会存在一个默认的交换机
*/
channel.basicPublish("",queueName,null,messqge.getBytes());
System.out.println("消息发送成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
//7:关闭通道
if (channel !=null && channel.isOpen()){
try {
channel.close();
}catch (Exception e){
e.printStackTrace();
}
}
//8:关闭连接
if (connection !=null && connection.isOpen()){
try {
connection.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
消费者
public class Consumer {
public static void main(String[] args) {
//所有的中间件技术都是基于TCP/IP协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp协议
//1:创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
Connection connection=null;
Channel channel=null;
try {
//2:创建连接Connection
connection = connectionFactory.newConnection("消费者");
//3:通过连接获取通道Channel
channel = connection.createChannel();
//4:通过通创建交换机,声明队列,绑定关系,路由Key,发送消息,和接收消息
channel.basicConsume("queue1",true,new DeliverCallback(){
public void handle(String consumerTag, Delivery message) throws IOException{
System.out.println("收到消息是:"+ newString(message.getBody(),"UTF-8"));
}
}, new CancelCallback(){
public void handle(String consumerTag) throws IOException{
System.out.println("接受失败");
}
});
System.out.println("开始接受消息");
System.in.read();
} catch (Exception e) {
e.printStackTrace();
} finally {
//7:关闭通道
if (channel !=null && channel.isOpen()){
try {
channel.close();
}catch (Exception e){
e.printStackTrace();
}
}
//8:关闭连接
if (connection !=null && connection.isOpen()){
try {
connection.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
RabbitMQ核心组成部分
核心概念:
Server: 又称Broker,接受客户端的连接,实现AMQP实体服务,安装rabbitmq-server
Connection :连接,引用程序与Broker的网络连接TCP/IP/三次握手/四次挥手
Channel: 网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道,客户端可以建立对各Channel,每个Channel代表一个会话任务。
Message :消息服务与应用程序之间传送的数据,由Properties和body组成,Properties可是对消息进行秀是,比如消息的优先级,延迟等高级特性,Body则就是消息体的内容。
Virtual Host :虚拟地址,用于进行逻辑隔离,最上层的消息路由,一个虚拟主机理由可以有若干个Exhange和Queue,同一个虚拟主机里面不能有相同名字的Exchange
Exchange:交换机,接受消息,根据路由键发送消息到绑定的队列(不具备消息存储的能力)
Bindings:Exchange和Queue之间的虚拟连接,binding中可以保护多个routing key
routing key:是一个路由规则,虚拟机可以用它来确定如何路由一个特定消息
Queue:队列,也称为Message Queue,消息队列,保存消息并将它们转发给消费者。
RabbitMQ的运行流程
RabbitMQ支持消息的模式
管网:https://www.rabbitmq.com/getstarted.html
简单模式 Simple
特点:生产者,队列,消费者
工作模式Work
特点:分发机制
发布订阅模式
特点:Fanout 发布与订阅模式,是一种广播机制,它是没有路由Key的模式
路由模式
特点:有routing-key的匹配模式
主题Topic模式
特点:模糊的routing-key的匹配模式
参数模式
特点:参数匹配模式
交换机的四个类型
direct
路由模式 添加这个就相当于根据条件发送消息,就跟特务接头,只有暗号对上才能是自己人
headers
其实跟路由模式很像,路由模式是只有一个暗号,而headers可以有几个暗号,只要对了其中一个那就是自己人
**fanout **
意思是订阅模式,只要添加了这个模式的队列都能收到消息
topic
主题模式 个人理解:
ues. * 这个星号代表的就是一个占位符,后面必须有一级,
案例:user.test能发送 , user 不能发送 ,user.test.test不能发送 ,com.user.test 不能发送
com.# 意思是可以有0个或者一个或者多个 都是能够发送的
案例:com,com.test,com.test .test.test 都是可以发送的
Fanout(订阅者模式)模式案例
public class Producer {
public static void main(String[] args) {
//所有的中间件技术都是基于TCP/IP协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp协议
//1:创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
Connection connection=null;
Channel channel=null;
try {
//2:创建连接Connection
connection = connectionFactory.newConnection("生产者");
//3:通过连接获取通道Channel
channel = connection.createChannel();
***重要点没有创建交换机 因为在图形化界面创建了 感觉更加的直观
//4:准备消息内容
String messqge = "你好啊 金刚";
//5:准备交换机
String exchangeName = "fanout-exchange";
//6:创建路由Key
String routeKey = "";
//7:指定交换机的类型
String type = "fanout";
主要就是更改5,6,7这上面三个参数来改变交换机不同的模式
______________________________________________________
//5:发送消息给队列queue
/**
* 交换机
* 队列,路由,key
* 消息的状态控制
* 消息主题
* 面试题:可以存在没有交换机的队列嘛? 不能,虽然很没有指定交换机但是一定会存在一个默认的交换机
*/
channel.basicPublish(exchangeName,routeKey,null,messqge.getBytes());
System.out.println("消息发送成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
//7:关闭通道
if (channel !=null && channel.isOpen()){
try {
channel.close();
}catch (Exception e){
e.printStackTrace();
}
}
//8:关闭连接
if (connection !=null && connection.isOpen()){
try {
connection.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
public class Consumer {
private static Runnable runnable = new Runnable() {
@Override
public void run() {
//所有的中间件技术都是基于TCP/IP协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp协议
//1:创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
//获取队列的名称
final String queueName = Thread.currentThread().getName();
Connection connection=null;
Channel channel=null;
try {
//2:创建连接Connection
connection = connectionFactory.newConnection("消费者");
//3:通过连接获取通道Channel
channel = connection.createChannel();
//4:通过通创建交换机,声明队列,绑定关系,路由Key,发送消息,和接收消息
channel.basicConsume(queueName,true,new DeliverCallback(){
public void handle(String consumerTag, Delivery message) throws IOException{
System.out.println("收到消息是:"+ new String(message.getBody(),"UTF-8"));
}
}, new CancelCallback(){
public void handle(String consumerTag) throws IOException{
System.out.println("接受失败");
}
});
System.out.println("开始接受消息");
System.in.read();
} catch (Exception e) {
e.printStackTrace();
} finally {
//7:关闭通道
if (channel !=null && channel.isOpen()){
try {
channel.close();
}catch (Exception e){
e.printStackTrace();
}
}
//8:关闭连接
if (connection !=null && connection.isOpen()){
try {
connection.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
};
public static void main(String[] args) {
new Thread(runnable,"queue1").start();
new Thread(runnable,"queue2").start();
new Thread(runnable,"queue3").start();
}
}
Direct(路由key模式)模式案例
public class Producer {
public static void main(String[] args) {
//所有的中间件技术都是基于TCP/IP协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp协议
//1:创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
Connection connection=null;
Channel channel=null;
try {
//2:创建连接Connection
connection = connectionFactory.newConnection("生产者");
//3:通过连接获取通道Channel
channel = connection.createChannel();
***重要点没有创建交换机 因为在图形化界面创建了 感觉更加的直观
//4:准备消息内容
String messqge = "你好啊 金刚";
//5:准备交换机
String exchangeName = "direct-exchange";
//6:创建路由Key
String routeKey = "email";
//7:指定交换机的类型
String type = "direct";
主要就是更改5,6,7这上面三个参数来改变交换机不同的模式
______________________________________________________
***下面代码是创建交换机 创建队列 然后两个绑定在一起
//声明交换机 所谓的持久化是指,交换机会不会随着服务器重启造成丢失,如果是true代表不丢失,false重启就会丢失
channel.exchangeDeclare(exchangeName,type,true);
//声明队列 队列名称,是否持久化,是否排他性,是否自动删除,是否有参数
channel.queueDeclare("queue6",true,false,false,null);
channel.queueDeclare("queue7",true,false,false,null);
channel.queueDeclare("queue8",true,false,false,null);
//绑定交换机和队列
channel.queueBind("queue6",exchangeName,"order");
channel.queueBind("queue7",exchangeName,"order");
channel.queueBind("queue8",exchangeName,"user");
//5:发送消息给队列queue
/**
* 交换机
* 队列,路由,key
* 消息的状态控制
* 消息主题
* 面试题:可以存在没有交换机的队列嘛? 不能,虽然很没有指定交换机但是一定会存在一个默认的交换机
*/
channel.basicPublish(exchangeName,routeKey,null,messqge.getBytes());
System.out.println("消息发送成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
//7:关闭通道
if (channel !=null && channel.isOpen()){
try {
channel.close();
}catch (Exception e){
e.printStackTrace();
}
}
//8:关闭连接
if (connection !=null && connection.isOpen()){
try {
connection.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
消费者跟上面一样
Topic(主题模式)模式案例
public class Producer {
public static void main(String[] args) {
//所有的中间件技术都是基于TCP/IP协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp协议
//1:创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");
Connection connection=null;
Channel channel=null;
try {
//2:创建连接Connection
connection = connectionFactory.newConnection("生产者");
//3:通过连接获取通道Channel
channel = connection.createChannel();
//4:准备消息内容
String messqge = "你好啊 金刚";
//5:准备交换机
String exchangeName = "topic_exchange";
//6:指定路由Key
String routeKey = "com.co.test";
//7:指定交换机的类型
String type = "topic";
主要就是更改5,6,7这上面三个参数来改变交换机不同的模式
______________________________________________________
//5:发送消息给队列queue
/**
* 交换机
* 队列,路由,key
* 消息的状态控制
* 消息主题
* 面试题:可以存在没有交换机的队列嘛? 不能,虽然很没有指定交换机但是一定会存在一个默认的交换机
*/
channel.basicPublish(exchangeName,routeKey,null,messqge.getBytes());
System.out.println("消息发送成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
//7:关闭通道
if (channel !=null && channel.isOpen()){
try {
channel.close();
}catch (Exception e){
e.printStackTrace();
}
}
//8:关闭连接
if (connection !=null && connection.isOpen()){
try {
connection.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
Work模式
当有多个消费者时,我们的消息会被那个消费者消费呢,我们又该如何均衡消费者消费信息的多少呢?
work轮询模式
轮询模式的分发:一个消费者一条,按均分配
work公平分发模式
公平分发:根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;按劳分配
RabbitMQ 使用场景
解耦,异步,削峰
同步异步的问题
串行:将订单信息写入数据库后,发送邮件,短信,一个个执行才可以,效率低
并行:将订单信息写入数据库后,同时发送邮件,短信,效率更高
当自己用线程池解决并行那么会存在问题:
- 耦合度高
- 需要自己写线程池自己维护
-
出现了消息可能丢失,需要你自己做消息补偿
- 如何保证消息的可靠性要自己写
- 如果服务器承载不了,需要自己去高可用
好处:
- 完全解耦,用MQ建立桥接
- 有独立的线程池和运行模式
- 出现了消息可能会丢失,MQ有持久化功能
- 如何保证消息的可靠性,队列和消息转移等
springBoot 整合RabbitMQ
RabbitMQ 配置
首先POM.XML 配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
YML配置
# 服务端口
server:
port: 8080
本地就不需要配置下面的,远程才需要配置
# 配置rabbitMQ 服务
#spring:
# rabbitmq:
# username: guest
# password: guest
# virtual-host: /
# host: 127.0.0.1
# port: 5672
fanout 模式
生产者模式
@SuppressWarnings("all")
@Service
public class OrederService {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 模拟用户下单
*/
public void makeOrder(String userId,String prouctid,int num){
// 1.根据商品Id查询库存是否充足
// 2.保存订单
String i = UUID.randomUUID().toString();
System.out.println("订单生产成功:"+i);
// 3.通过MQ来完成消息的分发
// 参数:交换机 参数2 路由Key/queue队列名称 参数3:消息内容
String exchangeName = "fanout_order_exchange";
String routingKey = "";
String message = "订单号:" +i+",商品id:"+prouctid+",数量:"+num;
rabbitTemplate.convertAndSend(exchangeName,routingKey,message);
}
}
配置交换机
@Configuration
public class RabbitMqConfiguration {
//1:声明注册fanout模式的交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("fanout_order_exchange",true,false);
}
//2:声明队列 sms.fanout.queue email.fanout.queue,duanxin.fanout.queue
@Bean
public Queue smsqueue(){
return new Queue("sms.fanout.queue",true);
}
@Bean
public Queue emailqueue(){
return new Queue("email.fanout.queue",true);
}
@Bean
public Queue duanxinqueue(){
return new Queue("duanxin.fanout.queue",true);
}
//3:绑定 队列和交换机完成绑定关系
@Bean
public Binding smsBinding(){
return BindingBuilder.bind(smsqueue()).to(fanoutExchange());
}
@Bean
public Binding emailBinding(){
return BindingBuilder.bind(emailqueue()).to(fanoutExchange());
}
@Bean
public Binding duanxinBinding(){
return BindingBuilder.bind(duanxinqueue()).to(fanoutExchange());
}
}
Test运行
@Autowired
private OrederService OrederService;
@Test
void contextLoads() {
OrederService.makeOrderDirect("1","2",50);
}
消费者
创建一个新的工程,配置也都一样
创建三个类
@RabbitListener(queues = {"duanxin.fanout.queue"})
@Service
public class DuanxinConsumer {
@RabbitHandler
public void reviceMessage(String message){
System.out.println("duanxin fanout ---接收到了订单消息是:-》"+message);
}
}
@RabbitListener(queues = {"email.fanout.queue"})
@Service
public class EmailConsumer {
@RabbitHandler
public void reviceMessage(String message){
System.out.println("email fanout ---接收到了订单消息是:-》"+message);
}
}
@RabbitListener(queues = {"sms.fanout.queue"})
@Service
public class SmsConsumer {
@RabbitHandler
public void reviceMessage(String message){
System.out.println("sms fanout ---接收到了订单消息是:-》"+message);
}
}
direct模式
生产者
@SuppressWarnings("all")
@Service
public class OrederService {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 模拟用户下单
*/
public void makeOrderDirect(String userId,String prouctid,int num){
// 1.根据商品Id查询库存是否充足
// 2.保存订单
String i = UUID.randomUUID().toString();
System.out.println("订单生产成功:"+i);
// 3.通过MQ来完成消息的分发
// 参数:交换机 参数2 路由Key/queue队列名称 参数3:消息内容
String exchangeName = "direct_order_exchange";
String routingKey = "";
String message = "订单号:" +i+",商品id:"+prouctid+",数量:"+num;
rabbitTemplate.convertAndSend(exchangeName,"email",message);
rabbitTemplate.convertAndSend(exchangeName,"sms",message);
}
}
@Configuration
public class DirectRabbitMqConfiguration {
//1:声明注册fanout模式的交换机
@Bean
public DirectExchange directExchange(){
return new DirectExchange("direct_order_exchange",true,false);
}
//2:声明队列 sms.fanout.queue email.fanout.queue,duanxin.fanout.queue
@Bean
public Queue directsmsqueue(){
return new Queue("sms.direct.queue",true);
}
@Bean
public Queue directemailqueue(){
return new Queue("email.direct.queue",true);
}
@Bean
public Queue directduanxinqueue(){
return new Queue("duanxin.direct.queue",true);
}
//3:绑定 队列和交换机完成绑定关系
@Bean
public Binding directsmsBinding(){
return BindingBuilder.bind(directsmsqueue()).to(directExchange()).with("sms");
}
@Bean
public Binding directemailBinding(){
return BindingBuilder.bind(directemailqueue()).to(directExchange()).with("email");
}
@Bean
public Binding directduanxinBinding(){
return BindingBuilder.bind(directduanxinqueue()).to(directExchange()).with("duanxin");
}
}
消费者
@RabbitListener(queues = {"duanxin.direct.queue"})
@Service
public class DirectDuanxinConsumer {
@RabbitHandler
public void reviceMessage(String message){
System.out.println("duanxin direct ---接收到了订单消息是:-》"+message);
}
}
@RabbitListener(queues = {"email.direct.queue"})
@Service
public class DirectEmailConsumer {
@RabbitHandler
public void reviceMessage(String message){
System.out.println("email direct ---接收到了订单消息是:-》"+message);
}
}
@RabbitListener(queues = {"sms.direct.queue"})
@Service
public class DirectSmsConsumer {
@RabbitHandler
public void reviceMessage(String message){
System.out.println("sms direct ---接收到了订单消息是:-》"+message);
}
}
RabbitMQ-过期时间TTL
过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者就收获取,过了之后消息将自动被删除。RabbitMQ可以对消息和队列设置TTL。目前有两种方法可以设置
- 通队列属性设置,队列中所有消息都有相同的过期时间。
- 对消息进行单独设置,每条消息的TLL可以不同
如果上述两种方法同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TLL值,就称谓dead message 被投递到死信队列,消费者将无法在收到该条信息
设置队列过期时间
@Configuration
public class TTLRabbitMqConfiguration {
//1:声明注册fanout模式的交换机
@Bean
public FanoutExchange ttlfanoutExchange(){
return new FanoutExchange("ttl_order_exchange",true,false);
}
//2:声明队列 sms.fanout.queue email.fanout.queue,duanxin.fanout.queue
@Bean
public Queue ttlsmsqueue(){
//设置过期时间
Map<String, Object> map = new HashMap<>();
map.put("x-message-ttl",5000);
return new Queue("ttl.fanout.queue",true,false,false,map);
}
//3:绑定 队列和交换机完成绑定关系
@Bean
public Binding ttlsmsBinding(){
return BindingBuilder.bind(ttlsmsqueue()).to(ttlfanoutExchange());
}
}
@Service
public class OrederService {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 模拟用户下单
*/
public void ttlmakeOrder(String userId,String prouctid,int num){
// 1.根据商品Id查询库存是否充足
// 2.保存订单
String i = UUID.randomUUID().toString();
System.out.println("订单生产成功:"+i);
// 3.通过MQ来完成消息的分发
// 参数:交换机 参数2 路由Key/queue队列名称 参数3:消息内容
String exchangeName = "ttl_order_exchange";
String routingKey = "";
String message = "订单号:" +i+",商品id:"+prouctid+",数量:"+num;
rabbitTemplate.convertAndSend(exchangeName,routingKey,message);
}
}
RabbitMQ-死信队列
DLX,全称为Dead-Letter-Exchange,可以称之为死信交换机,也有人称之为死信邮箱,当消息在一个队列中编程死信 (dead message) 之后,它能被重新发送到另一个交换机中,这个交换机就是DLX,绑定DLX的队列就称之为死信队列。消息变成死信,可能是由以下原因:
- 消息被拒绝
- 消息过期
- 队列达到最大长度
DLX也是一个正常的交换机,和一般的交换机没有区别,它能在任何的队列上被指定,实际上就是设置某一个队列的属性,当这个队列中存在死信时,Rabbitmq就会自动将这个消息重新发布到设置的DLX上去,进而被路由到另外一个队列,即死信队列。
要想使用死信队列,只需要在定义队列的时候设置队列参数 x-dead-letter-exchange 制定交换机即可
创建死信交换机
@Configuration
public class DeadRabbitMqConfiguration {
//1:声明注册fanout模式的交换机
@Bean
public FanoutExchange deadfanoutExchange(){
return new FanoutExchange("dead_order_exchange",true,false);
}
//2:声明队列 sms.fanout.queue email.fanout.queue,duanxin.fanout.queue
@Bean
public Queue deadsmsqueue(){
return new Queue("dead.fanout.queue",true);
}
//3:绑定 队列和交换机完成绑定关系
@Bean
public Binding deadbing(){
return BindingBuilder.bind(deadsmsqueue()).to(deadfanoutExchange());
}
}
个另外一个交换机进行绑定
*假如这个交换机已经创建了的话,记得删除 ,不然会报错
@Configuration
public class TTLRabbitMqConfiguration {
//1:声明注册fanout模式的交换机
@Bean
public FanoutExchange ttlfanoutExchange(){
return new FanoutExchange("ttl_order_exchange",true,false);
}
//2:声明队列 sms.fanout.queue email.fanout.queue,duanxin.fanout.queue
@Bean
public Queue ttlsmsqueue(){
//设置过期时间
Map<String, Object> map = new HashMap<>();
map.put("x-message-ttl",5000);
map.put("x-dead-letter-exchange","dead_order_exchange");
return new Queue("ttl.fanout.queue",true,false,false,map);
}
//3:绑定 队列和交换机完成绑定关系
@Bean
public Binding ttlsmsBinding(){
return BindingBuilder.bind(ttlsmsqueue()).to(ttlfanoutExchange());
}
}
-
TTL :过期时间
-
DLX:绑定了死信队列
-
DLK:带有路由Key,只有在direct模式下才会用到
-
LIM:队列的总长度
总结
基于MQ的分布式事务解决方案优点:
- 通用性强
- 拓展方便
- 耦合度低,方案也比较成熟
基于MQ的分布式解决方案缺点:
- 基于消息中间件,只适合异步场景
- 消息会延迟处理,需要业务上能容忍
建议
- 尽量去避免分布式事务
- 尽量将非核心业务做成异步
MQ 面试
如果消费者在消费时出现异常,RabbitMQ 会出现什么样的问题,和你的解决方案是什么?
答:这里会出现死循环,死循环会造成服务的重试,假如你是一个集群,就会重试个个集群,
直到冲垮,会出现一个磁盘,内存消耗殆尽,直到我们的程序宕机为止。
解决方案有: 解决消息重试的几种方案:
- 控制重发的次数 +死信队列
- try+catch+手动ack
- ry+catch+手动ack+死信队列+人工干预
Rabbitmq 为什么需要信道,为什么不是TCP直接通信
- TCP的创建和销毁,开销大,创建要三次握手,销毁要四次分手
- 如果用信道,那引用程序就会TCP连接到Rabbit服务器,高峰时每秒成千上万连接就会造成资源的巨大浪费,而且==底层操作系统没秒处理TCP连接数也是有限制的, ==必定造成性能瓶颈
- 信道的原理是一条线程一条信道,多条线程多条信道同用一条TCP连接,一条TCP连接可以容纳无限的信道,即使每秒成千上万的请求也不会成为性能瓶颈
queue队列到底在消费者创建还是生产者创建
- 一般建议是在rabbitmq操作面板上创建,这是一种稳妥的做法。
- 按照常理来说,确实应该消费者这边创建是最好,消息的消费是在这边。这样你承受一个后果,可能我生产在生产消息可能会丢失消息。
- 在生产者创建队列也是可以,这样稳妥的方法,消息是不会出现丢失,
- 如果你生产者和消费都创建的队列,谁先启动谁先创建,后面启动就覆盖前面的