小白也能懂的Redis分布式锁,故事版教程来了!

图片

哥们儿/姐们儿,今天咱们说一下这个 Redis 分布式锁啊!这玩意儿,简直就是我们这些搞分布式系统的“救星”啊,每次用起来都忍不住想给它点个大大的赞!

你想想啊,现在这年头,系统稍微搞大点儿,就是好几个服务、好几台机器一块儿跑,对吧?那万一它们都想去动同一个宝贝疙瘩(比如某个共享数据,或者一个只能一个人干的活儿),那不得乱套了?就跟一群人抢一个麦克风唱歌似的,不排好队,那肯定是“群魔乱舞”,系统数据指不定就给你整出“幺蛾子”了!

这时候,咱们的“Redis 大神殿”(就是 Redis 服务器啦)就闪亮登场了!我们可以把它想象成一个法力无边的“中央仲裁所”,里面供奉着一把“独一无二的魔法权杖”(这就是我们的锁)。

序章:契约的缔结与权杖的专属印记

// 代码片段 1: RedisDistributedLock 的构造与核心属性
publicclassRedisDistributedLock {
    // ... (常量定义,比如 LOCK_SUCCESS 什么的,就当是神殿的官方回复暗号吧)

    privatefinal JedisPool jedisPool; // 这就是通往“Redis大神殿”的“VIP快速通道”池
    privatefinal String lockKey;       // 权杖在神殿里的“大名”,比如 "lock:秒杀活动A的库存"
    privatefinal String lockValue;     // 刻在权杖上的“专属巫师印记”,绝对不能跟别人重样!
    privatefinalint expireTimeMillis; // 权杖魔力自动消散的时间,免得有人“耍赖”不还

    publicRedisDistributedLock(JedisPool jedisPool, String resourceName, int expireTimeMillis) {
        this.jedisPool = jedisPool;
        this.lockKey = LOCK_KEY_PREFIX + resourceName; // 给权杖起个响亮的名字
        // 这招儿绝了!每个想抢权杖的巫师,都会偷偷给自己准备一个独一无二的“暗号”(UUID + 线程ID)
        // 这样,他还权杖的时候,神殿一对暗号,“欸,是你小子,没错!”这才把权杖收回来。别人想冒名顶替?没门儿!
        this.lockValue = UUID.randomUUID().toString() + "-" + Thread.currentThread().getId();
        this.expireTimeMillis = expireTimeMillis; // 咱得给权杖设个“保质期”
    }
}

故事解读:
当一个“巫师”(比如咱们的一个服务线程)想要去操作那个“宝贝疙瘩”资源(比如 criticalTask)之前,他得先跟“Redis 大神殿”签个“魔法契约”(就是创建一个 RedisDistributedLock 实例)。

  • • jedisPool: 这玩意儿就牛了,相当于给每个巫师都配了好几条直达“Redis 大神殿”的“任意门”,想去就能去,效率杠杠的!

  • • lockKey: 这就是那根“魔法权杖”在神殿里的正式备案名,比如叫 lock:criticalTaskScepter。每种宝贝疙瘩都对应一根独一无二的权杖,不能搞混了!

  • • lockValue: 这个简直是神来之笔!每个巫师在准备抢权杖的时候,都会给自己生成一个绝对不会跟别人撞衫的“魔法印记”(用 UUID 再加上线程ID,双重保险!)。如果他抢到了权杖,这个印记就会“Duang”一下刻在权杖上。为啥要这么干?就是怕张三的权杖被李四给不小心还了,那不就乱套了嘛!有了这印记,谁的权杖谁负责,清清楚楚!

  • • expireTimeMillis: 这个设定太贴心了!权杖被施加了一个“有借有还,再借不难,但你要是忘了还,我就帮你还”的魔咒。万一哪个倒霉蛋巫师拿着权杖,自己服务器“嗝屁”了,或者程序卡死了,没来得及把权杖还回来,那这权杖也不能一直被他“霸占”着啊。到了设定的时间(比如5秒),权杖上的魔力自动“咻”地一下就消失了,权杖重新变成“无主”状态,其他巫师又能公平竞争了。这就完美避免了“死锁”——那种权杖被人永久拿走,别人干瞪眼的尴尬局面,你说赞不赞?


第一卷:夺宝奇兵之“权杖争夺战” - tryLock()

