javaweb集群實現定時任務搶占任務鎖

2021-09-22 07:52:23 字數 3744 閱讀 2740

1、背景

資料庫中存在乙個預約時間表(t_reserve),和乙個正在生效的時間表(t_time)

根據業務需求,在t_reserve表中配置多個不同的時間,然後定時去更新t_time表

2、問題

因為應用是集群部署,需要考慮

1)、多程序的任務鎖搶占

2)、當搶占到任務鎖的應用掛了,宕機的情況,沒有釋放任務鎖,造成死鎖

3)、集群部署,無法保證每台應用同時啟動定時任務,多個定時任務的觸發點會被錯開,定時任務的間隔執行時間無法保證

3、實現

需要設計乙個定時計畫表(t_job)

job_name

job__status

start_time

end_time

interval

任務執行狀態

0-未執行

1-執行中

開始時間

結束時間

任務鎖過期時間

job0

603.1)、任務鎖的實現(列舉當時想到的三種實現)

3.1.1)、本來打算使用forupdate實現悲觀鎖

a)、查詢t_job,看是否有任務,任務是否在執行中

select

*from t_job where job_name=

'job' forupdate;

#行鎖,會鎖住t_job表的job這一行

forupdate鎖表規則:

當有明確指定唯一行(如主鍵),那麼就是行鎖,其他行的資料,別的事務還是可以操作該錶的

指定行不明確(如不等於某個主鍵值,會返回好多資料),那麼就是表鎖了,這時候鎖了整個表,其他事務無法操作該錶

b)、這兒又需要分兩種情況 肯定會更新成功,悲觀鎖,同個只有同個時間可以操作

b.1)、當前查詢到沒有任務在執行

直接更新t_job表,將job_status置為1,表示搶占到任務鎖

update t_job set job_status=

1, start_time=

now(

)where job_name=

'job'

and job_status=

0and end_time=

'上一步查詢結果'

;

b.2)、當前查詢到任務在執行中

(這種情況需要注意判斷是否鎖沒有釋放掉,如果沒有釋放掉死鎖,會導致之後的定時任務無法執行(包括其他應用))

需要自己設定乙個任務鎖過期值,到時間了,直接就把任務鎖釋放掉,會有一可能就是當你定時任務跑的時間太長了,導致應用誤判為死鎖,所以這個過期值需要考慮,取乙個合適值

update t_job set job_status=

1, start_time=

now(

)where job_name=

'job'

and job_status=

1and timestampdiff(

second

,end_time,

now())

>=

interval

;

b.3)、如果update執行成功,則代表成功的獲取任務鎖,其他情況都是沒有獲取到

c)、正在實現業務邏輯

d)、釋放任務鎖

update t_job set job_status=0,

, end_time=

now(

)where job_name=

'job'

and job_stauts=

0and end_time=

'上一步查詢結果'

;

為什麼釋放任務鎖時做更新操作,where要使用end_time?

因為必須控制版本,只能釋放當前任務獲取到的任務鎖(誤判死鎖,其實本身任務還在執行,可能由於某個原因導致執行時間過長,超過任務鎖的過期值)

注:這兒還會有乙個問題,就是在select … from t_job where … forupdate鎖表的時候,因為是悲觀鎖,其他事務無法訪問到t_job表的這一行,所以會拋異常,需要進行異常捕獲

3.1.2)、使用悲觀鎖,將獲取任務鎖、邏輯處理、釋放任務鎖放在同乙個事務中(常見的一種處理方式)

a)、獲取任務鎖

update t_job set job_status=

1where job_name=

'job'

and job_status=

0;

b)、邏輯處理

c)、釋放任務鎖

update t_job set job_status=

0where job_name=

'job'

and job_status=

1;

也是存在行鎖,其他事務無法操作t_job表該行資料

因為在同個事務中,當發生異常,則該任務的所有資料庫操作都會被回滾,包括t_job表的job_status,也會被回滾成0,為未執行狀態。執行成功,則事務會自動提交所有的資料庫操作

優點不會造成死鎖

缺點如果邏輯處理時間太長,事務沒有釋放,可能會導致其他邏輯不能操作對應的業務表

3.1.3)、使用樂觀鎖。主要是對3.1.1的優化,不用forupdate來鎖表,直接查詢t_job的任務狀態後,去更新t_job表,更新成功則獲取到任務鎖,更新失敗則獲取不到(可能被其他應用先更新t_job表了,所以更新失敗了)

3.2)、多程序(應用)的任務鎖搶占、死鎖的釋放,3.1已經有了三種方案。但是還是存在了乙個問題,集群部署的時候,無法保證啟動時間的相同,這樣就會存在定時任務啟動的時間差問題

例如在程式中實現了乙個定時任務,隔間是1分鐘執行一次,部署在a、b兩台機器上

a應用在50分11秒啟動,定時任務也啟動了

b應用在50分31秒啟動,定時任務也啟動了

這時候a的定時任務執行時間是 50分11秒 51分11秒 52分11秒 53分11秒

這時候b的定時任務執行時間是 50分31秒 51分31秒 52分31秒 53分31秒

這樣的話,定時任務的執行間隔,就不是1分鐘了,a執行一次,30秒後b執行一次,30秒後a又執行一次(真實場景時間不確定,無法把控),任務鎖的搶占設計也就沒有意義了。

為了保證定時任務在多台應用的執行頻率相同,可以去控制任務鎖獲取的間隔時間

解決方案:

3.2.1)、在update語句的where中加上條件timestampdiff(second,end_time,now())>=60

才能去獲取鎖

3.2.2)、可以先查詢t_job,然後應用再根據查詢出來的end_time和當前時間比較,看是否超過間隔時間1分鐘,超過了間隔時間,則更新t_job獲取鎖

3.3)、定時任務隨著應用發布而啟動

spring***可以監聽下面的幾種事件,也可以監聽自定義事件。然後去實現對應動作

java web定時任務

cron表示式由6或7個由空格分隔的時間字段組成,如表1所示 表1 cron表示式時間字段 位置時間網域名稱 允許值允許的特殊字元 秒0 59 分鐘0 59 小時0 23 日期1 31 l w c 月份1 12 星期1 7 l c 年 可選 空值1970 2099 cron表示式的時間欄位除允許設定...

java web定時任務 quartz

寫在前面 前面有簡單的記錄下timer定時的用法,但是在此次專案中,選擇的是quartz來完成定時操作任務的。兩者都可以完成定時操作,但是spring可以整合quartz,並且配置起來也比較簡便,還可以同時跑多個任務。就選擇了quartz,quartz的用法也很強大,這裡也是簡單的記錄下。第一步還是...

Spring boot定時任務集群

該註解用在 scheduled的方法上,可選的,有以下屬性和作用 屬性必填說明 id否自定義任務id,同乙個時間段內同乙個id的任務只有乙個能執行成功 description 否任務描述 ignore 否是否忽略集群控制,作用跟level 1一樣,但只針對該任務 scheduledcluster i...