詳細(xì)討論了ShedLock的原理、配置步驟以及在實(shí)際項(xiàng)目中的應(yīng)用場(chǎng)景,為處理復(fù)雜的分布式系統(tǒng)任務(wù)提供了有力支持。
在當(dāng)今的分布式計(jì)算環(huán)境中,協(xié)調(diào)多個(gè)節(jié)點(diǎn)之間的任務(wù)執(zhí)行,確保它們?cè)跊](méi)有沖突或重復(fù)的情況下執(zhí)行,面臨著重大挑戰(zhàn)。無(wú)論是管理周期性任務(wù)、批處理過(guò)程還是關(guān)鍵系統(tǒng)任務(wù),保持同步和一致性對(duì)于無(wú)縫運(yùn)行至關(guān)重要。
問(wèn)題
假設(shè)我們需要按計(jì)劃運(yùn)行某些任務(wù),無(wú)論是數(shù)據(jù)庫(kù)清理任務(wù)還是某些數(shù)據(jù)生成任務(wù)。如果直接解決這個(gè)問(wèn)題,你可以使用Spring Framework中包含的`@Schedules`注解來(lái)解決這個(gè)問(wèn)題。該注解允許您按固定間隔或按cron計(jì)劃運(yùn)行代碼。但是,如果我們的服務(wù)實(shí)例數(shù)量超過(guò)一個(gè)怎么辦?在這種情況下,任務(wù)將在我們的每個(gè)服務(wù)實(shí)例上執(zhí)行。
ShedLock
ShedLock確保您的定時(shí)任務(wù)在同一時(shí)間最多只執(zhí)行一次。該庫(kù)通過(guò)外部存儲(chǔ)實(shí)現(xiàn)鎖。如果一個(gè)任務(wù)在一個(gè)實(shí)例上執(zhí)行,鎖被設(shè)置,所有其他實(shí)例不等待,并跳過(guò)任務(wù)的執(zhí)行。這實(shí)現(xiàn)了“最多執(zhí)行一次”。外部存儲(chǔ)可以是關(guān)系型數(shù)據(jù)庫(kù)(PostgreSQL、MySQL、Oracle等)通過(guò)JDBC工作,NoSQL(Mongo、Redis、DynamoDB)以及許多其他存儲(chǔ)(完整列表可以在項(xiàng)目頁(yè)面上找到)。
讓我們以使用PostgreSQL為例。首先,讓我們使用Docker啟動(dòng)數(shù)據(jù)庫(kù):
docker run -d -p 5432:5432 --name db \ -e POSTGRES_USER=admin \ -e POSTGRES_PASSWORD=password \ -e POSTGRES_DB=demo \ postgres:alpine
現(xiàn)在需要?jiǎng)?chuàng)建一個(gè)鎖表。在項(xiàng)目頁(yè)面上,我們需要找到針對(duì)PostgreSQL的SQL腳本:
CREATE TABLE shedlock( name VARCHAR(64) NOT NULL, lock_until TIMESTAMP NOT NULL, locked_at TIMESTAMP NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name) );
這里:
`name` - 鎖的唯一標(biāo)識(shí)符,通常表示被鎖定的任務(wù)或資源
`lock_until` - 指示持有鎖的結(jié)束時(shí)間的時(shí)間戳
`locked_at` - 指示獲取鎖的時(shí)間戳
`locked_by` - 獲取鎖的實(shí)體的標(biāo)識(shí)符(例如,應(yīng)用程序?qū)嵗?/p>
接下來(lái),創(chuàng)建一個(gè)Spring Boot項(xiàng)目并在`build.gradle`中添加必要的依賴項(xiàng):
implementation 'net.javacrumbs.shedlock:shedlock-spring:5.10.2' implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:5.10.2'
現(xiàn)在描述配置:
@Configuration @EnableScheduling @EnableSchedulerLock(defaultLockAtMostFor = "10m") public class ShedLockConfig { @Bean public LockProvider lockProvider(DataSource dataSource) { return new JdbcTemplateLockProvider( JdbcTemplateLockProvider.Configuration.builder() .withJdbcTemplate(new JdbcTemplate(dataSource)) .usingDbTime() .build() ); } }
讓我們創(chuàng)建一個(gè)`ExampleTask`,每分鐘開始一次并執(zhí)行一些耗時(shí)動(dòng)作。為此,我們將使用`@Scheduled`注解:
@Service public class ExampleTask { @Scheduled(cron = "0 * * ? * *") @SchedulerLock(name = "exampleTask", lockAtMostFor = "50s", lockAtLeastFor = "20s") public void scheduledTask() throws InterruptedException { System.out.println("task scheduled!"); Thread.sleep(15000); System.out.println("task executed!"); } }
在這里,我們使用`Thread.sleep`模擬任務(wù)的執(zhí)行時(shí)間為15秒。一旦應(yīng)用程序啟動(dòng)并任務(wù)執(zhí)行開始,將在數(shù)據(jù)庫(kù)中插入一條記錄。
如果同時(shí),另一個(gè)應(yīng)用程序嘗試運(yùn)行任務(wù),它將無(wú)法獲取鎖并跳過(guò)任務(wù)執(zhí)行:
2024-02-18 08:08:50.057 DEBUG 45988 --- [ scheduling-1] n.j.s.core.DefaultLockingTaskExecutor : Not executing 'exampleTask'. It's locked.
在第一個(gè)應(yīng)用程序獲取鎖時(shí),會(huì)在數(shù)據(jù)庫(kù)中創(chuàng)建一條記錄,該記錄的鎖時(shí)間等于鎖設(shè)置中的`lockAtMostFor`。這個(gè)時(shí)間是必要的,以確保鎖不會(huì)永遠(yuǎn)設(shè)置,以防應(yīng)用程序崩潰或由于某種原因終止(例如,在Kubernetes中從一個(gè)節(jié)點(diǎn)驅(qū)逐Pod到另一個(gè)節(jié)點(diǎn))。成功執(zhí)行任務(wù)后,應(yīng)用程序?qū)⒏聰?shù)據(jù)庫(kù)條目,并將鎖定時(shí)間減少到當(dāng)前時(shí)間,但如果任務(wù)執(zhí)行時(shí)間非常短,則此值不能小于配置中的`lockAtLeastFor`。此值有助于最大程度地減少實(shí)例之間的時(shí)鐘不同步。它確保您的定時(shí)任務(wù)只被同時(shí)執(zhí)行一次。
結(jié)論
ShedLock是協(xié)調(diào)復(fù)雜Spring應(yīng)用程序中任務(wù)的有用工具。它確保任務(wù)順利運(yùn)行且僅運(yùn)行一次,即使跨多個(gè)實(shí)例也是如此。它易于設(shè)置,并為Spring應(yīng)用程序提供了可靠的任務(wù)處理能力,使其成為處理分布式系統(tǒng)的任何人都很有價(jià)值的工具。文章來(lái)源:http://www.zghlxwxcb.cn/article/714.html
項(xiàng)目代碼可在GitHub上找到。https://github.com/vshago/shedlock-demo文章來(lái)源地址http://www.zghlxwxcb.cn/article/714.html
到此這篇關(guān)于分布式任務(wù)同步:在Spring中利用ShedLock的文章就介紹到這了,更多相關(guān)內(nèi)容可以在右上角搜索或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!