欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

redis實(shí)現(xiàn)紅鎖的示例代碼

 更新時間:2025年04月08日 10:22:53   作者:morris131  
在分布式系統(tǒng)中,實(shí)現(xiàn)一個可靠的鎖機(jī)制是非常重要的,本文主要介紹了redis實(shí)現(xiàn)紅鎖的示例代碼,具有一定的參考價值,感興趣的可以了解一下

舉個真實(shí)的例子:你的團(tuán)隊(duì)剛上線了一個秒殺系統(tǒng),用Redis鎖來防止超賣。測試環(huán)境明明跑得好好的,但大促當(dāng)晚卻出現(xiàn)了100件庫存賣出了120單的靈異事件。查看日志才發(fā)現(xiàn):就在用戶瘋狂點(diǎn)擊的瞬間,Redis主節(jié)點(diǎn)突然掛了,新的主節(jié)點(diǎn)還沒拿到鎖的信息,結(jié)果兩個用戶同時搶到了"同一把鎖"。

這就是很多開發(fā)者踩過的坑——你以為用了Redis分布式鎖就萬事大吉,其實(shí)這些情況隨時可能讓鎖失效:

  • 主節(jié)點(diǎn)剛給你加完鎖就崩潰了,從節(jié)點(diǎn)接班時一臉懵:“什么鎖?我沒聽說過啊”
  • 你的程序正處理到一半突然卡住了(比如GC停頓),等回過神來鎖早就過期了
  • 網(wǎng)絡(luò)抽風(fēng)導(dǎo)致鎖信息沒傳到位,多個客戶端都覺得自己拿到了鎖

為了解決這些頭疼問題,Redis作者提出了**紅鎖(RedLock)**方案。簡單來說就是"不要把雞蛋放在一個籃子里":讓多個獨(dú)立的Redis節(jié)點(diǎn)投票決定鎖的歸屬,只有半數(shù)以上同意才算真正拿到鎖。

但這套方案也引發(fā)過激烈爭論,有人甚至說它"數(shù)學(xué)上就不安全"。本文將用最直白的語言:

  • 先帶你看看傳統(tǒng)Redis鎖在集群環(huán)境為什么容易翻車
  • 拆解紅鎖這個"少數(shù)服從多數(shù)"的解決方案
  • 手把手教你用Java代碼實(shí)現(xiàn)紅鎖
  • 揭秘Redisson框架如何簡化紅鎖的使用

讀完本文你會明白:沒有完美的分布式鎖,只有適合場景的選擇。下次設(shè)計(jì)系統(tǒng)時,至少能清楚知道手里的鎖到底有幾成把握。

集群鎖的缺陷與挑戰(zhàn)

在Redis Cluster環(huán)境中,傳統(tǒng)的SETNX分布式鎖存在以下致命缺陷:主從切換導(dǎo)致鎖失效。

問題步驟復(fù)現(xiàn):

  • 客戶端A通過SET key random_val NX PX 30000主節(jié)點(diǎn)成功獲取鎖

  • 主節(jié)點(diǎn)宕機(jī),Redis Cluster觸發(fā)故障轉(zhuǎn)移,從節(jié)點(diǎn)升級為新主節(jié)點(diǎn)

  • 由于Redis主從復(fù)制是異步的,鎖可能未同步到新主節(jié)點(diǎn)

  • 客戶端B向新主節(jié)點(diǎn)申請相同資源的鎖,成功獲取導(dǎo)致數(shù)據(jù)競爭

# 主節(jié)點(diǎn)寫入鎖
SET resource_1 8a3e72 NX PX 10000  
OK

# 主節(jié)點(diǎn)宕機(jī),從節(jié)點(diǎn)晉升但未同步鎖數(shù)據(jù)
# 新主節(jié)點(diǎn)處理客戶端B的請求
SET resource_1 5b9fd2 NX PX 10000  
OK  # 鎖被重復(fù)獲?。?

