文章目录
- 配置
- 基于redission api的服务
- 订单的阻塞队列
在工作中,我们经常会遇到一些场景,比如订单到期未支付导致取消,或者到期后自动续费等。在这些情况下,我们发现延迟队列非常适合使用。常见的延迟队列实现包括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);
}
}