
咱们用大白话来好好理解一下这段代码的流程。这段代码的核心功能是发送和处理延迟消息 ,具体来说,它被用来实现订单的自动取消功能。
想象一下这样的场景:用户创建了一个订单,但是过了一段时间(比如 15 秒)没有付款,我们希望自动取消这个订单。这段代码就是用来实现这个"延迟取消"的功能的。
我们先来看发送延迟消息的部分,也就是 sendDelayMessage
这个方法:
java
/**
* 发送延迟消息
*/
private void sendDelayMessage(Long orderId) {
try {
// 创建一个队列
RBlockingDeque<Object> blockingDeque = redissonClient
.getBlockingDeque("queue_cancel");
// 将队列放入延迟队列中
RDelayedQueue<Object> delayedQueue = redissonClient
.getDelayedQueue(blockingDeque);
// 发送的内容
delayedQueue.offer(orderId.toString(),
15, TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
}
}
-
private void sendDelayMessage(Long orderId)
: 这个方法就像一个"发号员",它接收一个orderId
(订单的唯一编号),告诉系统我们需要对这个订单设置一个延迟任务。 -
RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque("queue_cancel");
: 这一行代码的意思是,我们从 Redisson 客户端获取了一个叫做"queue_cancel"
的阻塞队列。你可以把这个队列想象成一个停车场,只不过这个停车场是先进先出的,先来的车停在前面。这里的 "阻塞" 的意思是,如果停车场是空的,想要从里面取车的车辆(后面的处理逻辑)会一直等待,直到有车开进来。 -
RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
: 这一行更关键。我们不是直接把订单 ID 放到普通的队列里,而是通过redissonClient.getDelayedQueue(blockingDeque)
创建了一个延迟队列 ,并且这个延迟队列是和我们刚刚创建的阻塞队列"queue_cancel"
关联起来的。你可以把延迟队列想象成一个特殊的通道,里面的消息不会立刻进入停车场,而是要等一段时间才能放行。 -
delayedQueue.offer(orderId.toString(), 15, TimeUnit.SECONDS);
: 这行代码就是真正发送延迟消息的操作。orderId.toString()
: 我们把订单的 ID(转换成字符串)作为消息放进延迟队列。15
: 这是延迟的时间长度,这里是 15。TimeUnit.SECONDS
: 这是时间单位,这里是秒。 所以,这行代码的意思是:"把这个订单 ID 放进延迟队列,让它在 15 秒 之后才能被放进queue_cancel
这个阻塞队列里。"
-
catch (Exception e) { e.printStackTrace(); }
: 这是一个标准的异常处理,如果发送过程中出现任何错误,就打印错误信息。
接下来,我们再来看处理延迟消息的部分,也就是 RedisDelayHandle
这个类和它的 listener
方法:
java
@Slf4j
@Component
public class RedisDelayHandle {
@Autowired
private RedissonClient redissonClient;
@Autowired
private OrderInfoService orderInfoService;
@PostConstruct
public void listener() {
new Thread(()->{
while (true){
// 获取到阻塞队列
RBlockingDeque<String> blockingDeque = redissonClient
.getBlockingDeque("queue_cancel");
try {
// 从阻塞队列中获取到订单Id
String orderId = blockingDeque.take();
// 订单Id 不为空的时候,调用取消订单方法
if(!StringUtils.isEmpty(orderId)) {
log.info("接收延时队列成功,订单id:{}", orderId);
orderInfoService.orderCancel(Long.parseLong(orderId));
}
} catch (InterruptedException e) {
log.error("接收延时队列失败");
e.printStackTrace();
}
}
}).start();
}
}
-
@Slf4j
和@Component
: 这两个是 Spring 的注解。@Slf4j
会自动帮我们创建一个日志对象log
,用来记录程序的运行信息。@Component
表明这是一个 Spring 管理的组件。 -
@Autowired private RedissonClient redissonClient;
: 同样是 Spring 的注解,它会自动注入一个RedissonClient
的实例,这样我们就可以使用 Redisson 提供的功能来操作 Redis。 -
@Autowired private OrderInfoService orderInfoService;
: 这个注解注入了一个OrderInfoService
的实例。这个 Service 应该是用来处理订单相关的业务逻辑,比如取消订单。 -
@PostConstruct public void listener() { ... }
:@PostConstruct
注解表示这个listener
方法会在RedisDelayHandle
这个 Bean 被 Spring 创建之后自动执行。这个方法的主要作用是启动一个新的线程来持续监听延迟队列中的消息。 -
new Thread(()->{ while (true){ ... } }).start();
: 这里创建并启动了一个新的线程。while (true)
表示这个线程会一直运行下去,不断地从队列中获取消息。 -
RBlockingDeque<String> blockingDeque = redissonClient.getBlockingDeque("queue_cancel");
: 在这个线程里,我们又一次获取了名为"queue_cancel"
的阻塞队列。注意,这里的队列名称和发送延迟消息时用的是同一个。 -
String orderId = blockingDeque.take();
: 这里的blockingDeque.take()
是一个阻塞方法 。它会尝试从"queue_cancel"
这个队列中取出一个元素。如果队列是空的,这个线程就会一直等待,直到有新的元素被放进来(也就是延迟时间到了,延迟队列把消息放进来了)。一旦取到了一个元素,这个元素就是我们之前放进去的订单 ID(以字符串的形式)。 -
if(!StringUtils.isEmpty(orderId)) { ... }
: 拿到订单 ID 之后,这里会判断它是否为空。 -
log.info("接收延时队列成功,订单id:{}", orderId);
: 如果订单 ID 不为空,就记录一条日志,表明成功从延迟队列中接收到了订单 ID。 -
orderInfoService.orderCancel(Long.parseLong(orderId));
: 这一行是关键的业务逻辑!我们通过之前注入的OrderInfoService
调用了orderCancel
方法,并将接收到的订单 ID(转换成 Long 类型)传递给它。这个方法应该会执行实际的取消订单的逻辑,比如更新数据库中的订单状态等等。 -
catch (InterruptedException e) { ... }
: 这是一个异常处理,用来捕获线程在等待从队列中取数据时被中断的异常。如果发生中断,就记录错误日志并打印堆栈信息。
总结一下整个流程:
-
当需要设置一个延迟任务(比如 15 秒后取消订单)时,
sendDelayMessage
方法会被调用,它会将订单 ID 放入一个与阻塞队列关联的延迟队列中,并设置延迟时间。 -
在延迟时间到达之前,
queue_cancel
这个阻塞队列是空的。 -
RedisDelayHandle
中运行的监听线程会一直尝试从queue_cancel
这个阻塞队列中take()
数据,由于队列是空的,它会一直阻塞等待。 -
当 15 秒的延迟时间到了,Redisson 内部的机制会将之前放入延迟队列的订单 ID 取出来,并把它放回
queue_cancel
这个阻塞队列中。 -
这时,之前一直阻塞等待的监听线程被唤醒,它成功地从
queue_cancel
队列中take()
到了订单 ID。 -
监听线程拿到订单 ID 后,会调用
orderInfoService.orderCancel()
方法,执行真正的取消订单操作。 -
这个监听线程会一直运行在一个无限循环中,不断地从
queue_cancel
队列中获取到期的延迟消息并进行处理。