紅鎖(RedLock)的設(shè)計(jì)與實(shí)現(xiàn)

N個獨(dú)立Redis節(jié)點(diǎn)(非Cluster模式)中,當(dāng)客戶端在半數(shù)以上節(jié)點(diǎn)成功獲取鎖,且總耗時小于鎖有效期時,才認(rèn)為鎖獲取成功。

實(shí)現(xiàn)步驟詳解

假設(shè)部署5個Redis節(jié)點(diǎn)(N=5):

  • 獲取當(dāng)前時間:記錄開始時間T1(毫秒精度)

  • 依次向所有節(jié)點(diǎn)申請鎖

SET lock_key valueNX PX $ttl
  • value:全局唯一值(如UUID)
  • ttl:鎖自動釋放時間(如10秒)
  • 計(jì)算鎖有效性
  • 客戶端計(jì)算獲取鎖總耗時T_elapsed = T2 - T1(T2為最后響應(yīng)時間)

  • 僅當(dāng)以下兩個條件滿足時,鎖才有效:

    成功獲取鎖的節(jié)點(diǎn)數(shù) ≥ 3(N/2 + 1)

    T_elapsed < ttl(確保鎖未過期)

  • 加鎖成功,去操作共享資源

  • 釋放鎖:向所有節(jié)點(diǎn)發(fā)送Lua腳本刪除鎖(需驗(yàn)證值)

if redis.call("get",KEYS[1]) == ARGV[1] then
   return redis.call("del",KEYS[1])
else
   return 0
end

NPC爭議問題

紅鎖算法自誕生起就伴隨著**N(網(wǎng)絡(luò)延遲)、P(進(jìn)程暫停)、C(時鐘漂移)**三個核心爭議,這些現(xiàn)實(shí)世界中的不確定因素,動搖了紅鎖在數(shù)學(xué)意義上的絕對安全性。

網(wǎng)絡(luò)延遲(Network Delay)的致命時間差

問題場景

  • 客戶端在節(jié)點(diǎn)A、B、C成功獲取鎖,總耗時48ms(小于TTL 50ms)

  • 但由于跨機(jī)房網(wǎng)絡(luò)波動,實(shí)際鎖在節(jié)點(diǎn)上的有效時間存在差異:

    • 節(jié)點(diǎn)A記錄的鎖過期時間:客戶端本地時間+50ms = T+50
    • 節(jié)點(diǎn)B因網(wǎng)絡(luò)延遲,實(shí)際鎖過期時間為T+52
    • 節(jié)點(diǎn)C因網(wǎng)絡(luò)擁塞,實(shí)際鎖過期時間僅T+48
  • 在時間窗口T+48T+50之間,客戶端認(rèn)為鎖仍有效,但節(jié)點(diǎn)C的鎖已提前失效

后果
其他客戶端可能在此期間獲取節(jié)點(diǎn)C的鎖,導(dǎo)致鎖狀態(tài)分裂,多個客戶端同時進(jìn)入臨界區(qū)。

進(jìn)程暫停(Process Pause)的「薛定諤鎖」

經(jīng)典案例

// 偽代碼:獲取鎖后執(zhí)行業(yè)務(wù)邏輯
if (redLock.tryLock()) {
    // 觸發(fā)Full GC暫停300ms
    System.gc(); 
    
    // 此時鎖已過期,但客戶端仍在寫數(shù)據(jù)
    updateInventory(); 
}

關(guān)鍵時間線

  • T0: 獲取鎖(TTL=200ms)
  • T0+100ms: 進(jìn)入GC暫停,持續(xù)300ms
  • T0+400ms: GC結(jié)束,繼續(xù)執(zhí)行業(yè)務(wù)邏輯
  • 鎖實(shí)際在T0+200ms已失效,但客戶端在T0+400ms仍以為自己持有鎖

數(shù)據(jù)災(zāi)難
其他客戶端在T0+200ms到T0+400ms期間可能修改數(shù)據(jù),導(dǎo)致最終結(jié)果錯亂。

