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計時器,每隔一段時間對鎖進行重新設置過期時間,也就是對鎖續命,這樣這個鎖就算很完美啦

//沒玩過計時器,代碼不會寫
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章