Spring Boot 整合 ShedLock 處理定時任務(wù)重復(fù)執(zhí)行的問題小結(jié)
前言
在分布式系統(tǒng)中,定時任務(wù)的執(zhí)行往往需要考慮到多個實例的并發(fā)執(zhí)行問題。假設(shè)一個定時任務(wù)會在多個節(jié)點(diǎn)上并發(fā)執(zhí)行,可能導(dǎo)致重復(fù)執(zhí)行,甚至引發(fā)數(shù)據(jù)異?;蛳到y(tǒng)不一致問題。為了解決這一問題,ShedLock 是一個簡單而有效的解決方案,它可以確保在分布式環(huán)境中,只有一個節(jié)點(diǎn)在某一時刻執(zhí)行指定的定時任務(wù)。
什么是 ShedLock?
ShedLock 是一個輕量級的 Java 庫,用于解決分布式系統(tǒng)中定時任務(wù)的重復(fù)執(zhí)行問題。它的核心思想是在數(shù)據(jù)庫中加鎖,確保在分布式環(huán)境下,只有一個節(jié)點(diǎn)能夠在指定時間執(zhí)行某個任務(wù)。ShedLock 可以與 Spring Scheduler、Quartz 等定時任務(wù)框架結(jié)合使用。
github開源地址:https://github.com/lukas-krecan/ShedLock 目前擁有 3.7K Star,小伙伴們也可以針對文檔更系統(tǒng)性的學(xué)習(xí)如何應(yīng)用ShedLock。
ShedLock 的工作原理:
? 定時任務(wù)執(zhí)行時,ShedLock 會嘗試在數(shù)據(jù)庫中為該任務(wù)獲取鎖。
? 如果當(dāng)前節(jié)點(diǎn)成功獲取到鎖,則執(zhí)行定時任務(wù)。
? 如果當(dāng)前節(jié)點(diǎn)未能獲取到鎖(其他節(jié)點(diǎn)已獲取鎖),則該任務(wù)跳過執(zhí)行,等待下次調(diào)度。
定時任務(wù)重復(fù)執(zhí)行的問題
在分布式應(yīng)用中,定時任務(wù)往往通過多個實例運(yùn)行,每個實例都會按照自己的調(diào)度計劃執(zhí)行任務(wù)。例如,假設(shè)有一個定時任務(wù)負(fù)責(zé)同步某個外部系統(tǒng)的數(shù)據(jù)。如果該任務(wù)在多個實例上同時執(zhí)行,就會導(dǎo)致重復(fù)的數(shù)據(jù)同步,進(jìn)而造成數(shù)據(jù)不一致、重復(fù)處理等問題。
典型場景
我們來模擬一個場景,假設(shè)在一個電商系統(tǒng)中,定時任務(wù)負(fù)責(zé)將庫存數(shù)據(jù)同步到第三方庫存管理系統(tǒng)。如果該任務(wù)在多個節(jié)點(diǎn)上同時執(zhí)行,可能會導(dǎo)致:
1、重復(fù)的庫存更新請求。
2、數(shù)據(jù)不同步或數(shù)據(jù)丟失。
3、系統(tǒng)負(fù)載過高,造成性能瓶頸。
使用 ShedLock 解決定時任務(wù)重復(fù)執(zhí)行問題
接下來,博主將通過 Spring Boot 和 ShedLock 來演示如何解決定時任務(wù)的重復(fù)執(zhí)行問題。希望能給大家一個參考!
? 添加依賴
首先確保我們的的 pom.xml 中包含必要的依賴。需要添加 Spring Boot Starter、Spring Scheduling 和 ShedLock 相關(guān)的依賴。
其中博主使用的是Mysql作為ShedLock初始化數(shù)據(jù)庫,引入shedlock-provider-jdbc-template,大家可以根據(jù)自己的實際情況,根據(jù)作者的文檔選擇,如下圖:

