MQ的优劣
MQ全称为Message Queue,消息队列是应用程序和应用程序之间的通信方法。
先进先出
原先:ip:port/order/add?goodsId=1
现在:异步调用
这是添加订单的服务,因为它添加订单,放入队列就算成功了,后面可以然队列慢慢去写。
在项目中,可将一些无需即时返回且耗时的操作提取出来,进行异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量。
现在,应用开发和部署—微服务
MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。
order_table status 0 已下单 status 1 支付成功 status 2 已通知商家发货 status 3 商家发货 status 4 已经收货
如订单系统,在下单的时候就会往数据库写数据。但是数据库只能支撑每秒1000左右的并发写入,并发量再高就容易宕机。低峰期的时候并发也就100多个,但是在高峰期时候,并发量会突然激增到5000以上,这个时候数据库肯定卡死了。
消息被MQ保存起来了,然后系统就可以按照自己的消费能力来消费,比如每秒1000个数据,这样慢慢写入数据库,这样就不会卡死数据库了。
但是使用了MQ之后,限制消费消息的速度为1000,但是这样一来,高峰期产生的数据势必会被积压在MQ中,高峰就被“削”掉了。但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000QPS,直到消费完积压的消息,这就叫做“填谷”
系统可用性降低
需要设置为高可用,一旦宕机还有容错
系统复杂度提高
由于异步有以下问题
一致性问题
A 系统处理完业务,通过 MQ 给B、C、D三个系统发消息数据,如果 B 系统、C 系统处理成功,D 系统处理
失败。如何保证消息数据处理的一致性。
最终一致性
异步不用返回,直接塞队列
容许短暂的不一致性。
即解耦、提速、削峰这些方面的收益,超过加入MQ,管理MQ这些成本
MQ是消息通信的模型;实现MQ的大致有两种主流方式:AMQP、JMS。
MQ协议
java面向消息的API类似jdbc协议,但是大多使用上面这种协议
市场上常见的消息队列有如下:
RabbitMQ提供了6种模式:简单模式,work模式,Publish/Subscribe发布与订阅模式,Routing路由模式,Topics主题模式,RPC远程调用模式(远程调用,不太算MQ;暂不作介绍);
官网对应模式介绍:https://www.rabbitmq.com/getstarted.html
http 三次握手 四次挥手耗费性能 所以使用一个长连接
一个消费者监听“一个”“队列”
消息发布接收流程:
-----发送消息-----
1、生产者和Broker建立TCP连接。
2、生产者和Broker建立通道。
3、生产者通过通道消息发送给Broker,由Exchange将消息进行转发。
4、Exchange将消息转发到指定的Queue(队列)
----接收消息-----
1、消费者和Broker建立TCP连接
2、消费者和Broker建立通道
3、消费者监听指定的Queue(队列)
4、当有消息到达Queue时Broker默认将消息推送给消费者。
5、消费者接收到消息。
AMQP 一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
AMQP是一个二进制协议,拥有一些现代化特点:多信道、协商式,异步,安全,扩平台,中立,高效。
RabbitMQ是AMQP协议的Erlang的实现。
概念 | 说明 |
---|---|
连接Connection | 一个网络连接,比如TCP/IP套接字连接。 |
会话Session | 端点之间的命名对话。在一个会话上下文中,保证“恰好传递一次”。 |
信道Channel | 多路复用连接中的一条独立的双向数据流通道。为会话提供物理传输介质。 |
客户端Client | AMQP连接或者会话的发起者。AMQP是非对称的,客户端生产和消费消息,服务器存储和路由这些消息。 |
服务节点Broker | 消息中间件的服务节点;一般情况下可以将一个RabbitMQ Broker看作一台RabbitMQ 服务器。 |
端点 | AMQP对话的任意一方。一个AMQP连接包括两个端点(一个是客户端,一个是服务器)。 |
消费者Consumer | 一个从消息队列里请求消息的客户端程序。 |
生产者Producer | 一个向交换机发布消息的客户端应用程序。 |
在入门案例中:
启动,在sbin目录下
rabbitmq-plugins.bat enable rabbitmq_management
http://localhost:15672
默认用户名密码都是guest
String queueName = "my_queue";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
//1设置连接信息
//ip
factory.setHost("localhost");
//端口
factory.setPort(5672);
//设置虚拟主机
factory.setVirtualHost("/");
//设置用户名
factory.setUsername("root");
//设置密码
factory.setPassword("123456");
//2创建长连接
Connection connection = factory.newConnection();
//3创建通道
Channel channel = connection.createChannel();
//4声明队列
//String queue, 队列名
// boolean durable, 持久化
// boolean exclusive, 排他的
// boolean autoDelete, 自动删除
// Map arguments 属性
channel.queueDeclare("my_queue",true,false,false,null);
String msg = "第二条信息";
//5发消息
// String exchange, 交换机 ""是默认交换机
// String routingKey, 路由键
// AMQP.BasicProperties props, 属性
// byte[] body 消息 string byte[] char[]如何相互转换的?
channel.basicPublish("","my_queue",null,msg.getBytes());
//6关闭连接 资源关闭的顺序,先关后出来的资源,最后关,第一个资源
channel.close();
connection.close();
}
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
//1设置连接信息
//ip
factory.setHost("localhost");
//端口
factory.setPort(5672);
//设置虚拟主机
factory.setVirtualHost("/");
//设置用户名
factory.setUsername("root");
//设置密码
factory.setPassword("123456");
//2创建长连接
Connection connection = factory.newConnection();
//3创建通道
Channel channel = connection.createChannel();
//4声明队列 防止消费者先启动,队列没有声明导致失败
//String queue, 队列名
// boolean durable, 持久化
// boolean exclusive, 排他的
// boolean autoDelete, 自动删除
// Map arguments 属性
channel.queueDeclare("my_queue",true,false,false,null);
String msg = "adhlashdas";
//5消息
DefaultConsumer consumer = new DefaultConsumer(channel){
//consumerTag 消息者标签,在channel.basicConsume时候可以指定
//envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
//properties 属性信息
//body 消息
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//下面是业务逻辑
super.handleDelivery(consumerTag, envelope, properties, body);
System.out.println("consumerTag" + consumerTag);
System.out.println("交换机"+envelope.getExchange());
System.out.println("路由key"+envelope.getRoutingKey());
System.out.println("DeliveryTag" + envelope.getDeliveryTag());
}
};
//6监听消息
// 参数1:队列名称
// 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
// 参数3:消息接收到后回调
channel.basicConsume("my_queue",true,consumer);
}
一个生产者,一个通道队列,多个消费者
启动多个消费者实例监听同一个通道
和上面的基本一样,只是在消费者的类启动多个实例,两个实例进行抢消息
适用场景
中间有个交换机,然后由交换机复制多份给各个通道,然后让消费者监听其对应的队列
Exchange类型
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
//1设置连接信息
//ip
factory.setHost("localhost");
//端口
factory.setPort(5672);
//设置虚拟主机
factory.setVirtualHost("/");
//设置用户名
factory.setUsername("root");
//设置密码
factory.setPassword("123456");
//2创建长连接
Connection connection = factory.newConnection();
//3创建通道
Channel channel = connection.createChannel();
// 4声明队列
// String queue, 队列名
// boolean durable, 持久化
// boolean exclusive, 排他的
// boolean autoDelete, 自动删除
// Map arguments 属性
channel.queueDeclare("my_queue1",true,false,false,null);
channel.queueDeclare("my_queue2",true,false,false,null);
// 5声明交换机
// 参数1:交换机名称
// 参数2:交换机类型,fanout、topic、direct、headers
// 参数3:是否定义持久化
// 参数4:是否在不使用的时候自动删除
// 参数5:属性
channel.exchangeDeclare("my_exchange",BuiltinExchangeType.FANOUT,true,true,null);
// 6绑定交换机和队列
// 参数1 队列
// 参数2 交换机
// 参数3 路由箭
channel.queueBind("my_queue1","my_exchange","");
channel.queueBind("my_queue2","my_exchange","");
String msg = "第二条信息";
// 7发消息
// String exchange, 交换机 ""是默认交换机
// String routingKey, 路由键
// AMQP.BasicProperties props, 属性
// byte[] body 消息 string byte[] char[]如何相互转换的?
//这里的路由为"",因为由多个队列,不用指定对应的
channel.basicPublish("my_exchange","",null,msg.getBytes());
// 8关闭连接 资源关闭的顺序,先关后出来的资源,最后关,第一个资源
channel.close();
connection.close();
}
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
//1设置连接信息
//ip
factory.setHost("localhost");
//端口
factory.setPort(5672);
//设置虚拟主机
factory.setVirtualHost("/");
//设置用户名
factory.setUsername("root");
//设置密码
factory.setPassword("123456");
// 2创建长连接
Connection connection = factory.newConnection();
// 3创建通道
Channel channel = connection.createChannel();
// 4声明队列 防止消费者先启动,队列没有声明导致失败
//String queue, 队列名
// boolean durable, 持久化
// boolean exclusive, 排他的
// boolean autoDelete, 自动删除
// Map arguments 属性
channel.queueDeclare("my_queue2",true,false,false,null);
// channel.queueDeclare("my_queue1",true,false,false,null);
// 5声明交换机
channel.exchangeDeclare("my_exchange",BuiltinExchangeType.FANOUT,true,true,null);
// 6绑定交换机和队列
channel.queueBind("my_queue2","my_exchange","");
// channel.queueBind("my_queue1","my_exchange","");
// 7声明消费者逻辑
DefaultConsumer consumer = new DefaultConsumer(channel){
//consumerTag 消息者标签,在channel.basicConsume时候可以指定
//envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
//properties 属性信息
//body 消息
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//下面是业务逻辑
super.handleDelivery(consumerTag, envelope, properties, body);
System.out.println("consumerTag" + consumerTag);
System.out.println("交换机"+envelope.getExchange());
System.out.println("路由key"+envelope.getRoutingKey());
System.out.println("DeliveryTag" + envelope.getDeliveryTag());
}
};
// 8监听消息
// 参数1:队列名称
// 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
// 参数3:消息接收到后回调
channel.basicConsume("my_queue2",true,consumer);
}
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
//1设置连接信息
//ip
factory.setHost("localhost");
//端口
factory.setPort(5672);
//设置虚拟主机
factory.setVirtualHost("/");
//设置用户名
factory.setUsername("root");
//设置密码
factory.setPassword("123456");
// 2创建长连接
Connection connection = factory.newConnection();
// 3创建通道
Channel channel = connection.createChannel();
// 4声明队列 防止消费者先启动,队列没有声明导致失败
//String queue, 队列名
// boolean durable, 持久化
// boolean exclusive, 排他的
// boolean autoDelete, 自动删除
// Map arguments 属性
channel.queueDeclare("my_queue1",true,false,false,null);
// channel.queueDeclare("my_queue2",true,false,false,null);
// 5声明交换机
channel.exchangeDeclare("my_exchange", BuiltinExchangeType.FANOUT,true,true,null);
// 6绑定交换机和队列
channel.queueBind("my_queue1","my_exchange","");
// channel.queueBind("my_queue2","my_exchange","");
// 7声明消费者逻辑
DefaultConsumer consumer = new DefaultConsumer(channel){
//consumerTag 消息者标签,在channel.basicConsume时候可以指定
//envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
//properties 属性信息
//body 消息
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//下面是业务逻辑
super.handleDelivery(consumerTag, envelope, properties, body);
System.out.println("consumerTag" + consumerTag);
System.out.println("交换机"+envelope.getExchange());
System.out.println("路由key"+envelope.getRoutingKey());
System.out.println("DeliveryTag" + envelope.getDeliveryTag());
}
};
// 8监听消息
// 参数1:队列名称
// 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
// 参数3:消息接收到后回调
channel.basicConsume("my_queue1",true,consumer);
}
生产者流程
消费者流程
适用:
分布式日志收集系统,可以配合es进行实现
其交换机类型为direct
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
//1设置连接信息
//ip
factory.setHost("localhost");
//端口
factory.setPort(5672);
//设置虚拟主机
factory.setVirtualHost("/");
//设置用户名
factory.setUsername("root");
//设置密码
factory.setPassword("123456");
//2创建长连接
Connection connection = factory.newConnection();
//3创建通道
Channel channel = connection.createChannel();
// 4声明队列
// String queue, 队列名
// boolean durable, 持久化
// boolean exclusive, 排他的
// boolean autoDelete, 自动删除
// Map arguments 属性
channel.queueDeclare("my_queue1",true,false,false,null);
channel.queueDeclare("my_queue2",true,false,false,null);
// 5声明交换机
// 参数1:交换机名称
// 参数2:交换机类型,fanout、topic、direct、headers
// 参数3:是否定义持久化
// 参数4:是否在不使用的时候自动删除
// 参数5:属性
channel.exchangeDeclare("my_exchange1", BuiltinExchangeType.DIRECT,true,true,null);
// 6绑定交换机和队列
// 参数1 队列
// 参数2 交换机
// 参数3 路由箭
channel.queueBind("my_queue1","my_exchange1","error");
channel.queueBind("my_queue2","my_exchange1","error");
channel.queueBind("my_queue2","my_exchange1","info");
channel.queueBind("my_queue2","my_exchange1","warning");
String msg = "第二条信息 , routing = error";
String msg1 = "第二条信息 , routing = info";
String msg2 = "第二条信息 , routing = warning";
// 7发消息
// String exchange, 交换机 ""是默认交换机
// String routingKey, 路由键
// AMQP.BasicProperties props, 属性
// byte[] body 消息 string byte[] char[]如何相互转换的?
//这里的路由为"",因为由多个队列,不用指定对应的
channel.basicPublish("my_exchange1","error",null,msg.getBytes());
channel.basicPublish("my_exchange1","info",null,msg1.getBytes());
channel.basicPublish("my_exchange1","warning",null,msg2.getBytes());
// 8关闭连接 资源关闭的顺序,先关后出来的资源,最后关,第一个资源
channel.close();
connection.close();
}
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
//1设置连接信息
//ip
factory.setHost("localhost");
//端口
factory.setPort(5672);
//设置虚拟主机
factory.setVirtualHost("/");
//设置用户名
factory.setUsername("root");
//设置密码
factory.setPassword("123456");
// 2创建长连接
Connection connection = factory.newConnection();
// 3创建通道
Channel channel = connection.createChannel();
// 4声明队列 防止消费者先启动,队列没有声明导致失败
//String queue, 队列名
// boolean durable, 持久化
// boolean exclusive, 排他的
// boolean autoDelete, 自动删除
// Map arguments 属性
channel.queueDeclare("my_queue2",true,false,false,null);
// 5声明交换机
channel.exchangeDeclare("my_exchange1", BuiltinExchangeType.DIRECT,true,true,null);
// 6绑定交换机和队列
channel.queueBind("my_queue2","my_exchange1","error");
channel.queueBind("my_queue2","my_exchange1","info");
channel.queueBind("my_queue2","my_exchange1","warning");
// 7声明消费者逻辑
DefaultConsumer consumer = new DefaultConsumer(channel){
//consumerTag 消息者标签,在channel.basicConsume时候可以指定
//envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
//properties 属性信息
//body 消息
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//下面是业务逻辑
super.handleDelivery(consumerTag, envelope, properties, body);
System.out.println("consumerTag" + consumerTag);
System.out.println("交换机"+envelope.getExchange());
System.out.println("路由key"+envelope.getRoutingKey());
System.out.println("DeliveryTag" + envelope.getDeliveryTag());
}
};
// 8监听消息
// 参数1:队列名称
// 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
// 参数3:消息接收到后回调
channel.basicConsume("my_queue2",true,consumer);
}
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
//1设置连接信息
//ip
factory.setHost("localhost");
//端口
factory.setPort(5672);
//设置虚拟主机
factory.setVirtualHost("/");
//设置用户名
factory.setUsername("root");
//设置密码
factory.setPassword("123456");
// 2创建长连接
Connection connection = factory.newConnection();
// 3创建通道
Channel channel = connection.createChannel();
// 4声明队列 防止消费者先启动,队列没有声明导致失败
//String queue, 队列名
// boolean durable, 持久化
// boolean exclusive, 排他的
// boolean autoDelete, 自动删除
// Map arguments 属性
channel.queueDeclare("my_queue1",true,false,false,null);
// 5声明交换机
channel.exchangeDeclare("my_exchange1", BuiltinExchangeType.DIRECT,true,true,null);
// 6绑定交换机和队列
channel.queueBind("my_queue1","my_exchange1","error");
// 7声明消费者逻辑
DefaultConsumer consumer = new DefaultConsumer(channel){
//consumerTag 消息者标签,在channel.basicConsume时候可以指定
//envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志(收到消息失败后是否需要重新发送)
//properties 属性信息
//body 消息
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//下面是业务逻辑
super.handleDelivery(consumerTag, envelope, properties, body);
System.out.println("consumerTag" + consumerTag);
System.out.println("交换机"+envelope.getExchange());
System.out.println("路由key"+envelope.getRoutingKey());
System.out.println("DeliveryTag" + envelope.getDeliveryTag());
}
};
// 8监听消息
// 参数1:队列名称
// 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
// 参数3:消息接收到后回调
channel.basicConsume("my_queue1",true,consumer);
}
路由模式和发布订阅的代码差不多,只是换了个交换机类型Direct和添加了路由键
这种是完善了上面的路由模式,因为有可能有大量的路由key,所以一个个写不现实。
ydlclass.taiyuan.caiwubu.info "本月工资大家涨两千!"
ydlclass.taiyuan.renshi.error "李老师携款潜逃!"
ydlclass.beijing.caiwubu.error "因为李老师逃了,全国所有校区降薪两千。不行就毕业!"
ydlclass.lasa.caiwubu.info "lasa校区成立了!"
ydlclass.taiyuan.shitangbu.info "太原校区学生吃饭免费!"
1
2
3
4
5
Topic
类型与Direct
相比,都是可以根据RoutingKey
把消息路由到不同的队列。只不过Topic
类型Exchange
可以让队列在绑定Routing key
的时候使用通配符!
Routingkey` 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: `item.insert
通配符规则:
#
:匹配一个或多个词
*
:匹配不多不少恰好1个词
举例:
我是太原校区校长 ydlclass.taiyuan.*.*
itlils 我是总部财务主管 ydlclass.*.caiwubu.*
1
2
图解:
usa.#
,因此凡是以 usa.
开头的routing key
都会被匹配到#.news
,因此凡是以 .news
结尾的 routing key
都会被匹配package com.ydlclass.rabbitmq.topic;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* @Created by IT李老师
* 公主号 “元动力课堂”
* 个人微 itlils
*/
public class Producer {
//交换机名称
static final String TOPIC_EXCHAGE = "topic_exchange";
//队列名称
static final String TOPIC_QUEUE_1 = "topic_queue_1";
//队列名称
static final String TOPIC_QUEUE_2 = "topic_queue_2";
public static void main(String[] args) throws Exception {
//1创建连接工厂
ConnectionFactory connectionFactory=new ConnectionFactory();
//连接的ip
connectionFactory.setHost("localhost");
//连接的端口
connectionFactory.setPort(5672);
//设置虚拟主机
connectionFactory.setVirtualHost("/");
//设置用户名
connectionFactory.setUsername("itlils");
//设置密码
connectionFactory.setPassword("itlils");
//2创建长连接
Connection connection = connectionFactory.newConnection();
//3创建channel
Channel channel = connection.createChannel();
//声明队列
//String queue, 队列名
// boolean durable, 持久化
// boolean exclusive, 排他的
// boolean autoDelete, 自动删除
// Map arguments 属性
channel.queueDeclare(TOPIC_QUEUE_1,true,false,false,null);
channel.queueDeclare(TOPIC_QUEUE_2,true,false,false,null);
// 声明交换机
// String exchange, 交换机名称
// BuiltinExchangeType type, 交换机类型
// boolean durable, 持久化
// boolean autoDelete, 自动删除
// Map arguments 属性
channel.exchangeDeclare(TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC,true,false,null);
//队列绑定交换机
// String queue, 队列名称
// String exchange, 交换机名称
// String routingKey 路由键
channel.queueBind(TOPIC_QUEUE_1,TOPIC_EXCHAGE,"ydlclass.taiyuan.*.*"); //我是太原校区校长的队列
channel.queueBind(TOPIC_QUEUE_2,TOPIC_EXCHAGE,"ydlclass.*.caiwubu.*");//我是总部财务主管的队列
//4发消息
// String exchange, 交换机
// String routingKey, 路由键
// AMQP.BasicProperties props, 属性
// byte[] body 消息 string byte[] char[]如何相互转换的?
String msg1="hello rabbitmq!topic 本月工资大家涨两千!";
channel.basicPublish(TOPIC_EXCHAGE,"ydlclass.taiyuan.caiwubu.info",null,msg1.getBytes());
String msg2="hello rabbitmq!topic 李老师携款潜逃!";
channel.basicPublish(TOPIC_EXCHAGE,"ydlclass.taiyuan.renshi.error",null,msg2.getBytes());
String msg3="hello rabbitmq!topic 因为李老师逃了,全国所有校区降薪两千。不行就毕业!";
channel.basicPublish(TOPIC_EXCHAGE,"ydlclass.beijing.caiwubu.error",null,msg3.getBytes());
//5关闭连接 资源关闭的顺序,先关后出来的资源,最后关,第一个资源
channel.close();
connection.close();
}
}
接收两种类型的消息:更新商品和删除商品
package com.ydlclass.rabbitmq.topic;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @Created by IT李老师
* 公主号 “元动力课堂”
* 个人微 itlils
*/
public class Consumer1 {
//交换机名称
static final String TOPIC_EXCHAGE = "topic_exchange";
//队列名称
static final String TOPIC_QUEUE_1 = "topic_queue_1";
//队列名称
static final String TOPIC_QUEUE_2 = "topic_queue_2";
public static void main(String[] args) throws Exception {
//1创建连接工厂
ConnectionFactory connectionFactory=new ConnectionFactory();
//连接的ip
connectionFactory.setHost("localhost");
//连接的端口
connectionFactory.setPort(5672);
//设置虚拟主机
connectionFactory.setVirtualHost("/");
//设置用户名
connectionFactory.setUsername("itlils");
//设置密码
connectionFactory.setPassword("itlils");
//2创建长连接
Connection connection = connectionFactory.newConnection();
//3创建channel
Channel channel = connection.createChannel();
//声明队列
//String queue, 队列名
// boolean durable, 持久化
// boolean exclusive, 排他的
// boolean autoDelete, 自动删除
// Map arguments 属性
channel.queueDeclare(TOPIC_QUEUE_1,true,false,false,null);
channel.queueDeclare(TOPIC_QUEUE_2,true,false,false,null);
// 声明交换机
// String exchange, 交换机名称
// BuiltinExchangeType type, 交换机类型
// boolean durable, 持久化
// boolean autoDelete, 自动删除
// Map arguments 属性
channel.exchangeDeclare(TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC,true,false,null);
//队列绑定交换机
// String queue, 队列名称
// String exchange, 交换机名称
// String routingKey 路由键
channel.queueBind(TOPIC_QUEUE_1,TOPIC_EXCHAGE,"ydlclass.taiyuan.*.*"); //我是太原校区校长的队列
channel.queueBind(TOPIC_QUEUE_2,TOPIC_EXCHAGE,"ydlclass.*.caiwubu.*");//我是总部财务主管的队列
//4监听某个队列
// String queue, 监听的队列名
// boolean autoAck, 是否自动应答
// Consumer callback 回调函数,收到消息,我要干啥
Consumer consumer=new DefaultConsumer(channel){
// 回调函数,收到消息,我要干啥
// String consumerTag, 消费者标签
// Envelope envelope, 信封 保存很多信息
// AMQP.BasicProperties properties, 属性
// byte[] body 消息字节数组
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//业务逻辑
//现在的业务逻辑就是打印
// System.out.println("consumerTag:"+consumerTag);
// System.out.println("Exchange:"+envelope.getExchange());
// System.out.println("RoutingKey:"+envelope.getRoutingKey());
// System.out.println("DeliveryTag:"+envelope.getDeliveryTag()); //消息id
System.out.println(new String(body));
}
};
channel.basicConsume(TOPIC_QUEUE_1,true,consumer);
//5 千万别关闭连接,要不然queue有了消息 推不过来了
// channel.close();
// connection.close();
}
}
接收所有类型的消息:新增商品,更新商品和删除商品。
package com.ydlclass.rabbitmq.topic;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @Created by IT李老师
* 公主号 “元动力课堂”
* 个人微 itlils
*/
public class Consumer2 {
//交换机名称
static final String TOPIC_EXCHAGE = "topic_exchange";
//队列名称
static final String TOPIC_QUEUE_1 = "topic_queue_1";
//队列名称
static final String TOPIC_QUEUE_2 = "topic_queue_2";
public static void main(String[] args) throws Exception {
//1创建连接工厂
ConnectionFactory connectionFactory=new ConnectionFactory();
//连接的ip
connectionFactory.setHost("localhost");
//连接的端口
connectionFactory.setPort(5672);
//设置虚拟主机
connectionFactory.setVirtualHost("/");
//设置用户名
connectionFactory.setUsername("itlils");
//设置密码
connectionFactory.setPassword("itlils");
//2创建长连接
Connection connection = connectionFactory.newConnection();
//3创建channel
Channel channel = connection.createChannel();
//声明队列
//String queue, 队列名
// boolean durable, 持久化
// boolean exclusive, 排他的
// boolean autoDelete, 自动删除
// Map arguments 属性
channel.queueDeclare(TOPIC_QUEUE_1,true,false,false,null);
channel.queueDeclare(TOPIC_QUEUE_2,true,false,false,null);
// 声明交换机
// String exchange, 交换机名称
// BuiltinExchangeType type, 交换机类型
// boolean durable, 持久化
// boolean autoDelete, 自动删除
// Map arguments 属性
channel.exchangeDeclare(TOPIC_EXCHAGE, BuiltinExchangeType.TOPIC,true,false,null);
//队列绑定交换机
// String queue, 队列名称
// String exchange, 交换机名称
// String routingKey 路由键
channel.queueBind(TOPIC_QUEUE_1,TOPIC_EXCHAGE,"ydlclass.taiyuan.*.*"); //我是太原校区校长的队列
channel.queueBind(TOPIC_QUEUE_2,TOPIC_EXCHAGE,"ydlclass.*.caiwubu.*");//我是总部财务主管的队列
//4监听某个队列
// String queue, 监听的队列名
// boolean autoAck, 是否自动应答
// Consumer callback 回调函数,收到消息,我要干啥
Consumer consumer=new DefaultConsumer(channel){
// 回调函数,收到消息,我要干啥
// String consumerTag, 消费者标签
// Envelope envelope, 信封 保存很多信息
// AMQP.BasicProperties properties, 属性
// byte[] body 消息字节数组
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//业务逻辑
//现在的业务逻辑就是打印
// System.out.println("consumerTag:"+consumerTag);
// System.out.println("Exchange:"+envelope.getExchange());
// System.out.println("RoutingKey:"+envelope.getRoutingKey());
// System.out.println("DeliveryTag:"+envelope.getDeliveryTag()); //消息id
System.out.println(new String(body));
}
};
channel.basicConsume(TOPIC_QUEUE_2,true,consumer);
}
}
通配符模式和上面的路由模式很像,只是交换机模式为Topic,还有路由键使用了通配符
RabbitMQ工作模式:
一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)。
一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)。
需要设置类型为fanout的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列。
需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列。
需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列。
依赖
<dependency>
<groupId>org.springframework.amqpgroupId>
<artifactId>spring-rabbitartifactId>
<version>2.2.15.RELEASEversion>
dependency>
连接信息的配置文件
rabbitmq.host=localhost
rabbitmq.port=5672
rabbitmq.username=root
rabbitmq.password=123456
rabbitmq.virtual-host=/
xml配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<rabbit:connection-factory id="connectionFactory"
host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<rabbit:admin connection-factory="connectionFactory"/>
<rabbit:queue id="ydlqueue" name="ydlqueue" auto-declare="true"/>
<rabbit:queue id="spring_fanout_queue_1" name="spring_fanout_queue_1" auto-declare="true" auto-delete="false" durable="true"/>
<rabbit:queue id="spring_fanout_queue_2" name="spring_fanout_queue_2" auto-declare="true"/>
<rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="spring_fanout_queue_1"/>
<rabbit:binding queue="spring_fanout_queue_2"/>
rabbit:bindings>
rabbit:fanout-exchange>
<rabbit:queue id="spring_direct_queue_1" name="spring_direct_queue_1" auto-declare="true"/>
<rabbit:queue id="spring_direct_queue_2" name="spring_direct_queue_2" auto-declare="true"/>
<rabbit:direct-exchange id="spring_direct_exchange" name="spring_direct_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="spring_direct_queue_1" key="error"/>
<rabbit:binding queue="spring_direct_queue_2" key="info"/>
<rabbit:binding queue="spring_direct_queue_2" key="error"/>
<rabbit:binding queue="spring_direct_queue_2" key="warning"/>
rabbit:bindings>
rabbit:direct-exchange>
<rabbit:queue id="spring_topic_queue_1" name="spring_topic_queue_1" auto-declare="true"/>
<rabbit:queue id="spring_topic_queue_2" name="spring_topic_queue_2" auto-declare="true"/>
<rabbit:topic-exchange id="spring_topic_exchange" name="spring_topic_exchange">
<rabbit:bindings>
<rabbit:binding queue="spring_topic_queue_1" pattern="ydlclass.taiyuan.*.*"/>
<rabbit:binding queue="spring_topic_queue_2" pattern="ydlclass.*.caiwubu.*"/>
rabbit:bindings>
rabbit:topic-exchange>
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
beans>
@Autowired
RabbitTemplate rabbitTemplate;
//简单模式发送
@Test
public void helloTest(){
// String exchange, 交换机
// String routingKey, 路由键
// Object message Object消息体 User car lunchuan
String msg="hello rabbitmq!";
rabbitTemplate.convertAndSend("","ydlqueue",msg);
}
//测试发布订阅模式
@Test
public void publishTest(){
String msg="hello rabbitmq! publish";
rabbitTemplate.convertAndSend("spring_fanout_exchange","",msg);
}
//测试routing模式
@Test
public void routingTest(){
String msg="hello rabbitmq!routing error";
rabbitTemplate.convertAndSend("spring_direct_exchange","error",msg);
String msg1="hello rabbitmq!routing info";
rabbitTemplate.convertAndSend("spring_direct_exchange","info",msg1);
String msg2="hello rabbitmq!routing warning";
rabbitTemplate.convertAndSend("spring_direct_exchange","warning",msg2);
}
//测试topic模式
@Test
public void topicTest(){
String msg1="hello rabbitmq!topic 本月工资大家涨两千!";
rabbitTemplate.convertAndSend("spring_topic_exchange","ydlclass.taiyuan.caiwubu.info",msg1);
String msg2="hello rabbitmq!topic 李老师携款潜逃!";
rabbitTemplate.convertAndSend("spring_topic_exchange","ydlclass.taiyuan.renshi.error",msg2);
String msg3="hello rabbitmq!topic 因为李老师逃了,全国所有校区降薪两千。不行就毕业!";
rabbitTemplate.convertAndSend("spring_topic_exchange","ydlclass.beijing.caiwubu.error",msg3);
}
1,编写监听方法
//缺啥补啥 干就完事
public class SpringQueueListener implements MessageListener {
//有了消息 做什么
@Override
public void onMessage(Message message) {
//业务逻辑
byte[] body = message.getBody();
System.out.println(new String(body));
}
}
2,注入监听,并且绑定监听器和队列
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<rabbit:connection-factory id="connectionFactory"
host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<rabbit:admin connection-factory="connectionFactory"/>
<rabbit:queue id="ydlqueue" name="ydlqueue" auto-declare="true"/>
<rabbit:queue id="spring_fanout_queue_1" name="spring_fanout_queue_1" auto-declare="true" auto-delete="false" durable="true"/>
<rabbit:queue id="spring_fanout_queue_2" name="spring_fanout_queue_2" auto-declare="true"/>
<rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="spring_fanout_queue_1"/>
<rabbit:binding queue="spring_fanout_queue_2"/>
rabbit:bindings>
rabbit:fanout-exchange>
<rabbit:queue id="spring_direct_queue_1" name="spring_direct_queue_1" auto-declare="true"/>
<rabbit:queue id="spring_direct_queue_2" name="spring_direct_queue_2" auto-declare="true"/>
<rabbit:direct-exchange id="spring_direct_exchange" name="spring_direct_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="spring_direct_queue_1" key="error">rabbit:binding>
<rabbit:binding queue="spring_direct_queue_2" key="info">rabbit:binding>
<rabbit:binding queue="spring_direct_queue_2" key="error">rabbit:binding>
<rabbit:binding queue="spring_direct_queue_2" key="warning">rabbit:binding>
rabbit:bindings>
rabbit:direct-exchange>
<rabbit:queue id="spring_topic_queue_1" name="spring_topic_queue_1" auto-declare="true"/>
<rabbit:queue id="spring_topic_queue_2" name="spring_topic_queue_2" auto-declare="true"/>
<rabbit:topic-exchange id="spring_topic_exchange" name="spring_topic_exchange">
<rabbit:bindings>
<rabbit:binding queue="spring_topic_queue_1" pattern="ydlclass.taiyuan.*.*">rabbit:binding>
<rabbit:binding queue="spring_topic_queue_2" pattern="ydlclass.*.caiwubu.*">rabbit:binding>
rabbit:bindings>
rabbit:topic-exchange>
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
<bean id="springQueueListener" class="com.example.spring_consumer.listener.SpringQueueListener"/>
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
<rabbit:listener ref="springQueueListener" queue-names="ydlqueue"/>
rabbit:listener-container>
beans>
上面的都是使用spring模式的,接下来使用springboot
由于spring的xml方式太复杂,所以下面我们用配置类的方式来代替
springboot的MQ依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
配置类
//下面是使用配置文件的方式来代替xml
/**
* @Created by IT李老师
* 公主号 “元动力课堂”
* 个人微 itlils
*/
@Configuration
public class RabbitConfig {
@Bean("boot_hello_queue")
public Queue queue(){
//String queue, 队列名
// boolean durable, 持久化
// boolean exclusive, 排他的
// boolean autoDelete, 自动删除
// Map arguments 属性
return new Queue("boot_hello_queue",true,false,false,null);
}
//发布订阅模式
//交换机名称
public static final String FANOUT_EXCHAGE = "boot_fanout_exchange";
//队列名称
public static final String FANOUT_QUEUE_1 = "boot_fanout_queue_1";
//队列名称
public static final String FANOUT_QUEUE_2 = "boot_fanout_queue_2";
@Bean(FANOUT_QUEUE_1)
public Queue FANOUT_QUEUE_1(){
return new Queue(FANOUT_QUEUE_1,true,false,false,null);
}
@Bean(FANOUT_QUEUE_2)
public Queue FANOUT_QUEUE_2(){
return new Queue(FANOUT_QUEUE_2,true,false,false,null);
}
@Bean(FANOUT_EXCHAGE)
public Exchange FANOUT_EXCHAGE(){
return ExchangeBuilder.fanoutExchange(FANOUT_EXCHAGE).durable(true).build();
}
@Bean
public Binding FANOUT_QUEUE_1_FANOUT_EXCHAGE(@Qualifier(FANOUT_QUEUE_1) Queue queue,
@Qualifier(FANOUT_EXCHAGE) Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}
@Bean
public Binding FANOUT_QUEUE_2_FANOUT_EXCHAGE(@Qualifier(FANOUT_QUEUE_2) Queue queue,
@Qualifier(FANOUT_EXCHAGE) Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}
//队列名称和交换机名称
//交换机名称
public static final String DIRECT_EXCHAGE = "boot_direct_exchange";
//队列名称
public static final String DIRECT_QUEUE_1 = "boot_direct_queue_1";
//队列名称
public static final String DIRECT_QUEUE_2 = "boot_direct_queue_2";
//注入队列
//注入交换机
//然后声明队列
@Bean(DIRECT_QUEUE_1)
public Queue DIRECT_QUEUE_1(){
return new Queue(DIRECT_QUEUE_1,true,false,false,null);
}
@Bean(DIRECT_QUEUE_2)
public Queue DIRECT_QUEUE_2(){
return new Queue(DIRECT_QUEUE_2,true,false,false,null);
}
//声明交换机
@Bean(DIRECT_EXCHAGE)
public Exchange DIRECT_EXCHAGE(){
return ExchangeBuilder.directExchange(DIRECT_EXCHAGE).durable(true).build();
}
//声明绑定关系
@Bean
public Binding DIRECT_QUEUE_1_FANOUT_EXCHAGE(@Qualifier(DIRECT_QUEUE_1) Queue queue,
@Qualifier(DIRECT_EXCHAGE) Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("").noargs();//要写多个路由模式
}
@Bean
public Binding DIRECT_QUEUE_2_FANOUT_EXCHAGE(@Qualifier(DIRECT_QUEUE_2) Queue queue,
@Qualifier(DIRECT_EXCHAGE) Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}
//topic模式
}
配置yml
spring:
rabbitmq:
host: localhost
password: 123456
username: root
port: 5672
virtual-host: /
然后直接注入就可以使用了
以下和生产者一样
依赖
配置信息
配置类
开启监听类
@Component
public class MyLisener {
//监听那个队列
@RabbitListener(queues = RabbitConfig.DIRECT_QUEUE_1)
public void receiveMsg(Message message){
MessageProperties messageProperties = message.getMessageProperties();//参数
//数据
Object targetBean = messageProperties.getTargetBean();
//还可以去获取你的交换机等等
}
}
配置类,就是这个路由键变成confirm
//队列名称和交换机名称
//交换机名称
public static final String DIRECT_EXCHAGE = "boot_direct_exchange";
//队列名称
public static final String DIRECT_QUEUE_1 = "boot_direct_queue_1";
//队列名称
public static final String DIRECT_QUEUE_2 = "boot_direct_queue_2";
//注入队列
//注入交换机
//然后声明队列
@Bean(DIRECT_QUEUE_1)
public Queue DIRECT_QUEUE_1(){
return new Queue(DIRECT_QUEUE_1,true,false,false,null);
}
@Bean(DIRECT_QUEUE_2)
public Queue DIRECT_QUEUE_2(){
return new Queue(DIRECT_QUEUE_2,true,false,false,null);
}
//声明交换机
@Bean(DIRECT_EXCHAGE)
public Exchange DIRECT_EXCHAGE(){
return ExchangeBuilder.directExchange(DIRECT_EXCHAGE).durable(true).build();
}
//声明绑定关系
@Bean
public Binding DIRECT_QUEUE_1_FANOUT_EXCHAGE(@Qualifier(DIRECT_QUEUE_1) Queue queue,
@Qualifier(DIRECT_EXCHAGE) Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("confirm").noargs();//要写多个路由模式
}
@Bean
public Binding DIRECT_QUEUE_2_FANOUT_EXCHAGE(@Qualifier(DIRECT_QUEUE_2) Queue queue,
@Qualifier(DIRECT_EXCHAGE) Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}
开启确认模式
使用xml实在配置连接信息的时候设置
<rabbit:connection-factory id="connectionFactory"
host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"
publisher-confirms="true"
/>
springboot配置文件也可以设置
spring:
rabbitmq:
username: root
password: 123456
virtual-host: /
host: localhost
port: 5672
publisher-confirm-type: correlated
确认模式就是为了不让其出错,也就是在消息发送和接收出错了,的一种换回机制
我们需要在template中重写回调函数
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("接收到确认信息");
System.out.println("是否成功" + ack);
System.out.println("失败原因" + cause);
}
});
}
上面是无论成不成功都会调用,而这个只有失败了才会调用
xml中开启退回模式
<rabbit:connection-factory id="connectionFactory"
host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"
publisher-confirms="true"
publisher-returns="true"
/>
spring:
rabbitmq:
username: root
password: 123456
virtual-host: /
host: localhost
port: 5672
publisher-confirm-type: correlated
publisher-returns: true
设置交换机处理失败消息模式
编写回调
@Test
void contextLoads2() {
//设置开启return
rabbitTemplate.setMandatory(true);
//2.设置ReturnCallBack
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
*
* @param message 消息对象
* @param replyCode 错误码
* @param replyText 错误信息
* @param exchange 交换机
* @param routingKey 路由键
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("return 执行了....");
System.out.println(message);
System.out.println(replyCode);
System.out.println(replyText);
System.out.println(exchange);
System.out.println(routingKey);
//处理
}
});
//3. 发送消息
rabbitTemplate.convertAndSend("test_exchange_confirm", "confirm", "message confirm....");
}
return相当于Exception
confirm相当于过滤器
有三种确认方式
自动确认:acknowledge=“none”
• 手动确认:acknowledge=“manual”
• 根据异常情况确认:acknowledge=“auto”
1,设置手动ACK
spring:
rabbitmq:
username: root
password: 123456
virtual-host: /
host: localhost
port: 5672
publisher-confirm-type: correlated
publisher-returns: true
listener:
simple:
acknowledge-mode: manual
2,编写ACK过滤器
若不成功就要保存。
@Component
public class AckListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
//接收者ID
long deliveryTag = message.getMessageProperties().getDeliveryTag();
//做业务逻辑
try {
//做业务逻辑 没问题
//告诉MQ说接收到消息,也就是可以删除消息了
channel.basicAck(deliveryTag,true);
}catch (Exception e){
//出错了,重回队列 第三个布尔类型,如果是true就重回队列,不然拒绝接收
channel.basicNack(deliveryTag,true,true);
}
}
}
手动ACK好像是全局的。刚刚上面的 @RabbitListener(queues = RabbitConfig.DIRECT_QUEUE_1)是针对监听某个队列,而且无ACK。
上面就是可靠性投递。
1,生产者return模式
2,消费者手动ACK
3,持久化MQ的队列和交换机
4,集群部署
5, message要持久化
MQ可以进行削峰减流
1,首先要是手动ACK模式
spring:
rabbitmq:
username: root
password: 123456
virtual-host: /
host: localhost
port: 5672
publisher-confirm-type: correlated
publisher-returns: true
listener:
simple:
acknowledge-mode: manual
过滤器
@Component
public class AckListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
//接收者ID
long deliveryTag = message.getMessageProperties().getDeliveryTag();
//做业务逻辑
try {
//做业务逻辑 没问题
//告诉MQ说接收到消息,也就是可以删除消息了
channel.basicAck(deliveryTag,true);
}catch (Exception e){
//出错了,重回队列
channel.basicNack(deliveryTag,true,true);
}
}
}
2,监听器设定prefetch批处理条数
每次处理100条
spring:
rabbitmq:
username: root
password: 123456
virtual-host: /
host: localhost
port: 5672
publisher-confirm-type: correlated
publisher-returns: true
listener:
simple:
acknowledge-mode: manual
direct:
prefetch: 100
场景带入,比如我们下单了一样商品,但是在30分钟内没有支付也就自动放弃了。所以,对此我们可以进行对队列里面的消息进行设置实现限制,如果没有
在规定时间内消费,就被剔除。
我们可以从两个方面进行设置时间
注意:
xmlBean
<rabbit:queue name="test_queue_ttl" id="test_queue_ttl">
<rabbit:queue-arguments>
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
rabbit:queue-arguments>
rabbit:queue>
springboot注入bean的方式
注入bean的方式是注入一个属性,其实和上面一样,也就是在属性里面插入一个时间,然后拿出来判断而已
我们可以设置以下属性,所以我们就可以添加一个map,带x-message-ttl时间。
如下,这样他的超时时间就是设置的时间,系统自动处理
@Bean(DIRECT_QUEUE_1)
public Queue DIRECT_QUEUE_1(){
Map<String, Object> map = new HashMap<>();
map.put("x-message-ttl",100000L);
return new Queue(DIRECT_QUEUE_1,true,false,false,map);
}
x-message-ttl:队列中消息的存活时间(毫秒),达到TTL的消息可能会被删除。
x-expires:队列在多长时间(毫秒)没有被访问以后会被删除。
x-max-length:队列中的最大消息数。
x-max-length-bytes:队列的最大容量(bytes)。
overflow:队列溢出之后的策略。主要可以配置如下参数:reject-publish - 直接丢弃最近发布的消息,如若启用了publisher confirm(发布者确认),发布者将通过发送 basic.nack 消息通知拒绝,如果当前队列绑定有多个消费者,则消息在收到 basic.nack 拒绝通知后,仍然会被发布到其他队列;drop-head - 丢弃队列头部消息(集群模式下只支持这种策略) reject-publish-dlx - 最近发布的消息会进入死信队列。
x-dead-letter-exchange:队列的死信交换机。
x-dead-letter-routing-key:死信交换机的路由键。
x-single-active-consumer:true/false。表示是否最多只允许一个消费者消费,如果有多个消费者同时绑定,则只会激活第一个,除非第一个消费者被取消或者死亡,才会自动转到下一个消费者。
x-max-priority:队列中消息的最大优先级, 消息的优先级不能超过它。
x-queue-mode:3.6.0 版本引入的,主要是为了实现惰性加载。队列将收到的消息尽可能快的进行持久化操作到磁盘上,然后只有在用户请求的时候才会加载到 RAM 内存。这个参数支持两个值:default 和 lazy。当不进行设置的时候,就是默认为 default,不做任何改变;当设置为 lazy 就会进行懒加载。
x-queue-master-locator:为了保证消息的 FIFO,所以在高可用集群模式下需要选择一个节点作为主节点。这个参数主要有三种模式:min-masters- 托管最小数量的绑定主机的节点;client-local- 选择声明的队列已经连接到客户端的节点;random- 随机选择一个节点。
就是实现MessagePostProcessor消息后处理对象,重写postProcessMessage(Message message)方法。
@Test
public void testTtl2() {
// 消息后处理对象,设置一些消息的参数信息
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//1.设置message的信息
message.getMessageProperties().setExpiration("5000");//消息的过期时间
//2.返回该消息
return message;
}
};
就是一些过期消息放到死信交换机上面,也就是发送到死信交换机和死信队列,然后发送给运维人员。
死信的3种情况
在ACK我们的catch中也可以放到死信
1,在消息的生产方中,在 spring-rabbitmq-producer.xml 配置文件中,添加如下配置:
声明正常的队列(test_queue_dlx)和交换机(test_exchange_dlx)
<rabbit:queue name="test_queue_dlx" id="test_queue_dlx">
rabbit:queue>
<rabbit:topic-exchange name="test_exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx">rabbit:binding>
rabbit:bindings>
rabbit:topic-exchange>
声明死信队列(queue_dlx)和死信交换机(exchange_dlx)
<rabbit:queue name="queue_dlx" id="queue_dlx">rabbit:queue>
<rabbit:topic-exchange name="exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="dlx.#" queue="queue_dlx">rabbit:binding>
rabbit:bindings>
rabbit:topic-exchange>
正常队列绑定死信交换机,并设置相关参数信息
<rabbit:queue name="test_queue_dlx" id="test_queue_dlx">
<rabbit:queue-arguments>
<entry key="x-dead-letter-exchange" value="exchange_dlx" />
<entry key="x-dead-letter-routing-key" value="dlx.hehe" />
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer" />
<entry key="x-max-length" value="10" value-type="java.lang.Integer" />
rabbit:queue-arguments>
rabbit:queue>
<rabbit:topic-exchange name="test_exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx">rabbit:binding>
rabbit:bindings>
rabbit:topic-exchange>
2,编写测试方法
/**
* 发送测试死信消息:
* 1. 过期时间
* 2. 长度限制
* 3. 消息拒收
*/
@Test
public void testDlx(){
//1. 测试过期时间,死信消息
rabbitTemplate.convertAndSend("test_exchange_dlx",
"test.dlx.haha","我是一条消息,我会死吗?");
//2. 测试长度限制后,消息死信
for (int i = 0; i < 20; i++) {
rabbitTemplate.convertAndSend("test_exchange_dlx",
"test.dlx.haha","我是一条消息,我会死吗?");
}
//3. 测试消息拒收
rabbitTemplate.convertAndSend("test_exchange_dlx",
"test.dlx.haha","我是一条消息,我会死吗?");
}
3,消息拒绝接受消费者增加监听
package com.ydlclass.listener;
/**
* creste by ydlclass.itcast
*/
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
/**
* Consumer ACK机制:
* 1. 设置手动签收。acknowledge="manual"
* 2. 让监听器类实现ChannelAwareMessageListener接口
* 3. 如果消息成功处理,则调用channel的 basicAck()签收
* 4. 如果消息处理失败,则调用channel的basicNack()拒绝签收,broker重新发送给consumer
*/
@Component
public class DlxListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//1.接收转换消息
System.out.println(new String(message.getBody()));
//2. 处理业务逻辑
System.out.println("处理业务逻辑...");
int i = 3/0;//出现错误
//3. 手动签收
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
//e.printStackTrace();
System.out.println("拒绝接受");
//4.拒绝签收
/*
第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
*/
channel.basicNack(deliveryTag,true,false);
// 了解
//channel.basicReject(deliveryTag,true);
}
}
}
配置文件
<rabbit:listener ref="dlxListener" queue-names="test_queue_dlx"/>
死信队列的应用
下单秒杀
配置类
//交换机名称
public static String ITEM_TOPIC_EXCHANGE="xwl_exchange";
//队列名称
public static final String ITEM_QUEUE = "xwl_queue";
//声明交换机
@Bean("xwlTopicExchange")
public Exchange topicExchange(){
return ExchangeBuilder.topicExchange(ITEM_TOPIC_EXCHANGE).durable(true).build();
}
//声明主队列
@Bean("xwlQueue")
public Queue itemQueue(){
Map<String, Object> args = new HashMap<>();
//声明死信交换器
args.put("x-dead-letter-exchange", "deal_exchange");
//声明死信路由键
args.put("x-dead-letter-routing-key", "DelayKey");
//声明主队列如果发生堵塞或其它-10秒自动消费消息
args.put("x-message-ttl",10000);
return QueueBuilder.durable(ITEM_QUEUE).withArguments(args).build();
}
//主队列绑定交换机以及-路由(此处采用TOPC通配符)
@Bean
public Binding itemQueueExchange(@Qualifier("xwlQueue") Queue queue,
@Qualifier("xwlTopicExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("item.#").noargs();
}
//声明死信队列
@Bean("dealQueue")
public Queue dealQueue(){
return QueueBuilder.durable("deal_queue").build();
}
//声明死信交换机
@Bean("dealExchange")
public Exchange dealExchange(){
return ExchangeBuilder.topicExchange("deal_exchange").durable(true).build();
}
//死信队列绑定交换机以及路由key
@Bean
public Binding dealQueueExchange(@Qualifier("dealQueue") Queue queue,
@Qualifier("dealExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("DelayKey").noargs();
}
配置yml
spring:
rabbitmq:
host: localhost #地址
port: 5672 #端口
username: xwl #用户名
password: 258000 #密码
virtual-host: /xwl #虚拟机地址/权限
template:
#exchange: xwl_exchange #交换机
retry:
initial-interval: 10000ms #如果没有接收到消费回执,即每隔10秒访问一次
enabled: true #开启重试机制
max-interval: 30000ms #最大叠加制不超过30秒
max-attempts: 2 #每次访问一次间隔后都以2倍叠加再次访问
listener:
simple:
default-requeue-rejected: false #监听器抛出异常而拒绝的消息是否被重新放回队列。默认值为true
#none无应答确认发送
#manual意味着监听者必须通过调用Channel.basicAck()来告知所有的消息。
#auto意味着容器会自动应答,除非MessageListener抛出异常,这是默认配置方式。
acknowledge-mode: manual
prefetch: 1
concurrency: 5 #消费者监听 分发5个队列执行
type: simple
publisher-confirms: true
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/mythread?characterEncoding=utf-8
eureka:
client:
register-with-eureka: false #单体应用测试-不注册eureka
fetch-registry: false #不发送心跳到注册中心
#配置mybatis-plus打印sql语句于控制台
mybatis-plus:
configuration:
#log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#开启驼峰命名转换
map-underscore-to-camel-case: true
下单请求接口
@RequestMapping("skill")
public class SeckKillController {
private RedisTemplate redisTemplate;
private ISeckKillService seckKillService;
/**
**模拟用户组
**/
public List<String> getUsers(){
return Arrays.asList("张三","李四","王五","赵六","李珏","郭思","吕布","王月英","嘻哈","田丰");
}
/**
* 模拟抢单-入口
* @param -用户名
* @param -商品类型 -此处默认1
* @return
*/
@GetMapping("getShopByType")
public String getShopByType(){
//为了演示结果需
redisTemplate.opsForValue().set("stockCount",null);
getUsers().stream().forEach(name ->{
seckKillService.getShopByType(name,1);
});
return "已经收到您的抢购申请,请稍后留意信息提示结果";
}
}
@AllArgsConstructor
public class SeckKillServiceImpl implements ISeckKillService {
//交换机名称
public static final String ITEM_TOPIC_EXCHANGE = "xwl_exchange";
//下单队列路由key
public static final String ITEM_ROUKEY = "item.sendKill";
//引入消息发送API
private RabbitTemplate rabbitTemplate;
@Override
@SneakyThrows
public void getShopByType(String userName, Integer shopType) {
//不做任何操作处理~直接进去队列-
rabbitTemplate.convertAndSend(ITEM_TOPIC_EXCHANGE,ITEM_ROUKEY,userName);
}
}
消费监听
@Component
@Configuration
@SuppressWarnings("ALL")
public class SendKillListener {
//交换机名称
public static final String ITEM_TOPIC_EXCHANGE = "xwl_exchange";
//队列名称
public static final String ITEM_QUEUE = "xwl_queue";
private RedisTemplate re;
private StokOrderMapper stokOrderMapper;
private OrderInfoMapper orderInfoMapper;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StokOrderMapper sr;
@Autowired
private OrderInfoMapper oo;
@PostConstruct
public void init() {
this.re = redisTemplate;
this.orderInfoMapper = oo;
this.stokOrderMapper = sr;
}
/**
* 监听主队列~
*
* @param message
* @param map
* @param channel
* @throws InterruptedException
* @throws IOException
*/
@RabbitListener(queues = "xwl_queue")
public void sendMiss(Message message, @Headers Map<String, Object> map, Channel channel) throws InterruptedException, IOException {
String msg = new String(message.getBody(), "UTF-8");
Integer shopCount=0;
//第一个请求进来获取库存-先去缓存redis找对应key值如果没有发送一个连接查询后续无需再次获取库存
if (StringUtils.isEmpty(re.opsForValue().get("stockCount"))) {
re.opsForValue().set("stockCount", stokOrderMapper.findCountByShopType(1), 60, TimeUnit.MINUTES);
shopCount = ((Integer) re.opsForValue().get("stockCount"));
}else {
//自减缓存内库存量- 每次减-
shopCount = ((Integer) re.opsForValue().get("stockCount"))-1;
}
//如果库存量小于等于0即已经抢完
if (shopCount<= 0) {
//即放入死信队列-推送后续队列内消息即为抢单失败-
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
//返回 不做后续处理
return;
}
//如果库存数不为0即没有抢完
//数据库存储订单
orderInfoMapper.insert(new OrderInfo(msg, UUID.randomUUID().toString(), 1));
//设置缓存库存量key过期时间-redis自行删除(赋值减值)
re.opsForValue().set("stockCount", shopCount, 60, TimeUnit.MINUTES);
//手动设置ACK接收确认当前消息消费完毕
;
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println(msg + "抢购成功,恭喜!");
}
/**
* 监听死信队列-即推送抢单失败
*
* @param message
* @param map
* @param channel
* @throws InterruptedException
* @throws IOException
*/
@RabbitListener(queues = "deal_queue")
public static void sendMiss2(Message message, @Headers Map<String, Object> map, Channel channel) throws InterruptedException, IOException {
String msg = new String(message.getBody(), "UTF-8");
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println(msg + "商品已被抢空~下次再来");
}
}
实现方法
注意:在RabbitMQ中并未提供延迟队列功能。
但是可以使用:TTL+死信队列 组合实现延迟队列的效果
乐观锁机制