之前項目中使用redis鎖實現秒殺等一些併發業務,在這裏整理一下基於Redis實現分佈式鎖的簡單入門實例,記錄一下,便於以後查看、學習。
參考博客:
https://blog.csdn.net/l_bestcoder/article/details/79336986
https://blog.csdn.net/starlh35/article/details/79759630
1、簡介
在分佈式系統中存在併發場景,爲了解決這一問題,基於redis鎖一定程度可以解決這一問題,但是也有缺點,如死鎖等。。。,
這塊的優缺點,大家自行百度學習一下。。。這裏主要介紹的基於redis實現鎖的機制!
分佈式系統實現的時候要注意的幾個關鍵點:
1、鎖信息必須是會過期超時的,不能讓一個線程長期佔有一個鎖而導致死鎖;
2、同一時刻只能有一個線程獲取到鎖。
主要實現鎖功能的redis命令:
setnx(key, value):“set if not exits”,若該key-value不存在,則成功加入緩存並且返回1,否則返回0。
get(key):獲得key對應的value值,若不存在則返回nil。
getset(key, value):先獲取key對應的value值,若不存在則返回nil,然後將舊的value更新爲新的value。
expire(key, seconds):設置key-value的有效期爲seconds秒,避免死鎖
delete (key):刪除key
2、實現思路
a、加鎖
當執行一個線程業務時,通過加鎖(獲取鎖)實現防止別的線程去執行它;
獲取鎖的時候,使用setnx加鎖,並使用expire命令爲鎖添加一個超時時間,超過該時間則自動釋放鎖,鎖的value值爲一個隨機生成的UUID,通過此在釋放鎖的時候進行判斷。
獲取鎖的時候還設置一個獲取的超時時間,若超過這個時間則放棄獲取鎖。
b、釋放鎖
釋放鎖的時候,通過UUID判斷是不是該鎖,若是該鎖,則執行delete進行鎖釋放。
3、代碼實現
其實代碼實現大同小異,實現思想就是上面的思路。。。
/**
* 實現 redis 加鎖、釋放鎖
*/
public class DistributedLock
{
private final JedisPool jedisPool;
public DistributedLock(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
* 加鎖
*
* @param locaName
* 鎖的key
* @param acquireTimeout
* 獲取超時時間
* @param timeout
* 鎖的超時時間
* @return 鎖標識
*/
public String lockWithTimeout(String locaName, long acquireTimeout, long timeout) {
Jedis conn = null;
String retIdentifier = null;
try {
// 獲取連接
conn = jedisPool.getResource();
// 隨機生成一個value
String identifier = UUID.randomUUID().toString();
// 鎖名,即key值
String lockKey = "lock:" + locaName;
// 超時時間,上鎖後超過此時間則自動釋放鎖
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;
}
}
4、測試案例
模擬線程併發業務。。。
線程服務
加鎖後運行測試: 20個子線程業務被順序執行
不加鎖後運行測試: 20個子線程業務被非順序執行,誰搶到資源誰先執行。。