哥们儿/姐们儿,今天咱们说一下这个 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. 先是斥巨资修建了通往神殿的“任意门网络” (
jedisPool
),保证巫师们来去自如。 -
2. 然后从“巫师公会”高薪聘请了5位身怀绝技的巫师(一个有5个线程的
ExecutorService
)。 -
3. 接着发布了10个“十万火急”的魔法任务,这些任务都得拿到那根名叫
criticalTask
的权杖才能搞定。 -
4. 每当一个任务被指派给一位巫师时:
-
• 这位巫师会郑重其事地掏出他的“魔法契约书”(
new RedisDistributedLock(...)
),上面已经用隐形墨水写好了他这次行动的专属魔法印记。 -
• 他会深吸一口气,给自己两秒钟的时间(
myLock.tryLock(2000)
),使出浑身解数去抢那根权杖。 -
• 要是祖师爷保佑,抢成功了,他就会得意洋洋地开始处理共享资源,干完活儿之后,他会非常潇洒地把权杖还给神殿,绝不拖泥带水。
-
• 要是运气不好,没抢到,那也只能自认倒霉,任务失败。
-
你跑一下这段代码,就能看到控制台里,这些巫师们是如何“你方唱罢我登场”,虽然有时需要排队等待,但总能保证那根“魔法权杖”在任何时候都只被一位巫师握在手中,共享资源的操作因此变得井井有条,简直不要太完美!
终章:这“魔法权杖契约”,真是个宝!
契约精髓 |
Redis 分布式锁 (这套“魔法权杖系统”) |
核心价值 |
在“群雄并起”的分布式大陆上,确保同一时刻,只有一位天选之子(节点/线程)能掌控特定的“传国玉玺”(共享资源)。 |
SET NX PX 神技 |
“一步到位”式的“宣告主权并设定有效期”仪式,既公平又自带“后悔药”(过期机制)。 |
独门暗器 lockValue |
给权杖打上“专属防伪标签”,妈妈再也不用担心我的权杖被别人乱还了! |
“救命稻草” expireTime |
权杖自带“定时自毁”功能,就算持有者“英年早逝”(节点崩溃),也不会让权杖变成“传家宝”代代相传(死锁),其他继承者还有机会! |
“保险柜” Lua 脚本 unlock |
“验明正身再回收”的原子级仪式,安全释放权杖,稳如老狗! |
“屡败屡战”策略 |
允许巫师在一段时间内“死缠烂打”,增加抢到权杖的几率,同时又不会“无脑空等”。 |
啥时候该祭出这套“魔法契约”呢?
简单说,当你的“魔法王国”(分布式系统)里,有好几位“封疆大吏”(服务器/进程)都想去动同一块“皇家御用宝地”(一次只能由一个进程操作的敏感资源)时——比如,防止一件热门商品被“超卖”到姥姥家去、确保某个重要的初始化任务在整个集群里只执行一次、或者控制对某个配置文件的写入权限等等——这时候,这套“Redis 魔法权杖契约”绝对是你的“不二法宝”,能帮你把场面控制得妥妥帖帖!
兄弟/姐妹,讲了这么多,我自己都又激动了一遍!这 Redis 分布式锁,用起来是真香!你要是也碰到类似头疼的问题,真心强烈推荐你试试看,保证“药到病除”,系统稳定性“噌噌噌”往上涨!用了它,晚上睡觉都踏实多了,哈哈!