Redis(三) 構建鎖
watch,multi,exec組成的事務並不具有可擴展性,原因在於程序嘗試完成一個事務的時候,可能因為事務執行失敗而反復進行重試。
1. SETNX命令:只會在鍵不存在的情況下為鍵設置值,而鎖要做的就是將一個隨機生成的128位UUID設置為鍵的值,並使用這個值來防止鎖被其他進程取得。如果程序在嘗試獲取鎖的時候失敗,那麽它將不斷進行重試,直到成功地取得鎖或超過給定的時間限為止;
代替了WATCH命令實現;解決了watch下操作競爭過多導致的延遲劇增問題;
實現方式:加鎖時,創建一個鍵為"lock:" + lockName,值為隨機UUID的鍵值對,只有當鍵值對創建成功時(被加鎖的
public String acquireLock(Jedis conn, String lockName, long acquireTimeout){
String identifier = UUID.randomUUID().toString();
long end = System.currentTimeMillis() + acquireTimeout;
while (System.currentTimeMillis() < end){
if (conn.setnx("lock:" + lockName, identifier) == 1){
return identifier;
}
try {
Thread.sleep(1);
}catch(InterruptedException ie){
Thread.currentThread().interrupt();
}
}
return null;
}
鎖的釋放:
public boolean releaseLock(Jedis conn, String lockName, String identifier) {
String lockKey = "lock:" + lockName;
while (true){
conn.watch(lockKey);
if (identifier.equals(conn.get(lockKey))){ //檢查進程是否仍然持有鎖
Transaction trans = conn.multi();
trans.del(lockKey);
List<Object> results = trans.exec();
if (results == null){
continue;
}
return true;
}
conn.unwatch();
break;
}
return false;
}
2. 細粒度鎖
只鎖住需要的部分。
在高負載情況下,使用鎖可以減少重試次數,降低延遲時間,提升性能並將加鎖的粒度調整至合適的大小。
3. 超時鎖
目前的鎖在持有者崩潰時不會自動釋放,導致鎖一直處於被獲取狀態;
給鎖加上超時限制特性,程序在獲得鎖後,調用EXPIRE命令為鎖設置過期時間;
public String acquireLockWithTimeout(
Jedis conn, String lockName, long acquireTimeout, long lockTimeout)
{
String identifier = UUID.randomUUID().toString();
String lockKey = "lock:" + lockName;
int lockExpire = (int)(lockTimeout / 1000);
long end = System.currentTimeMillis() + acquireTimeout;
while (System.currentTimeMillis() < end) {
if (conn.setnx(lockKey, identifier) == 1){
conn.expire(lockKey, lockExpire);
return identifier;
}
if (conn.ttl(lockKey) == -1) {
conn.expire(lockKey, lockExpire);
}
try {
Thread.sleep(1);
}catch(InterruptedException ie){
Thread.currentThread().interrupt();
}
}
// null indicates that the lock was not acquired
return null;
}
4. 計數信號量
計數信號量是一種鎖,它可以讓用戶限制一項資源能夠同時被多少個進程訪問,通常用於限定能夠同時使用的資源數量;(前面介紹的鎖都是只能被一個進程訪問的信號量);
使用ZSet有序集合存儲多個信號量持有者的信息:成員為進程在嘗試獲取信號量時生成的標識符;分值為當前時間戳;
過程:生成標識符;清除所有過期信號量;將新的標識符添加到zset;檢查新添標識符在zset中的排名;
Redis(三) 構建鎖