問題描述:
在集群模式部署服務端時,會出現所有的定時任務在各自的節點處均會執行一遍,這顯然不符合實際的開發場景,針對這種問題,本文給出一種springboot集成shedlock的解決方案
第一步:引入相關包;
<!-- 負載均衡定時任務執行一次 --> <dependency> <groupId>net.javacrumbs.shedlock</groupId> <artifactId>shedlock-spring</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>net.javacrumbs.shedlock</groupId> <artifactId>shedlock-provider-jdbc-template</artifactId> <version>2.2.1</version> </dependency>
注意:因為本公司的項目持久化采用的mysql數據庫,所以引入的是JDBC數據庫進行協調
ShedLock還可以使用Mongo,Redis,Hazelcast,ZooKeeper等外部存儲進行協調,例如使用redis則引入下面的包(只嘗試過jdbc方式):
<dependency> <groupId>net.javacrumbs.shedlock</groupId> <artifactId>shedlock-provider-redis-spring</artifactId> <version>2.5.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
針對不同的協調方式,相關的配置信息可參考https://www.jianshu.com/p/9c6791b617a7
第二步:向數據庫中插入表shedlock;
CREATE TABLE shedlock(
NAME VARCHAR(64),
lock_until TIMESTAMP(3) NULL,
locked_at TIMESTAMP(3) NULL,
locked_by VARCHAR(255),
PRIMARY KEY (NAME)
)
第三步:在application.properties中添加數據庫配置信息
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true spring.datasource.username=root1 spring.datasource.password=root1
第四步:添加配置類
import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; import net.javacrumbs.shedlock.spring.ScheduledLockConfiguration; import net.javacrumbs.shedlock.spring.ScheduledLockConfigurationBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.annotation.EnableScheduling; import javax.sql.DataSource; import java.time.Duration; @Configuration @EnableScheduling public class ShedlockConfig { @Bean public LockProvider lockProvider(DataSource dataSource) { return new JdbcTemplateLockProvider(dataSource); } // @Bean // public TaskScheduler taskScheduler(){ // return new MySpecialTaskScheduler(); // } @Bean public ScheduledLockConfiguration scheduledLockConfiguration(LockProvider lockProvider) { return ScheduledLockConfigurationBuilder .withLockProvider(lockProvider) .withPoolSize(10) .withDefaultLockAtMostFor(Duration.ofMinutes(10)) .build(); } }
第五步:在啟動類上添加啟動注解,否則SchedulerLock不會生效
@EnableSchedulerLock(defaultLockAtMostFor = "PT50S")
第六步:添加@SchedulerLock到定時器業務方法入口
private static final int TWENTY_NINE_MIN = 29 * 60 * 1000;
@Scheduled(cron = "0 */30 * * * ?") @SchedulerLock(name = "scheduledTask", lockAtMostFor = TWENTY_NINE_MIN, lockAtLeastFor = TWENTY_NINE_MIN) public void scheduledTask() { // System.out.println(new Date() + "scheduledTask執行1次"); }
參數解釋:
name屬性:鎖名稱,必須指定,每次只能執行一個具有相同名字的任務,鎖名稱應該是全局唯一的;
lockAtMostFor屬性:設置鎖的最大持有時間,為了解決如果持有鎖的節點掛了,無法釋放鎖,其他節點無法進行下一次任務;
lockAtMostForString屬性:成功執行任務的節點所能擁有的獨占鎖的最長時間的字符串表達,例如“PT14M”表示為14分鍾
lockAtLeastFor屬性:指定保留鎖的最短時間。主要目的是在任務非常短的且節點之間存在時鍾差異的情況下防止多個節點執行。這個屬性是鎖的持有時間。設置了多少就一定會持有多長時間,再此期間,下一次任務執行時,其他節點包括它本身是不會執行任務的
lockAtLeastForString屬性:成功執行任務的節點所能擁有的獨占鎖的最短時間的字符串表達,例如“PT14M”表示為14分鍾