1,Redis使用setnx 實現
2,Redisson 分佈式鎖;
Redis基於 setnx 實現分佈式鎖原理:
Redis Setnx 實現分佈式鎖:
Setnx key value
Redis Setnx(SET if Not eXists) 命令在指定的key 不存在時,爲key設置指定的值。
設置成功,返回1, 不成功返回0.
Redis具有先天性,能夠保證線程安全問題,多個redis客戶端最終只有一個Redis客戶端設置成功。
Setnx: key=mayiktRedisLock value=1;該key 如果不存在的時候 執行結果返回1 java層面返回true.
Setnx: key=mayiktRedisLock value=1;該key 如果存在的時候 執行結果返回0 java層面返回false.
set之間的區別:
如果該key 不存在的時候,直接創建,如果存在的時候覆蓋。
原理:
獲取鎖原理:
多個redis客戶端執行setnx指令,設置一個相同的Rediskey,誰能創建key成功,誰能獲取鎖。
如果該key已經存在的情況下,在創建的時候就會返回false。
釋放原理:
就是刪除key.
Redis實現分佈式鎖如何避=避免死鎖的問題?
如果Redis客戶端(獲取鎖的jvm)宕機的話,如何避免死鎖的問題?
zk如何避免該問題?先天性解決了該問題。
可以設置過期時間,過期後該key自動刪除。
獲取到鎖的jvm 業務執行時間>過期key的時間如何處理?
續命:開啓一個定時任務實現續命,當我們的業務邏輯沒有執行完畢的時候,就會延長過期key的時間。
一直不斷續命的情況下,也會發生死鎖的問題。
設定續命的次數,續命多次如果還沒有執行完業務邏輯的情況下,就應該回滾業務,主動釋放鎖。
如果當前線程已經獲取到鎖的情況下,不需要重複獲取鎖,而是直接複用。
如何考慮避免死鎖的問題。
對我們的key 設置 設置鎖的過期時間,避免死鎖的問題。
如何確保該鎖是自己創建,被自己刪除。
當我們在執行set的時候value爲uuid,如果刪除的uuid與該uuid值保持一致,則是自己獲取的鎖,可以被自己刪除。
Redis key 過期了,但是業務還沒有執行完畢如何處理;
當redis的過期了,應該採取續命設計,繼續延長時間,如果續命多次還是失敗的情況下,爲了避免死鎖的問題,應該主動釋放鎖和當前的事務操作。
相關核心代碼:
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @ClassName RedisLockImpl
* @Author 螞蟻課堂餘勝軍 QQ644064779 www.mayikt.com
* @Version V1.0
**/
@Component
@Slf4j
public class RedisLockImpl implements RedisLock {
private String redisLockKey = "mayiktLock";
private Long timeout = 3000L;
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static Map<Thread, RedisLockInfo> lockCacheMap = new ConcurrentHashMap<>();
private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
@Override
public boolean tryLock() {
Thread cuThread = Thread.currentThread();
RedisLockInfo redisLockInfo = lockCacheMap.get(cuThread);
if (redisLockInfo != null && redisLockInfo.isState()) {
log.info("<<重入鎖,直接從新獲取鎖成功>>");
return true;
}
Long startTime = System.currentTimeMillis();
for (; ; ) {
// 1.創建setnx
String lockId = UUID.randomUUID().toString();
Long expire = 30L;
Boolean getLock = stringRedisTemplate.opsForValue().setIfAbsent(redisLockKey, lockId, expire, TimeUnit.SECONDS);
if (getLock) {
log.info("<<獲取鎖成功>>");
// 將該鎖緩存到Map集合中 實現重入鎖
lockCacheMap.put(cuThread, new RedisLockInfo(lockId, cuThread, expire));
return true;
}
// 2.繼續循環重試獲取 判斷是否已經超時重試
long endTime = System.currentTimeMillis();
if (endTime - startTime > timeout) {
return false;
}
//3.避免頻繁重試 調用阻塞方法等待
try {
Thread.sleep(100);
} catch (Exception e) {
}
}
}
public RedisLockImpl() {
//開始定時任務實現續命
// this.scheduledExecutorService.scheduleAtFixedRate(new LifeExtensionThread(), 0, 5, TimeUnit.SECONDS);
}
@Override
public boolean releaseLock() {
log.info("<<釋放鎖成功>>");
RedisLockInfo redisLockInfo = lockCacheMap.get(Thread.currentThread());
if (redisLockInfo == null) {
return false;
}
boolean state = redisLockInfo.isState();
if (!state) {
return false;
}
String redisLockId = stringRedisTemplate.opsForValue().get(redisLockKey);
if (StringUtils.isEmpty(redisLockId)) {
return false;
}
if (!redisLockId.equals(redisLockInfo.getLockId())) {
log.info("<<非本線程自己的鎖,無法刪除>>");
return false;
}
Boolean delete = stringRedisTemplate.delete(redisLockKey);
if (!delete) {
return false;
}
return lockCacheMap.remove(redisLockKey) != null ? true : false;
}
/**
* 續命次數設計
*/
class LifeExtensionThread implements Runnable {
@Override
public void run() {
lockCacheMap.forEach((k, lockInfo) -> {
// 判斷線程是否爲終止狀態,如果是爲終止狀態 則開始對key實現續命
Thread lockThread = lockInfo.getLockThread();
if (!lockInfo.isState() && lockThread.isInterrupted()) {
log.info("獲取鎖失敗或者當前獲取鎖線程已經成功執行完方法");
return;
}
Integer lifeCount = lockInfo.getLifeCount();
//開始實現續命 爲了避免續命爲了避免續命多次還是無法釋放鎖 則應該回滾業務 主動釋放鎖
if (lifeCount > 3) {
// 移除不在繼續續命
lockCacheMap.remove(lockThread);
// 回滾當前線程事務
// 停止該線程
return;
}
// 開始延長時間
stringRedisTemplate.expire(redisLockKey, lockInfo.getExpire(), TimeUnit.SECONDS);
});
}
}
}