一、pom.xml
引入redisson-spring-boot-starter的jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.19.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
二、application.properties
增加redis配置,以及redission延迟队列的开关配置redission.delayqueue.enable
server.port=8089
server.servlet.context-path=/demo
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/pagestudent?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0
spring.redis.timeout=3000
# 是否启用redis延迟队列
redission.delayqueue.enable=true
三、目录结构
四、延迟队列添加任务的工具类
package com.ykq.redis.util;
import io.micrometer.core.instrument.util.StringUtils;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingDeque;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @author: kqyin
* @date: 2023/1/8 16:51
* @Description: 延迟队列增删工具类
*/
@Slf4j
@Component
public class RedisDelayQueueUtil {
@Autowired
private RedissonClient redissonClient;
/**
* @Author kqyin
* @Date 2023/1/9 19:10
* @Description 添加延迟队列
*/
public <T> boolean addDelayQueue(@NonNull T value, @NonNull long delay, @NonNull TimeUnit timeUnit, @NonNull String queueName) {
if (StringUtils.isBlank(queueName) || Objects.isNull(value)) {
return false;
}
try {
RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque(queueName);
RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
delayedQueue.offer(value, delay, timeUnit);
log.info("(添加延时队列成功) 队列名:{},队列值:{},延迟时间:{}", queueName, value, timeUnit.toSeconds(delay) + "秒");
} catch (Exception e) {
log.error("(添加延时队列失败) {}", e.getMessage());
throw new RuntimeException("(添加延时队列失败)");
}
return true;
}
/**
* 获取延迟队列
*
* @param queueName
* @param <T>
*/
public <T> T getDelayQueue(@NonNull String queueName) throws InterruptedException {
if (StringUtils.isBlank(queueName)) {
return null;
}
RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque(queueName);
RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
T value = (T) delayedQueue.poll();
return value;
}
/**
* 删除指定队列中的消息
*
* @param o 指定删除的消息对象队列值(同队列需保证唯一性)
* @param queueName 指定队列键
*/
public boolean removeDelayedQueue(@NonNull Object o, @NonNull String queueName) {
if (StringUtils.isBlank(queueName) || Objects.isNull(o)) {
return false;
}
RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque(queueName);
RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
boolean flag = delayedQueue.remove(o);
return flag;
}
}
五、延迟任务枚举
package com.ykq.redis.enums;
import lombok.AllArgsConstructor;
/**
* @Author kqyin
* @Date 2023/1/9 19:13
* @Description 延迟队列执行任务枚举
*/
@AllArgsConstructor
public enum RedisDelayQueueEnum {
TWO_SECOND_CONSUME_QUEUE("two_second_consume_queue", "2S后消费的延迟队列", "twoSecondConsumeHandler"),
THREE_SECOND_CONSUME_QUEUE("three_second_consume_queue", "3S后消费的延迟队列", "threeSecondConsumeHandler");
// 队列名
private String queueName;
// 描述
private String desc;
// 处理类beanId
private String beanId;
public String getQueueName() {
return queueName;
}
public String getDesc() {
return desc;
}
public String getBeanId() {
return beanId;
}
}
六、延迟任务执行的模板方法
package com.ykq.redis.handler;
/**
* @author: kqyin
* @date: 2023/1/9 14:58
* @Description: 延迟队列执行方法,需要具体实现
*/
public interface RedisDelayQueueHandler<T> {
/**
* @Author kqyin
* @Date 2023/1/9 18:46
* @Description 执行方法
*/
void exec(T t);
}
七、延迟队列配置类
通过@ConditionalOnProperty注解实现配置类是否生效,也就是功能的开启和关闭
package com.ykq.redis.config;
import com.alibaba.fastjson.JSONObject;
import com.ykq.redis.enums.RedisDelayQueueEnum;
import com.ykq.redis.handler.RedisDelayQueueHandler;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author: kqyin
* @date: 2023/1/9 15:14
* @Description: redis延迟队列配置
*/
@Configuration
// 当redission.delayqueue.enable属性配置为true时,该配置才会生效,也就是启用redission的延迟队列
@ConditionalOnProperty(value = "redission.delayqueue.enable")
@Slf4j
public class RedisDelayQueueConfig {
/**
* @Author kqyin
* @Date 2023/1/9 19:15
* @Description 线程池
*/
@Bean("delayExecutor")
public ExecutorService getDelayExecutor() {
return Executors.newFixedThreadPool(5);
}
/**
* @Author kqyin
* @Date 2023/1/9 19:15
* @Description 获取延迟队列组,并执行
*/
@Bean
public RedisDelayQueueEnum[] startRedisDelayQueue(ApplicationContext applicationContext, RedissonClient redissonClient,
@Qualifier("delayExecutor") ExecutorService executorService) {
RedisDelayQueueEnum[] redisDelayQueueArr = RedisDelayQueueEnum.values();
for (RedisDelayQueueEnum queueEnum : redisDelayQueueArr) {
startThread(applicationContext, redissonClient, executorService, queueEnum);
}
return redisDelayQueueArr;
}
/**
* @Author kqyin
* @Date 2023/1/9 19:15
* @Description 执行延迟队列
*/
private <T> void startThread(ApplicationContext applicationContext, RedissonClient redissonClient,
ExecutorService executorService, RedisDelayQueueEnum queueEnum) {
RBlockingQueue<T> blockingFairQueue = redissonClient.getBlockingQueue(queueEnum.getQueueName());
// 由于此线程需要常驻,可以新建线程,不用交给线程池管理
Thread thread = new Thread(() -> {
log.info("启动监听延迟队列线程:{}", queueEnum.getQueueName());
while (true) {
try {
// 获取到执行类
RedisDelayQueueHandler redisDelayQueueHandler = (RedisDelayQueueHandler) applicationContext.getBean(queueEnum.getBeanId());
// 获取到被执行对象,作为参数传递给redisDelayQueueHandler
T t = blockingFairQueue.take();
log.info("监听延迟队列线程:{}, 获取到值:{}", queueEnum.getQueueName(), JSONObject.toJSONString(t));
executorService.submit(() -> redisDelayQueueHandler.exec(t));
} catch (Exception e) {
log.error("监听延迟队列线程错误,", e);
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {
}
}
}
});
thread.setName(queueEnum.getQueueName());
thread.start();
}
}
八、建一个延迟任务
package com.ykq.redis.handler;
import org.springframework.stereotype.Component;
/**
* @author: kqyin
* @date: 2023/1/9 16:21
* @Description: 2s后消费的测试方法
*/
@Component("twoSecondConsumeHandler")
public class TwoSecondConsumeHandler implements RedisDelayQueueHandler<String> {
@Override
public void exec(String str) {
System.out.println("2s后输出字符串:" + str);
}
}
九、测试
package com.ykq.redis.controller;
import com.ykq.redis.entity.Student;
import com.ykq.redis.enums.RedisDelayQueueEnum;
import com.ykq.redis.util.RedisDelayQueueUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
/**
* @author: kqyin
* @date: 2023/1/9 16:22
* @Description: 测试controller
*/
@RestController
public class TestController {
@Autowired
private RedisDelayQueueUtil redisDelayQueueUtil;
@RequestMapping("/test")
public void test() {
// 模拟业务中添加延迟任务
redisDelayQueueUtil.addDelayQueue("嘿嘿嘿,我是被添加的延迟任务的待处理的对象", 2, TimeUnit.SECONDS, RedisDelayQueueEnum.TWO_SECOND_CONSUME_QUEUE.getQueueName());
// redisDelayQueueUtil.addDelayQueue(new Student("001", "张三", 1), 3, TimeUnit.SECONDS, RedisDelayQueueEnum.THREE_SECOND_CONSUME_QUEUE.getQueueName());
}
}