一、基于 redis 键空间通知(适合精确延时任务)
原理:利用 redis 的键过期事件(expire)触发任务执行,通过监听 __keyevent@*__:expired 通道捕获事件。
步骤:
启用 redis 键空间通知(redis.conf 或运行时配置):
config set notify-keyspace-events ex
spring boot 监听器实现:
@component
public class keyexpiredlistener extends keyexpirationeventmessagelistener {
public keyexpiredlistener(redismessagelistenercontainer listenercontainer) {
super(listenercontainer);
}
@override
public void onmessage(message message, byte[] pattern) {
string expiredkey = new string(message.getbody());
if (expiredkey.startswith("task:")) { // 过滤业务键
system.out.println("执行任务: " + expiredkey);
// 例如:task:123 过期时执行订单超时逻辑
}
}
}
注册监听器:
@configuration
public class redisconfig {
@bean
redismessagelistenercontainer container(redisconnectionfactory factory,
keyexpiredlistener listener) {
redismessagelistenercontainer container = new redismessagelistenercontainer();
container.setconnectionfactory(factory);
container.addmessagelistener(listener, new patterntopic("__keyevent@*__:expired"));
return container;
}
}
调度任务(设置带过期时间的键):
@service
public class taskscheduler {
@autowired
private stringredistemplate redistemplate;
public void scheduletask(string taskid, long delayseconds) {
redistemplate.opsforvalue().set("task:" + taskid, "data",
delayseconds, timeunit.seconds); // 键在 delayseconds 秒后过期
}
}
注意:需在 redis 配置中开启 notify-keyspace-events ex。
二、基于 redis 有序集合轮询(适合批量定时任务)
原理:将任务执行时间作为 zset 的 score,通过定时任务查询到期的任务并执行。
步骤:
添加任务到 zset:
public void addtask(string taskid, instant executetime) {
stringredistemplate.opsforzset().add("scheduled_tasks", taskid,
executetime.getepochsecond());
}
定时扫描并执行任务(每分钟轮询):
@scheduled(cron = "0 * * * * *") // 每分钟执行
public void polltasks() {
long now = instant.now().getepochsecond();
set<string> tasks = stringredistemplate.opsforzset()
.rangebyscore("scheduled_tasks", 0, now); // 获取所有到期任务
for (string task : tasks) {
system.out.println("执行任务: " + task);
// 执行后移除任务
stringredistemplate.opsforzset().remove("scheduled_tasks", task);
}
}
优点:避免键空间通知的丢失风险,适合任务量大的场景。
三、基于 redis 分布式锁(防集群任务重复执行)
原理:在分布式环境中,通过 redis 锁确保同一时间只有一个实例执行定时任务。
代码示例:
@component
public class distributedtask {
@autowired
private stringredistemplate redistemplate;
private static final string lock_key = "task_lock:order_clean";
@scheduled(cron = "0 0 3 * * ?") // 每天凌晨3点执行
public void dailytask() {
boolean locked = redistemplate.opsforvalue()
.setifabsent(lock_key, "locked", duration.ofminutes(10));
if (boolean.true.equals(locked)) {
try {
cleanexpiredorders(); // 执行核心任务
} finally {
redistemplate.delete(lock_key); // 释放锁(可选)
}
}
}
}
关键点:
- 使用
setifabsent原子操作获取锁,避免并发冲突。 - 锁自动过期防止死锁(如任务执行超时)。
版本要求:
spring data redis ≥ 2.3.0:该版本引入了 setifabsent(key, value, duration) 方法,支持原子性设置键值+过期时间(对应 redis 的 set key value nx ex seconds 命令)
四、方案对比
| 方案 | 适用场景 | 注意事项 |
|---|---|---|
| 键空间通知 | 精确延时任务(如30分钟后关单) | 需配置 redis,事件可能丢失 |
| 有序集合轮询 | 批量任务、高可靠性场景 | 需自行处理任务分页和重试 |
| 分布式锁 | 集群环境防重复执行(如日报生成) | 锁超时时间需大于任务执行时间 |
*补充:
- 关键业务(如支付超时)建议结合 数据库日志+重试机制 补偿;
- 高频任务优先选 zset 轮询,避免键空间通知的性能瓶颈;
- 分布式锁的锁键需包含业务标识(如
lock_key:业务名)。
到此这篇关于springboot使用redis实现定时任务的三种方式的文章就介绍到这了,更多相关springboot redis 定时任务内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论