【微服务实战】K8s多副本引发定时任务乱象?基于 ShedLock 的轻量级互斥解法
在单体应用时代,使用 Spring Boot 的 @Scheduled 编写定时任务(如每日凌晨对账、定时清理过期 Token)极其方便。但当应用上云并部署到 CCE 集群,为了高可用将 replicas 扩展为 3 个副本时,灾难就发生了:3 个 Pod 会在同一秒钟同时触发该定时任务,导致严重的并发写入冲突与脏数据。
许多开发者会因此引入 XXL-JOB 或 ElasticJob。但如果你的系统只有几个简单的定时清理逻辑,引入如此庞大的调度调度中心无疑是“大炮打蚊子”。
标准的云原生轻量级解法是:保持 @Scheduled 不变,引入 ShedLock 基于底层 Redis 或数据库实现无侵入的分布式锁。
1. 极速引入 ShedLock 依赖
ShedLock 并不是一个任务调度框架,它只做一件事:保证配置了锁的任务在同一时刻最多只有一个节点在执行。在 pom.xml 中引入 Redis 支持包:
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>5.10.0</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-redis-spring</artifactId>
<version>5.10.0</version>
</dependency>
2. 注入全局锁提供者(Lock Provider)
利用应用现有的 Redis 连接池,初始化 ShedLock 的存储介质。无需额外部署任何中间件:
@Configuration
// 💡 核心:开启默认配置,支持定时任务加锁
@EnableSchedulerLock(defaultLockAtMostFor = "10m")
public class ShedLockConfig {
@Bean
public LockProvider lockProvider(RedisConnectionFactory connectionFactory) {
// 利用当前的云缓存 DCS(Redis) 实例作为分布式锁的载体
return new RedisLockProvider(connectionFactory, "my_app_lock_env");
}
}
3. 在业务任务上声明锁边界
无需修改任何业务逻辑代码,只需在原有的 @Scheduled 方法上追加一个 @SchedulerLock 注解即可:
@Component
public class DailyBillingTask {
@Scheduled(cron = "0 0 2 * * ?")
@SchedulerLock(
name = "dailyBillingLock",
lockAtLeastFor = "30s", // 💡 核心:哪怕任务1秒执行完,锁也要强制保留30秒,防止其他 Pod 时钟不同步导致的二次执行
lockAtMostFor = "5m" // 💡 兜底:如果拿到锁的 Pod 意外宕机,5分钟后锁自动释放
)
public void execute() {
log.info("开始执行每日凌晨对账任务...");
// 核心对账逻辑...
}
}
总结: 在微服务云原生演进中,基础中间件的“克制”尤为重要。通过 ShedLock 巧妙的切面设计,我们在不改变 Spring 原生调度习惯、不引入重型调度中心的前提下,以极低的成本完美规避了多副本定时任务并发执行的致命风险。
- 点赞
- 收藏
- 关注作者
评论(0)