> 文档中心 > 集群下的定时任务解决方案——Redis分布式锁——超级容易理解——附带例子

集群下的定时任务解决方案——Redis分布式锁——超级容易理解——附带例子


集群下的定时任务解决方案——Redis分布式

1. 问题描述

描述:同时运行多个相同的服务A,A中有一个定时任务,我们希望即使多个A服务同时运行时在同一个时间段也只有一个定时任务执行。

2. 解决方案

2.1解决方案一

单独开一个服务来运行定时任务,该服务也不做集群部署。

2.2 解决方案二——Redis分布式锁

首先引入相应的依赖

 <dependencies>  <dependency>     <groupId>redis.clients</groupId>     <artifactId>jedis</artifactId>     <version>3.3.0</version> </dependency>  <dependency>     <groupId>org.redisson</groupId>     <artifactId>redisson</artifactId>     <version>3.15.5</version> </dependency>    </dependencies>

先来看一下Jedis中的SETNX方法,reids分布式锁的也是用的SETNX方法

package com.alpha.redis;import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool;public class JedisUtil {    public static Jedis getJedis(){ JedisPool jedisPool = new JedisPool("127.0.0.1",6379); Jedis resource = jedisPool.getResource(); return resource;    }    public static void close(Jedis jedis){ if( jedis != null){     jedis.close(); }    }}
package com.alpha.redis;import redis.clients.jedis.Jedis;public class RedisTest {    private static int num = 0; public static void main(String[] args) throws InterruptedException {    task(); //等待上面任务执行完毕 Thread.sleep(5000); System.out.println("num的值是:" + num);    }    public static void add(){    //这里我们只做了上锁动作,没有解锁动作,保证只有一个线程能够对num做操作 if(lock()){     num++; }    }    public static boolean lock(){ Jedis jedis = JedisUtil.getJedis(); // 1-创建成功,0-已存在,创建失败,这里没有去模拟一直尝试获取锁,超出某个时间没有获取到再退出 Long lock = jedis.setnx("lock", "1"); JedisUtil.close(jedis); return lock == 1L;    }    /     * 模仿集群下的实际业务代码,假设部署了100个相同的服务,同时对一个num进行加法操作,这个num可能是数据库中的值     */    public static void task(){ for (int i = 0; i < 100; i++) {     new Thread(RedisTest::add).start(); }    }}

运行结果如图,发现即使是1000个线程去执行任务,最终num的值也只是加了1

在这里插入图片描述
但是我们发现如果再执行一次main方法,num的值就不会再变化了。这是因为我们没有删除redis中的lock键,所以我们再执行main方法,num的值就再也不会是1了。但是我们不可能在实际中也只让定时任务只跑一次,就再也没法执行了吧。所以我们要在任务执行完毕之后将lock键释放掉(删除)。那么这里又产生一个问题。

假设有两个相同的服务第一个线程A获取到了锁对num进行了操作之后释放了锁,这个时候另一个服务的线程B刚好在尝试获取锁,那么此时肯定能获取到锁,那么B也就对num执行操作了。这个时候的锁就将两个相同的定时任务变成了串行执行,就不符合我们的要求了。我们的要求是在这段时间内,只能对num执行一次操作。

解决方案:我们在A线程获取到锁之后,不释放锁,而是给锁设定过期时间Time1(这里的锁就是redis中的一个键值对),给线程设置一个时间 Time2,Time2 时间内获取不到锁就不由该线程执行任务。显然 Time1 > Time2,定时任务间隔时间Time3,则有 Time3 > Time1 > Time2,这是针对每次定时任务获取的锁名称都是一致的情况下,防止下一次定时任务运行的时候,锁还被上一个定时任务占用未过期,导致获取不到锁造成这次定时任务全部未执行。每次定时任务获取的锁名称变化,则可以只考虑 Time1 > Time2

这里就可以使用Redission中的锁了,代码如下:

//上面代码中加入如下代码    private static Redisson redisson = null;    static { Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); redisson = (Redisson)Redisson.create(config);    } public static void add02(){ RLock rLock = getRLock(); // 尝试获取锁 ,花费5s去获取锁,获取到返回True,锁10s自动释放。获取不到,返回false if(!rLock.tryLock(5,10,TimeUnit.SECONDS)){     return; } num++;    }    public static RLock getRLock(){ // 生成一个锁对象,此时并不实际在redis中生成锁,但是如果redis中存在该锁,就返回该锁 final RLock lock = redisson.getLock("locklock"); return lock;    } /     * 模仿实际业务代码     */    public static void task02(){ for (int i = 0; i < 1000; i++) {     new Thread(RedisTest::add02).start(); }    }

运行结果如图
集群下的定时任务解决方案——Redis分布式锁——超级容易理解——附带例子
参考文档:

Redis分布式锁-这一篇全了解(Redission实现分布式锁完美方案)