基于redission的redis延迟队列实现


在工作中,我们经常会遇到一些场景,比如订单到期未支付导致取消,或者到期后自动续费等。在这些情况下,我们发现延迟队列非常适合使用。常见的延迟队列实现包括rabbitMQ的死信队列和RocketMQ的延迟队列。然而,有时候项目规模较小,没有引入消息中间件,但又需要使用延迟队列的场景。在这种情况下,我们可以利用已有的redis实现的延迟队列来解决问题。
所以今天介绍Redisson的延迟队列,Redisson有一个RDelayedQueue是一个专门用于处理延迟任务的队列,RDelayedQueue不会阻止生产者向已满的队列中添加元素,而是会将它们放入一个等待队列中,直到有空间可用。允许元素在一定时间后被重新入队,或者在指定的时间间隔内被消费。下面我们来实现延迟队列:
核心思想是通过线程执行redission的延时队列(DelayedQueue),通过take()方法尝试从队列中取出元素。

引入redission jar包:

 <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.17.4</version>
        </dependency>

配置

先配置项目中的redission 配置文件:

@Configuration
public class RedissonConfig {

    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.port}")
    private String redisPort;
    @Value("${spring.redis.password}")
    private String password;

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + redisHost + ":" + redisPort);
        if(StringUtils.isBlank(password)) {
            config.useSingleServer().setPassword(null);
        } else {
            config.useSingleServer().setPassword(password);
        }
        return Redisson.create(config);
    }
}

也可以使用redission springboot 集成配置,引入redisson-spring-data-2x,这样就无需配置类了。详见官网:

https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter

基于redission api的服务

增加封装操作api的方法,提供crud队列元素功能:

@Service
public class RedissonDelayedQueueService {

    @Autowired
    private RedissonClient redissonClient;

    public <E> void addQueue(E e, long delay, TimeUnit timeUnit, String queueName) {
        RBlockingDeque<E> blockingDeque = redissonClient.getBlockingDeque(queueName);
        RDelayedQueue<E> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
        delayedQueue.offer(e, delay, timeUnit);
    }

    public <E> RBlockingDeque<E> getQueue(String queueName) {
        RBlockingDeque<E> blockingDeque = redissonClient.getBlockingDeque(queueName);
        redissonClient.getDelayedQueue(blockingDeque);
        return blockingDeque;
    }

    public <E> void removeQueueElement(E e, String queueName) {
        RBlockingDeque<E> blockingDeque = redissonClient.getBlockingDeque(queueName);
        RDelayedQueue<E> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
        delayedQueue.remove(e);
    }

}
  • add为添加一个元素到队列方法;
  • get 为获取一个队列
  • removeQueueElement 为删除队列中的元素,适用于某些想直接删除已经添加的键值场景,比如用户下单没有立即完成付款,系统规定30分钟内完成支付,否则按废单处理,这时用户在5分钟时完成了付款,则需要直接删除当前用户的延时任务。

订单的阻塞队列

现在我们就可以实现我们一开始提到的核心思想,比如我们想实现一个订单的阻塞队列监听:
先定义一个键值:

public interface CacheKeyDefinition {
 /**
     * 订单延时到生效时间执行
     */
    String REDIS_KEY_ORDER = "redis_key_publish_order";
}
@Slf4j
@Component
public class OrderDelayedQueueListener {
    @Resource
    private RedissonDelayedQueueService redissonDelayedQueueService;
    @Resource
    private OrderHandler orderHandler;
    private final ExecutorService singlePoolExecutor
            = Executors.newSingleThreadExecutor();
    /**
     * 每次
     */
    @PostConstruct
    public void listener() {
        singlePoolExecutor.execute(()->{
            while (true){
                RBlockingDeque<String> blockingDeque = redissonDelayedQueueService.getQueue(CacheKeyDefinition.REDIS_KEY_ORDER);
                String priceListCode  = null;
                try {
                    id = blockingDeque.take();
                } catch (InterruptedException e) {
                    log.error("消费异常",e);
                }
                log.info("获取到订单队列:{}",id);
                orderHandler.handle(id);
            }
        });
    }
}

