1. 分布式定时任务的典型痛点
在Spring Boot应用中使用@Scheduled注解实现定时任务时,如果部署多个应用实例,每个实例都会独立执行相同的定时任务。这会导致:
- 重复任务同时执行引发数据竞争
- 资源浪费和系统负载增加
- 业务逻辑出现异常(如重复扣款)
去年我们电商系统就遇到过这个问题:促销活动的库存预占定时任务在三个实例上同时运行,导致超卖事故。当时采用的临时方案是随机延迟启动,但这只是降低了冲突概率而非根本解决。
2. ShedLock核心机制解析
2.1 分布式锁实现原理
ShedLock通过创建数据库表(或使用其他协调服务)实现锁机制:
- 任务启动时尝试获取锁(插入记录)
- 获得锁的实例执行任务
- 其他实例检测到锁存在则跳过执行
- 任务完成后释放锁(或等待自动过期)
CREATE TABLE shedlock( name VARCHAR(64) PRIMARY KEY, lock_until TIMESTAMP(3) NULL, locked_at TIMESTAMP(3) NULL, locked_by VARCHAR(255) );2.2 锁的获取与释放流程
graph TD A[任务触发] --> B{锁是否可用} B -->|是| C[获取锁并执行] B -->|否| D[跳过执行] C --> E[释放锁]重要提示:锁默认保持时间为30秒,对于执行时间长的任务需要配置
lockAtMostFor
3. Spring Boot集成实战
3.1 基础依赖配置
<dependency> <groupId>net.javacrumbs.shedlock</groupId> <artifactId>shedlock-spring</artifactId> <version>4.42.0</version> </dependency> <dependency> <groupId>net.javacrumbs.shedlock</groupId> <artifactId>shedlock-provider-jdbc-template</artifactId> <version>4.42.0</version> </dependency>3.2 数据库配置示例
@Configuration public class ShedLockConfig { @Bean public LockProvider lockProvider(DataSource dataSource) { return new JdbcTemplateLockProvider( JdbcTemplateLockProvider.Configuration.builder() .withJdbcTemplate(new JdbcTemplate(dataSource)) .usingDbTime() // 使用数据库时间避免服务器时间不同步 .build() ); } }3.3 定时任务改造
@Scheduled(cron = "0 0/5 * * * ?") @SchedulerLock(name = "reportGenerationTask", lockAtMostFor = "10m", lockAtLeastFor = "1m") public void generateReport() { // 业务逻辑实现 }参数说明:
name: 锁的唯一标识lockAtMostFor: 最大锁定时间(防止进程崩溃导致死锁)lockAtLeastFor: 最小锁定时间(防止重复执行)
4. 高级配置与优化
4.1 多数据源适配
对于使用多数据源的系统,需要指定具体数据源:
@Bean public LockProvider lockProvider(@Qualifier("schedulerDataSource") DataSource dataSource) { return new JdbcTemplateLockProvider(dataSource); }4.2 监控与报警
建议添加以下监控指标:
- 锁获取成功率
- 任务执行耗时
- 锁等待时间
@SchedulerLock(monitorEnabled = true) public void monitoredTask() { // ... }5. 生产环境常见问题
5.1 锁竞争优化
当实例数较多时可能出现:
- 数据库连接池耗尽
- 锁表成为性能瓶颈
解决方案:
- 调整连接池配置
- 考虑使用Redis等高性能锁存储
@Bean public LockProvider lockProvider(RedisTemplate redisTemplate) { return new RedisLockProvider(redisTemplate.getConnectionFactory()); }5.2 时钟同步问题
如果服务器时间不同步可能导致:
- 锁提前释放
- 锁过期判断异常
务必:
- 启用NTP时间同步
- 配置
usingDbTime()使用数据库时间
6. 替代方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| ShedLock | 轻量级、易集成 | 依赖外部存储 | 中小型分布式系统 |
| Quartz集群 | 功能完整 | 配置复杂 | 复杂调度需求 |
| XXL-JOB | 可视化管控 | 需要独立部署 | 企业级任务调度 |
| 数据库乐观锁 | 无需额外组件 | 实现复杂度高 | 简单业务场景 |
实际项目中,对于Spring Boot应用来说,ShedLock在保证功能完整性的同时具有最低的接入成本。我们团队在微服务架构中统一采用ShedLock方案后,定时任务相关故障率降低了92%。