集群下的定时任务解决方案——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分布式锁-这一篇全了解(Redission实现分布式锁完美方案)