RabbitMq实战
概念
Linux安装
docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management
RabbitMq运行机制
Exchange类型
快速开始-整合SpringBoot
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
主启动类
/**
* 1.引入 <dependency>
* <groupId>org.springframework.boot</groupId>
* <artifactId>spring-boot-starter-amqp</artifactId>
* </dependency>
* 给容器中配置了 CachingConnectionFactory RabbitConnectionFactoryBean RabbitTemplate RabbitMessagingTemplate AmqpAdmin
* 2.配置yaml
* 3.主启动类 @EnableRabbit
* 4.监听消息: 使用@RabbitListener,前提是开启@EnableRabbit,并且队列必须存在
* @RabbitListener: 用于类上或者方法上(监听哪些队列即可)
* @RabbitHandler: 用在方法上(重载区分接收不同的消息)
*
*/
@SpringBootApplication
@EnableRabbit
public class GulimallOrderApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallOrderApplication.class, args);
}
}
application.yaml
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
配置序列化为Json格式
package com.lin.gulimall.order.config;
import org.springframework.amqp.support.converter.Jackson2JavaTypeMapper;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Created by Lin Weihong
* @date on 2022/9/11 17:16
*/
@Configuration
public class MyRabbitConfig {
@Bean
public MessageConverter messageConverter(){
//使用json序列化
return new Jackson2JsonMessageConverter();
}
}
操作代码
发送消息
@SpringBootTest
@Slf4j
class GulimallOrderApplicationTests {
@Resource
AmqpAdmin amqpAdmin;
@Resource
RabbitTemplate rabbitTemplate;
/**
* 1.如何创建Exchange Queue Binding
* 1)、AmqpAdmin进行创建
* 2)、
* 2.如何收发消息
*/
@Test
void testCreateExchange(){
// public DirectExchange(String name, boolean durable, boolean autoDelete, Map<String, Object> arguments) {
DirectExchange directExchange = new DirectExchange("hello-java-exchange", true, false);
amqpAdmin.declareExchange(directExchange);
log.info("******Exchange创建成功{}", "hello-java-exchange");
}
@Test
void testCreateQueue() {
// public Queue(String name, boolean durable,
// boolean exclusive //是否排他,若为true,就是只能被一个声明的连接使用,工作中都为false
// , boolean autoDelete,@Nullable Map<String, Object> arguments) {
Queue queue = new Queue("hello-java-queue",true,false,false);
amqpAdmin.declareQueue(queue);
log.info("******Queue创建成功{}", "hello-java-queue");
}
/**
* 队列与交换机绑定
*/
@Test
void testCreateBinding() {
/**
* public Binding(
* String destination, 目的地
* DestinationType destinationType,目的地类型
* String exchange,交换机
* String routingKey,路由键
* @Nullable Map<String, Object> arguments) 自定义参数
* {
* 将exchange指定的交换机和destination目的地进行绑定,使用routingKey作为路由键
*/
Binding binding = new Binding("hello-java-queue", Binding.DestinationType.QUEUE, "hello-java-exchange", "hello.java", new HashMap<>());
amqpAdmin.declareBinding(binding);
// amqpAdmin.removeBinding(binding);
log.info("绑定成功exchange和queue");
}
/**
* 测试发送消息
*/
@Test
void testSendMessage(){
//发送对象,会使用序列化机制,必须实现Serializable接口
OrderReturnReasonEntity orderReturnReasonEntity = new OrderReturnReasonEntity();
orderReturnReasonEntity.setId(1L);
orderReturnReasonEntity.setName("你好测试rabbit");
String message = "hello";
// rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",message);
// rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",orderReturnReasonEntity);//默认是JDK序列化,rO0ABXNyADVjb20ubGluLmd1bGltYWxsLm9yZGVyLmVudGl0eS5PcmRlclJldHVyblJlYXNvbkVudGl0eQAAAAAAAAABAgAFTAAKY3JlYXRlVGltZXQAEExq...
//用Json传递对象,可以手动进行转换,也可以看MyRabbitConfig配置的,默认转化改为json
// String obj = JSON.toJSONString(orderReturnReasonEntity);
// rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",obj);
rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",orderReturnReasonEntity);
}
}
@RestController
public class RabbitController {
@Resource
RabbitTemplate rabbitTemplate;
@GetMapping("/sendMq")
public String sendMq(Integer sum){
for (int i = 0; i < sum; i++) {
if (i % 2 == 0) {
OrderReturnReasonEntity orderReturnReasonEntity = new OrderReturnReasonEntity();
orderReturnReasonEntity.setId(1L);
orderReturnReasonEntity.setName("你好测试rabbit-->" + i);
//最后一个参数是设置消息的唯一ID,方便我们消息投递失败的回调展示的Id更容易被我们定位
rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", orderReturnReasonEntity,new CorrelationData(UUID.randomUUID().toString()));
} else {
OrderEntity orderEntity = new OrderEntity();
orderEntity.setOrderSn(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", orderEntity);
}
}
return "OK";
}
}
接收消息
/**
* queues: 要接听的队列
*
* message.getClass(): org.springframework.amqp.core.Message
* T<发送的消息类型> OrderReturnReasonEntity content
* Channel channel: 当前传输消息的通道
*/
@RabbitListener(queues = {"hello-java-queue"})
public void receiveMessage(Message message, OrderReturnReasonEntity content, Channel channel) {
byte[] body = message.getBody();
//消息头信息
MessageProperties messageProperties = message.getMessageProperties();
// System.out.println("接收到的消息为: " + message +" ====> " + message.getClass());
System.out.println("接收到的消息为: " + message +" 内容 " + content);
}
多个服务器监听同一个Queue场景
生产者发送十条消息:
结果:
还有三条在单元测试里,不影响,实验问题
业务耗时场景
模拟服务端耗时业务
结果:
消息确认机制
ConfirmCallback
配置
application.yml
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
# publisher-confirms: true
# 开启发送端确认,上面这个过时了,用下面这个
publisher-confirm-type: correlated
MyRabbitConfig.java
package com.lin.gulimall.order.config;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JavaTypeMapper;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
/**
* @author Created by Lin Weihong
* @date on 2022/9/11 17:16
*/
@Configuration
public class MyRabbitConfig {
@Resource
RabbitTemplate rabbitTemplate;
/**
* 定制RabbitTemplate
* 1.在application.yaml里面开启
* spring:
* rabbitmq:
* publisher-confirm-type: correlated
* 2.设置确认回调
*/
@PostConstruct //MyRabbitConfig对象创建完成以后,执行这个方法
public void initRabbitTemplate() {
//设置确认回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* 1.只要消息到达Broker就会ack=true
* @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id)
* @param ack 消息是否成功收到
* @param cause 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("确认回调.....["+correlationData+"]==>ack["+ack+"]==>["+cause+"]");
}
});
}
}
效果
即使没有被消费也会触发,因为这是从publish->Exchange的 和消费无关
ReturnCallback
配置
application.yaml
spring:
rabbitmq:
publisher-returns: true
# 只要抵达队列,以异步方式优先回调这个returnConfirm
template:
mandatory: true
MyRabbitConfig.java
//设置消息抵达队列的确认回调
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("失败的消息.....["+message+"]==>replyCode["+replyCode+"]==>replyText["+replyText+"]==>exchange["+exchange+"]==>routingKey["+routingKey+"]");
}
});
效果
Ack消息确认机制
配置
spring:
rabbitmq:
# 手动确认ack,不要自动ack
listener:
simple:
acknowledge-mode: manual //一定要开启,否则都会自动ack
@RabbitHandler
public void receiveMessage(Message message, OrderReturnReasonEntity content, Channel channel) throws InterruptedException {
byte[] body = message.getBody();
//消息头信息
System.out.println("接收到的消息为:" + content);
MessageProperties messageProperties = message.getMessageProperties();
//channel内按顺序递增
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("deliveryTag ====> " + deliveryTag);
//签收货物,非批量签收
try {
if (deliveryTag % 2 == 0) {//模拟宕机,因为debug强制停止进程以后,还是会走完
//收货
channel.basicAck(deliveryTag, false);
System.out.println("签收了货物: " + deliveryTag);
} else {
//退货 requeue如果为false就是丢弃,为true就是发回服务器
// void basicNack(long deliveryTag, boolean multiple, boolean requeue 是否重新放入队列)
channel.basicNack(deliveryTag,false,true);
// void basicReject(long deliveryTag, boolean requeue) throws IOException;
// channel.basicReject();//这个和上面Nack都可以用,参数区别,下面不能批量操作
System.out.println("没有签收货物: " + deliveryTag);
}
} catch (IOException e) {
//网络异常
e.printStackTrace();
}
// Thread.sleep(3000);
// System.out.println("接收到的消息为: " + message +" ====> " + message.getClass());
System.out.println("消息处理结束:" + content.getName());
}
}
效果
小结
/**
* 定制RabbitTemplate
* 1.服务收到消息就回调
* 1.在application.yaml里面开启
* spring:
* rabbitmq:
* publisher-confirm-type: correlated
* 2.设置确认回调
* 2.消息抵达队列就回调
* 1.配置yaml
* # 开启发送端消息抵达队列的确认
* publisher-returns: true
* # 只要抵达队列,以异步方式优先回调这个returnConfirm
* template:
* mandatory: true
* 2.设置消息抵达队列的确认回调
*
* 3.消费端确认 (保证每个消息都被正确消费,此时才可以broker删除这个消息)
* 1.默认是自动ack,只要消息接收到,客户端回自动确认,服务端就会移除这个消息
* 问题:如果我们收到了很多消息,这个时候处理完前几条以后,后面的还没处理就宕机了,会导致所有消息都丢失,连同没被消费的
* 解决:手动确认模式,只要没有明确告诉mq货物被签收,即没有ACK,消息就会一直是unchecked状态,这个时候即使consumer宕机,所有unchecked的消息
* 都会重新进入队列当中,下次启动新的Consumer连接进来会继续消费这些消息
* 2.如何签收
* 签收 业务成功就应该签收 channel.basicAck(deliveryTag, false);
* 拒签 业务失败 channel.basicNack(deliveryTag,false,true);
*
*/