時鐘漂移(Clock Drift)的時空扭曲

物理機(jī)時鐘偏移實(shí)驗(yàn)數(shù)據(jù)

節(jié)點(diǎn)時鐘誤差范圍常見誘因
節(jié)點(diǎn)A±200ms/分鐘虛擬機(jī)時鐘不同步
節(jié)點(diǎn)B±500ms/天NTP服務(wù)異常
節(jié)點(diǎn)C±10秒/小時宿主機(jī)硬件時鐘故障

連鎖反應(yīng)

  • 客戶端計(jì)算鎖有效期基于本地時鐘(假設(shè)為T+100ms)
  • 但節(jié)點(diǎn)B的時鐘比實(shí)際快30秒,導(dǎo)致其記錄的鎖過期時間為T-29000ms
  • 鎖在客戶端認(rèn)為的有效期內(nèi)提前被節(jié)點(diǎn)B自動釋放

行業(yè)領(lǐng)袖的正面交鋒

Martin Kleppmann(《數(shù)據(jù)密集型應(yīng)用設(shè)計(jì)》作者)

“紅鎖依賴的假設(shè)——『客戶端能準(zhǔn)確感知鎖存活時間』,在異步分布式系統(tǒng)中根本無法保證。即使沒有節(jié)點(diǎn)故障,NPC問題也會導(dǎo)致鎖狀態(tài)的不確定性。”

Antirez(Redis作者)的反駁

"工程實(shí)踐中可以通過以下手段控制風(fēng)險(xiǎn):

使用帶溫度補(bǔ)償?shù)脑隅娪布?/p>

禁用NTP服務(wù)的時鐘跳變調(diào)整

監(jiān)控進(jìn)程暫停(如GC日志分析)

為鎖TTL設(shè)置冗余緩沖時間(如額外20%)"

紅鎖的Java實(shí)現(xiàn)示例

使用Jedis客戶端實(shí)現(xiàn)紅鎖:

package com.morris.redis.demo.redlock;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 使用jedis手寫RedLock
 */
public class JedisRedLock {

    public static final int EXPIRE_TIME = 30_000;

    private final List<JedisPool> jedisPoolList;

    private final String lockKey;

    private final String lockValue;

    public JedisRedLock(List<JedisPool> jedisPoolList, String lockKey) {
        this.jedisPoolList = jedisPoolList;
        this.lockKey = lockKey;
        this.lockValue = UUID.randomUUID().toString();
    }

    public void lock() {
        while (!tryLock()) {
            try {
                TimeUnit.MILLISECONDS.sleep(100); // 失敗后短暫等待
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public boolean tryLock() {
        long startTime = System.currentTimeMillis();
        int successCount = 0;
        try {
            for (JedisPool jedisPool : jedisPoolList) {
                try (Jedis jedis = jedisPool.getResource();) {
                    // 原子化加鎖:SET lockKey UUID NX PX expireTime
                    String result = jedis.set(lockKey, lockValue,
                            SetParams.setParams().nx().px(EXPIRE_TIME));
                    if ("OK".equals(result)) {
                        successCount++;
                    }
                }
            }
            // 計(jì)算獲取鎖耗時
            long elapsedTime = System.currentTimeMillis() - startTime;

            // 驗(yàn)證:多數(shù)節(jié)點(diǎn)成功 且 耗時小于TTL
            return successCount >= (jedisPoolList.size() / 2 + 1) && elapsedTime < EXPIRE_TIME;
        } finally {
            // 若加鎖失敗,立即釋放已獲得的鎖
            if (successCount < (jedisPoolList.size() / 2 + 1)) {
                unlock();
            }
        }
    }

    public void unlock() {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

        for (JedisPool jedisPool : jedisPoolList) {
            try (Jedis jedis = jedisPool.getResource()) {
                jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
            }
        }
    }

}

手寫RedLock的使用:

package com.morris.redis.demo.redlock;

import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 手寫RedLock的使用
 */
public class JedisRedLockDemo {

    private volatile static int count;

    public static void main(String[] args) throws InterruptedException {
        List<JedisPool> jedisPoolList = new ArrayList<>();
        jedisPoolList.add(new JedisPool(new JedisPoolConfig(), "127.0.0.1", 6379));
        jedisPoolList.add(new JedisPool(new JedisPoolConfig(), "127.0.0.1", 6380));
        jedisPoolList.add(new JedisPool(new JedisPoolConfig(), "127.0.0.1", 6381));
        jedisPoolList.add(new JedisPool(new JedisPoolConfig(), "127.0.0.1", 6382));
        jedisPoolList.add(new JedisPool(new JedisPoolConfig(), "127.0.0.1", 6383));

        int threadCount = 3;
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);

        for (int i = 0; i < threadCount; i++) {
            executorService.submit(() -> {
                JedisRedLock jedisRedLock = new JedisRedLock(jedisPoolList, "lock-key");
                jedisRedLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "獲得鎖,開始執(zhí)行業(yè)務(wù)邏輯。。。");
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "獲得鎖,結(jié)束執(zhí)行業(yè)務(wù)邏輯。。。");
                    count++;
                } finally {
                    jedisRedLock.unlock();
                }
                countDownLatch.countDown();
            });
        }

