84 redis實現分佈式鎖的原理

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);
            });
        }
    }
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章