spring 分布式鎖 SchedulerLock

(spring 分布式鎖 SchedulerLock,整合redis沖突慨菱、jdbc異常問題)

簡(jiǎn)介

ShedLock的作用焰络,確保任務(wù)在同一時(shí)刻最多執(zhí)行一次。如果一個(gè)任務(wù)正在一個(gè)節(jié)點(diǎn)上執(zhí)行符喝,則它將獲得一個(gè)鎖闪彼,該鎖將阻止從另一個(gè)節(jié)點(diǎn)(或線程)執(zhí)行同一任務(wù)。如果一個(gè)任務(wù)已經(jīng)在一個(gè)節(jié)點(diǎn)上執(zhí)行协饲,則在其他節(jié)點(diǎn)上的執(zhí)行不會(huì)等待备蚓,只需跳過它即可 课蔬。
ShedLock使用Mongo,JDBC數(shù)據(jù)庫(kù)郊尝,Redis,Hazelcast战惊,ZooKeeper或其他外部存儲(chǔ)進(jìn)行協(xié)調(diào)流昏,即通過外部存儲(chǔ)來實(shí)現(xiàn)鎖機(jī)制。

用法

  • 啟用和配置計(jì)劃的鎖定
  • 注釋您的計(jì)劃任務(wù)
  • 配置鎖提供者

此處以jdbc數(shù)據(jù)庫(kù)方式為例吞获,如需要使用其他外部存儲(chǔ)方式况凉,對(duì)應(yīng)修改shedlock-provider。

啟用和配置計(jì)劃的鎖定
  • 導(dǎo)入schedulerLock項(xiàng)目

gradle方式

// shedlock 項(xiàng)目 , 2.1.0穩(wěn)定版
compile('net.javacrumbs.shedlock:shedlock-provider-jdbc-template:2.1.0')
// JDBC 各拷,匹配shedlock的2.2.0版
compile('net.javacrumbs.shedlock:shedlock-spring:2.2.0')

Maven方式

// shedlock 項(xiàng)目 , 2.1.0穩(wěn)定版
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
    <version>2.1.0</version>
</dependency>
// JDBC 刁绒,匹配shedlock的2.2.0版
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-jdbc-template</artifactId>
    <version>2.2.0</version>
</dependency>
  • 啟用SchedulerLock

    使用@EnableSchedulerLock注釋將庫(kù)集成到Spring中。


import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
// 開啟定時(shí)任務(wù)注解
@EnableScheduling
// 開啟定時(shí)任務(wù)鎖烤黍,默認(rèn)設(shè)置鎖最大占用時(shí)間為30s
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
@MapperScan(basePackages="cn.pilipa.finance.salary.persistence")
@ServletComponentScan
public class SalaryApplication {

    public static void main(String[] args) {
        SpringApplication.run(SalaryApplication.class, args);
    }
}
注釋計(jì)劃任務(wù)
  • 在定時(shí)任務(wù)上添加@SchedulerLock注解
import net.javacrumbs.shedlock.core.SchedulerLock;

...

@Scheduled(...)
@SchedulerLock(name = "scheduledTaskName")
public void scheduledTask() {
   // do something
}
配置鎖提供者
  • 創(chuàng)建提供鎖的外部存儲(chǔ)(jdbc知市、redis、MongoDB...)
    例 : JDBC 速蕊,創(chuàng)建表結(jié)構(gòu)
CREATE TABLE shedlock(
    name VARCHAR(64) , 
    lock_until TIMESTAMP(3) NULL, 
    locked_at TIMESTAMP(3) NULL, 
    locked_by  VARCHAR(255), 
    PRIMARY KEY (name)
) 

說明

屬性 說明
name 鎖名稱 嫂丙,name必須是主鍵
lock_until 釋放鎖時(shí)間
locked_at 獲取鎖時(shí)間
locked_by 鎖提供者

DB形式的外部存儲(chǔ)需要?jiǎng)?chuàng)建表結(jié)構(gòu),redis等非結(jié)構(gòu)形式的外部存儲(chǔ)template會(huì)根據(jù)@SchedulerLock聲明的鎖名稱自動(dòng)創(chuàng)建對(duì)應(yīng)的鍵值對(duì)规哲,提供鎖跟啤。

  • 配置Bean
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
...
@Bean
public LockProvider lockProvider(DataSource dataSource) {
    return new JdbcTemplateLockProvider(dataSource);
}

