1.什麼是分布式鎖
為了防止分布式系統中的多個程序之間互相幹擾,我們需要一種分布式協調技術來對這些程序進行排程。而這個分布式協調技術的核心就是來實作這個分布式鎖。
2.java中redis分布式鎖的實作
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static final Long RELEASE_SUCCESS = 1L;
/**
* 嘗試擷取分布式鎖
*
* @param jedis Redis用戶端
* @param lockKey 鎖
* @param requestId 請求辨別
* @param expireTime 超期時間
* @return 是否擷取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
/**
* 釋放分布式鎖 保證原子性
* Lua代碼将被當成一個指令去執行,并且直到eval指令執行完成,Redis才會執行其他指令
*
* @param jedis Redis用戶端
* @param lockKey 鎖
* @param requestId 請求辨別
* @return 是否釋放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
3.還存在的問題
1)假設目前存在服務 A、B,當服務A擷取分布式鎖後,redis還沒來的及去将鎖資訊同步至其他redis伺服器,這時候B去擷取鎖也會成功,如何解決這個問題呢?
現階段redis提供了基于叢集的 redlock 來解決這個問題,假設有5個redis節點,這些節點之間既沒有主從,也沒有叢集關系。用戶端用相同的key和随機值在5個節點上請求鎖,請求鎖的逾時時間應小于鎖自動釋放時間。當在3個(超過半數)redis上請求到鎖的時候,才算是真正擷取到了鎖。如果沒有擷取到鎖,則把部分已鎖的redis釋放掉。
但這種方式并不适用于主從模式,主從模式下,master挂掉後,新選舉出來的master如果沒有同步到鎖,則B扔可以加鎖成功,這時候可以考慮,是否當鎖到過期時間後再選舉新的master,這樣就可以避免這種情況發生,但代價就是,在等待期間寫不可用,請慎重選擇。
2)假設目前存在服務A、B,當服務A擷取分布式鎖後去執行,設定的過期時間為30秒,但是A由于一些狀況,執行了30秒後仍未執行完畢,這時候B來擷取分布式鎖,導緻鎖失效。
這種情況下,redis提供了一個看門狗,及在擷取鎖的同時,我們啟動一個線程,定時去檢視鎖的狀态,如果持有鎖,我們就對目前鎖延長有效時間,直道釋放鎖,當然這樣如果redis服務挂掉了也是存在問題的,是以一般情況下,我們會設定一個次數限制,或者最大延遲時間限制,以防止無線延遲下去。
最後說下,其實項目中我們可以直接使用Redisson架構,他的API中提供了大量的封裝好的功能,可以去如下位址進行檢視學習:
https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95