上篇講了分佈式鎖的數據庫實現,這篇我們繼續來講分佈式鎖的redis實現。
那麼如何通過redis來實現一個分佈式鎖呢? 一般最容易想到的命令,就是setNx
那在使用redis setNx命令時,還需要關注哪些點呢?
-
redis分佈式鎖常用命令,SETNX(key, val)
當且僅當key不存在時,設置成功,返回“1”,否者什麼都不做,返回“0”
我們可以利用該命令的特性進行加鎖操作。
假如同時有兩個線程要競爭資源,其中一個線程先獲取到資源,調用setnx命令,將設置成功,返回“1”,表示加鎖成功。另一個線程也調用setnx命令,返回“0”,表示該key已經設置過了,加鎖失敗,需要重新等待。 -
expire key timeout
爲了防止,加鎖過程中出現異常,鎖一直不釋放,需要給加鎖的key,設置一個超時時間,超時時間的設置,是所有的分佈式鎖實現都需要考慮的。 -
delete key
刪除某個key,釋放對應的鎖。
下面是簡單的源碼實現:
/**
* 分佈式鎖的簡單實現代碼
*/
public class DistributedLock {
private final JedisPool jedisPool;
public DistributedLock(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
* 加鎖
* @param lockName 鎖的key
* @param acquireTimeout 獲取超時時間
* @param timeout 鎖的超時時間
* @return 鎖標識
*/
public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) {
Jedis conn = null;
String retIdentifier = null;
try {
// 獲取連接
conn = jedisPool.getResource();
// 隨機生成一個value
String identifier = UUID.randomUUID().toString();
// 鎖名,即key值
String lockKey = "lock:" + lockName;
// 超時時間,上鎖後超過此時間則自動釋放鎖
int lockExpire = (int) (timeout / 1000);
// 獲取鎖的超時時間,超過這個時間則放棄獲取鎖
long end = System.currentTimeMillis() + acquireTimeout;
while (System.currentTimeMillis() < end) {
if (conn.setnx(lockKey, identifier) == 1) {
conn.expire(lockKey, lockExpire);
// 返回value值,用於釋放鎖時間確認
retIdentifier = identifier;
return retIdentifier;
}
// 返回-1代表key沒有設置超時時間,爲key設置一個超時時間
if (conn.ttl(lockKey) == -1) {
conn.expire(lockKey, lockExpire);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} catch (JedisException e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.close();
}
}
return retIdentifier;
}
/**
* 釋放鎖
* @param lockName 鎖的key
* @param identifier 釋放鎖的標識
* @return
*/
public boolean releaseLock(String lockName, String identifier) {
Jedis conn = null;
String lockKey = "lock:" + lockName;
boolean retFlag = false;
try {
conn = jedisPool.getResource();
while (true) {
// 監視lock,準備開始事務
conn.watch(lockKey);
// 通過前面返回的value值判斷是不是該鎖,若是該鎖,則刪除,釋放鎖
if (identifier.equals(conn.get(lockKey))) {
Transaction transaction = conn.multi();
transaction.del(lockKey);
List<Object> results = transaction.exec();
if (results == null) {
continue;
}
retFlag = true;
}
conn.unwatch();
break;
}
} catch (JedisException e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.close();
}
}
return retFlag;
}
}