自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一個Redis分布式鎖的實現(xiàn)引發(fā)的思考

數(shù)據(jù)庫 Redis
釋放鎖時,會通過 UUID 去判斷這個鎖的值,避免釋放其他線程加的鎖,但是沒有考慮到這個 get 和 del 是兩個操作,還是會有意外,比如 releaseLock 時,執(zhí)行完 get ,判斷這個 uuid 是自己的,準(zhǔn)備刪除,但此時 鎖過期 了,其他線程剛好加鎖成功,結(jié)果又被你刪除了。

最近看了一個老項目(2018年的),發(fā)現(xiàn)其中用 Redis 來實現(xiàn)分布式鎖??。

代碼如下 ??

// jedis 

public String lock(String lockName, long acquireTimeout) {
    return lockWithTimeout(lockName, acquireTimeout, DEFAULT_EXPIRE);
}

public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) {

        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection redisConnection = connectionFactory.getConnection();
        /** 隨機生成一個value */
        String identifier = UUID.randomUUID().toString();
        String lockKey = LOCK_PREFIX + lockName;
        int lockExpire = (int) (timeout / 1000);

        long end = System.currentTimeMillis() + acquireTimeout;    /** 獲取鎖的超時時間,超過這個時間則放棄獲取鎖 */
        while (System.currentTimeMillis() < end) {
            if (redisConnection.setNX(lockKey.getBytes(), identifier.getBytes())) {
                redisConnection.expire(lockKey.getBytes(), lockExpire);
                /** 獲取鎖成功,返回標(biāo)識鎖的value值,用于釋放鎖確認(rèn) */
                RedisConnectionUtils.releaseConnection(redisConnection, connectionFactory);
                return identifier;
            }
            /** 返回-1代表key沒有設(shè)置超時時間,為key設(shè)置一個超時時間 */
            if (redisConnection.ttl(lockKey.getBytes()) == -1) {
                redisConnection.expire(lockKey.getBytes(), lockExpire);
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                log.warn("獲取分布式鎖:線程中斷!");
                Thread.currentThread().interrupt();
            }
        }
        RedisConnectionUtils.releaseConnection(redisConnection, connectionFactory);
        return null;
    }

public boolean releaseLock(String lockName, String identifier) {

    if (StringUtils.isEmpty(identifier)) return false;

    RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
    RedisConnection redisConnection = connectionFactory.getConnection();
    String lockKey = LOCK_PREFIX + lockName;
    boolean releaseFlag = false;
    while (true) {
        try {
            byte[] valueBytes = redisConnection.get(lockKey.getBytes());

            /**  value為空表示鎖不存在或已經(jīng)被釋放*/
            if (valueBytes == null) {
                releaseFlag = false;
                break;
            }

            /** 通過前面返回的value值判斷是不是該鎖,若是該鎖,則刪除,釋放鎖 */
            String identifierValue = new String(valueBytes);
            if (identifier.equals(identifierValue)) {
                redisConnection.del(lockKey.getBytes());
                releaseFlag = true;
            }
            break;
        } catch (Exception e) {
            log.warn("釋放鎖異常", e);
        }
    }
    RedisConnectionUtils.releaseConnection(redisConnection, connectionFactory);
    return releaseFlag;
}


public void lockTest(String lockName, Long acquireTimeout, CouponSummary couponSummary)  {

    String lockIdentify = redisLock.lock(lockName,acquireTimeout);
    if (StringUtils.isNotEmpty(lockIdentify)){
        // 業(yè)務(wù)代碼
        redisLock.releaseLock(lockName, lockIdentify);
    }
    else{
        System.out.println("get lock failed.");
    }

}

分析

