微信搜索“onebyte”发现更多技术文章。
01 什么是ShedLock
ShedLock利用锁(分布式锁)机制,确保处于多个节点的定时任务只执行一次。注意,ShedLock本身并不是一个分布式定时调度器。
Please note that ShedLock is not and will never be full-fledged scheduler, it's just a lock.
此外,ShedLock是基于时间来实现的分布式锁,它假定每个节点上的时钟已同步。
Moreover, the locks are time-based and ShedLock assumes that clocks on the nodes are synchronized.
02
ShedLock核心组件
Core - 锁机制的支持;
Integration - 使用Spring AOP,Micronaut AOP或自定义开发与应用程序集成;
Lock provider - 基于额外扩展程序(例如SQL数据库,Mongo,Redis等)实现锁机制。
03
如何使用
使用ShedLock非常简单,需遵循以下3个步骤:
启用并配置ShedLock,@EnableSchedulerLock;
使用注解标注调度任务,使用@SchedulerLock注解标注即可;
配置锁实现。
第一步:启用并配置ShedLock;Enable and configure Scheduled locking (Spring)
添加依赖
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>4.12.0</version>
</dependency>
启用,其中@EnableScheduling开启Spring对任务调度的支持
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "30s")
public class ShedLockConfig {
...}
第二步:使用注解标注调度任务;Annotate your scheduled tasks
import net.javacrumbs.shedlock.core.SchedulerLock;
@Scheduled(cron = "0 */2 * * * *")
@SchedulerLock(name = "scheduledTaskName", lockAtMostFor = "2s", lockAtLeastFor = "30s")
public void scheduledTask() {
// do something}
@SchedulerLock注解说明:
标识调度任务以被ShedLock识别,并执行代理(获取锁后再执行任务);
name属性:必须指定,ShedLock保证具有相同name的定时任务同一时刻仅执行一次;
lockAtMostFor属性:任务获得锁后的最长持有时间;在正常情况下,任务执行完毕后会立即释放锁,这里的时间设置防止程序无法正常释放锁导致死锁。此外,lockAtMostFor设置的时间务必大于任务的执行时间,否则可能存在多个线程持有该锁,不能保证任务执行结果的正确性。如果未在@SchedulerLock中指定lockAtMostFor,则将使用@EnableSchedulerLock中的默认值。
第三步:配置锁实现;Configure LockProvider
官方支持如下组件的锁实现,这里我们选用Redis,Redis (using Spring RedisConnectionFactory);
Lock Providers
JdbcTemplate
Mongo
DynamoDB
DynamoDB 2
ZooKeeper (using Curator)
Redis (using Spring RedisConnectionFactory)
Redis (using Jedis)
Hazelcast
Couchbase
ElasticSearch
CosmosDB
Cassandra
Consul
ArangoDB
Etcd
Multi-tenancy
添加Redis依赖,这里我们以SpringBoot为例:
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- commons-pool2,为redis提供连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-redis-spring</artifactId>
<version>4.12.0</version>
</dependency>
配置lockProvider:
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider;
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
/**
* @ClassName ShedLockConfiguration
* @Author yangqk */
@Configuration
@EnableSchedulerLock(defaultLockAtMostFor = "30s")
public class ShedLockConfig {
@Bean
public LockProvider lockProvider(RedisConnectionFactory redisConnectionFactory) {
return new RedisLockProvider(redisConnectionFactory);
}}
注意:Redis实现的锁在哨兵或者集群模式下,发生主从故障时候,将存在不可靠的问题。04
ShedLock的原理简析
ShedLock基于代理实现对任务调度的控制。在执行任务前,会先获取锁,拿到锁的任务才能执行。如下图所示:
与Spring整合的2种代理模式
一种基于aop对ScheduledMethod进行代理(PROXY_METHOD),另一种是基于aop对TaskScheduler进行代理(PROXY_SCHEDULER)。
代理模式一:Scheduled Method proxy
从4.0.0版本开始,默认的代理方式。
这种模式的主要优点是,它与希望以某种方式更改默认Spring调度机制的其他框架很好地配合使用。缺点是即使直接调用该方法也需要先获取锁。如果该方法返回一个值并且该锁由另一个进程持有,则将返回null或空的Optional对象(不支持原始返回类型)。
注意:Final和non-public的方法无法执行代理,必须将其改为public的,或者使用TaskScheduler代理。
代理模式二:TaskScheduler proxy
这种模式将Spring TaskScheduler包装在AOP代理中。其开启方式(PROXY_SCHEDULER是4.0.0之前的默认方法):
@EnableSchedulerLock(interceptMode = PROXY_SCHEDULER)
如果在spring容器内没有发现TaskScheduler的实例,则会为其创建一个默认的实例。如果你有特殊需要,只需创建一个实现TaskScheduler接口的bean,它将自动包装到AOP代理中。如下代码的MySpecialTaskScheduler既是自定义的实现:
@Bean
public TaskScheduler taskScheduler() {
return new MySpecialTaskScheduler();}
此种代理方式的原理示意图如下:
总结:ShedLock的使用比较简单,但并不是分布式调度任务的完美解决方案;对于追求分布式调度任务高可用、强一致性的的系统来说,建议选型更为优秀的解决方案。
05
引用
[1] https://github.com/lukas-krecan/ShedLock
[2] https://www.baeldung.com/shedlock-spring
[3] https://rieckpil.de/lock-scheduled-tasks-with-shedlock-and-spring-boot/
点击上方“蓝字”,发现更多精彩。