异步调用的优势:
- 解除耦合,扩展性强,
- 异步调用,无需等待,性能好
- 故障隔离,下游故障不影响上游业务
- 缓存消息,流量削峰填谷
异步调用的问题
- 不能立刻得到结果,时效性差
- 不能确定下游业务是否执行成功
- 业务安全依赖于Broker(代理)的可靠性
docker安装启动rabbitmq
https://blog.csdn.net/a3562323/article/details/104222229
因为docker启动rabbitmq的时候,没有设置用户名密码,所以默认都是guest
控制台测试
- 手动创建队列 在这里创建了两个,一个叫test.queues1,一个叫test.queues2
- 交换机绑定队列 先选择fanout,广播消息,选择交换机后,要先给交换机绑定队列,不然的话,消息不知道要发送到哪里, 在队列中也可以看到,队列绑定了对应的交换机
- 发送消息
- 查看消息
如果遇到只有Overview,没有Messages的情况,需要去修改rabbitmq的配置文件,
/etc/rabbitmq/conf.d/management_agent.disable_metrics_collector.conf
,management_agent.disable_metrics_collector.conf
中的属性true改为false,然后重启rabbitmq,就有messages了
在Get messages中查看队列接受的消息,这里只是查看消息内容,并没有消费掉
数据隔离
- 新建用户
- 创建成功,但是显示没有任何虚拟主机
- 切换成刚创建的用户,然后看看消息
- 显示被拒绝了。能看到是因为这个账户是管理员,看不到消息是因为虚拟主机不同。
- 为用户创建虚拟主机
- 测试不同虚拟主机之间数据隔离
- 创建好虚拟主机以后,切换成自己的虚拟主机,对应的队列也会切换成对应的虚拟主机的队列,因为这个虚拟主机队列是空的,所以这里队列也是空的
AMQP和Spring AMQP
AMQP:消息通信协议,与语言和平台无关。 Spring AMQP:基于AMQP协议定义的一套API规范,提供了模板发送和接收消息。
java+RabbitMq
目录结构
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置文件
配置ip,端口,虚拟空间,用户名,密码
spring:
rabbitmq:
host: 192.168.1.14
port: 5672
virtual-host: /z
username: zhangsan
password: 123
消息发送
package com.publisher;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class PublisherApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessage(){
String queueName = "zhang.queues1";
String message = "Hello Java Message";
rabbitTemplate.convertAndSend(queueName, message);
}
}
消息接收
编写rabbitmq的监听类
package com.consumer.listeners;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MqListener {
@RabbitListener(queues = "zhang.queues1")
public void listenSimpleQueue(String msg){
System.out.println("消费者消费消息:"+msg);
}
}
启动consumer的服务,启动成功后,显示消费了消息
查看rabbitmq的控制台,消息已经被消费了
多次测试,只要消费者在线,发送者一发送,消息就会被消费。
队列
Work Queues
一个队列绑定多个消费者,可以提高消息处理速度,每个消息只能被消费一次,但是每个消费者通过轮询的方式进行消费。
- 创建新的队列测试
- 修改消费者监听代码,创建两个监听,监听刚创建的队列work.queue,启动消费者
package com.consumer.listeners;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MqListener {
@RabbitListener(queues = "zhang.queues1")
public void listenSimpleQueue(String msg){
System.out.println("消费者消费消息:"+msg);
}
@RabbitListener(queues = "work.queue")
public void listenWorkQueue1(String msg){
System.out.println("消费者1消费消息-------------:"+msg);
}
@RabbitListener(queues = "work.queue")
public void listenWorkQueue2(String msg){
System.out.println("消费者2消费消息=============:"+msg);
}
}
- 发送消息,发送50条,查看效果
package com.publisher;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class PublisherApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessage(){
String queueName = "zhang.queues1";
String message = "Hello Java Message";
rabbitTemplate.convertAndSend(queueName, message);
}
@Test
public void testWorkQueue() throws InterruptedException {
String queueName = "work.queue";
for (int i = 0; i < 50; i++) {
String message = "Hello Java Message"+i;
rabbitTemplate.convertAndSend(queueName, message);
Thread.sleep(20);
}
}
}
通过观察,是轮询的机制,一个监听消费一条消息
- 调整预取数量,控制它的消费方式配置文件
spring:
rabbitmq:
host: 192.168.1.14
port: 5672
virtual-host: /z
username: zhangsan
password: 123
listener:
simple:
prefetch: 1 //预取数量设置为1,先消费完的先处理消息
监听方法
@RabbitListener(queues = "work.queue")
public void listenWorkQueue1(String msg) throws InterruptedException {
System.out.println("消费者1消费消息-------------:"+msg);
//如果不配置预取数量,就算这里睡眠了,还是会发现,通过轮询来消费的
Thread.sleep(200);
}
@RabbitListener(queues = "work.queue")
public void listenWorkQueue2(String msg){
System.out.println("消费者2消费消息=============:"+msg);
}
可以看到现在不是轮询了
交换机
真实环境中都是通过交换机发送消息,而不是直接通过队列,交换机有以下三种:
- Fanout:广播
- Direct:定向
- Topic:话题
Fanout交换机
Fanout Exchange会将接收到的消息广播到每一个跟它绑定的队列,所以也叫广播模式
- 控制台创建队列
fanout.queue1
和fanout.queue2
- 控制台选择交换机,并绑定刚才创建的队列
fanout.queue1
和fanout.queue2
- 编写消费者,监听刚才创建的两个队列,然后启动消费者
package com.consumer.listeners;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MqListener {
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) throws InterruptedException {
System.out.println("消费者1消费消息,fanout.queue1-------------:"+msg);
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg){
System.out.println("消费者2消费消息,fanout.queue2=============:"+msg);
}
}
- 编写发送者,发送消息到前面绑定好队列的交换机,
@Test
public void testFanoutMessage(){
String exchangeName = "amq.fanout";
String message = "Hello Java Message";
rabbitTemplate.convertAndSend(exchangeName,null, message);
}
因为已经绑定了队列到交换机,当有消息进入交换机,交换机会将消息路由到每个绑定的队列
Direct交换机
Direct Exchange会将接收到的消息根据规则路由到指定的Queue,因此称为定向路由。
- 控制台创建两个队列
direct.queue1
和direct.queue2
- 控制台绑定队列到交换机
amq.direct
,需要注意的是,我们这里做的是Direct交换机
,所以是有路由键的 - 消费者代码,监听刚才创建的队列,
direct.queue1
和direct.queue2
,然后启动消费者
package com.consumer.listeners;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MqListener {
@RabbitListener(queues = "direct.queue1")
public void listenDirectQueue1(String msg){
System.out.println("消费者1消费消息,direct.queue1-------------:"+msg);
}
@RabbitListener(queues = "direct.queue2")
public void listenDirectQueue2(String msg){
System.out.println("消费者2消费消息,direct.queue2=============:"+msg);
}
}
- 发送者代码
@Test
public void testDirectMessage(){
String exchangeName = "amq.direct";
String message = "Hello Java Message Red";
rabbitTemplate.convertAndSend(exchangeName,"red", message);
message = "Hello Java Message blue";
rabbitTemplate.convertAndSend(exchangeName,"blue", message);
message = "Hello Java Message yellow";
rabbitTemplate.convertAndSend(exchangeName,"yellow", message);
}
- 结果
Topic交换机
Topic Echange和DirectExchange类似,却别在于routingKey可以是多个单词列表,并且以.
分割。
- 创建两个队列
topic.queue1
和topic.queue2
- 队列与topic交换机绑定
- 消费者监听topic的两个队列
package com.consumer.listeners;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MqListener {
@RabbitListener(queues = "topic.queue1")
public void listenTopicQueue1(String msg){
System.out.println("消费者1消费消息,topic.queue1-------------:"+msg);
}
@RabbitListener(queues = "topic.queue2")
public void listenTopicQueue2(String msg){
System.out.println("消费者2消费消息,topic.queue2=============:"+msg);
}
}
- 发送者发送消息
@Test
public void testTopicMessage(){
String exchangeName = "amq.topic";
String message = "Hello Java Message ";
rabbitTemplate.convertAndSend(exchangeName,"japan.news", message);
}
只匹配了这一个路由
所以结果,只有消费者2收到
改一下,
@Test
public void testTopicMessage(){
String exchangeName = "amq.topic";
String message = "Hello Java Message ";
rabbitTemplate.convertAndSend(exchangeName,"china.news", message);
}
两个路由都匹配到了
所以结果
java声明交换机和队列
之前为了测试入手,都使用的控制台创建的队列以及交换机,这样的效率无疑是很低的
fanout交换机
配置配置类,然后启动消费者
package com.consumer.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FanoutConfiguration {
//第一种,绑定时候通过入参绑定
@Bean
public FanoutExchange fanoutExchange(){
//Exchange build = ExchangeBuilder.fanoutExchange("zs.fanout").build();
return new FanoutExchange("java.fanout");
}
@Bean
public Queue fanoutQueue(){
//Queue queue = QueueBuilder.durable("java.fanout.queue").build();//持久化
return new Queue("java.fanout.queue");
}
@Bean
public Binding fanoutBinding(Queue fanoutQueue,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue).to(fanoutExchange);
}
//第二种,绑定时候通过方法名绑定
@Bean
public FanoutExchange fanoutExchange1(){
//Exchange build = ExchangeBuilder.fanoutExchange("zs.fanout").build();
return new FanoutExchange("java.fanout1");
}
@Bean
public Queue fanoutQueue1(){
//Queue queue = QueueBuilder.durable("java.fanout.queue").build();//持久化
return new Queue("java.fanout.queue1");
}
@Bean
public Binding fanoutBinding1(){
//fanoutQueue1()这种不是直接调用当前类,当前类是个配置类,并且对象都是@Bean修饰,都是从spring中去取的
return BindingBuilder.bind(fanoutQueue1()).to(fanoutExchange1());
}
}
direct交换机
设置配置类,然后启动消费者
package com.consumer.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DirectConfiguration {
//创建交换机
@Bean
public DirectExchange directExchange(){
return new DirectExchange("java.direct");
}
//创建队列
@Bean
public Queue directQueue1(){
return new Queue("java.direct.queue1");
}
//绑定队列到交换机,并且设置路由键
@Bean
public Binding directQueue1BindingRed(DirectExchange directExchange,Queue directQueue1){
return BindingBuilder.bind(directQueue1).to(directExchange).with("red");
}
//绑定队列到交换机,并且设置路由键
@Bean
public Binding directQueue1BindingBlue(DirectExchange directExchange,Queue directQueue1){
return BindingBuilder.bind(directQueue1).to(directExchange).with("blue");
}
//创建队列2
@Bean
public Queue directQueue2(){
return new Queue("java.direct.queue2");
}
//绑定队列2到交换机,并绑定路由键
@Bean
public Binding directQueue2BindingRed(DirectExchange directExchange,Queue directQueue2){
return BindingBuilder.bind(directQueue2).to(directExchange).with("red");
}
//绑定队列2到交换机,并绑定路由键
@Bean
public Binding directQueue2BindingBlue(DirectExchange directExchange,Queue directQueue2){
return BindingBuilder.bind(directQueue2).to(directExchange).with("yellow");
}
}
基于注解的声明交换机和队列
之前有用过一个注解@RabbitListener
,之前只是用它来声明队列的名称,其实也可以绑定对应的交换机和队列现在删除之前通过@Bean创建的队列和交换机,然后注释掉之前的两个配置类,然后再来测试
- 编写监听类
package com.consumer.listeners;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MqListener {
//绑定队列java.direct.queue1到交换机java.direct,交换机类型是ExchangeTypes.DIRECT,路由key是red和blue
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name="java.direct.queue1",durable = "true"),
exchange = @Exchange(name="java.direct",type = ExchangeTypes.DIRECT),
key={"red","blue"}
))
public void javaListenDirectQueue1(String msg){
System.out.println("消费者1消费了消息:"+msg);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name="java.direct.queue2",durable = "true"),
exchange = @Exchange(name="java.direct",type = ExchangeTypes.DIRECT),
key={"red","yellow"}
))
public void javaListenDirectQueue2(String msg){
System.out.println("消费者2消费了消息:"+msg);
}
}
- 发送消息
@Test
public void testDirectMessage(){
String exchangeName = "java.direct";
String message = "Hello Java Message Red";
rabbitTemplate.convertAndSend(exchangeName,"red", message);
message = "Hello Java Message blue";
rabbitTemplate.convertAndSend(exchangeName,"blue", message);
message = "Hello Java Message yellow";
rabbitTemplate.convertAndSend(exchangeName,"yellow", message);
}
结果:
消息转换器
发送Map结构的数据
- 先创建一个测试用的queue
- 发送消息
@Test
public void testObject(){
Map<String, Object> object = new HashMap<String, Object>();
object.put("name","zhangsan");
object.put("age",17);
rabbitTemplate.convertAndSend("object.queue",object);
}
- 观察结果 被序列化了。 然后重新发送消息,查看结果
@Test
public void testObject(){
Map<String, Object> object = new HashMap<String, Object>();
object.put("name","zhangsan");
object.put("age",17);
rabbitTemplate.convertAndSend("object.queue",object);
}
- 消费者消费消息
@RabbitListener(queues = "object.queue")
public void objectQueue(Map<String,Object> map){
System.out.println(map);
}
- 发送者发送消息
@Test
public void testObject(){
Map<String, Object> object = new HashMap<String, Object>();
object.put("name","zhangsan");
object.put("age",17);
rabbitTemplate.convertAndSend("object.queue",object);
}
业务改造
- 消费者,trade-service服务原本直接调用接口,标记订单已支付,现在进行改造。接口代码不动。
@ApiOperation("标记订单已支付")
@ApiImplicitParam(name="orderId",value="订单id",paramType="path")
@PutMapping("/{orderId}")
public void markOrderPaySucess(@PathVariable("orderId")Long orderId){
orderService.markOrderPaySucess(orderId);
}
添加rabbitmq的配置信息到配置文件,然后新增rabbitmq监听类
@Component
@RequiredArgsConstructor
public class PayStatusListener{
private final IOrderService orderService;
@RabbitListener(bindings=@QueueBinding(
value=@Queue(name="mark.order.pay.queue",durable="true"),
exchange=@Exchange(name="pay.topic",type=ExchangeTypes.TOPIC),
key="pay.success"
))
public void listenerOrderPay(Long orderId){
//标记订单状态已支付
orderService.markOrderPaySucess(orderId);
}
}
- 发送者将原本调用trade-service服务,标记订单已支付的方法进行修改,不直接进行调用借口了,而是向队列发送消息。
//修改订单状态
//tradeClient.markOrderPaySucess(po.getBizOrderNo());
rebbitTemplate.converAndSend("pay.topic","pay.success",po.getBizOrderNo());
对应的队列消费者监听到有消息进来后,就会处理修改订单的支付状态
消息的可靠性
- 消息发送的时候丢失了
- mq把消息丢了
- 消费者把消息丢了消息进入mq,mq突然挂掉,或者交易服务突然挂掉。
生产者的可靠性
生产者重连
需要注意的是,这个生产者重连,会阻塞线程
- 测试发送者发送失败打开连接失败的重试,停掉rabbitmq服务
生产者确认
- 修改配置文件
- 编写配置类
package com.publisher.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class MqConfirmConfig implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
//配置回调
rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
log.debug("收到消息得到return callback,exchange:{},routingkey:{},message:{},replycode:{},replytext:{}",returnedMessage.getExchange(),returnedMessage.getRoutingKey(),returnedMessage.getMessage(),returnedMessage.getReplyCode(),returnedMessage.getReplyText());
}
});
}
}
- 生产者的确认,在每次发送都得配置
@Test
public void testConfirmCallBack() throws InterruptedException {
//创建cd
CorrelationData cd = new CorrelationData(UUID.randomUUID().toString());
//添加confirmcallback
cd.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
@Override
public void onFailure(Throwable ex) {
log.error("消息回调失败");
}
@Override
public void onSuccess(CorrelationData.Confirm result) {
log.debug("收到confirm callback回调");
if(result.isAck()){
//消息发送成功
log.debug("消息发送成功,收到ACK");
}else{
//消息发送失败
log.debug("消息发送成功,收到NACK,原因:{}",result.getReason());
}
}
});
rabbitTemplate.convertAndSend("java.direct","red","hello",cd);
Thread.sleep(2000);
}
MQ的可靠性
数据持久化
RabbitMq实现数据持久化的三个方面:
- 交换机持久化
- 队列持久化
- 消息持久化
Lazy Queue
- 控制台创建时
- 代码创建时
- 注解创建时
消费者的可靠性
消费者确认机制
- 配置文件配置好自动模式,消费者监听这里故意抛出异常,然后debug卡在抛出异常这里
@RabbitListener(queues = "ack.queue")
public void ackQueue(String msg){
System.out.println("----------------"+msg);
throw new RuntimeException("异常处理");
}
- 然后发送消息
@Test
public void testAck(){
String queue = "ack.queue";
String msg="asdaaaa";
rabbitTemplate.convertAndSend(queue,msg);
}
- 消息显示未返回结果
- 因为在消费者监听那里抛出异常,然后消息就会一直重发,所以一直都卡在抛出异常的位置
- 换一个消息转换异常抛出,就会删除消息
@RabbitListener(queues = "ack.queue")
public void ackQueue(String msg){
System.out.println("----------------"+msg);
throw new MessageConversionException("异常处理");
}
消费失败处理
- 编写消费者的配置类
package com.consumer.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.retry.MessageRecoverer;
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* 重试机制开启的时候,才配置这个策略
*/
@Configuration
@ConditionalOnProperty(prefix = "spring.rabbitmq.listener.simple.retry",name = "enabled",havingValue = "true") //这个配置类在属性spring.rabbitmq.listener.simple.retry。enabled为true的时候生效
public class ErrorConfiguration {
//配置交换机
@Bean
public DirectExchange directExchange(){
return new DirectExchange("error.direct");
}
//配置队列
@Bean
public Queue errorQueue(){
return new Queue("error.queue");
}
//绑定队列到交换机,并指定路由
@Bean
public Binding errorBinding(DirectExchange directExchange,Queue errorQueue){
return BindingBuilder.bind(errorQueue).to(directExchange).with("error");
}
@Bean
public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate){
return new RepublishMessageRecoverer(rabbitTemplate,"error.direct","error");
}
}
- 监听这里故意报错
@RabbitListener(queues = "zhang.queues1")
public void listenSimpleQueue(String msg){
System.out.println("消费者消费消息:"+msg);
throw new RuntimeException("111111");
}
- 发送请求
@Test
public void test1(){
String queue = "zhang.queues1";
String msg="asdaaaa";
rabbitTemplate.convertAndSend(queue,msg);
}
- 结果,重试了三次,发送消息到指定的错误的交换机了
- 查看控制台
业务幂等性
- 配置一下创建消息id
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
public MessageConverter jacksonMessageConverter() {
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
jackson2JsonMessageConverter.setCreateMessageIds(true);
return jackson2JsonMessageConverter;
}
}
- 配置发送者创建消息id
@SpringBootApplication
public class PublisherApplication {
public static void main(String[] args) {
SpringApplication.run(PublisherApplication.class, args);
}
@Bean
public MessageConverter jacksonMessageConverter() {
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
jackson2JsonMessageConverter.setCreateMessageIds(true);
return jackson2JsonMessageConverter;
}
}
- 看一下没消息id的效果
- 配置消息id后的效果
- 修改前面的业务代码,先获取对应的订单的状态
@Component
@RequiredArgsConstructor
public class PayStatusListener{
private final IOrderService orderService;
@RabbitListener(bindings=@QueueBinding(
value=@Queue(name="mark.order.pay.queue",durable="true"),
exchange=@Exchange(name="pay.topic",type=ExchangeTypes.TOPIC),
key="pay.success"
))
public void listenerOrderPay(Long orderId){
//查询订单
Order order=oderService.getById(orderId);
//判断订单状态是否未支付
if(null==order || order.getStatus() != 1){
//订单不存在或者状态异常
return;
}
//标记订单状态已支付
orderService.markOrderPaySucess(orderId);
}
}
上面的代码可能涉及到多线程安全问题,也可以利用数据库的乐观锁来实现,这条sql不管执行多少次,不满足条件,都不会执行,也保证了幂等性
//update order set status=2 where id=? and status=1
orderService.lambdaUpdate()
.set(Order::getStatus,2)
.set(Order::getPayTime,LocalDateTime.now())
.eq(Order::getId,orderId)
.eq(Order::getStatus,1)
.update();
延时消息
死信交换机
- 控制台创建一个队列simple.queue,创建队列的时候,记得指定死信交换机, 然后创建交换机simple.direct,队列绑定到交换机,路由是hi,但是不绑定消费者,进入队列的消息就会变成死信
- 然后创建一个死信队列dlx.queue,然后再创建一个死信交换机dlx.direct,队列绑定好交换机
- 消费者监听死信队列
@RabbitListener(queues = "dlx.queue")
public void listenerDlxQueue(String msg){
log.info("dlx.queue收到了消息----------------"+msg);
}
- 发送一条消息到未绑定消息者的交换机,设置它的死信时间10s
@Test
public void ackQueue(){
rabbitTemplate.convertAndSend("simple.direct", "hi", "hello", new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("10000");
return message;
}
});
log.info("消息发送成功");
}
延迟消息插件
下载对应rabbitmq版本的插件:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/tag/v3.13.0
下载以后放在docker容器中的rabbitmq的插件目录
docker exec -it rabbitmq bash
然后把下载好的rabbitmq_delayed_message_exchange-3.13.0.ez
,放到插件目录
docker cp rabbitmq_delayed_message_exchange-3.13.0.ez rabbitmq:/plugins
然后进入容器中的rabbitmq的插件目录后,启用插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
启用成功
通过设置setDelay来延迟消息
-消费者注册监听 绑定队列delay.queu到交换机delay.direct
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "delay.queue",durable = "true"),
exchange = @Exchange(value = "delay.direct",delayed = "true"),
key = "hi"
))
public void listenDelayQueue(String msg){
log.info("接收到delay.queue的消息:{}",msg);
}
- 启动消费者,队列和交换机都成功创建
- 发送消息
@Test
public void ackQueue(){
rabbitTemplate.convertAndSend("simple.direct", "hi", "hello", new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("10000"); //延迟10s发送
return message;
}
});
log.info("消息发送成功");
}
- 发送消费都成功,14秒发送,延时10秒,24秒收到
取消超时订单
资料来源:https://www.bilibili.com/video/BV1mN4y1Z7t9?p=1&vd_source=a72a5eec20ffdda34c9612310392f433