目錄
SpringBoot2.x整合輕量級分佈式定時任務ShedLock3.x的使用詳解
四、SpringBoot集成ShedLock(基於JDBC來提供鎖)
前言
前段時間,根據項目要求,需要實現微服務的高可用,即,將某個服務部署多個實例,但是其中涉及到了很多定時任務調度的問題(。。。其他廢話不多說),思來想去,暫定使用了 ShedLock 來實現分佈式定時任務鎖,本篇主要介紹了SpringBoot2.x整合輕量級分佈式定時任務ShedLock3.x的使用詳解。
SpringBoot2.x整合輕量級分佈式定時任務ShedLock3.x的使用詳解
一、關於ShedLock
ShedLock採用非侵入式編程的思想,通過註解的方式來實現相應的功能。ShedLock是一個在分佈式環境中使用的定時任務框架,用於解決在分佈式環境中的多個實例的相同定時任務在同一時間點重複執行的問題,解決思路是通過對公用的數據庫中的某個表進行記錄和加鎖,使得同一時間點只有第一個執行定時任務併成功在數據庫表中寫入相應記錄的節點能夠成功執行而其他節點直接跳過該任務。當然不只是數據庫,目前已經實現的支持數據存儲類型除了經典的關係型數據庫,還包括JDBC、Redis、MongoDB、CosmosDB、DynamoDB、以及分佈式協調服務Zookeeper、還有ElasticSearch等等,是非常的豐富的。
二、ShedLock的三個核心組件
Core - 鎖機制
Integration - 通過Spring AOP、Micronaut AOP或者手寫代碼進行與應用程序的集成
Lock provider - 使用關係型數據庫(RDBMS),非關係型數據庫:Mongo、Redis等外部進程來提供鎖
三、ShedLock使用三步走
1)、啓用並配置Scheduled的鎖
2)、在Scheduled任務上面添加註解
3)、配置鎖的提供者(JDBC、Redis、Mongo等)
四、SpringBoot集成ShedLock(基於JDBC來提供鎖)
1)、在pom.xml中新增引入Spring整合ShedLock依賴包
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>3.0.1</version>
</dependency>
2)、配置LockProvider,這裏使用的JDBC,所以需要配置JDBC的LockProvider,在每個實例對應的數據庫中新建鎖表-shedlock表(請注意名稱須是主鍵)建表語句如下:
CREATE TABLE shedlock(
name VARCHAR(64),
lock_until TIMESTAMP(3) NULL,
locked_at TIMESTAMP(3) NULL,
locked_by VARCHAR(255),
PRIMARY KEY (name)
);
COMMENT ON COLUMN "shedlock"."name" IS '鎖名稱(name必須是主鍵)';
COMMENT ON COLUMN "shedlock"."lock_until" IS '釋放鎖時間';
COMMENT ON COLUMN "shedlock"."locked_at" IS '獲取鎖時間';
COMMENT ON COLUMN "shedlock"."locked_by" IS '鎖提供者';
*注意:基於關係型數據庫(RDBMS)的形式的外部存儲必須創建表結構,而非關係型,例如:Redis等非關係型數據庫形式的外部存儲,template會根據註解 @SchedulerLock 聲明的鎖名稱自動創建對應的鍵值對,以此來提供鎖的實現。
3)、配置LockProvider,關於LockProvider有幾種配置實現,這兒以JDBC爲實現的,
package com.huazai.aiyou.common.config;
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
*
* @author HuaZai
* @contact [email protected]
* <ul>
* @description 配置 LockProvider
* </ul>
* @className ShedlockConfig
* @package com.huazai.aiyou.common.config
* @createdTime 2019年03月20日
*
* @version V1.0.0
*/
@Configuration
public class ShedlockConfig
{
@Bean
public LockProvider lockProvider(DataSource dataSource)
{
return new JdbcTemplateLockProvider(dataSource);
}
}
當然還有更細粒度的配置,通過 Configuration 對象來實現,可以自己去嘗試一下,內容如下:
new JdbcTemplateLockProvider(builder()
.withTableName("shdlck")
.withColumnNames(new ColumnNames("n", "lck_untl", "lckd_at", "lckd_by"))
.withJdbcTemplate(new JdbcTemplate(getDatasource()))
.withLockedByValue("my-value")
.build())
*注意:在ShedLock使用的過程中,千萬不要從數據庫表中手動刪除鎖行或文檔。ShedLock有一個現有鎖的內存緩存,因此在應用程序重新啓動之前不會自動重新創建該行。如果需要,您可以編輯行或文檔,這樣做的風險是隻持有多個鎖。在1.0.0可以通過調用LockProvider上的clearCache()方法來清除緩存。
4)、在項目的啓動類中新增 “ @EnableSchedulerLock ” 註解,開啓ShedLock
package com.huazai.aiyou.controller;
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
*
* @author HuaZai
* @contact [email protected]
* <ul>
* @description 門戶網站Web端啓動類
* </ul>
* @className AiyouApplication
* @package com.huazai.aiyou
* @createdTime 2019年03月20日
*
* @version V1.0.0
*/
@EnableHystrix
@EnableCaching
@EnableDiscoveryClient
@EnableFeignClients
@EnableEurekaClient
@EnableCircuitBreaker
@EnableAutoConfiguration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT20M", defaultLockAtLeastFor = "PT20M")
public class AiyouApplication
{
public static void main(String[] args)
{
SpringApplication.run(AiyouApplication.class, args);
}
}
5)、在需要加鎖的Scheduled任務上添加註解 “ @SchedulerLock ”
package com.huazai.aiyou.controller.task.schedu;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import net.javacrumbs.shedlock.core.SchedulerLock;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
*
* @author HuaZai
* @contact [email protected]
* <ul>
* @description TODO
* </ul>
* @className TaskSchedulerController
* @package com.huazai.aiyou
* @createdTime 2019年03月20日
*
* @version V1.0.0
*/
@RestController
@Slf4j
public class TaskSchedulerController extends BaseController
{
// TODO
/**
*
* @author HuaZai
* @contact [email protected]
* @title sendItemInfoMessage
* <ul>
* @description TODO
* </ul>
* @createdTime 2019年03月20日
* @throws GlobalException
* @return void
*
* @version : V1.0.0
*/
@Scheduled(cron = "0 0 0 * * ?")
@SchedulerLock(name = "sendItemInfoMessage")
public void sendItemInfoMessage() throws GlobalException
{
try
{
// TODO
} catch (Exception e)
{
throw new GlobalException(ExceptionCode.global.ITEM_FAIL, ParamContext.NONE_ITEM_INFO);
}
}
}
參數說明:
@SchedulerLock 作用:只有某個方法上添加了該註解的方法纔會被鎖定,該庫將忽略所有其他計劃任務。且必須爲鎖指定名稱,這樣才能在同一時間只能有一個具有相同名稱的任務執行,以達到預期的鎖的目的。
參數 | 類型 | 描述 |
name | String | 用來標註一個定時服務的名字,被用於寫入數據庫作爲區分不同服務的標識,如果有多個同名定時任務則同一時間點只有一個執行成功。默認爲:"" |
lockAtMostFor | long |
成功執行任務的節點所能擁有獨佔鎖的最長時間,單位是毫秒ms。默認值爲:-1L,表示不生效,當設置爲正整數時才生效。 該屬性主要指定在執行節點死亡的情況下鎖應該保持多長時間。這只是一個應變的計劃,在正常情況下,鎖會在任務完成時釋放,即使節點死亡,過了設定的該時間值也會被釋放,避免死鎖的情況發生。但是必須將lockAtMostFor設置爲一個比正常執行時間長得多的值,具體的多少,這個就需要根據自身對任務的判斷了。如果任務花費的時間與lockAtMostFor時間相比,結果可能是不可預測的(尤其當多個進程將有效地持有鎖時)。 |
lockAtMostForString | String | 成功執行任務的節點所能擁有的獨佔鎖的最長時間的字符串表達式。例如:"PT30S",表示30秒;"PT10M",表示14分鐘。 |
lockAtLeastFor | long |
成功執行任務的節點所能擁有獨佔所的最短時間,單位是毫秒ms。默認值爲:-1L,表示不生效,當設置爲正整數時才生效。 lockAtLeastFor屬性指定應該保留鎖的最小時間值,它的主要目的是爲了解決多個節點執行時任務執行時間短,節點和節點之間的時間差異問題(例如:A節點的時間爲18:00,B節點的時間爲18:08,此時的時間差爲8分鐘,所以這個時候就因爲指定lockAtLeastFor>=8M,確保相同的任務在這個時段內只執行一次,不會超過一次)。 |
lockAtLeastForString | String | 成功執行任務的節點所能擁有的獨佔鎖的最短時間的字符串表達式。例如:"PT30S",表示30秒;"PT10M",表示14分鐘。 |
@EnableSchedulerLock 作用:開啓ShedLock的支持
interceptMode | 默認爲:EnableSchedulerLock.InterceptMode.PROXY_METHOD |
defaultLockAtMostFor | 成功執行任務的節點所能擁有的獨佔鎖的最長時間的字符串表達,例如“PT30S”表示爲30秒 |
defaultLockAtLeastFor | 成功執行任務的節點所能擁有的獨佔鎖的最短時間的字符串表達,例如“PT30S”表示爲30秒,默認爲:"PT0S" |
mode | 默認爲:AdviceMode.PROXY |
proxyTargetClass | 默認爲:false |
*注意:通過設置lockAtMostFor,我們可以確保即使該節點死亡,鎖也會被正常釋放;通過設置lockAtLeastFor,我們可以確保它在指定的時間內不會執行超過一次。請注意,對於執行任務的節點死亡的情況,lockAtMostFor只是能確保節點執行的一個安全範圍,所以在設置這個時間時,這個時間需要遠遠大於最大評估任務的執行時間。如果任務執行花費的時間比設置的lockAtMostFor更長,那麼它可能會再次執行,以至於出現同一個任務重複執行的情況,結果將是不可預測的(導致更多的進程持有鎖)。
五、Spring 集成 ShedLock的兩種模式
ShedLock支持兩種Spring集成模式。一個是使用關於調度方法的AOP代理(PROXY_METHOD),另一個是代理任務調度器(PROXY_SCHEDULER),如下圖:
關於ShedLock的集成模式,一般情況下都是使用的默認值,瞭解一下就可以,這兒就不再寫了,如果有興趣的小夥伴可以去官網查看。
參考文獻:
ShedLock GitHub 相關文檔:https://github.com/lukas-krecan/ShedLock
好了,關於 SpringBoot2.x整合輕量級分佈式定時任務ShedLock3.x的使用詳解 就寫到這兒了,如果還有什麼疑問或遇到什麼問題歡迎掃碼提問,也可以給我留言哦,我會一一詳細的解答的。
歇後語:“ 共同學習,共同進步 ”,也希望大家多多關注CSND的IT社區。
作 者: | 華 仔 |
聯繫作者: | [email protected] |
來 源: | CSDN (Chinese Software Developer Network) |
原 文: | https://blog.csdn.net/Hello_World_QWP/article/details/103710853 |
版權聲明: | 本文爲博主原創文章,請在轉載時務必註明博文出處! |