分布式锁,即分布式系统中的锁。在单体应用中我们使用锁解决的是控制共享资源访问的问题,通常使用的是synchronized关键字;而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。
命令: setnx(key,value);
在java代码中,当一个线程执行 setnx 返回 ture,说明加锁成功,key原本不存在在redis中;当一个线程执行 setnx 返回 false,说明 key 已经存在,该线程抢锁失败。
String lockKey = "product_001";//lockKey为需要抢占的资源
String clientId = UUID.randomUUID().toString();//解决 高并发情况下 redis分布式锁 永久失效 的问题
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId);
有加锁就得有解锁。当得到锁的线程执行完任务后,需要释放锁,以便其他线程可以进入。释放锁的最简单方式是执行 del 指令,伪代码:del(key) ,释放锁之后其他线程就可以通过执行setnx,来获取锁。
if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
stringRedisTemplate.delete(lockKey);//释放锁
}
假如一个得到锁的线程在执行任务的过程中挂掉,来不及释放锁,这块资源将会永远被锁住(死锁),别的线程再也别想进来。所以,setnx的 key 必须设置一个超时时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放。
Boolean result =
stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10,TimeUnit.SECONDS);
当某线程执行 setnx,成功得到了锁,setnx刚执行成功,还未来得及执行 expire 指令,节点 可能 挂掉了。这样一来,这把锁就没有设置过期时间,变成死锁,别的线程再也无法获得锁了。
怎么解决呢?setnx 指令本身是不支持传入超时时间的,set 指令增加了可选参数,伪代码如下:
setnx set(key,1,30,NX),java中就比较简单:
Boolean result =
stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
假如线程A成功得到了锁,并且设置的超时时间是 30 秒;
可能某些原因导致线程 A 执行的很慢很慢,过了 30 秒都没执行完,这时候锁过期自动释放,此时线程 B 得到了锁;
随后,线程 A 执行完了任务,线程 A 随之释放锁。但这时候线程 B 还没执行完,线程A实际上 删除的是线程 B加的锁。
解决方案:可以在 释放锁之前做一个判断,验证当前的锁是不是自己加的锁。
String clientId = UUID.randomUUID().toString();//解决 高并发情况下 redis分布式锁 永久失效 的问题
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
、、、、、、
if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
stringRedisTemplate.delete(lockKey);//释放锁
}
如同上述描述:当线程 A 执行的很慢很慢,过了 30 秒都没执行完,这时候锁过期自动释放,线程 B得到了锁。此时就有多个线程在访问同步代码块
解决:我们可以让获得锁的线程同步开启一个守护线程,用来给快要过期的锁“续命”。
使用redisson:Redisson - 是一个高级的分布式协调Redis客户端。
package com.huawei.distributedlock;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 分布式锁介绍
*/
@RestController
public class IndexController {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Autowired
private Redisson redisson;
@RequestMapping("/deduct_stock")
public String deductStock() {
String lockKey = "product_001";
String clientId = UUID.randomUUID().toString();//解决 高并发情况下 redis分布式锁 永久失效 的问题
RLock redissonLock = redisson.getLock(lockKey);
try {
//Boolean result =
// stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
//if (!result) {
// return "error";
//}
redissonLock.lock(30, TimeUnit.SECONDS);
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock + "");
} else {
System.out.println("扣减失败,库存不足");
}
} finally {
redissonLock.unlock();
//if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
// stringRedisTemplate.delete(lockKey);//释放锁
//}
}
return "end";
}
}
视频介绍https://www.bilibili.com/video/BV1d4411y79Y?p=1&vd_source=78546917c5323450fe30994834259fd3