重启服务后Redisson队列一直阻塞 不消费过期数据
前言
- 用
Redisson + Redis
做了个延迟队列,但是我重启之后居然不消费到期的数据了,非要我再往队列新增一条才开始消费。blockingDeque.take()
一直阻塞状态。
解决方案
- 之前是这样写的。
public boolean delayOffer(RedisDelayQueueMessage msg, long time, TimeUnit timeUnit) {
RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque(delayQueueName);
RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
delayedQueue.offer(msg, time, timeUnit);
return Boolean.TRUE;
}
- 把
blockingDeque
和delayedQueue
启动的时候就初始化好。
/**
* @author Zhou Zhongqing
* @ClassName RedisDelayQueueConfig
* @description: 预先初始化延迟队列和阻塞队列对象
* @date 2022-08-20 14:35
*/
@Configuration
public class RedisDelayQueueConfig {
@Value("${xxx.delay.queue.name:xxxs_delay_queue}")
private String delayQueueName;
@Bean
public RBlockingDeque blockingDeque(RedissonClient redissonClient) {
return redissonClient.getBlockingDeque(delayQueueName);
}
@Bean
public RDelayedQueue delayedQueue(RedissonClient redissonClient) {
return redissonClient.getDelayedQueue(blockingDeque(redissonClient));
}
}
- 然后修改下使用的地方。
@Resource
private RBlockingDeque<Object> blockingDeque;
@Resource
private RDelayedQueue<Object> delayedQueue;
@Override
public boolean delayOffer(RedisDelayQueueMessage msg, long time, TimeUnit timeUnit) {
delayedQueue.offer(msg, time, timeUnit);
return Boolean.TRUE;
}
- 监听消息的代码如下
... 省略 ...
private void initListener() {
threadPoolTaskExecutor.execute(() -> { // threadPoolTaskExecutor是Spring封装的线程池类
while (!destroy) { // destroy 标记是否程序关闭
try {
RedisDelayQueueMessage result = redisQueueService.delayTake();
if (!destroy) {
log.info("接收到消息 {}", objectMapperFace.writeValueAsString(result));
if (!ObjectUtils.isEmpty(result)) {
// 根据不同消息类型走不同实现类
RedisDelayQueueHandlerService redisDelayQueueHandler = redisDelayQueueHandlerMapping.get(result.getMessageType());
if (!ObjectUtils.isEmpty(redisDelayQueueHandler)) {
R res = redisDelayQueueHandler.execute(result);
if (!res.isSuccess()) {
log.error("消息处理失败了 {} ", objectMapperFace.writeValueAsString(result));
}
} else {
log.error("不能处理的消息 ");
}
}
}
} catch (Exception e) {
log.error("延迟队列消费消息出现异常" + e);
}
}
});
}
...省略...
RedisQueueServiceImpl.java
@Override
public RedisDelayQueueMessage delayTake() {
try {
return (RedisDelayQueueMessage) blockingDeque.take();
} catch (InterruptedException e) {
if (!destroy) {
e.printStackTrace();
throw new ServiceException(e.getMessage());
}
}
return null;
}
- 现在消息过期后,服务已启动就能消费消息了,如下图所示。
关闭程序时的异常
- 程序在关闭的时候,有可能正在执行
take()
,所以会报一堆异常(poll也是一样的),我是这样解决的。利用了关闭程序时候Spring的回调。 - 这个办法和我之前写的解决Redis java.lang.IllegalStateException: Cannot connect, Event executor group is terminated很相似,可以参考下。
private volatile boolean destroy = false;
@Override
public RedisDelayQueueMessage delayTake() {
try {
return (RedisDelayQueueMessage) blockingDeque.take();
} catch (InterruptedException e) {
if (!destroy) {
// 如果是正在关闭程序就不要抛异常了
e.printStackTrace();
throw new ServiceException(e.getMessage());
}
}
return null;
}
@PreDestroy
public void destroy() {
destroy = true;
}
private void initListener() {
threadPoolTaskExecutor.execute(() -> {
while (!destroy) {
try {
RedisDelayQueueMessage result = redisQueueService.delayTake();
if (!destroy) {
log.info("接收到消息 {}", objectMapperFace.writeValueAsString(result));
if (!ObjectUtils.isEmpty(result)) {
...省略...
}
}
} catch (Exception e) {
log.error("延迟队列消费消息出现异常" + e);
}
}
});
}
这里我感觉会有一致性的问题,建议增加消费日志,记录消费每个消息的状态,方便补偿。