        countDownLatch.await();
        executorService.shutdown();
        System.out.println(count);
    }

}

Redisson中紅鎖的使用

Redisson已封裝紅鎖實(shí)現(xiàn),自動處理節(jié)點(diǎn)通信與鎖續(xù)期:

package com.morris.redis.demo.redlock;

import org.redisson.Redisson;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * Redisson中紅鎖的使用
 */
public class RedissonRedLockDemo {

    private volatile static int count;

    public static void main(String[] args) throws InterruptedException {

        List<String> serverList = Arrays.asList("redis://127.0.0.1:6379", "redis://127.0.0.1:6380", "redis://127.0.0.1:6381",
                "redis://127.0.0.1:6382", "redis://127.0.0.1:6383");

        List<RedissonClient> redissonClientList = new ArrayList<>(serverList.size());
        for (String server : serverList) {
            Config config = new Config();
            config.useSingleServer()
                    .setAddress(server);

            redissonClientList.add(Redisson.create(config));
        }

        List<RLock> lockList = new ArrayList<>(redissonClientList.size());
        for (RedissonClient redissonClient : redissonClientList) {
            lockList.add(redissonClient.getLock("java-lock"));
        }
        
        int threadCount = 3;
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);

        for (int i = 0; i < threadCount; i++) {
            executorService.submit(() -> {
                RedissonRedLock redissonRedLock = new RedissonRedLock(lockList.toArray(new RLock[0]));
                redissonRedLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "獲得鎖,開始執(zhí)行業(yè)務(wù)邏輯。。。");
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "獲得鎖,結(jié)束執(zhí)行業(yè)務(wù)邏輯。。。");
                    count++;
                } finally {
                    redissonRedLock.unlock();
                }
                countDownLatch.countDown();
            });
        }

        countDownLatch.await();
        executorService.shutdown();
        System.out.println(count);

        for (RedissonClient redissonClient : redissonClientList) {
            redissonClient.shutdown();
        }
    }

}

Redisson優(yōu)勢

  • 自動續(xù)期:通過WatchDog機(jī)制延長鎖有效期
  • 簡化API:封裝底層細(xì)節(jié),支持異步/響應(yīng)式編程
  • 故障容錯:自動跳過宕機(jī)節(jié)點(diǎn),保證半數(shù)以上成功即可

總結(jié)

紅鎖通過多節(jié)點(diǎn)投票機(jī)制,顯著提升了分布式鎖的可靠性,但需權(quán)衡其實(shí)現(xiàn)復(fù)雜度與運(yùn)維成本。建議在以下場景選擇紅鎖:

  • 需要跨機(jī)房/地域部署
  • 業(yè)務(wù)對數(shù)據(jù)一致性要求極高
  • 已具備獨(dú)立Redis節(jié)點(diǎn)運(yùn)維能力

