目录
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 who.seek.me@java98k.vip
* <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 who.seek.me@java98k.vip
* <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 who.seek.me@java98k.vip
* <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 who.seek.me@java98k.vip
* @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社区。
作 者: | 华 仔 |
联系作者: | who.seek.me@java98k.vip |
来 源: | CSDN (Chinese Software Developer Network) |
原 文: | https://blog.csdn.net/Hello_World_QWP/article/details/103710853 |
版权声明: | 本文为博主原创文章,请在转载时务必注明博文出处! |
csdn-华仔 博客专家 发布了319 篇原创文章 · 获赞 643 · 访问量 148万+ 关注