Java中使用redis作为消息队列 实战案例 (详细注释 必能看懂)

延迟队列的作用

延迟队列常用于需要延迟处理的场景,比如定时任务、过期任务的处理、缓存过期等。这个 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实现类的接口,这个接口里面放的就是我们写的消费的任务队列 这样线程执行的时候就把我们写的实现类里面放的任务给执行了  

按照这个顺序和注释  理解透 简简单单 怼!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值