SpringBoot定時任務多實例互斥執(zhí)行的完整指南
Spring Boot 的 @Scheduled 寫定時任務很方便,但多實例部署時有個問題:同一個定時任務會在每臺機器上都觸發(fā)執(zhí)行。
比如部署了兩臺應用服務器,凌晨 2 點的數(shù)據(jù)統(tǒng)計任務會同時跑兩遍,數(shù)據(jù)重復、文件重復生成。
解決這個問題通常有幾種思路。
常見方案
方案一:單機執(zhí)行
只在一臺指定的機器上跑任務:
@Scheduled(cron = "0 0 2 * * ?")
public void scheduledTask() {
String hostname = InetAddress.getLocalHost().getHostName();
if (!"app-server-01".equals(hostname)) {
return;
}
// do something
}
問題很明顯:那臺機器掛了,任務就不執(zhí)行了。
方案二:Redis 分布式鎖
用 Redis 的 SETNX 實現(xiàn)互斥(或者Redission):
@Scheduled(cron = "0 0 2 * * ?")
public void scheduledTask() {
Boolean locked = stringRedisTemplate.opsForValue()
.setIfAbsent("task:data-sync", "1", 10, TimeUnit.MINUTES);
if (Boolean.FALSE.equals(locked)) {
return;
}
try {
// do something
} finally {
stringRedisTemplate.delete("task:data-sync");
}
}
能用,但每個任務都要寫一遍加鎖釋放邏輯,而且需要項目里有 Redis。
ShedLock 方案
ShedLock 是一個專門解決定時任務重復執(zhí)行問題的框架,支持多種存儲后端(數(shù)據(jù)庫、Redis、MongoDB 等)。
核心思路:在存儲層記錄每個任務的鎖狀態(tài),任務執(zhí)行前先搶鎖,搶到了才執(zhí)行。
集成步驟
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>
2. 創(chuàng)建鎖表
CREATE TABLE shedlock (
name VARCHAR(64) NOT NULL,
lock_until TIMESTAMP(3) NOT NULL,
locked_at TIMESTAMP(3) NOT NULL,
locked_by VARCHAR(255) NOT NULL,
PRIMARY KEY (name)
);
3. 配置 LockProvider
@Configuration
@EnableSchedulerLock(defaultLockAtMostFor = "10m")
public class ShedLockConfig {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(
JdbcTemplateLockProvider.Configuration.builder()
.withDataSource(dataSource)
.withTableName("shedlock")
.build()
);
}
}
4. 給定時任務加注解
@Scheduled(cron = "0 0 2 * * ?")
@SchedulerLock(name = "dataSyncTask", lockAtMostFor = "5m")
public void syncData() {
// do something
}
完成。多臺服務器部署時,只有搶到鎖的那臺會執(zhí)行任務。
注解參數(shù)說明
@SchedulerLock 有幾個關鍵參數(shù):
name:鎖名稱,相同 name 的任務會互斥執(zhí)行。建議用任務名來命名,保持唯一。
lockAtMostFor:鎖的最大持有時間。
這是為了防止任務執(zhí)行過程中機器宕機,導致鎖永遠不釋放。比如設置 5m,即使任務執(zhí)行超時,鎖也會在 5 分鐘后自動過期。
一般按任務預期執(zhí)行時間的 2 倍左右設置,留些余量。
lockAtLeastFor:鎖的最小持有時間。
這是為了防止任務執(zhí)行太快,鎖立即釋放被其他機器搶到。
比如定時任務每分鐘執(zhí)行一次,任務 5 秒就跑完了。如果沒有這個參數(shù),其他機器可能會在同一分鐘內(nèi)再次搶到鎖執(zhí)行。
這種情況下可以設置 lockAtLeastFor = "1m",確保鎖保持到下一分鐘。
實現(xiàn)原理
ShedLock 的實現(xiàn)邏輯不復雜。
任務執(zhí)行前,會向數(shù)據(jù)庫插入或更新鎖記錄:
INSERT INTO shedlock (name, lock_until, locked_at, locked_by)
VALUES ('dataSyncTask', '2025-01-25 02:05:00', NOW(), '192.168.1.10')
ON DUPLICATE KEY UPDATE
lock_until = '2025-01-25 02:05:00',
locked_at = NOW(),
locked_by = '192.168.1.10'
WHERE lock_until < NOW();
關鍵是最后的 WHERE lock_until < NOW() 條件:
- 如果當前鎖已過期,UPDATE 成功,搶到鎖
- 如果當前鎖未過期,UPDATE 影響行數(shù)為 0,搶鎖失敗,任務不執(zhí)行
任務執(zhí)行完成后不需要主動釋放鎖,等待 lock_until 時間到期即可。
適用場景
ShedLock 的定位很明確:專門為定時任務設計的分布式鎖框架。
適合
- 定時任務需要互斥執(zhí)行,避免重復
- 希望用注解方式簡化鎖的代碼邏輯
- 需要自動鎖過期機制,防止死鎖
不適合
- 高并發(fā)搶鎖的業(yè)務場景(比如秒殺、庫存扣減),ShedLock 不是為此設計
- 需要可重入鎖、讀寫鎖等復雜特性
- 需要精確控制鎖獲取和釋放時機的業(yè)務邏輯
ShedLock 和通用分布式鎖是互補關系,不是替代關系。如果你的業(yè)務代碼里需要手動加鎖解鎖,用 Redisson 或手動實現(xiàn) Redis SETNX 更合適。但如果只是為了解決定時任務重復執(zhí)行的問題,ShedLock 是更簡潔的方案。
幾個注意事項
1. 存儲后端選擇
本文演示用的是 JDBC 方式(基于數(shù)據(jù)庫表)。ShedLock 還支持 Redis、MongoDB、ZooKeeper、Hazelcast 等多種存儲后端,根據(jù)項目現(xiàn)有技術棧選擇即可。
2. 表名自定義(數(shù)據(jù)庫存儲時)
如果用 JDBC 作為存儲后端,默認表名是 shedlock,可以按需修改: 3. 機器標識
locked_by 字段記錄是哪臺機器拿到的鎖,默認是主機名??梢宰远x成更有意義的標識:
.withLockedByValue("app-" + getServerIp())
4. 主從/多數(shù)據(jù)源場景(數(shù)據(jù)庫存儲時)
如果項目有多個數(shù)據(jù)源,確保 ShedLock 用的是主庫,避免主從延遲導致的鎖問題。
總結
ShedLock 是一個專門為定時任務設計的分布式鎖框架:
- 優(yōu)點:注解式使用、集成簡單、自動鎖過期、支持多種存儲后端
- 局限性:只適用于定時任務場景,不適用于通用業(yè)務加鎖
和手動寫 Redis 分布式鎖相比,ShedLock 把定時任務鎖的邏輯抽象出來了,代碼更簡潔。但如果你需要的是通用業(yè)務鎖,還是用 Redisson 或手寫 SETNX 更合適。
以上就是SpringBoot定時任務多實例互斥執(zhí)行的完整指南的詳細內(nèi)容,更多關于SpringBoot定時任務的資料請關注腳本之家其它相關文章!
相關文章
Jedis出現(xiàn)connection timeout問題解決方法(JedisPool連接池使用實例)
這篇文章主要介紹了Jedis出現(xiàn)connection timeout問題解決方法,使用Jedis的JedisPool連接池解決了這個問題,需要的朋友可以參考下2014-05-05
springboot集成mybatisPlus+多數(shù)據(jù)源的實現(xiàn)示例
這篇文章主要介紹了springboot集成mybatisPlus+多數(shù)據(jù)源的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-12-12
idea?maven依賴引入失效無法正常導入依賴問題的解決方法
有時候idea導入一個新項目,或者pom文件修改(新增)了依賴,pom文件和代碼會報紅,提示依賴包不存在,下面這篇文章主要給大家介紹了關于idea?maven依賴引入失效無法正常導入依賴問題的解決方法,需要的朋友可以參考下2023-04-04