// 代码片段 2: 非阻塞尝试获取锁 - tryLock()
publicbooleantryLock() {
    try (Jedisjedis= jedisPool.getResource()) { // 赶紧开一道“任意门”去神殿
        // 关键咒语来了!尝试在神殿中以权杖之名 (lockKey) 宣告“这权杖现在归我了!”,并刻上我的印记 (lockValue)
        // NX: "若权杖现在没人要 (Not eXists)",我的宣告才有效!先到先得!
        // PX: "并且,这权杖的魔力只能持续这么久 (eXpire in Milliseconds)"
        // 这一整套操作,在神殿里是“一步到位”的,绝不会出岔子!
        Stringresult= jedis.set(lockKey, lockValue, SetParams.setParams().nx().px(expireTimeMillis));
        return LOCK_SUCCESS.equals(result); // 神殿要是回了个 "OK",那恭喜你,权杖到手!
    } catch (Exception e) { /* ...万一路上出意外了,比如网络不通,那就算抢失败了 */returnfalse; }
}

故事解读:
巫师准备妥当,就开始执行“抢权杖”的仪式(就是调用 tryLock() 方法)。

  • • try (Jedis jedis = jedisPool.getResource()): 巫师麻利地从“任意门池”里掏出一扇门,直达 Redis 大神殿。

  • • jedis.set(lockKey, lockValue, SetParams.setParams().nx().px(expireTimeMillis)): 这可是整个仪式的灵魂所在!这句咒语念出去,意思就是:

    • • “报告神殿!我要用 lockKey 这个名号的权杖,请立刻刻上我的专属印记 lockValue!”

    • • NX (Not eXists) 这俩字母是精髓:“前提是这根权杖现在得是‘无主’的,谁都还没拿着!” 如果已经有别的巫师的印记在上面了,那这次宣告就“凉凉”了。

    • • PX expireTimeMillis: “并且,我这次持有权杖的有效时间就是 expireTimeMillis 这么长!”

    • • 原子性,我的天,太重要了! 整个“宣告-刻印-设有效期”的过程,在神殿里是“一气呵成”的,要么就全套动作完美成功,权杖归你;要么就压根儿没成功,你啥也没捞着。绝不可能出现“刻上印了,但有效期忘了设”这种不上不下的倒霉事儿。

  • • LOCK_SUCCESS.equals(result): 神殿如果回了个 OK 的信儿,那真是谢天谢地,巫师成功抢到了权杖!可以开始“为所欲为”(在共享资源上操作)了!要是没回 OK,那就说明手慢了,权杖被别的“快手”巫师抢走了。

“锲而不舍”的争夺 - tryLock(long timeoutMillis)

// 代码片段 3: 带超时的尝试获取锁
publicbooleantryLock(long timeoutMillis)throws InterruptedException {
    longstartTime= System.currentTimeMillis();
    while (System.currentTimeMillis() - startTime < timeoutMillis) { // 我给自己设个闹钟,就在这段时间里拼了!
        if (tryLock()) { returntrue; } // 先理直气壮地试一次!
        // 要是没抢到,也别灰心,咱“猥琐发育”,稍微“打个盹儿”(LockSupport.parkNanos),别把神殿的门槛给踏平了,然后再试试手气!
        LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(50 + (long)(Math.random() * 50)));
        if (Thread.currentThread().isInterrupted()) { // 万一在“打盹儿”的时候被人叫醒了(中断了),那就不抢了
            thrownewInterruptedException("抢权杖的时候被人打断了,不开心!");
        }
    }
    returnfalse; // 唉,闹钟响了,还是没抢到,只能“忍痛割爱”了
}

故事解读:
有些巫师啊,比较有毅力,他会执行一个“不抛弃不放弃”版的抢权杖仪式(就是那个带超时参数的 tryLock)。他会心里默念:“我就不信这个邪,我等!我再等!” 在他设定的最长等待时间内,他会像个“执着的追求者”一样,隔三差五就去神殿问问:“权杖空出来没有呀?” 每次碰壁后,他会很识趣地“稍作休整”(LockSupport.parkNanos),比如去喝口水、定定神,免得把神殿的长老们给“烦死”(避免无效空转,消耗CPU)。如果在规定时间内,他“守得云开见月明”,抢到了权杖,那自然是美滋滋。要是时间到了,权杖还是别人的,那也只能“认栽”,下次再来了。


第二卷:“完璧归赵”的艺术 - unlock()

