1.概述
? ? 現在大部分公司的服務通過分布式部署實現了高可用,但是問題來了:如果有一段邏輯只希望一個應用中的一個線程進行執行,如何能夠保證用戶的一次操作,同時只受理一次呢?以前單機應用,可以通過java的synchronize和Lock來實現。現在則需要使用分布式鎖。
? ? 分布式鎖的實現主要有redis、zookeeper、db等方式。本文主要說明用redis實現方式。
2.方案1
? ? 因為redis是單線程處理的,并且支持cas操作。不難想到的一個實現是:
? ? lock:
????????setnx? ? key? ? value
? ? unlock:
? ? ? ? del? key
? ? 其中,setnx語義為:若給定的?key?已經存在,則?SETNX?不做任何動作。? 成功設置返回1、設置失敗返回0。
? ? 根據setnx的返回值判斷是否加鎖成功。
? ?問題:
? ? ? ? 如果一個服務lock完之后,崩潰了或者重啟了,那么將永遠不會unlock,對于需要繼續執行的場景來說,是無法接受的。
3.方案2
? ?針對方案2的一些問題,不難想到對key設置一個超時就ok了。那么方案2來了:
? ? lock:
? ? ? ? set? ?key? ?value? px? timeout_millisecond? nx
? ? unlock:
? ? ? ? del key
? ? 其中set方法支持同時設置超時和nx,這種方案是否ok?
? ? 問題:
? ? ? ? 如果最早拿到鎖的服務器1執行結束,調用unlock失敗(實際redis刪除成功),另外一個服務器2加鎖成功。某種補償導致服務器1重復調用unlock。這時候問題出現了,服務器2加的鎖被服務器1釋放了。
4.方案3? ??
? ?針對上面的問題:一個全備的分布式鎖要求是:
? ? key 一致
? ? value 包含了持有者信息、加鎖的次數(可重入)
? ? 對于redis來說,基于lua腳本實現。代碼如下(參考redisson):
? ? ? ? lock:
? ??????????if (redis.call('exists', key) == 0)? then?
? ? ? ? ? ? ? ? redis.call('hset', key, holder_info, 1);?
? ? ? ? ? ? ? ? redis.call('pexpire', key, timeout);?
? ? ? ? ? ? ? ? return nil;?
? ? ? ? ? ? end;
? ? ? ? if (redis.call('hexists', key, holder_info) == 1)? then?
? ? ? ? ? ? ????redis.call('hincrby', ?key, holder_info, 1);?
? ? ? ? ? ????? redis.call('pexpire', ?key, timeout);?
? ? ? ? ? ????? return nil;?
? ? ? ? end;?
? ? ? ? return redis.call('pttl', key);
unlock:
? ??if (redis.call('exists', key) == 0) then?
? ? ? ? return 1;??
? ?end;
? ? if (redis.call('hexists', key, holder_info) == 0) then?
? ? ? ? return nil; //非本持有者加的鎖
? ? end;?
? ? local counter = redis.call('hincrby', key, holder_info, -1);?
? ? if (counter > 0) then?
? ? ? ? redis.call('pexpire', key, timeout);?
? ? ? ? return 0;?
? ? ?else?
? ? ? ? redis.call('del', key);?
? ? ? ? return 1;
? ? end;?
? ? return nil;
5.總結
? ??那么真正對于分布式鎖的要求是什么,有以下幾點:
????????1. 不同服務器之間操作串行化(鎖的含義)
????????2.可重入(根據業務場景、代碼來決定)
????????3.一個服務器加的鎖,不可被另外的服務器刪除(誤刪除)
? ? ????4.具有應用崩潰恢復機制
6.展望
? ? 希望以下的能夠引起讀者的思考:
? ? ????1.如果一個應用沒有獲取到鎖,并且業務需求是需要進行重試加鎖,那么只能夠輪詢。是否還有別的辦法?
? ? ? ? 2.目前討論的都是基于redis單點且服務正常的情況下。 如果redis崩潰,那么如何處理?
? ? ? ? 3.redis、zookeeper、db實現的分布式鎖的優缺點各是什么?