<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Scheduler -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-scheduled</artifactId>
</dependency>
<!-- ShedLock Core -->
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>6.2.0</version>
</dependency>
<!-- ShedLock JDBC -->
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>6.2.0</version>
</dependency>
<!-- MySQL JDBC Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>對應(yīng)版本兼容性依賴:
| ShedLock版本 | 最低JVM版本 | Tested with |
|---|---|---|
| 6.x.x | 17 | Spring 6.2, 6.1 Spring Boot 3.4, 3.3 Micronaut 4 |
| 5.x.x | 17 | Spring 6.1, 6.0 Spring Boot 3.4, 3.3, 3.2 Micronaut 3, 4 |
| 4.x.x | 8 | Spring 6.0, 5.3 Spring Boot 3.0, 2.7, 2.6 |
| 3.x.x | 8 | Spring 5.2, 5.1 Spring Boot 2.2, 2.1 |
| 2.x.x | 8 | Spring 5.1, 5.0 Spring Boot 2.1 |
| 1.x.x | 8 | Spring 5.0 Spring Boot 2.0 |
? 配置數(shù)據(jù)庫
ShedLock 需要在數(shù)據(jù)庫中存儲鎖的信息。這里博主使用 MySQL 來存儲鎖。首先,創(chuàng)建一個名為 shedlock 的表來存儲鎖數(shù)據(jù)。
CREATE TABLE shedlock (
name VARCHAR(64) PRIMARY KEY,
lock_until TIMESTAMP(3) NOT NULL,
locked_at TIMESTAMP(3) NOT NULL,
locked_by VARCHAR(255) NOT NULL
);該表的字段意義如下:
- name: 鎖的名稱,用于區(qū)分不同的任務(wù)。
- lock_until: 鎖的過期時間,任務(wù)完成后應(yīng)釋放鎖。
- locked_at: 鎖的創(chuàng)建時間。
- locked_by: 鎖被哪個節(jié)點(diǎn)持有。
? 配置 ShedLock
接下來,我們在 Spring Boot 配置類中進(jìn)行 ShedLock 的配置。以下是一個基本的配置類:
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "30m") //默認(rèn)鎖最大過期時間為 30 秒
public class ShedLockConfig {
}注解說明:
@EnableSchedulerLock: 啟用 ShedLock 功能,defaultLockAtMostFor 參數(shù)指定鎖的最大過期時間。如果任務(wù)執(zhí)行超過此時間,鎖將會被釋放。
@EnableScheduling: 啟用 Spring 的調(diào)度功能。
? 創(chuàng)建定時任務(wù)
接下來我們創(chuàng)建一個定時任務(wù),并為其添加 ShedLock 鎖,確保在分布式環(huán)境中只有一個實例會執(zhí)行該任務(wù)。
package com.example;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class DataSyncService {
/**
* 使用 @SchedulerLock 注解來確保該任務(wù)只會在一個節(jié)點(diǎn)上執(zhí)行
*/
@Scheduled(fixedRate = 5000) // 每 5 秒執(zhí)行一次
@SchedulerLock(name = "syncInventoryTask", lockAtMostFor = "5m", lockAtLeastFor = "5m")
public void syncInventory() {
System.out.println("同步庫存數(shù)據(jù)...");
// 模擬庫存同步操作
try {
Thread.sleep(3000); // 模擬執(zhí)行耗時 3 秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("庫存同步完成!");
}
}上述代碼中,我們在在 syncInventory 方法上,我們使用了 @SchedulerLock 注解,指定鎖的名稱 syncInventoryTask,并設(shè)置鎖的最大持續(xù)時間為 5 秒。
- name: 鎖的名稱,確保每個定時任務(wù)有唯一的名稱。
- lockAtMostFor: 鎖的最大過期時間,防止任務(wù)因為異常而無法釋放鎖。
- lockAtLeastFor: 鎖的最小持續(xù)時間,確保鎖至少保持一定時間。
? 配置數(shù)據(jù)庫連接
最后 application.properties 或 application.yml 配置我們的數(shù)據(jù)庫連接信息
spring.datasource.url=jdbc:mysql://localhost:3306/your_database spring.datasource.username=root spring.datasource.password=your_password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.hibernate.ddl-auto=update
? 啟動應(yīng)用測試
最后我們啟動 Spring Boot 應(yīng)用,查看控制臺輸出。你將會看到 syncInventory 任務(wù)每 5 秒鐘執(zhí)行一次,但只有一個節(jié)點(diǎn)能夠在任何時刻成功執(zhí)行該任務(wù),避免了多節(jié)點(diǎn)并發(fā)執(zhí)行造成的問題。
總結(jié)
相信小伙伴們通過本文的示例,我們演示了如何使用 ShedLock 解決分布式系統(tǒng)中定時任務(wù)重復(fù)執(zhí)行的問題。ShedLock 提供了一種簡單有效的方式來確保在多實例部署的環(huán)境中,定時任務(wù)不會被重復(fù)執(zhí)行,避免了數(shù)據(jù)不一致等問題。
- 應(yīng)用場景: 分布式定時任務(wù)、任務(wù)調(diào)度、數(shù)據(jù)同步等場景。
- 優(yōu)勢: 簡單易用、無需復(fù)雜配置、適配多種存儲方式(如 JDBC、MongoDB、Redis等)
到此這篇關(guān)于Spring Boot 整合 ShedLock 處理定時任務(wù)重復(fù)執(zhí)行的問題的文章就介紹到這了,更多相關(guān)Spring Boot ShedLock 定時任務(wù)重復(fù)執(zhí)行內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談java中為什么重寫equals后需要重寫hashCode
今天帶各位學(xué)習(xí)一下java中為什么重寫equals后需要重寫hashCode,文中有非常詳細(xì)的圖文介紹及代碼示例,對正在學(xué)習(xí)java的小伙伴們有很好的幫助,需要的朋友可以參考下2021-05-05
Java實現(xiàn)DelayQueue延遲隊列示例代碼
Java中的DelayQueue是一個特殊的隊列,它只允許在指定的延遲時間之后才能從隊列中取出元素,這篇文章主要給大家介紹了關(guān)于Java實現(xiàn)DelayQueue延遲隊列的相關(guān)資料,需要的朋友可以參考下2025-07-07
Spring init-method與destroy-method屬性的用法解析
這篇文章主要介紹了Spring init-method與destroy-method屬性的用法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08