// 代码片段 4: 释放锁 - unlock()
publicvoidunlock() {
    // 这段 Lua 古咒语可厉害了,是保证“安全还权杖”的不二法门!
    Stringscript="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    try (Jedisjedis= jedisPool.getResource()) { // 再次打开“任意门”
        // 巫师虔诚地向神殿吟唱这段 Lua 古咒语
        Objectresult= jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
        if (RELEASE_SUCCESS.equals(result)) {
            System.out.println(Thread.currentThread().getName() + ": 权杖 " + lockKey + " 已成功归还,我的印记是 " + lockValue.substring(0,8) + "... 感觉自己棒棒哒!");
        } else {
            // 这就有意思了,可能是我拿着权杖的时候“超时”了,权杖自己“跑路”了,或者被别人“捷足先登”了
            // 也可能是我压根就没抢到权杖,在这儿瞎操作呢
            System.out.println(Thread.currentThread().getName() + ": 哎呀,没能归还权杖 " + lockKey + "!是不是我的印记 " + lockValue.substring(0,8) + "... 跟权杖上现在的不一样了,或者权杖已经不见了?");
        }
    } catch (Exception e) { /* ...万一又出意外了... */ }
}

故事解读:
“好借好还,再借不难!” 巫师用完了共享魔法资源,就得有公德心,赶紧把权杖还回去(调用 unlock() 方法)。

  • • String script = "...";: 这可不是普通的字符串,这是一段用“上古神语 Lua”写成的“安全归还密令”。它的意思是:

    • • if redis.call('get', KEYS[1]) == ARGV[1]: “神殿啊神殿,请您老人家先帮忙瞅一眼,名叫 KEYS[1](就是咱的 lockKey)的这根权杖上,刻着的印记是不是跟我现在报的 ARGV[1](就是咱的 lockValue)一模一样啊?”

    • • then return redis.call('del', KEYS[1]): “如果真是一样,那妥了,麻烦您大发慈悲,把这根权杖从您的登记簿上划掉(就是删除这个key),让它重新变成‘无主’的。”

    • • else return 0: “如果不一样(哎哟,那说明在我不知道的时候,权杖可能已经‘易主’了,或者因为超时自己‘消失’了),那您可千万别动它,就当无事发生。”

  • • jedis.eval(script, ...): 巫师毕恭毕敬地把这段 Lua 密令传给神殿。神殿收到后,会在自己的“密室”里(Redis 服务器内部)执行这段密令。重点又来了:这也是原子性的! “检查印记”和“删除权杖”这两个动作是“打包执行”,中间绝不会有任何其他巫师的操作能插进来捣乱。

  • • RELEASE_SUCCESS.equals(result): 如果密令执行后,神殿回了个 1(咱们代码里用 RELEASE_SUCCESS 代表),那就说明权杖妥妥地还回去了,巫师可以安心地“功成身退”了。如果回的是 0,那巫师就得琢磨了:“咦?难道我磨叽太久,权杖超时失效,被别人抢先一步刻上新印记了?还是我压根就没抢到,在这儿瞎激动呢?”

为啥非得用这么复杂的 Lua 脚本呢?直接先 get 再 del 不行吗?
兄弟,这你就不懂了!你想啊,如果巫师先念个咒语(get)看看权杖上的印记是不是自己的,“嗯,是我的!” 然后他刚准备念下一个咒语(del)把权杖还了,就在这千钧一发的零点零零一秒,他手里的权杖因为设置的有效期到了,“biu”地一下失效了!紧接着,另一个眼疾手快的巫师B“嗖”地一下抢到了这根刚“自由”的权杖,并刻上了自己的印记。这时候,我们倒霉的巫师A还傻乎乎地念出第二个咒语(del),结果呢?他把巫师B刚抢到手的、热乎乎的权杖给删了!你说这叫什么事儿?天下大乱啊!所以,用 Lua 脚本,把“检查印记”和“删除权杖”这两个动作“锁死”在一起,变成一个不可分割的整体,在神殿内部一次性搞定,这才叫专业,这才叫安全!


第三卷:群雄逐鹿之“巫师竞技场” - main() 方法的热闹场面