對于大多數(shù)場景,可優(yōu)先使用Redisson等成熟框架,避免重復(fù)造輪子。若對一致性有極致要求,可考慮ZooKeeper/etcd等基于共識算法的方案。

到此這篇關(guān)于redis實(shí)現(xiàn)紅鎖的示例代碼的文章就介紹到這了,更多相關(guān)redis實(shí)現(xiàn)紅鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • redis常用命令、常見錯誤、配置技巧等分享

    redis常用命令、常見錯誤、配置技巧等分享

    這篇文章主要介紹了redis常用命令、常見錯誤、配置技巧等分享,本文分享了12條redis知識,需要的朋友可以參考下
    2015-02-02
  • CentOS系統(tǒng)安裝Redis及Redis的PHP擴(kuò)展詳解

    CentOS系統(tǒng)安裝Redis及Redis的PHP擴(kuò)展詳解

    這篇文章主要介紹了CentOS系統(tǒng)下安裝Redis數(shù)據(jù)的教程,以及詳解了Redis數(shù)據(jù)庫的PHP擴(kuò)展,文中介紹的很詳細(xì),相信對大家的理解和學(xué)習(xí)具有一定的參考借鑒價值,有需要的朋友們可以參考借鑒,下面來一起看看吧。
    2016-12-12
  • Redis2.8配置文件中文詳解

    Redis2.8配置文件中文詳解

    這篇文章主要介紹了Redis2.8配置文件中文詳解,本文提供的是是Redis2.8.9的配置文件各項(xiàng)的中文解釋,需要的朋友可以參考下
    2015-06-06
  • redis解決高并發(fā)看門狗策略的實(shí)現(xiàn)

    redis解決高并發(fā)看門狗策略的實(shí)現(xiàn)

    本文主要介紹了redis解決高并發(fā)看門狗策略的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-02-02
  • 大家都應(yīng)該知道的Redis過期鍵與過期策略

    大家都應(yīng)該知道的Redis過期鍵與過期策略

    這篇文章主要給大家介紹了一些應(yīng)該知道的Redis過期鍵與過期策略的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • redis?setIfAbsent返回null的問題及解決

    redis?setIfAbsent返回null的問題及解決

    這篇文章主要介紹了redis?setIfAbsent返回null的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • Ubuntu22.04 LTS 上安裝Redis的過程

    Ubuntu22.04 LTS 上安裝Redis的過程

    Redis是一種開源的內(nèi)存數(shù)據(jù)存儲,可以用作數(shù)據(jù)庫、緩存和消息代理等,本文將會介紹兩種不同的安裝方式,包括從源代碼編譯安裝以及通過apt包管理器安裝,需要的朋友參考下吧
    2023-11-11
  • Redis協(xié)議具體用法詳解

    Redis協(xié)議具體用法詳解

    在本篇文章中小編給大家整理了關(guān)于Redis協(xié)議具體用法以及相關(guān)內(nèi)容知識點(diǎn),需要的朋友們學(xué)習(xí)下。
    2019-06-06
  • Redis實(shí)現(xiàn)延遲任務(wù)的常見方案詳解

    Redis實(shí)現(xiàn)延遲任務(wù)的常見方案詳解

    延遲任務(wù)(Delayed?Task)是指在未來的某個時間點(diǎn),執(zhí)行相應(yīng)的任務(wù),本文為大家整理了Redis實(shí)現(xiàn)延遲任務(wù)的幾個常見方案,希望對大家有所幫助
    2024-04-04
  • Redis和Lua實(shí)現(xiàn)分布式限流器的方法詳解

    Redis和Lua實(shí)現(xiàn)分布式限流器的方法詳解

    這篇文章主要給大家介紹了關(guān)于Redis和Lua實(shí)現(xiàn)分布式限流器的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Redis和Lua具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06

最新評論