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