MQ全称 Message Queue(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。
MQ的优势:
常见的 MQ 产品有很多,在这里我们主要简绍RabbitMQ这个产品
RabbitMQ的基础架构如下:
RabbitMQ提供了6种模式:
在这里主要简绍Publish/Subscribe发布与订阅模式,Routing路由模式,Topics主题模式
参考文档:https://blog.csdn.net/shz_123/article/details/122803739?spm=1001.2014.3001.5502
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-amqp
spring:
rabbitmq:
#rabbitmq安装宿主机IP
host: 192.168.126.140
#登录管理控制台端口
port: 5672
#登录管理控制台的用户名
username: admin
password: 123456
virtual-host: /song
package com.song.springbootrabbitmqproducer.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
*Publish/Subscribe发布与订阅模式的config
* @author song
*/
@Configuration
public class PublishModelConfig {
/**队列_1名称*/
public static final String PUBLISH_QUEUE_1 = "publish_queue_1";
/**队列_2名称*/
public static final String PUBLISH_QUEUE_2 = "publish_queue_2";
/**交换器名称*/
public static final String PUBLISH_EXCHANGE = "publish_exchange";
/**
* 申明交换机 durable=true表示持久化
* @return 申明好的交换机
*/
@Bean("publishEx")
public Exchange getExchange() {
return ExchangeBuilder.fanoutExchange(PUBLISH_EXCHANGE).durable(true).build();
}
/**
* 申明队列 持久化
* @return 申明好的队列
*/
@Bean("publishQueue1")
public Queue getQueue1(){
return QueueBuilder.durable(PUBLISH_QUEUE_1).build();
}
/**
* 申明队列 持久化
* @return 申明好的队列
*/
@Bean("publishQueue2")
public Queue getQueue2(){
return QueueBuilder.durable(PUBLISH_QUEUE_2).build();
}
/**
* 将队列_1与交换器进行绑定
* @param queue 要绑定的队列
* @param exchange 要绑定的交换器
* @return 绑定好的信息
*/
@Bean
public Binding getBinding1(@Qualifier("publishQueue1") Queue queue,
@Qualifier("publishEx") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}
/**
* 将队列_2与交换器进行绑定
* @param queue 要绑定的队列
* @param exchange 要绑定的交换器
* @return 绑定好的信息
*/
@Bean
public Binding getBinding2(@Qualifier("publishQueue2") Queue queue,
@Qualifier("publishEx") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}
}
测试 发布与订阅模式
@SpringBootTest
class SpringbootRabbitmqProducerApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void test1() {
//发送消息===》发布与订阅模式
rabbitTemplate.convertAndSend(PublishModelConfig.PUBLISH_EXCHANGE,"","发布与订阅模式");
}
}
在RabbitMQ管理控制台可以看到,两个队列分别都接收到了一条信息
在Consumer模块接收信息
@Component
public class MyListener{
@RabbitListener(queues = "publish_queue_2")
public void pubListener2(String message) {
System.out.println("消费者2======接收到的消息为:" + message);
}
@RabbitListener(queues = "publish_queue_1")
public void pubListener1(String message) {
System.out.println("消费者1=====接收到的消息为:" + message);
}
}
测试
@SpringBootTest
class SpringbootRabbitmqCustomerApplicationTests {
@Autowired
private MyListener myListener;
@Test
//测试设置为死循环,只要生产者发送消息,消费者就可以接收
void test() {
while(true){
}
}
}
Routing路由模式的config(生产者发送消息,交换机根据routingKey将消息分发到队列,在由消费者接收)
/**
* Routing路由模式的config
* @author song
*/
@Configuration
public class RoutingModelConfig {
/**队列名称 routingkey=insert*/
public static final String ROUTING_QUEUE_KEY_INSERT = "routing_queue_key_insert";
/**队列_1名称 routingkey=delete*/
public static final String ROUTING_QUEUE_KEY_DELETE = "routing_queue_key_delete";
/**交换器名称*/
public static final String ROUTING_EXCHANGE = "routing_exchange";
/**
* 申明交换机 durable=true表示持久化
* @return 申明好的交换机
*/
@Bean("routingEx")
public Exchange getExchange() {
return ExchangeBuilder.directExchange(ROUTING_EXCHANGE).durable(true).build();
}
/**
* 申明队列 持久化
* @return 申明好的队列
*/
@Bean("routingQueue_insert")
public Queue getQueue1(){
return QueueBuilder.durable(ROUTING_QUEUE_KEY_INSERT).build();
}
/**
* 申明队列 持久化
* @return 申明好的队列
*/
@Bean("routingQueue_delete")
public Queue getQueue2(){
return QueueBuilder.durable(ROUTING_QUEUE_KEY_DELETE).build();
}
/**
* 队列名称 路由key=insert与交换器进行绑定
* @param queue 要绑定的队列
* @param exchange 要绑定的交换器
* @return 绑定好的信息
*/
@Bean
public Binding getRoutBinding1(@Qualifier("routingQueue_insert") Queue queue,
@Qualifier("routingEx") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("insert").noargs();
}
/**
* 队列_1名称 路由key=delete与交换器进行绑定
* @param queue 要绑定的队列
* @param exchange 要绑定的交换器
* @return 绑定好的信息
*/
@Bean
public Binding getRoutBinding2(@Qualifier("routingQueue_delete") Queue queue,
@Qualifier("routingEx") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("delete").noargs();
}
}
测试 routing路由模式
@SpringBootTest
class SpringbootRabbitmqProducerApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void test2() {
rabbitTemplate.convertAndSend(RoutingModelConfig.ROUTING_EXCHANGE,"insert","routing模式====routingKey:" +
"insert");
rabbitTemplate.convertAndSend(RoutingModelConfig.ROUTING_EXCHANGE,"delete","routing模式====routingKey:" +
"delete");
}
}
在RabbitMQ管理控制台可以看到
在Consumer模块接收信息
@Component
public class MyListener{
@RabbitListener(queues = "routing_queue_key_delete")
public void routListener2(String message) {
System.out.println("消费者2======接收到的消息为:" + message);
}
@RabbitListener(queues = "routing_queue_key_insert")
public void routListener1(String message) {
System.out.println("消费者1=====接收到的消息为:" + message);
}
}
测试 与 上述发布与订阅模式一样,不再叙述,直接看结果
Topics通配符模式的config(生产者发送消息,交换机根据routingKey将消息分发到队列,在由消费者接收)
Topic 类型与 Routining相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型
Exchange 可以让队列在绑定 Routing key 的时候使用通配符!
Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
通配符规则:
#:匹配一个或多个词
*:匹配不多不少恰好1个词
/**Topics主题模式的config
* @author song
*/
@Configuration
public class TopicModelConfig {
/**队列名称 路由key=item.# */
public static final String TOPIC_QUEUE_KEY_MONEY = "topic_queue_key_#";
/**队列_1名称 路由key=item.* */
public static final String TOPIC_QUEUE_KEY_ONE = "topic_queue_key_*";
/**交换器名称*/
public static final String TOPIC_EXCHANGE = "topic_exchange";
/**
* 申明交换机 durable=true表示持久化
* @return 申明好的交换机
*/
@Bean("topicEx")
public Exchange getExchange() {
return ExchangeBuilder.topicExchange(TOPIC_EXCHANGE).durable(true).build();
}
/**
* 申明队列 持久化
* @return 申明好的队列
*/
@Bean("topicQueue_#")
public Queue getQueue1(){
return QueueBuilder.durable(TOPIC_QUEUE_KEY_MONEY).build();
}
/**
* 申明队列 持久化
* @return 申明好的队列
*/
@Bean("routingQueue_*")
public Queue getQueue2(){
return QueueBuilder.durable(TOPIC_QUEUE_KEY_ONE).build();
}
/**
* 队列名称 路由key=item.# 与交换器进行绑定
* @param queue 要绑定的队列
* @param exchange 要绑定的交换器
* @return 绑定好的信息
*/
@Bean
public Binding getTopBinding1(@Qualifier("topicQueue_#") Queue queue,
@Qualifier("topicEx") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("item.#").noargs();
}
/**
* 队列名称 路由key=item.* 与交换器进行绑定
* @param queue 要绑定的队列
* @param exchange 要绑定的交换器
* @return 绑定好的信息
*/
@Bean
public Binding getTopBinding2(@Qualifier("routingQueue_*") Queue queue,
@Qualifier("topicEx") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("item.*").noargs();
}
}
测试 Topics通配符模式
@SpringBootTest
class SpringbootRabbitmqProducerApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void test3() {
rabbitTemplate.convertAndSend(TopicModelConfig.TOPIC_EXCHANGE,
"item.insert","商品新增,routing key 为item.insert");
rabbitTemplate.convertAndSend(TopicModelConfig.TOPIC_EXCHANGE,
"item.update.date", "商品修改,routing key 为item.update.date");
rabbitTemplate.convertAndSend(TopicModelConfig.TOPIC_EXCHANGE,
"item.delete", "商品删除,routing key 为item.delete");
}
}
在RabbitMQ管理控制台可以看到
在Consumer模块接收信息
@Component
public class MyListener{
@RabbitListener(queues = "topic_queue_key_#")
public void topListener1(String message) {
System.out.println("消费者1=====接收到的消息为:" + message);
}
@RabbitListener(queues = "topic_queue_key_*")
public void topListener2(String message) {
System.out.println("消费者2=====接收到的消息为:" + message);
}
}
测试 与 上述发布与订阅模式一样,不再叙述,直接看结果
1.消息的可靠投递
在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。
RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。
confirm 确认模式
消息从 producer 到 exchange 发生错误,则会返回一个 confirmCallback
return 退回模式
消息从 exchange–>queue 投递失败则会返回一个 returnCallback 。
消息的可靠投递配置
spring:
rabbitmq:
#rabbitmq安装宿主机IP
host: 192.168.126.140
#端口
port: 5672
#登录管理控制台的用户名
username: admin
password: 123456
virtual-host: /song
#消息的可靠投递
# 旧版本 开启 confirm 确认模式
# publisher-confirms: true
# 新版的开启 confirm 确认模式
publisher-confirm-type: correlated
# 开启 return 退回模式
publisher-returns: true
测试
/**
* 确认模式测试
*/
@Test
void confirmTest() {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("confirm方法被执行了....");
if (ack) {
//接收成功
System.out.println("接收成功消息" + cause);
} else {
//接收失败
System.out.println("接收失败消息" + cause);
//做一些处理,让消息再次发送。
}
}
});
// rabbitTemplate.convertAndSend(TopicModelConfig.TOPIC_EXCHANGE,
// "item.insert", "商品新增,routing key 为item.insert");
rabbitTemplate.convertAndSend("error",
"item.insert", "商品新增,routing key 为item.insert");
}
模拟当发送消息时,交换机名称不对时,结果
/**
* 返回模式测试
*/
@Test
public void testReturn() {
//设置交换机处理失败消息的模式
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(TopicModelConfig.TOPIC_EXCHANGE, "item.insert",
// "message confirm....");
rabbitTemplate.convertAndSend(TopicModelConfig.TOPIC_EXCHANGE, "item1.insert",
"message confirm....");
}
模拟当发送消息时,routingKey不对时,结果
2.Consumer Ack 与 消费端限流
ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。
有三种确认方式:
自动确认:acknowledge=“none”
自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message从RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失
手动确认:acknowledge="manual
手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息
根据异常情况确认:acknowledge=“auto”,(这种方式使用麻烦,不作讲解)
消费端限流:是消费端每次从mq拉取限定个数的消息来消费
Consumer Ack 与 消费端限流 的配置文件(消费端配置)
spring:
rabbitmq:
host: 192.168.126.140
port: 5672
username: admin
password: 123456
virtual-host: /song
listener:
# RabbitMQ模式使用simple simple支持事务的
simple:
# Consumer ACK机制:设置为手动签收
acknowledge-mode: manual
# 限流,配置3 表示消费端每次向MQ拉取最大3条消息
prefetch: 3
消费者接收消息的代码
当消费者接收消息出错时
3.TTL
Time To Live,消息过期时间设置
当消息到达存活时间后,还没有被消费,会被自动清除。
RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。
设置队列过期时间使用参数:x-message-ttl,单位:ms(毫秒),会对整个队列消息统一过期
设置消息过期时间使用参数:expiration。单位:ms(毫秒),当该消息在队列头部时(消费时),会单独判断这一消息是否过期。
如果两者都进行了设置,以时间短的为准。
在列队代码中设置队列TTL
/**
* 申明队列 持久化
* @return 申明好的队列
*/
@Bean("routingQueue_*")
public Queue getQueue2(){
return QueueBuilder.durable(TOPIC_QUEUE_KEY_ONE).withArgument("x-message-ttl",10000).build();
}
在消息代码中设置队列TTL
@Test
void test2() {
rabbitTemplate.convertAndSend(RoutingModelConfig.ROUTING_EXCHANGE, "insert", "routing模式====routingKey:" +
"insert", new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("5000");//设置消息过期时间为5秒
return message;
}
});
}
4.死信队列
死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Deadmessage后,可以被重新发送到另一个交换机,这个交换机就是DLX。
消息成为死信的三种情况:
管控台中设置死信队列
在代码中设置死信队列
@Bean
public Exchange exchangeDlx(){
return ExchangeBuilder.topicExchange("dead_exchange").build();
}
@Bean
public Queue queueNormalDlx(){
return QueueBuilder.durable("queue_Normal_DLX")//正常队列的名称
.withArgument("x-dead-letter-exchange","dead_exchange")//设置改队列的死信交换机
.withArgument("x-dead-letter-routing-key","dlx.xf")//设置该队列的发送消息时指定的routingkey
.withArgument("x-message-ttl",10000)//设置队列中消息的过期时间
.withArgument("x-max-length",10).build();//设置队列的最大容量
}
5.延迟队列
延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。
但是可以使用:TTL+死信队列 组合实现延迟队列的效果。