Redis分布式锁的演化

使用redis来实现一把分布式锁

普通分布式锁

一把普通的redis分布式锁,原理是setnx只有在第一次设置的时候才生效,等于第一个setnx成功的线程得到了这把锁,其他线程设置失败就直接返回了,只有当这个线程执行完毕,把这个锁给删除,别的线程才能得到这个锁。

    //伪代码
    private Jedis jedis;
    public String doLock(){
        boolean result = jedis.setnx("lockKey","Lock");//setnx 只能当redis里面没有这个key的时候才能设置成功
        if(!result){
            return "error";
        }
        //主体业务逻辑
        jedis.delete("lockKey");

        return null;
    }

思考:如果主体业务中抛了异常怎么办?在执行主体业务逻辑的时候,向外抛出了一个异常,那么就没有执行释放锁的过程,这样程序就死锁了

改进

由于我们一定要释放这个锁,也就是让释放锁的操作总要执行,所以应该把释放锁的操作放进try - finally里面,这样,抛了异常也能释放这个锁了。

    private Jedis jedis;
    public String doLock(){
        boolean result = jedis.setnx("lockKey","Lock");//setnx 只能当redis里面没有这个key的时候才能设置成功
        if(!result){
            return "error";
        }
        try {
            //主体业务逻辑
        }finally {
            jedis.delete("lockKey");
        }

        return null;
    }

思考:如果在执行业务逻辑的时候发生了服务器宕机怎么办?直接宕机的话还是没有执行到finally语句,这样这个线程的锁依然没有释放,照样死锁了。

改进

可以设置一个过期时间,如果这个线程宕机,导致死锁,但是10s之后这个key过期了,也可以把这个锁释放掉

	    private Jedis jedis;
        boolean result = jedis.setnx("lockKey","Lock");//setnx 只能当redis里面没有这个key的时候才能设置成功
        jedis.expire("lockKey",10);//设置过期时间为10s
        if(!result){
            return "error";
        }
        try {
            //主体业务逻辑
        }finally {
            jedis.delete("lockKey");
        }

        return null;
    }

思考:如果程序执行到设置过期时间的时候宕机了怎么办?这样的话并没有成功的给这把锁设置好过期时间。

改进

保持设置锁和锁的过期时间的一致性就可以了

    private Jedis jedis;
    public String doLock(){
        boolean result = jedis.set("lockKey","Lock","NX","EX",10);//setnx 只能当redis里面没有这个key的时候才能设置成功
        if(!result){
            return "error";
        }
        try {
            //主体业务逻辑
        }finally {
            jedis.delete("lockKey");
        }

        return null;
    }

思考:如果主体业务逻辑执行的时间非常长,而给这个锁设置的时间比较短,在这个线程执行的时候还没有执行完,这个锁已经过期了,这个锁就会被别的线程得到,当第一个线程执行完释放锁的时候,释放的可能就是第二个线程的锁,如果是高并发的环境下,就直接乱套了,谁也不知道这个线程释放的是谁的锁。

改进

可以定义一个唯一的id,就可以让线程只能释放自己的锁,这样就不会被别的线程释放了

    private Jedis jedis;
    private String keyId = UUID.randomUUID().toString();//线程的唯一标识
    public String doLock(){
        boolean result = jedis.set("lockKey",keyId,"NX","EX",10);//setnx 只能当redis里面没有这个key的时候才能设置成功
        if(!result){
            return "error";
        }
        try {
            //主体业务逻辑
        }finally {
            if(keyId.equals(jddis.get(lockKey))){//只能释放自己的锁
                jedis.delete("lockKey");
            }
        }

        return null;
    }

思考:这样虽然当前线程的锁不会被别的线程的锁释放,但是如果这个线程的锁还是过期了,那么别的线程也可以得到锁,可以进行主体业务逻辑的执行,这样就不能保证执行的原子性了。

改进

可以在业务逻辑中加一个timer计时器,每隔一段时间对锁进行重新设置过期时间,也就是对锁续命,这样这个锁就算很完美啦

//没玩过计时器,代码不会写
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章