應用場景:
當多個應用進程(客戶端)需要互斥地訪問共享資源時,可以使用分佈式鎖。其中Redis官方權威提出了RedLock。Java中可使用Redssion提供的實現。
注意點:
- 互斥,保證任何時刻只能有一個客戶端 獲取到鎖;
- 效率之死鎖,保證獲取到鎖的客戶端即使在出現網絡分區或者宕機的情況下,也能釋放掉鎖;
- 效率之容錯,保證只要大多數Redis節點正常工作,客戶端就能正常獲取到鎖和正常釋放鎖。
原理:
獲取鎖:
SET resource_name my_random_value NX [EX|PX] 30000
Redssion中的實現使用了Hash, 可重入:
if (redis.call(‘exists’, KEYS[1]) == 0)
then redis.call(‘hset’, KEYS[1], ARGV[2], 1);
redis.call(‘pexpire’, KEYS[1], ARGV[1]);
return nil;
end;
if (redis.call(‘hexists’, KEYS[1], ARGV[2]) == 1)
then redis.call(‘hincrby’, KEYS[1], ARGV[2], 1);
redis.call(‘pexpire’, KEYS[1], ARGV[1]);
return nil;
end;
return redis.call(‘pttl’, KEYS[1]);
Lua腳本解釋:
如果鎖對應的Hash, key=resource_name的數據不存在,
那麼就設置:
hset resource_name my_random_value 1
pexpire resource_name 30000
如果鎖對應的Hash下, fieldKey=my_random_value存在,
那麼對其加1,即執行:
hincrby resource_name my_random_value 1
pexpire resource_name 30000
最後返回pttl resource_name
釋放鎖:
if redis.call(“get”,KEYS[1]) == ARGV[1] then
return redis.call(“del”,KEYS[1])
else
return 0
end
Redisson的釋放鎖實現:
if (redis.call(‘hexists’, KEYS[1], ARGV[3]) == 0)
then return nil;
end;
local counter = redis.call(‘hincrby’, KEYS[1], ARGV[3],
-1);
if (counter > 0)
then redis.call(‘pexpire’, KEYS[1], ARGV[2]);
return 0;
else redis.call(‘del’, KEYS[1]);
redis.call(‘publish’, KEYS[2], ARGV[1]);
return 1;
end;
return nil;
Redis分佈鎖常見的幾種實現方案:
-
單機
缺點:單點故障,整個服務無法使用。 -
主從複製
缺點:failover問題。 根本原因在於主從複製有延遲。當master在slave複製成功前宕機,slave晉升爲master時沒有宕機時的鎖信息,導致有可能出現兩個客戶端同時持有同一把鎖。
舉例:
a. client A 在master拿到鎖
b. master節點把A創建的鎖信息同步到slave之前宕機了
c. slave晉升爲master節點
d. client B 在新master拿到與A相同的鎖(此時A以爲自己仍持有鎖)
…TODO
Redisson使用DEMO代碼片段:
使用Redisson加鎖 釋放鎖業務代碼