看完之后,有這幾點感悟

  1. setNX 和 expire 兩個操作是分開的,有一定的風(fēng)險(忘了釋放鎖,expire 失?。?/li>
  2. 加鎖時,除了 setNX ,還會去 ttl ,防止死鎖的發(fā)生。
  3. 釋放鎖時,會通過 UUID 去判斷這個鎖的值,避免釋放其他線程加的鎖,但是沒有考慮到這個 get 和 del 是兩個操作,還是會有意外,比如 releaseLock 時,執(zhí)行完 get ,判斷這個 uuid 是自己的,準(zhǔn)備刪除,但此時 鎖過期 了,其他線程剛好加鎖成功,結(jié)果又被你刪除了。
  4. 釋放鎖時沒有在 finally 塊中執(zhí)行
  5. 獲取不到鎖時,嘗試自旋等待鎖

再結(jié)合 redisson 框架來看的話,就會發(fā)現(xiàn)

  1. 少了 自動續(xù)期 的功能,如果業(yè)務(wù)執(zhí)行時間較長,鎖過期釋放掉了,就可能出現(xiàn)并發(fā)問題。
  2. 少了 可重入鎖 的功能,可以預(yù)見獲取鎖的線程,再次去加鎖也會失敗。
  3. 少了 lua腳本 ,lua 腳本能保證原子性操作,減少這個網(wǎng)絡(luò)開銷。

再把視角移到 Redis 服務(wù)器來,就會發(fā)現(xiàn) 單點問題 的存在,此時分布式鎖就無法使用了。

這個問題可以通過 主從,哨兵,集群 模式解決,但是又有了一個 故障轉(zhuǎn)移問題 。

先簡要介紹下這幾個模式

  1. Redis 主從復(fù)制模式:

一主多從,主節(jié)點負(fù)責(zé)寫,并同步到從節(jié)點。

從節(jié)點負(fù)責(zé)備份數(shù)據(jù),處理讀操作,提供讀負(fù)載均衡和故障切換。

  1. Redis 哨兵模式:
  • 主從基礎(chǔ)上增加了哨兵節(jié)點(Sentinel),一個獨立進程,去監(jiān)控所有節(jié)點,當(dāng)主節(jié)點宕機時,會從 slave 中選舉出新的主節(jié)點,并通知其他從節(jié)點更新配置
  • 哨兵節(jié)點負(fù)責(zé)執(zhí)行故障轉(zhuǎn)移、選舉新的主節(jié)點等操作
  1. Redis 集群模式:
  • 多個主從組成,由 master 去瓜分 16384 個 slot, 將數(shù)據(jù)分片存儲在多個節(jié)點上。
  • 節(jié)點間通過 Gossip 協(xié)議進行廣播通信,比如 新節(jié)點的加入,主從變更等

回到 分布式鎖 這個話題,通過主從切換,可以實現(xiàn)故障轉(zhuǎn)移。但是當(dāng)加鎖成功時,master 掛了,此時還沒同步鎖信息到這個 slave 上,那這個分布式鎖也是失效了。

網(wǎng)上的方案是通過  Redlock(紅鎖) 來解決。

Redlock 的大致意思就是給多個節(jié)點加鎖,超過半數(shù)成功的話,就認(rèn)為加鎖成功。

redisson 的紅鎖用法??

RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同時加鎖:lock1 lock2 lock3
// 紅鎖在大部分節(jié)點上加鎖成功就算成功。
lock.lock();
...
lock.unlock();

我更偏向于解決這個 主從復(fù)制延遲 的問題,比如

  • 升級硬件,更好的 CPU,帶寬
  • 避免從節(jié)點阻塞,比如操作一些 大Key
  • 調(diào)大 repl_backlog_size 參數(shù),避免全量同步

當(dāng)然,具體問題具體分析,可以根據(jù)業(yè)務(wù)準(zhǔn)備補償措施,但也要避免這個過度設(shè)計。

紅鎖爭論

在查閱資料時,看到了這么一個事情 ??

《數(shù)據(jù)密集型應(yīng)用系統(tǒng)設(shè)計》的作者 Martin 去反駁這個 Redlock ,并用一個進程暫停(GC)的例子,指出了 Redlock 安全性問題:

  1. 客戶端 1 請求鎖定節(jié)點 A、B、C、D、E
  2. 客戶端 1 的拿到鎖后,進入 GC(時間比較久)
  3. 所有 Redis 節(jié)點上的鎖都過期了
  4. 客戶端 2 獲取到了 A、B、C、D、E 上的鎖
  5. 客戶端 1 GC 結(jié)束,認(rèn)為成功獲取鎖
  6. 客戶端 2 也認(rèn)為獲取到了鎖,發(fā)生「沖突」

圖片圖片

還有 時鐘 漂移的問題

這里我就不過多 CV 了,可以看看原文??

相關(guān)文章

《一文講透Redis分布式鎖安全問題》:https://cloud.tencent.com/developer/article/2332108

《How to do distributed locking》https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

NPC 異常場景

  • N:Network Delay,網(wǎng)絡(luò)延遲
  • P:Process Pause,進程暫停(GC)
  • C:Clock Drift,時鐘漂移
責(zé)任編輯:武曉燕 來源: Java4ye
相關(guān)推薦

2020-07-30 09:35:09

Redis分布式鎖數(shù)據(jù)庫

2021-11-01 12:25:56

Redis分布式

2022-04-14 07:56:30

公平鎖Java線程

2024-02-19 00:00:00

Redis分布式

2019-06-19 15:40:06

分布式鎖RedisJava

2024-07-15 08:25:07

2022-09-29 08:28:57

SpringRedis分布式

2022-09-22 13:28:34

Redis分布式鎖

2023-03-01 08:07:51

2023-08-21 19:10:34

Redis分布式

2022-01-06 10:58:07

Redis數(shù)據(jù)分布式鎖

2020-05-12 14:03:51

RedisZooKeeper分布式鎖

2019-02-26 09:51:52

分布式鎖RedisZookeeper

2023-10-11 09:37:54

Redis分布式系統(tǒng)

2024-11-28 15:11:28

2022-12-18 20:07:55

Redis分布式

2023-09-21 22:22:51

開發(fā)分布式鎖

2024-04-01 05:10:00

Redis數(shù)據(jù)庫分布式鎖

2024-10-07 10:07:31

2022-11-11 08:19:03

redis分布式
點贊
收藏

51CTO技術(shù)棧公眾號