SchedulerLock 參數(shù)

  • @SchedulerLock
    只有帶注釋的方法被鎖定,庫(kù)忽略所有其他計(jì)劃的任務(wù)唉锌。您還必須指定鎖的名稱隅肥。同一時(shí)間只能執(zhí)行一個(gè)任務(wù)。
  • name
    分布式鎖名稱袄简,注意 鎖名稱必須唯一腥放。
  • lockAtMostFor & lockAtMostForString
    指定在執(zhí)行節(jié)點(diǎn)死亡時(shí)應(yīng)將鎖保留多長(zhǎng)時(shí)間。這只是一個(gè)備用選項(xiàng)痘番,在正常情況下捉片,任務(wù)完成后立即釋放鎖定。 您必須將其設(shè)置lockAtMostFor為比正常執(zhí)行時(shí)間長(zhǎng)得多的值汞舱。如果任務(wù)花費(fèi)的時(shí)間超過 lockAtMostFor了所導(dǎo)致的行為伍纫,則可能無法預(yù)測(cè)(更多的進(jìn)程將有效地持有該鎖)。
    lockAtMostFor 單位 毫秒
    lockAtMostForString 使用“ PT14M” 意味著它將被鎖定不超過14分鐘昂芜。
  • lockAtLeastFor & lockAtLeastForString
    該屬性指定應(yīng)保留鎖定的最短時(shí)間莹规。其主要目的是在任務(wù)很短且節(jié)點(diǎn)之間的時(shí)鐘差的情況下,防止從多個(gè)節(jié)點(diǎn)執(zhí)行泌神。

例:假設(shè)您有一個(gè)任務(wù)良漱,每15分鐘執(zhí)行一次舞虱,通常需要花費(fèi)幾分鐘才能運(yùn)行。此外母市,您希望每15分鐘最多執(zhí)行一次矾兜。在這種情況下,您可以像這樣配置

@Scheduled(fixedDelay = 1000*60*15)
    @SchedulerLock(name = "queryRechargeBill", lockAtMostFor = 1000*60*15, lockAtLeastFor = 1000*60*5)
    public void queryRechargeBill(){
    // do something
}

該鎖將持有5分鐘患久,5分鐘釋放椅寺,當(dāng)節(jié)點(diǎn)異常或者死亡蒋失,該鎖默認(rèn)在15分鐘后自動(dòng)釋放返帕。在正常情況下,ShedLock在任務(wù)完成后立即釋放鎖定篙挽。實(shí)際上荆萤,我們不必這樣做,因?yàn)锧EnableSchedulerLock中提供了默認(rèn)值铣卡, 但我們選擇在此處覆蓋它链韭。

版本沖突問題

  • jdbc外部存儲(chǔ)實(shí)現(xiàn) 啟動(dòng)報(bào)錯(cuò)
    nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.data.redis.connection.RedisConnectionFactory' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
    該問題是因?yàn)轫?xiàng)目中的redis自定義bean造成的,使用的redis jar(org.springframework.data:spring-data-redis)算行,造成版本沖突梧油。解決方法:
    更換redis庫(kù)
compile('org.springframework.boot:spring-boot-starter-parent:2.0.2.RELEASE')
compile('org.springframework.boot:spring-boot-starter-data-redis')
  • redis 外部存儲(chǔ)實(shí)現(xiàn) 啟動(dòng)報(bào)錯(cuò)
    shedlock-provider-redis-spring 與 org.springframework.data:spring-data-redis 沖突,
    java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[BLorg/springframework/data/redis/core/types/Expiration;Lorg/springframework/data/redis/connection/RedisStringCommands$SetOption;)Ljava/lang/Boolean;

    解決方法州邢,更換低版本的 shedlock儡陨,這里我選擇的是1.8.2版本

    • 如果您依賴spring-data-redis 2-請(qǐng)使用ShedLock 1.xx
    • 如果您依賴spring-data-redis 1-請(qǐng)使用ShedLock 0.xx
    • 在所有其他情況下,您可以使用兩個(gè)版本量淌,最好是1.xx
  • 要求和依賴性
    Java 8
    SLF4J的API
    Spring框架(可選)

