分布式锁核心需求
redis分布式锁常见场景
redis分布式锁方案设计与实现
分布式锁核心需求
互斥性
同一时刻只能有一个客户端加锁,不可出现多个客户端同时持有锁的情况
防止死锁
防止一台机器出现 宕机,没有释放锁,导致其他机器无法加锁的情况。此处可通过锁超时机制来实现,给锁设置超时时间,超过某个时长则自动释放锁。
高性能
分布式锁应该具备高并发的能力,对于访问量大的资源,需要考虑减少锁等待的时间,减少线程阻塞的情况。故在锁设计考虑:
1、粒度尽可能小:加锁尽量到最细的业务维度。比如牌价信息,可加锁到具体产品id上
2、锁范围尽可能小:加锁lock和unlock内部核心代码控制在有效范围内
可重入性
类似 ReentrantLock的可重入锁,其特点是:同一个线程可以重复拿到同一资源的锁,有利于资源的高效利用。
redis分布式锁常见场景
基于服务级别的分布式锁
即多机器启动后,同时抢锁;一台机器抢到锁,其它机器不断等待,如果持有锁的机器宕机,其它机器重新抢锁,知道有一台抢到为止。
基于用户级别的分布式锁
用户在做交易的过程当中,同一用户在同一时刻做交易(此交易会引起资金变动);此时只能让每笔交易通过同步方式一笔一笔处理完成,不可同时处理。
redis分布式锁方案设计
redisson处理redis分布式锁分布式锁
解决基于服务级别的分布式锁
设计原理:
加锁机制
线程去获取锁,获取成功: 执行lua脚本,保存数据到redis数据库。
线程去获取锁,获取失败: 一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis数据库。
watch dog自动延期机制
在一个分布式环境下,假如一个线程获得锁后,突然服务器宕机了,那么这个时候在一定时间后这个锁会自动释放,你也可以设置锁的有效时间(不设置默认30秒),这样的目的主要是防止死锁的发生。
可重入加锁机制
redisson可以实现可重入加锁机制。
代码实现:
引入依赖:
配置服务:单机配置,也可配置集群
spring:
redis:
host: 118.190.173.250
port: 6379
password: zyj
逻辑实现:
1、加锁逻辑:
//redisson默认就是加锁30秒,建议也是30秒以上,默认的lockWatchdogTimeout会每隔10秒观察一下,
// 待到20秒的时候如果主进程还没有释放锁,就会主动续期30秒
// a、lock.tryLock(5, 30, TimeUnit.SECONDS); 有锁等待5s,加锁延迟一次30s,只延迟一次
// b、lock.tryLock(5, TimeUnit.SECONDS); 有锁等待5s
// c、lock.lock();
//a、b、c三种方式的结果是用a方式没有实现lockwatchdong机制,用c机制类似于普通加锁,无加锁等待,用c机制有实现lockwatchdog机制
public boolean lock(Object obj, String lockKey, String requestId, int expireTime) {
RedissonClient redissonClient= (RedissonClient) obj;
RLock lock = redissonClient.getLock(lockKey);
System.out.println(Thread.currentThread()+"==========是否已加锁1:"+lock.isLocked());
boolean b=false;
try {
//有锁等待5s,加锁延迟一次30s
// b = lock.tryLock(5, 30, TimeUnit.SECONDS);
//有锁等待5s
b = lock.tryLock(5,TimeUnit.SECONDS);
System.out.println(Thread.currentThread()+"加锁操作=====加锁结果:"+b+"=====是否已加锁2:"+lock.isLocked());
while(!b){
System.out.println(Thread.currentThread()+"==========已被加锁,重新开始获取:"+b);
//等待3s后重新加锁
b = lock.tryLock(5,TimeUnit.SECONDS);
}
System.out.println(Thread.currentThread()+"==========获取锁结果:"+b);
}catch (Exception e){
lock.unlock();
}
return b;
}
2、解锁逻辑
public boolean unLock(Object obj, String lockKey, String requestId) {
RedissonClient redissonClient= (RedissonClient) obj;
RLock lock = redissonClient.getLock(lockKey);
if(lock.isLocked()){
lock.unlock();
return true;
}
return false;
}
3、业务测试:
@RequestMapping("/redisson/{id}")
public long testRedisDemo(@PathVariable String id ) throws InterruptedException {
long startTime=System.currentTimeMillis();
boolean lock= redisLockService.lock(redissonClient,id,id,3000);
System.out.println(Thread.currentThread()+"===========锁定redis:"+lock);
System.out.println(Thread.currentThread()+"==========处理业务逻辑===等待3s开始========");
List
list.add("test1");
list.add("test2");
RSortedSet
if(null==sortedSet||sortedSet.size()==0){
sortedSet.addAll(list);
}
System.out.println(Thread.currentThread()+"==========获取有序集合========:"+sortedSet);
TimeUnit.SECONDS.sleep(50);
System.out.println(Thread.currentThread()+"==========处理业务逻辑===等待3s结束========");
boolean unLock= redisLockService.unLock(redissonClient,id,id);
System.out.println(Thread.currentThread()+"===========解锁redis:"+unLock);
long endTime=System.currentTimeMillis();
return (endTime-startTime);
}
4、测试结果: 线程1,5和线程2,5 。线程1,5线抢到锁,线程2,5 进入等待;直至线程1,5释放锁后,线程2,5 持有锁
Thread[http-nio-18081-exec-1,5,main]==========是否已加锁1:false
Thread[http-nio-18081-exec-1,5,main]加锁操作=====加锁结果:true=====是否已加锁2:true
Thread[http-nio-18081-exec-1,5,main]==========获取锁结果:true
Thread[http-nio-18081-exec-1,5,main]===========锁定redis:true
Thread[http-nio-18081-exec-1,5,main]==========处理业务逻辑===等待3s开始========
Thread[http-nio-18081-exec-1,5,main]==========获取有序集合========:[test1, test2]
Thread[http-nio-18081-exec-2,5,main]==========是否已加锁1:true
Thread[http-nio-18081-exec-2,5,main]加锁操作=====加锁结果:false=====是否已加锁2:true
Thread[http-nio-18081-exec-2,5,main]==========已被加锁,重新开始获取:false
Thread[http-nio-18081-exec-2,5,main]==========已被加锁,重新开始获取:false
Thread[http-nio-18081-exec-2,5,main]==========已被加锁,重新开始获取:false
Thread[http-nio-18081-exec-2,5,main]==========已被加锁,重新开始获取:false
Thread[http-nio-18081-exec-2,5,main]==========已被加锁,重新开始获取:false
Thread[http-nio-18081-exec-1,5,main]==========处理业务逻辑===等待3s结束========
Thread[http-nio-18081-exec-1,5,main]===========解锁redis:true
Thread[http-nio-18081-exec-2,5,main]==========获取锁结果:true
Thread[http-nio-18081-exec-2,5,main]===========锁定redis:true
Thread[http-nio-18081-exec-2,5,main]==========处理业务逻辑===等待3s开始========
Thread[http-nio-18081-exec-2,5,main]==========获取有序集合========:[test1, test2]
Thread[http-nio-18081-exec-2,5,main]==========处理业务逻辑===等待3s结束========
Thread[http-nio-18081-exec-2,5,main]===========解锁redis:true
jedis处理redis分布式锁
解决基于用户级别的分布式锁
1、引入依赖:
2、逻辑实现:加锁、解锁
public class JedisLockServiceImpl implements RedisLockService {
//返回结果
private static final String LOCK_SUCCESS = "OK";
//是否存在
private static final String SET_IF_NOT_EXIST = "NX";
//过期时间:px毫秒 ex秒
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static final Long RELEASE_SUCCESS = 1L;
@Override
public boolean lock(Object obj, String lockKey, String requestId, int expireTime) {
Jedis jedis= (Jedis) obj;
String result =jedis.set(lockKey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,3000);
if(LOCK_SUCCESS.equals(result)){
return true;
}
return false;
}
@Override
public boolean unLock(Object obj, String lockKey, String requestId) {
Jedis jedis= (Jedis) obj;
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
System.out.println("=========:"+result);
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
3、业务测试
@RequestMapping("/jedis/{id}")
public long testDemo(@PathVariable String id ) throws InterruptedException {
long startTime=System.currentTimeMillis();
jedis.auth("zyj");
boolean lock= jedisLockService.lock(jedis,"redis",id,3000);
System.out.println(Thread.currentThread()+"===========锁定redis:"+lock);
System.out.println(Thread.currentThread()+"==========处理业务逻辑===等待3s开始========");
jedis.zadd("id",10,id);
Set
System.out.println(Thread.currentThread()+"==========获取有序集合========:"+sets);
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread()+"==========处理业务逻辑===等待3s结束========");
boolean unLock= jedisLockService.unLock(jedis,"redis",id);
System.out.println(Thread.currentThread()+"===========解锁redis:"+unLock);
long endTime=System.currentTimeMillis();
return (endTime-startTime);
}
4、测试结果:线程9,5和线程10,5 。线程9,5线抢到锁,线程10,5 进入等待;直至线程9,5释放锁后,线程10,5 持有锁
Thread[http-nio-18081-exec-9,5,main]===========锁定redis:true
Thread[http-nio-18081-exec-9,5,main]==========处理业务逻辑===等待3s开始========
Thread[http-nio-18081-exec-9,5,main]==========获取有序集合========:[test, test1]
Thread[http-nio-18081-exec-9,5,main]==========处理业务逻辑===等待3s结束========
=========:0
Thread[http-nio-18081-exec-9,5,main]===========解锁redis:false
Thread[http-nio-18081-exec-10,5,main]===========锁定redis:true
Thread[http-nio-18081-exec-10,5,main]==========处理业务逻辑===等待3s开始========
Thread[http-nio-18081-exec-10,5,main]==========获取有序集合========:[test, test1]
Thread[http-nio-18081-exec-10,5,main]==========处理业务逻辑===等待3s结束========
=========:0
Thread[http-nio-18081-exec-10,5,main]===========解锁redis:false
参考资料:
https://www.cnblogs.com/yorkd/p/12793101.html(redisson 设计原理)
https://blog.csdn.net/weixin_43691942/article/details/107591137(redisson)
https://www.cnblogs.com/moxiaotao/p/10829799.html(jdis分布式锁)
https://blog.csdn.net/zhanglong_4444/article/details/105795104?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-5.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-5.channel_param(redis分布式锁的5个坑)
https://www.cnblogs.com/qdhxhz/p/11046905.html(看门狗源码解析)