暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

分布式定时任务轻量级解决方案ShedLock

Onebyte 2021-01-25
1888





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个步骤:

  1. 启用并配置ShedLock,@EnableSchedulerLock;

  2. 使用注解标注调度任务,使用@SchedulerLock注解标注即可;

  3. 配置锁实现。

第一步:启用并配置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中的默认值。

  • lockAtLeastFor属性:任务获取锁后最短持有时间;在任务执行时间很短且节点之间的时钟不同步的情况下,该属性阻止任务在多个节点执行。

第三步:配置锁实现;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/
点击上方“蓝字”,发现更多精彩。
文章转载自Onebyte,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论