延迟队列的作用
延迟队列常用于需要延迟处理的场景,比如定时任务、过期任务的处理、缓存过期等。这个 addQueue
方法正是为了解决类似的问题,在延迟时间到达时,设备状态的更新任务将被自动触发,保证操作的时效性。
@Component @Slf4j public class RedissonQueueProducer { @Autowired private RedissonClient redissonClient; //重点注意这行代码 用于与 Redis 交互。 /** * 添加到队列 * * @param t 实际业务参数参数 * @param delay 延迟时间数 * @param timeUnit 延迟时间单位 * @param queueName 队列名 * @param <T> 泛型 */ public <T> void addQueue(String queueName, long delay, TimeUnit timeUnit, T t) { log.info("添加到队列:{}, 延迟时间数:{}, 延迟时间单位:{}, 实际参数:{}", queueName, delay, timeUnit, t); //获取一个阻塞队列 这个队列是线程安全的,且可以在消费端阻塞等待消息的到来。 RBlockingQueue<T> blockingFairQueue = redissonClient.getBlockingQueue(queueName); //获取一个延迟队列 这个延迟队列支持将消息延迟一定时间后再进行消费。 RDelayedQueue<T> delayedQueue = redissonClient.getDelayedQueue(blockingFairQueue); //将消息添加到延迟队列中 延迟时间delay 延迟时间的单位timeUnit delayedQueue.offer(t, delay, timeUnit); }
写好了之后接下来我们进行使用写好的延迟队列
将任务添加到 延迟队列中
//开启设备 更改设备状态
public void isDeviceUse(OrderVo payOrder) {
// 获取设备信息,使用设备的ID来查询设备详细信息
DeviceVo info = deviceService.getInfo(DeviceDto.builder().id(payOrder.getDeviceId()).build());
// 更改设备状态为使用中
info.setUseStatus(DeviceUseStatusConstants.IN_USE);
// 更新设备信息
deviceService.updateById(BeanUtil.copy(info,DeviceDto.class));
// 添加比例结算记录
ratioBalanceRecordsAdd(payOrder);
// 开启设备,传递设备编码、使用时间、压力以及订单ID,调用设备控制服务
boolean start = deviceSwitchUtils.start(info.getDeviceCode(), payOrder.getUseTime(), pressure, payOrder.getOrderId());
// 记录设备开启状态
log.info("订单号{}开启{}",payOrder.getOrderId(),start);
// 获取订单中的使用时间
Integer useTime = payOrder.getUseTime();
//将任务放入延迟队列中 以下这段代码为关键 理解
//其实相当于定时任务 useTime使用时间比如是2 那么两分钟之后这个任务就会开始执行 redissonQueueProducer.addQueue(QueueConstant.DEVICE_STATUS_DELAYED_TASK_QUEUE,useTime * 60, TimeUnit.SECONDS, DeviceDto.builder()
.id(info.getId())
//.useStatus(DeviceUseStatusConstants.NOT_IN_USE)
.build());
//将设备的剩余使用时间存储到 Redis 中,方便后续对设备状态进行管理和判断
redisCache.setCacheObject(info.getDeviceCode(),0,useTime * 60,TimeUnit.SECONDS);
}
//写一个抽象接口 然后去实现
public interface RedissonQueueConsumer<T> { /** * 延迟队列任务执行方法,实现该方法执行具体业务 * * @param t 具体任务参数 */ void execute(T t); String getQueueName(); }
//实现类
@Slf4j
@Component
@RequiredArgsConstructor
public class DeviceStatusCustomer implements RedissonQueueConsumer<DeviceDto> {
private String queueName = QueueConstant.DEVICE_STATUS_DELAYED_TASK_QUEUE;
private final IDeviceService deviceService;
private final DeviceSwitchUtils deviceSwitchUtils;
@Override
@Transactional
public void execute(DeviceDto deviceDto) {
log.info("消费队列消息:{}",deviceDto.toString());
//这是我写的消费任务 调用的方法在下面到时间去消费 是为了使用时间到了之后自动去执行下面我写的方法
this.checkAndUpdateDeviceStatus(deviceDto);
log.info("消费队列消息执行完毕:{}",deviceDto.toString());
}
private void checkAndUpdateDeviceStatus(DeviceDto deviceDto) {
Long id = deviceDto.getId();
//根据id查询该设备的deviceCode
DeviceVo info = deviceService.getInfo(DeviceDto.builder().id(id).build());
// 获取实时压力
JSONObject jsonObject = deviceSwitchUtils.queryInfo(info.getDeviceCode());
BigDecimal realTimePressure = new BigDecimal(jsonObject.getString("pressure"));
// 判断实时压力是否小于10
if (realTimePressure.compareTo(new BigDecimal("10")) < 0) {
// 如果压力小于10,更新设备状态为未使用 并且关闭设备
info.setUseStatus(DeviceUseStatusConstants.NOT_IN_USE);
deviceService.updateById(BeanUtil.copy(info, DeviceDto.class));
deviceSwitchUtils.deviceClose(info.getDeviceCode());
log.info("设备 {} 实时压力低于 10,状态已修改为未使用", info.getDeviceCode());
} else {
// 如果压力大于或等于10,记录信息但不修改设备状态
log.info("设备 {} 实时压力为 {},未达到修改设备状态的条件", info.getDeviceCode(), realTimePressure);
}
}
下面这段代码定义了一个名为 RedissonQueueListener
的类,它实现了 ApplicationContextAware
接口,目的是监听并消费来自 Redis 队列的数据。它通过 Redisson 客户端操作 Redis 队列,并在指定的时间间隔内从队列中取出数据进行处理
@Slf4j @SuppressWarnings("all") @Component public class RedissonQueueListener implements ApplicationContextAware { //这是一个用于管理和调度定时任务的线程池。它允许定期执行任务,并支持延迟执行 //创建一个拥有 3 个线程的线程池,用于执行定时任务。这里指定了线程池大小为 3,意味着最多同时可以执行 3 个任务。 ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3); @Autowired private RedissonClient redissonClient; private int count = 0; /** * 从应用上下文中获取具体的队列消费者,并执行业务 * * @param applicationContext 应用上下文 * @throws BeansException BeansException */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { Map<String, RedissonQueueConsumer> map = applicationContext.getBeansOfType(RedissonQueueConsumer.class); map.values().forEach(this::startThread); } /** * 启动线程获取队列,并执行业务 * * @param queueName 队列名 * @param CommonDelayQueueConsumer 任务回调监听 * @param <T> 泛型 */ private <T> void startThread(RedissonQueueConsumer queueConsumer) { RBlockingQueue<T> blockingFairQueue = redissonClient.getBlockingQueue(queueConsumer.getQueueName()); //由于此线程需要常驻,所以可以直接新建线程,不需要交给线程池管理 executorService.scheduleAtFixedRate(() -> { //使用线程池定期执行队列消费任务 try { T t = blockingFairQueue.take(); //从队列中取出一个元素(如果队列为空则阻塞直到有元素)。 queueConsumer.execute(t);//处理从队列中取出的元素 } catch (Exception e) { log.info("错误:", e); log.error("{},队列监听线程错误",queueConsumer.getQueueName()); } }, 5, 1, TimeUnit.SECONDS); } }
最后由我们写的这个线程去执行我们写的这个 RedissonQueueConsumer queueConsumer实现类的接口,这个接口里面放的就是我们写的消费的任务队列 这样线程执行的时候就把我们写的实现类里面放的任务给执行了
按照这个顺序和注释 理解透 简简单单 怼!