初始:实现redis中库存数据-1的基本逻辑
@RestController
public class disLockController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/deduct_stock")
public String deductStock(){
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("扣减失败,库存不足!");
}
return "end";
}
}
问题:单进程多线程下出现并发问题
演进一:加synchronized同步锁,避免了单进程多线程下并发错误
关键代码:synchronized (this){}
@RestController
public class disLockController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/deduct_stock")
public String deductStock(){
synchronized (this){
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("扣减失败,库存不足!");
}
}
return "end";
}
}
问题:集群下,多进程下的多线程并发问题
演进二:
1、采用redis的setnx方法,只有key不存在,才能设置值
2、当处理完后,del掉该key,其他线程可获得分布式锁执行
关键代码:
// redis分布式锁
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "zhuge");
if(!result){
return "error";
}
@RestController
public class disLockController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/deduct_stock")
public String deductStock(){
// jedis.setnx(key, value)
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "zhuge");
if(!result){
return "error";
}
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("扣减失败,库存不足!");
}
stringRedisTemplate.delete("lockKey");
return "end";
}
}
问题:如果某线程加锁后,还没释放锁,代码出现问题,则锁永远不会被释放,其他线程无法进行
演进三:加try-finally代码块,保证代码出现问题,锁也会释放
关键代码:try{}finally {}
@RestController
public class disLockController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/deduct_stock")
public String deductStock(){
try {
// jedis.setnx(key, value)
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "zhuge");
if(!result){
return "error";
}
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 {
stringRedisTemplate.delete("lockKey");
}
return "end";
}
}
问题:如果机器宕机了,则不会执行finally代码内容,锁不会释放,其他集群线程也无法进行
演进四:给锁设置超时时间,集群宕机了,redis设置key-value超时了,则自动删除了,不会导致其他集群线程
关键代码:
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", clientID, 10, TimeUnit.SECONDS);
@RestController
public class disLockController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/deduct_stock")
public String deductStock(){
try {
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", clientID, 10, TimeUnit.SECONDS);
if(!result){
return "error";
}
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 {
stringRedisTemplate.delete("lockKey");
}
return "end";
}
}
问题:如果A线程给锁设置10秒,可是A线程逻辑10秒内都没执行完,B线程设置锁,A执行完逻辑后,把B的锁给删掉了
演进五:在
del
释放锁之前做一个判断,验证当前的锁是不是自己加的锁,是才删除关键代码:value设置为uuid,线程A设置的锁,只有A能删除......
String clientID = UUID.randomUUID().toString();
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", clientID, 10, TimeUnit.SECONDS);
if(clientID.equals(stringRedisTemplate.opsForValue().get("lockKey"))){
stringRedisTemplate.delete("lockKey");
}
@RestController
public class disLockController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/deduct_stock")
public String deductStock(){
String clientID = UUID.randomUUID().toString();
try {
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", clientID, 10, TimeUnit.SECONDS);
if(!result){
return "error";
}
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 {
if(clientID.equals(stringRedisTemplate.opsForValue().get("lockKey"))){
stringRedisTemplate.delete("lockKey");
}
}
return "end";
}
}
问题:上面只是保证了A只能删A,B只能删B...,但是当A逻辑没执行完,B拿到锁,就可以执行和A同样的逻辑,就会造成数据不一致
演进六(最终版):采用redission watch dog 自动延期机制,客户端 1一旦加锁成功,就会启动一个 watch dog 看门狗,是一个后台线程,会每隔 10 秒检查一下,如果客户端还持有锁 key,那么就会不断的延长锁 key 的生存时间
关键代码:
@Autowired
Redisson redisson;RLock lock = redisson.getLock("lockKey");
lock.unlock();
@RestController
public class disLockController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
Redisson redisson;
@GetMapping("/deduct_stock")
public String deductStock(){
RLock lock = redisson.getLock("lockKey");
try {
lock.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 {
lock.unlock();
}
return "end";
}
}