消息中间件RabbitMQ
1.简介
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,基于AMQP协议实现,支持多种场景,高性能,高可用,支持海量数据。
rabbitmq 的一些概念,需要了解的话 点这里
2.使用
1.依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<!--版本跟你项目springboot保持一致即可-->
</dependency>
2.客户端
可以下载rabbitmq:management,可以通过它图形化使用rabbitmq,具体使用教程百度上挺详细的,我就不详解了
3.配置信息
1.application.properties 文件中加上以下配置信息
spring.rabbitmq.host=你的IP
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
2.启动类上或者配置类上加 @EnableRabbit
3.监听消息 :使用@RabbitListener 必须要有@EnableRabbit
@RabbitListener:类+方法上 (监听那些队列)
@RabbitHandler: 方法上 (重载区分不同类型的消息)
4.引入amqp场景 RabbitAutoConfiguration 就会自动生效
* 给容器中自动配置了RabbitTemplate , AmqpAdmin,CachingConnectionFactory
* RabbitMessagingTemplate所有的属性都在这
@EnableConfigurationProperties({RabbitProperties.class})
4. 代码实现
1.发送消息,创建 Exchange,Queue,Binding测试
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class GulimallOrderApplicationTests {
@Autowired
AmqpAdmin amqpAdmin;
@Autowired
RabbitTemplate rabbitTemplate;
@Test
public void sendMessageTest(){
OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity();
reasonEntity.setId(1L);
reasonEntity.setCreateTime(new Date());
reasonEntity.setName("哈哈");
reasonEntity.setSort(1);
//1.发送消息 如果发送的消息是个对象,我们会使用序列化机制,将对象写出去。对象必须实现序列化接口
String message = "hello world";
rabbitTemplate.convertAndSend("hello-java-exchange","hello.java", reasonEntity);
log.info("消息发送完成{}",reasonEntity);
}
/*
*1.如何创建 Exchange,Queue,Binding
* 1) 使用AmqpAdmin
*2.如何收发消息
*/
@Test
public void createExchange(){
DirectExchange directExchange = new DirectExchange("hello-java-exchange",true,false);
amqpAdmin.declareExchange(directExchange);
log.info("Exchange[{}]创建成功",directExchange.getName());
}
@Test
public void createQueue(){
Queue queue = new Queue("hello-java-queue", true, false, false);
amqpAdmin.declareQueue(queue);
log.info("Queue[{}]创建成功",queue.getName());
}
@Test
public void createBinding(){
//String destination【目的地】,
// Binding.DestinationType destinationType【目的地类型】
// String exchange【交换机】,
// String routingKey【路由键】
// Map<String, Object> arguments)【自定义参数】
//将exchange指定的交换机和destination
Binding binding = new Binding("hello-java-queue",
Binding.DestinationType.QUEUE,
"hello-java-exchange",
"hello.java",
null);
amqpAdmin.declareBinding(binding);
log.info("Binding[{}]创建成功","hello-java-binding");
}
2.rabbit 配置
@Configuration
public class MyRabbitConfig {
// @Autowired
RabbitTemplate rabbitTemplate;
// public MyRabbitConfig(RabbitTemplate rabbitTemplate){
// this.rabbitTemplate = rabbitTemplate;
// initRabbitTemplate();
// }
//TODO
@Primary
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
this.rabbitTemplate = rabbitTemplate;
rabbitTemplate.setMessageConverter(messageConverter());
initRabbitTemplate();
return rabbitTemplate;
}
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
/*
* 定制RabbitTemplate
* 1.服务端收到消息就回调
* 1.spring.rabbitmq.publisher-confirms=true
* 2.设置确认回调
* 2.消息正确抵达队列就进行回调
* 1.spring.rabbitmq.publisher-returns=true
* 2.spring.rabbitmq.template.mandatory=true
* 3.消费端确认(保证每个消息被正确消费,此时才可以broker删除这个消息)
* spring.rabbitmq.listener.simple.acknowledge-mode=manual
* 1.默认是自动确认的,只要有消息接收到,客户端自动确认,服务端就会移除这个消息
* 问题:
* 我们收到很多消息,自动回复给服务器ack,只有一个消息处理成功,宕机了 会发生消息丢失
* 消费者 手动确认模式:只要我们没有手动告诉MQ 消息已被接收 没有ack 消息就一直是unacked 即使Consumer宕机 消息也不会丢失
* 会重新变为ready 下一次有新的Consumer进来 就发给他
*/
// @PostConstruct //MyRabbitConfig 对象创建完成以后执行这个方法
public void initRabbitTemplate(){
//设置确认回调
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
/**
* @Description: confirm
* @Param: correlationData 当前消息的唯一关联数据(这个消息的唯一id)
* @Param: b 消息是否成功收到
* @Param: cause 失败的原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String s) {
//1.做好消息确认机制(publisher consumer[手动ack])
//2.每一个发送的消息都在数据库做好记录.定期将失败的消息再次发送一遍
//服务器收到了
System.out.println("confirm....correlationData["+correlationData+"]====>b["+ack+"]s=========>["+s+"]");
}
});
//设置消息抵达队列确认回调
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
/**
* @Description: returnedMessage 只要消息投递失败就会触发这个回调
* @Param: message 投递失败的消息详细信息
* @Param: i 回复的状态码
* @Param: s 回复的文本内容
* @Param: s1 当时这个消息发给那个交换机
* @Param: s2 当时这个消息用的那个路由键
*/
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
//报错误了 修改数据库
System.out.println("Fail Message["+message+"]===>replyCode["+i+"]====>s"+s+"===>s1["+s1+"]=====>s2["+s2+"]");
}
});
}
}
3队列 交换机等创建
@Configuration
public class MyMQConfig {
/*
* 容器中的 Binding , Queue , Exchange 都会自动创建(RabbitMQ没有的情况)
* RabbitMQ 只要创建好了 就算属性发生变化也不会覆盖
*/
@Bean
public Queue orderDelayQueue(){
Map<String,Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange","order-event-exchange");
arguments.put("x-dead-letter-routing-key","order.release.order");
arguments.put("x-message-ttl",60000);
Queue queue = new Queue("order.delay.queue", true, false, false,arguments);
return queue;
}
@Bean
public Queue orderReleaseOrderQueue(){
Queue queue = new Queue("order.release.order.queue", true, false, false);
return queue;
}
@Bean
public Exchange orderEventExchange(){
TopicExchange exchange = new TopicExchange("order-event-exchange",true,false);
return exchange;
}
// @Bean
// public Exchange orderReleaseOrderExchange(){
// TopicExchange exchange = new TopicExchange("order-event-exchange",true,false);
// return exchange;
// }
@Bean
public Binding orderCreateOrderBinding(){
return new Binding("order.delay.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order-create-order",
null);
}
@Bean
public Binding orderReleaseOrderBinding(){
return new Binding("order.release.order.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.release.order",
null);
}
//订单释放直接和库存释放进行绑定
@Bean
public Binding orderReleaseOtherBingding(){
return new Binding("stock.release.stock.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.release.other.#",
null);
}
@Bean
public Queue orderSeckillOrderQueue(){
Queue queue = new Queue("order.seckill.order.queue", true, false, false);
return queue;
}
@Bean
public Binding orderSeckillOrderQueueBingding(){
return new Binding("order.seckill.order.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.seckill.order",
null);
}
}
消费端代码举例
@Service
@RabbitListener(queues = "stock.release.stock.queue")
public class StockReleaseListener {
@Autowired
WareSkuService wareSkuService;
/*
*1.库存自动解锁
* 下单成功后,锁定库存.接下来的业务调用失败,导致订单回滚
* 之前锁定的库存就要自动解锁
* 2.订单失败
* 锁库存失败引起的
*
* 只要解锁库存的消息失败,一定要告诉服务器不要删,重试
*/
@RabbitHandler
public void handleStockLockedRelease(StockLockedTo to, Message message, Channel channel) throws IOException {
System.out.println("收到解锁库存的消息");
try {
//当前消息是否被第二次及以后重新派发过来的
// Boolean redelivered = message.getMessageProperties().getRedelivered();
wareSkuService.unLockStock(to);
// 手动确认
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}catch (Exception e){
//告诉服务器 消息未被成功处理 该消息会重新进入队列 变成ready 模式
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
}
}
@RabbitHandler
public void handleStockLockedRelease(OrderTo orderTo, Message message, Channel channel) throws IOException {
System.out.println("订单关闭,准备解锁库存");
try {
wareSkuService.unLockStock(orderTo);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}catch (Exception e){
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
}
}
}