在实际业务中使用示例:

public class OrderserviceImpl{
	@Autowired
	private  RedissonDelayedQueueService delayedQueueService;
	
	void create(){
	//创建订单
	String id=;
	//将当前订单放入延时任务中:
	 delayedQueueService.addQueue(id, 30, TimeUnit.MINUTES, CacheKeyDefinition.REDIS_KEY_ORDER);
	
}
	
	//付款
	void pay(String id){
		//判定付款开始。。
		//判定付款成功。。
		
		 //删除redis 延迟队列元素
        delayedQueueService.removeQueueElement(id, CacheKeyDefinition.REDIS_KEY_ORDER);
	}
}
 

订单的延时处理

class OrderHandler{
	void handle(String id){
		//关闭当前订单
		close(id);
	}
}
Redis延迟队列可以通过利用Redis的zset和list数据结构来实现。下面是一个简单的实现方式: 1. 创建一个JobPool,用来存放所有Job的元信息。可以使用Hash数据结构来存储每个Job的详细信息,例如Job的ID、执行时间、参数等。 2. 创建一组以时间为维度的DelayBucket,用来存放所有需要延迟的Job。可以使用zset数据结构来存储Job的ID,并将Job的执行时间作为分数,以便按照时间顺序进行排序。 3. 创建一个Timer,负责实时扫描各个Bucket,并将延迟时间到达的Job从DelayBucket中取出,放入ReadyQueue中等待执行。 4. 创建一个ReadyQueue,用来存放已经到达执行时间的Job。可以使用list数据结构来存储Job的ID,按照先进先出的顺序进行执行。 5. 当需要添加一个延迟Job时,将Job的元信息存入JobPool,并将Job的ID添加到对应的DelayBucket中。 6. Timer定时扫描各个Bucket,将延迟时间到达的Job从DelayBucket中取出,放入ReadyQueue中。 7. 从ReadyQueue中取出Job的ID,根据Job的ID从JobPool中获取Job的详细信息,并执行相应的操作。 下面是一个简单的Python代码示例,演示了如何使用Redis实现延迟队列: ```python import redis import time # 连接Redis r = redis.Redis(host='localhost', port=6379, db=0) # 添加延迟Job def add_delayed_job(job_id, execute_time): # 将Job的元信息存入JobPool r.hset('JobPool', job_id, execute_time) # 将Job的ID添加到对应的DelayBucket中 r.zadd('DelayBucket', {job_id: execute_time}) # Timer定时扫描Bucket def timer_scan(): while True: # 获取当前时间 current_time = int(time.time()) # 扫描DelayBucket,将延迟时间到达的Job放入ReadyQueue r.zrangebyscore('DelayBucket', 0, current_time, start=0, num=10).pipe( lambda x: x if x else None, lambda x: r.lpush('ReadyQueue', *x), lambda x: r.zremrangebyscore('DelayBucket', 0, current_time) ).execute() # 休眠1秒 time.sleep(1) # 从ReadyQueue中取出Job并执行 def process_ready_queue(): while True: # 从ReadyQueue中取出Job的ID job_id = r.brpop('ReadyQueue')[1] # 根据Job的ID从JobPool中获取Job的详细信息 job_info = r.hget('JobPool', job_id) # 执行相应的操作 print(f"Executing Job: {job_id}, Info: {job_info}") # 启动Timer和处理ReadyQueue的线程 timer_thread = threading.Thread(target=timer_scan) process_thread = threading.Thread(target=process_ready_queue) timer_thread.start() process_thread.start() ``` 请注意,上述代码只是一个简单的示例,实际的实现可能需要根据具体需求进行调整和优化。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

π克

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值