故障排除

  1. 檢查存儲(chǔ)骗村。如果使用的是JDBC,請(qǐng)檢查ShedLock表呀枢。如果為空胚股,則未正確配置ShedLock。如果有多個(gè)同名記錄裙秋,則您缺少主鍵琅拌。
  2. 使用ShedLock調(diào)試日志。ShedLock使用記錄器名稱在DEBUG級(jí)別記錄有趣的信息net.javacrumbs.shedlock摘刑。它應(yīng)該可以幫助您了解正在發(fā)生的事情进宝。
  3. 對(duì)于短期任務(wù),請(qǐng)考慮使用lockAtLeastFor枷恕。如果任務(wù)是短期運(yùn)行的党晋,它們可以一個(gè)接一個(gè)地執(zhí)行,lockAtLeastFor可以防止執(zhí)行。
  4. 如果遇到奇怪的錯(cuò)誤未玻,抱怨代理不是proxy類灾而,ThreadPoolTaskScheduler請(qǐng)檢查https://github.com/lukas-krecan/ShedLock/issues/115或 此StackOverflow問題

總結(jié)

ShedLock支持兩種模式的Spring集成,分別是 預(yù)定方法代理扳剿、TaskScheduler代理旁趟。我們默認(rèn)選擇最簡(jiǎn)單也是最實(shí)用的方式:預(yù)定方法代理(即 @SchedulerLock 的形式),ShedLock會(huì)圍繞每個(gè)帶有@SchedulerLock注釋的方法創(chuàng)建AOP代理舞终。這種方法的主要優(yōu)點(diǎn)是它不依賴于Spring調(diào)度轻庆。缺點(diǎn)是即使直接調(diào)用該方法也會(huì)應(yīng)用鎖定。還應(yīng)注意敛劝,當(dāng)前僅支持返回void的方法,如果您注釋并調(diào)用具有非void返回類型的方法纷宇,則會(huì)引發(fā)異常夸盟。

預(yù)定方法代理

預(yù)定方法代理 流程圖

由于大多數(shù)項(xiàng)目都依賴redis緩存,此處建議使用jdbc方式提供外部存儲(chǔ)像捶,可以避免很多版本間互相沖突的問題上陕。如果對(duì)TaskScheduler代理方式有興趣,可以參考源碼拓春。
ShedLock源碼庫(kù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末释簿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子硼莽,更是在濱河造成了極大的恐慌庶溶,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件懂鸵,死亡現(xiàn)場(chǎng)離奇詭異偏螺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)匆光,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門套像,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人终息,你說我怎么就攤上這事夺巩。” “怎么了周崭?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵柳譬,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我休傍,道長(zhǎng)征绎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮人柿,結(jié)果婚禮上柴墩,老公的妹妹穿的比我還像新娘。我一直安慰自己凫岖,他們只是感情好江咳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著哥放,像睡著了一般歼指。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上甥雕,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天踩身,我揣著相機(jī)與錄音,去河邊找鬼社露。 笑死挟阻,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的峭弟。 我是一名探鬼主播附鸽,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼瞒瘸!你這毒婦竟也來了坷备?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤情臭,失蹤者是張志新(化名)和其女友劉穎省撑,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谎柄,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡丁侄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了朝巫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸿摇。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖劈猿,靈堂內(nèi)的尸體忽然破棺而出拙吉,到底是詐尸還是另有隱情,我是刑警寧澤揪荣,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布筷黔,位于F島的核電站,受9級(jí)特大地震影響仗颈,放射性物質(zhì)發(fā)生泄漏佛舱。R本人自食惡果不足惜椎例,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望请祖。 院中可真熱鬧订歪,春花似錦、人聲如沸肆捕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)慎陵。三九已至眼虱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間席纽,已是汗流浹背捏悬。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留润梯,地道東北人邮破。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像仆救,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子矫渔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355