Redis实现分布式锁

初始:实现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";
    }
}

 

你可能感兴趣的:(redis,分布式集群,Redis实现分布式锁)