介绍
分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。
使用分布式锁方案
针对同一个key操作
@RequestMapping("/deduct_stock")
public String deductStock() {
String lockKey = "lock:product_101";
//放到value中,校验uuid,防止别的线程释放锁
String clientId = UUID.randomUUID().toString();
//方式一、加锁
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS); //jedis.setnx(k,v)
if (!result) {
return "error_code";
}
//方式二、加锁 获取锁对象
RLock redissonLock = redisson.getLock(lockKey);
//加分布式锁
redissonLock.lock(); // .setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
try {
//业务逻辑
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
System.out.println("扣减成功,剩余库存:" + realStock);
} else {
System.out.println("扣减失败,库存不足");
}
} finally {
//方式一、解锁
if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
stringRedisTemplate.delete(lockKey);
}
//方式二、解锁
redissonLock.unlock();
}
return "end";
}
方式一存在问题:
- 高并发情况下,如果业务逻辑代码执行的时间大于超时时间,key失效,线程1还没执行完,线程2就会加锁成功;线程1执行完后删除key,删除的是线程2的锁,接着线程3又会加锁成功;(可以把
uuid放到value中,谁加的锁才能谁释放) - 释放锁时存在原子性问题,如果线程1校验成功后,出现卡顿,线程2加锁成功,线程1还是会删掉线程2的锁,那线程3又会加锁成功
解决办法:锁续命
分布式锁源码分析
Redis加锁流程
流程1
加锁逻辑(设置key,并设置过期时间)主要由上面这段lua脚本完成,由于redis单线程的特性,这段lua脚本具有原子性,不会被打断;
lua脚本
- 减少网络开销,可以一次执行多条命令,类似管道
- 原子操作,整个脚本作为一个整体执行,中间不会被其他命令插入;(管道不是原子的)
- 替代redis事务功能
参数介绍
KEYS[1] = Collections.<Object>singletonList(getName()) //传入的key的名字
ARGV[1] = internalLockLeaseTime = lockWatchdogTimeout = 30 * 1000 //看门狗超时时间
ARGV[2] = getLockName(threadId) = uuid:threadId //hash的field
流程2
加锁成功后,返回0,回调执行scheduleExpirationRenewal方法,进行锁续命
锁续命逻辑
延迟10s执行
run方法,也是一段lua脚本,如果存在当前主线程存在的key,重新设置超时时间为30s
如果设置成功,返回1,再隔10s调用自己
流程5自旋
流程1加锁逻辑中,如果加锁成功返回nil,不成功,返回剩余ttl,线程2阻塞对应时间后,再去尝试重新加锁
流程6
线程1执行完释放锁后,发布消息到一个队列,当前正在阻塞的线程订阅这个队列,阻塞的线程2收到消息后重新执行加锁逻辑
线程1在释放锁时发布消息,同样是lua脚本实现
1、如果锁不存在,发布unlockMessage
2、删除锁,发布unlockMessage
收到到消息后,释放信号量
主从架构锁失效问题
master接收客户端的写入操作后,还没同步消息到slave就挂了,slave升级为master时,数据就会丢失 解决办法:zookeeper(强一致性),RedLock(未完全解决,不推荐使用)
RedLock
客户端向3个节点设置锁,有2个加锁成功,才算加锁成功(所以节点最好是奇数)
主从锁失效问题
- 假设每个master都有一个slave,保证高可用。客户端写入master后未同步slave就挂了,slave升级为master后还是没有key,依然可以加锁成功
- 假设没有slave,主节点挂了半数以上,
RedLock就无法加锁成功 - 假设没有slave,master加锁成功后通过客户端,这是还未持久化到
aof文件(持久化策略不是every),redis这个时候重启,也会造成锁失效