描述:同时运行多个相同的服务A,A中有一个定时任务,我们希望即使多个A服务同时运行时在同一个时间段也只有一个定时任务执行。
2. 解决方案 2.1解决方案一单独开一个服务来运行定时任务,该服务也不做集群部署。
2.2 解决方案二——Redis分布式锁首先引入相应的依赖
<dependencies>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.3.0version>
dependency>
<dependency>
<groupId>org.redissongroupId>
<artifactId>redissonartifactId>
<version>3.15.5version>
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实现分布式锁完美方案)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)