redis分佈式鎖api:redisson
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.7.2</version>
</dependency>
redisson多線程執行流程:
注意點說明:
- 線程一獲取鎖必須加等待獲取時間,持有鎖時間:在等待時間內沒有獲取鎖則失敗,防止無限等待造成死鎖,持有鎖時間:防止程序異常後沒有執行釋放鎖代碼使得該鎖一致有效,其他線程無法獲取鎖造成的死鎖。
- 持有鎖時間必須大於獲取鎖後業務代碼執行時間,防止業務代碼還沒有執行完成就把鎖提前釋放的異常,但是生產上難免因爲網絡等問題造成 業務代碼還沒有執行完鎖的有效時間就到了的情況:針對這種情況redisson在線程獲取鎖成功後另外起了一個線程,在鎖到期後判斷業務代碼沒執行完就會延長鎖的時間
- 獲取鎖對象時,需要加入redisKey的鎖對象標識,用於和其他業務的鎖對象做區分,防止釋放鎖時把別人的鎖釋放掉
- 線程1獲取鎖成功後,線程2會while循環,自旋等待獲取鎖知道線程1釋放後獲取成功
- 生產上redis一般不是單機的,採用集羣或者哨兵,場景:線程1獲取鎖成功後正在處理業務代碼,如果此時master宕機,系統啓用slave,鎖是不是會丟失,造成其他線程在線程1沒結束前就獲取到了鎖呢?解決:redisson中會把集羣或者哨兵中的鎖同步到每一個redis服務器中,防止這種情況的發生
- 除了一般普通的鎖之外,redisson中還提供了讀寫鎖,用來解決多讀,少寫的情況:場景:N個線程在讀取資源,1個線程去修改,修改了一半發生了回滾,N個讀線程會不會讀取到修改了一般又回滾的髒數據呢,此時1個寫線程可以加寫鎖,N個讀線程可以加讀鎖,獲取寫鎖後所有的讀鎖會進入自旋等待,直到讀鎖主動或回滾釋放後,讀鎖才能獲繼續讀取資源。所有的讀鎖是共享鎖,不會互斥,所以多個讀線程不會有鎖等待,不會影響效率。
- redisson底層採用的是LUA腳本來確保redis操作的原子性
具體使用:
@Autowired
@Lazy
private RedissonClient redissonClient;
public void lockTest() {
// 生成redisKey常量
String redisKey = "{redisPrefix}:" + "鎖的唯一標識(業務字段)";
// 由redisKey獲取分佈式鎖對象
RLock lock = redissonClient.getLock(redisKey);
try {
// 獲取鎖
// 參數1:獲取鎖等待的時間,參數2:持有鎖的時間,參數3:時間單位,返回值:是否獲取成功
boolean lockResult = lock.tryLock("5", "10",TimeUnit.SECONDS);
} catch (Exception e) {
// 獲取鎖過程中的異常處理
log.error("獲取鎖異常,redisKey:{},異常信息:{}",redisKey,e);
throw new serviceException("錯誤碼");
}
// 判斷是否獲取成功
if (!lockResult) {
log.error("獲取鎖超時或失敗,redisKey:{},異常信息:{}",redisKey,e);
throw new serviceException("錯誤碼")
}
// 開始執行業務代碼,這裏的代碼越少越好
try {
} catch (Exception e) {
log.error("處理業務異常代碼失敗,{}",e)
thorw new serviceException("錯誤碼")
} finally {
// 釋放鎖:必須判斷是否是自己的鎖,防止把別人的鎖釋放掉
lock.unlock();
}
}