在 Spring Cloud 微服务架构中,多个实例部署同一个微服务时,若需要执行定时任务(如每天凌晨清理日志、同步数据等),必须确保同一时间只有一个实例执行该任务,否则会造成重复执行、数据不一致等问题。为实现这一点,可以借助 Redis 分布式锁 来协调多个实例。 下面将详细介绍如何在 Spring Cloud 微服务中使用 Redis 实现分布式定时任务的方案。
一、整体思路
-
使用 @Scheduled 注解定义定时任务。
-
在定时任务执行前尝试获取 Redis 分布式锁。
-
若成功获取锁,则执行业务逻辑;否则跳过本次执行。
-
任务执行完成后释放锁。
-
锁需设置合理的过期时间(防止死锁)。
-
使用 Redis 的 SET key value NX PX 命令原子性地加锁。
二、核心组件设计
1. Redis 分布式锁工具类
查看项目,我的在 common 包中已经存在了可以使用的 redis 组件
建议去git仓库找开源的代码或者直接让ai写个工具类
其中存在 set() 方法可以很方便设置 redis 分布式事务锁
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
* @author c02822
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
log.error("except.", e);
return false;
}
}
2. 定时任务服务类
需要的定时任务:每天在固定的某一个时间将所有产品,所有环境的所有产品的工作负载状态存储到一个数据库表中。
以上面这个需求为例,首先需要明确的是用于存储数据的数据表。为此你新建两个数据库,建表语句如下:
-- 1. 工作负载每日快照表
CREATE TABLE workload_daily_snapshot (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
snapshot_date DATE NOT NULL, -- 快照日期
namespace VARCHAR(100) NOT NULL,
workload_name VARCHAR(255) NOT NULL,
workload_type VARCHAR(50) NOT NULL, -- 'Deployment'/'StatefulSet'
product VARCHAR(100), -- 产品线
environment VARCHAR(50), -- 环境
desired_replicas INT NOT NULL DEFAULT 0,
available_replicas INT NOT NULL DEFAULT 0,
image VARCHAR(500),
cpu_request_m DECIMAL(10,2), -- CPU请求(毫核)
memory_request_mi INT, -- 内存请求(MiB)
cpu_limit_m DECIMAL(10,2), -- CPU限制(毫核)
memory_limit_mi INT, -- 内存限制(MiB)
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_date_namespace (snapshot_date, namespace),
INDEX idx_product_env (product, environment),
UNIQUE KEY uk_daily_workload (snapshot_date, namespace, workload_name, workload_type)
);
-- 2. Pod每日状态表
CREATE TABLE pod_daily_status (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
snapshot_date DATE NOT NULL,
namespace VARCHAR(100) NOT NULL,
pod_name VARCHAR(255) NOT NULL,
workload_name VARCHAR(255), -- 所属工作负载
workload_type VARCHAR(50), -- 所属工作负载类型
status VARCHAR(50) NOT NULL, -- Running/Pending/Failed等
node_name VARCHAR(255),
restart_count INT DEFAULT 0,
product VARCHAR(100),
environment VARCHAR(50),
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_date_status (snapshot_date, status),
INDEX idx_workload (namespace, workload_name),
INDEX idx_node (node_name)
);
此外我写了个定时任务的模板
// ScheduledTaskService.java
@Slf4j
@Service
public class ScheduledTaskService {
/**
* redis工具包
*/
@Autowired
@Qualifier("redisUtil")
private RedisUtil redisUtil;
// 每天凌晨 2 点执行
@Scheduled(cron = "0 0 2 * * ?")
public void dailyCleanupTask() {
String lockKey = "lock:daily_cleanup_task";
long expireTime = 60 * 10; // 10 分钟,根据任务最大执行时间设定
if (!redisUtil.hasKey(lockKey)) {
redisUtil.set(lockKey, 1 , expireTime);
// 业务逻辑
。。。
}
}
3. 配置类(启用定时任务 & Redis)
// AppConfig.java
package com.example.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
@Configuration
@EnableScheduling
public class AppConfig {
// 启用 @Scheduled
}
三、注意事项
锁的过期时间:必须大于任务最大可能执行时间,避免任务未完成锁就过期,导致其他节点抢占锁造成并发执行。
锁的 key 命名规范:如 lock:service_name:task_name,便于管理和监控。
异常处理:务必在 finally 块中释放锁,防止因异常导致锁未释放。
避免主从切换下的锁失效:Redis 主从异步复制可能导致锁在主节点加锁后,主挂掉,从节点未同步锁信息就被提升为主,此时另一个客户端可能再次加锁。若对一致性要求极高,应使用 RedLock 算法或改用 ZooKeeper / etcd。但在大多数业务场景中,单 Redis 实例 + 合理过期时间已足够。
四、总结
通过 Redis 分布式锁,我们可以在 Spring Cloud 多实例环境下安全地执行定时任务,确保幂等性和唯一性。本方案轻量、易理解,适用于大多数业务场景。