Springboot项目集成Shedlock、Redis

在系统开发的初期,通常都是一个单体的架构,后面随着业务的发展,单体架构已经扛不住业务的压力。慢慢的会向微服务的方向去发展,在多节点的情况下,就会出现多个节点的定时任务可能会重复执行的情况。

1、Shedlock基本介绍

相对于xxl-job,Shedlock的集成更加的轻量,同时也不需要对服务进行过多的改造,我们当时在技术选型的时候就是使用Shedlock。

Shedlock从严格意义上来说,并不是一个分布式任务调度框架,设计的初衷也不是作为一个调度框架,而是一种分布式锁。所谓的分布式锁,解决的核心问题就是各个节点中无法通信的痛点。各个节点并不知道这个定时任务有没有被其他节点的定时器执行,所以理论上只需要有一个各个节点都能够访问到的资源,用这个资源去标记这个定时任务有没有执行就可以了。

Shedlock也有很多种方案:

  • JdbcTemplate
  • Mongo
  • DynamoDB
  • DynamoDB 2
  • ZooKeeper (using Curator)
  • Redis (using Spring RedisConnectionFactory)
  • Redis (using Jedis)
  • Hazelcast
  • Couchbase
  • ElasticSearch
  • CosmosDB
  • Cassandra
  • Multi-tenancy

我们选用的是Redis方式。

2、使用RedisConnectionFactory方式

  1. 在pom文件中,引入以下依赖
            
                net.javacrumbs.shedlock
                shedlock-spring
                4.2.0
            
            
                net.javacrumbs.shedlock
                shedlock-provider-redis-spring
                4.2.0
            
  2. 编写配置类
    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.core.RedisTemplate;
    import org.springframework.scheduling.annotation.EnableScheduling;
    
    @Configuration
    @EnableScheduling
    @EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
    public class ShedlockConfig {
    
        @Bean
        public LockProvider lockProvider(RedisTemplate redisTemplate) {
            return new RedisLockProvider(redisTemplate.getConnectionFactory());
        }
    }
    

    这种是使用的redisTemplate直接获取到ConnectionFactory的写法,如果是老工程的话,可能还要换一种写法,等下会再介绍。

  3. 在定时器方法上加上@SchedulerLock
    package com.xiaohuihui.task;
    
    import net.javacrumbs.shedlock.core.LockAssert;
    import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    @Component
    public class OrderExpireTestTask {
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        /**
         * 通过设置lockAtMostFor,我们可以确保即使节点死亡,锁也会被释放;通过设置lockAtLeastFor,
         * 我们可以确保它在9分钟内不会执行超过一次。请注意,对于执行任务的节点死亡的情况,
         * lockAtMostFor只是一个安全网,所以将它设置为一个时间,这个时间远远大于最大估计执行时间。
         * 如果任务花费的时间比lockAtMostFor更长,那么它可能会再次执行,结果将是不可预测的(更多的进程将持有锁)。
         */
        @Scheduled(cron = "0 0/10 0 * * ? ")
        @SchedulerLock(name = "scheduledTaskName", lockAtMostFor = "9m", lockAtLeastFor = "9m")
        public void sendExpireOrderMsg() {
    
            // To assert that the lock is held (prevents misconfiguration errors)
            LockAssert.assertLocked();
            // do something
            String nowTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
            redisTemplate.opsForValue().set("testTime", nowTime);
    
            System.out.println("==========>  开始发送订单过期消息     " + nowTime);
    
        }
    
    
    }
    

    等定时器执行的时候,就会在redis中看到对应的key,job-lock:default:scheduledTaskName,还可以看到过期时间。

3、使用Jedis方式

  1. 在pom文件中,引入以下依赖
            
                net.javacrumbs.shedlock
                shedlock-spring
                2.2.0
            
            
                net.javacrumbs.shedlock
                shedlock-provider-redis-jedis
                2.2.0
            
  2. 编写配置类
    import net.javacrumbs.shedlock.core.LockProvider;
    import net.javacrumbs.shedlock.provider.redis.jedis.JedisLockProvider;
    import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.annotation.EnableScheduling;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
    
    @Configuration
    @EnableScheduling
    @EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
    public class ShedlockConfig {
        private static final Logger logger = LoggerFactory.getLogger(ShedlockConfig.class);
    
        @Value("${redis.server.host}")
        private String host;
        @Value("${redis.server.port}")
        private Integer port;
        @Value("${redis.server.timeout}")
        private Integer timeout;
        @Value("${redis.server.password}")
        private String password;
        @Value("${redis.server.database}")
        private Integer database;
    
        @Bean
        public LockProvider lockProvider(JedisPoolConfig jedisPoolConfig) {
            JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password, database);
            return new JedisLockProvider(jedisPool);
        }
    
    }
    

    使用的方式跟上面的一种方式是一样的,只不过注解当中的配置会稍稍的有却别,点进源码中,写的也比较清楚。

         Springboot项目集成Shedlock、Redis_第1张图片

4、Shedlock原理

使用AOP对定时器方法进行了代理,对于加了注解方法,会先执行下面的代码。

Springboot项目集成Shedlock、Redis_第2张图片

Springboot项目集成Shedlock、Redis_第3张图片

此时如果向redis中设置值的操作成功(博主的截图中的redis key不一样,忽略,是两个不同的定时器),就会执行定时器中的逻辑。

Springboot项目集成Shedlock、Redis_第4张图片

没有设置成功,就不能够执行,直接打印日志。

在加锁成功的情况下,只要redis中的key没有过期,在这个时间内,这个方法都是不会被执行的。

你可能感兴趣的:(个人总结,分布式,spring,boot,redis)