// 代码片段 5: 主程序中的模拟
publicstaticvoidmain(String[] args)throws InterruptedException {
    // ... 一通操作,初始化好通往神殿的“任意门池”(JedisPool) ...
    ExecutorServiceexecutor= Executors.newFixedThreadPool(5); // 咱从巫师公会摇了5位顶级巫师过来!
    for (inti=0; i < numberOfTasks; i++) { // 布置了一堆需要抢权杖才能完成的魔法任务
        executor.submit(() -> {
            // 每位巫师都摩拳擦掌,为那根名叫 "criticalTask" 的权杖,精心准备了自己的“魔法契约”
            RedisDistributedLockmyLock=newRedisDistributedLock(jedisPool, resourceName, lockExpireTime);
            System.out.println(Thread.currentThread().getName() + " (任务 " + taskId + "): 我," + myLock.lockValue.substring(0,8) + "...号巫师,准备为'" + resourceName + "'权杖拼一把!");

            booleanacquired=false;
            try {
                // “兄弟们,给我两秒钟,看我能不能把权杖抢到手!” (tryLock with timeout)
                if (myLock.tryLock(2000)) {
                    acquired = true;
                    System.out.println(Thread.currentThread().getName() + " (任务 " + taskId + "): 哈哈哈!权杖到手!小的们,跟我一起干活儿!");
                    // ... 巫师拿到权杖,心满意足地开始施展他的独门绝技(处理共享资源)...
                    Thread.sleep(1000 + (long)(Math.random() * 2000)); // 象征性地忙活一阵子
                    System.out.println(Thread.currentThread().getName() + " (任务 " + taskId + "): 搞定!完美!");
                } else {
                    System.out.println(Thread.currentThread().getName() + " (任务 " + taskId + "): 呜呜呜,手太慢了,没抢到权杖,这次任务白跑一趟...");
                }
            } catch (InterruptedException e) {
                // ...万一在抢或者干活的时候被打断了...
            } finally {
                if (acquired) { // 干完活儿,或者就算中途出错了,只要咱拿了权杖,就得负责还回去!
                    System.out.println(Thread.currentThread().getName() + " (任务 " + taskId + "): 任务结束,把权杖还给神殿,深藏功与名...");
                    myLock.unlock();
                }
            }
        });
    }
    // ... 等所有的巫师都忙活完 ...
}

故事解读:
在 main 这个“巫师竞技场”里,我们导演了一出好戏:

  1. 1. 先是斥巨资修建了通往神殿的“任意门网络” (jedisPool),保证巫师们来去自如。

  2. 2. 然后从“巫师公会”高薪聘请了5位身怀绝技的巫师(一个有5个线程的 ExecutorService)。

  3. 3. 接着发布了10个“十万火急”的魔法任务,这些任务都得拿到那根名叫 criticalTask 的权杖才能搞定。

  4. 4. 每当一个任务被指派给一位巫师时:

    • • 这位巫师会郑重其事地掏出他的“魔法契约书”(new RedisDistributedLock(...)),上面已经用隐形墨水写好了他这次行动的专属魔法印记

    • • 他会深吸一口气,给自己两秒钟的时间(myLock.tryLock(2000)),使出浑身解数去抢那根权杖。

    • • 要是祖师爷保佑,抢成功了,他就会得意洋洋地开始处理共享资源,干完活儿之后,他会非常潇洒地把权杖还给神殿,绝不拖泥带水。

    • • 要是运气不好,没抢到,那也只能自认倒霉,任务失败。

你跑一下这段代码,就能看到控制台里,这些巫师们是如何“你方唱罢我登场”,虽然有时需要排队等待,但总能保证那根“魔法权杖”在任何时候都只被一位巫师握在手中,共享资源的操作因此变得井井有条,简直不要太完美!


终章:这“魔法权杖契约”,真是个宝!

契约精髓

Redis 分布式锁 (这套“魔法权杖系统”)

核心价值

在“群雄并起”的分布式大陆上,确保同一时刻,只有一位天选之子(节点/线程)能掌控特定的“传国玉玺”(共享资源)。

SET NX PX 神技

“一步到位”式的“宣告主权并设定有效期”仪式,既公平又自带“后悔药”(过期机制)。

独门暗器 lockValue

给权杖打上“专属防伪标签”,妈妈再也不用担心我的权杖被别人乱还了!

“救命稻草” expireTime

权杖自带“定时自毁”功能,就算持有者“英年早逝”(节点崩溃),也不会让权杖变成“传家宝”代代相传(死锁),其他继承者还有机会!

“保险柜” Lua 脚本 unlock

“验明正身再回收”的原子级仪式,安全释放权杖,稳如老狗!

“屡败屡战”策略

允许巫师在一段时间内“死缠烂打”,增加抢到权杖的几率,同时又不会“无脑空等”。

啥时候该祭出这套“魔法契约”呢?
简单说,当你的“魔法王国”(分布式系统)里,有好几位“封疆大吏”(服务器/进程)都想去动同一块“皇家御用宝地”(一次只能由一个进程操作的敏感资源)时——比如,防止一件热门商品被“超卖”到姥姥家去、确保某个重要的初始化任务在整个集群里只执行一次、或者控制对某个配置文件的写入权限等等——这时候,这套“Redis 魔法权杖契约”绝对是你的“不二法宝”,能帮你把场面控制得妥妥帖帖!

兄弟/姐妹,讲了这么多,我自己都又激动了一遍!这 Redis 分布式锁,用起来是真香!你要是也碰到类似头疼的问题,真心强烈推荐你试试看,保证“药到病除”,系统稳定性“噌噌噌”往上涨!用了它,晚上睡觉都踏实多了,哈哈!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java干货

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值