MySQL, Oracle, Linux, 软件架构及大数据技术知识分享平台

网站首页 > 精选文章 / 正文

分布式锁——程序员の抢厕所哲学(分布式锁锁的是什么?)

2025-03-26 12:43 huorong 精选文章 2 ℃ 0 评论

在高并发世界里,分布式锁就像公司唯一卫生间的钥匙。想象一下,双十一零点,10个程序员捂着肚子冲向厕所——这时候,谁先抢到"锁",谁就能优雅地解决问题(字面意义和隐喻意义上都是)。下面我们用抢厕所的视角,拆解分布式锁的奥义!


1. 基础操作:Redis 锁の初体验

如何优雅地抢坑位?

  1. 占坑(加锁): 使用 SET key value NX EX timeout 命令,相当于在厕所门口贴张纸条:"有人,30秒后自动销毁"。
String lockKey = "厕所:坑位:" + 程序员工号;
String clientId = "打工人_" + UUID.randomUUID(); // 用UUID防止冒名顶替
boolean success = redisTemplate.opsForValue()
    .setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS); 
// NX:没人用才占坑,EX:30秒后纸条自燃
  1. 释放坑位(删锁): 用Lua脚本保证原子操作,防止手抖删了别人的锁——这相当于离开时不仅要撕纸条,还得对暗号!
if redis.call("get", KEYS[1]) == ARGV[1] then
 return redis.call("del", KEYS[1]) // 暗号对上了才撕纸条
else
 return 0 // 否则可能是隔壁老王贴的纸条,不能动!
end

2. 锁の进阶生存指南

问题1:蹲坑时间太长被踢出门?——Watchdog续命大法

场景: 你带手机进厕所刷短视频,30秒到了纸条自燃,下一个人破门而入...画面太美不敢看。

解决方案: 召唤一只电子狗(Redisson的Watchdog),每10秒检查一次你是否还在坑位,如果是就续费30秒。

RLock lock = redissonClient.getLock(lockKey);
lock.lock(); // 启动电子狗盯梢
try {
    // 放心刷半小时短视频吧(不建议)
} finally {
    lock.unlock(); // 离开时记得关门!
}

问题2:一个线程反复抢坑位?——可重入锁

场景: 你在厕所里突然想再拿手机,结果发现手机还在外面桌上——于是你试图出门拿手机,但系统认为你已经在厕所里,拒绝让你再进(锁不可重入的悲剧)。

解决方案: 用Hash结构记录"进入次数",像进出办公室打卡一样:

-- 加锁Lua脚本(支持反复横跳)
if (redis.call('exists', KEYS[1]) == 0) then
    redis.call('hincrby', KEYS[1], ARGV[1], 1); -- 第一次进入
    redis.call('pexpire', KEYS[1], ARGV[2]);
    return 1;
end;
if (redis.call('hexists', KEYS[1], ARGV[1]) == 1) then
    redis.call('hincrby', KEYS[1], ARGV[1], 1); -- 第N次进入
    redis.call('pexpire', KEYS[1], ARGV[2]);
    return 1;
end;
return 0; -- 别人占着坑呢!

问题3:Redis集群脑裂?——RedLockの民主投票

场景: 老板A说厕所没人,老板B说有人,老板C掉线了——这时候该听谁的?(Redis主从切换导致锁状态混乱)

解决方案: RedLock算法:在5个独立Redis实例上投票,超过半数同意才算抢到坑位。

// 相当于找了5个老板同时审批你的如厕申请
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock(lockKey);
lock.lock(); // 获得3个老板的"同意"才能进去

3. Redis锁 vs ZooKeeper锁:便利店 vs 老派管家

ZooKeeper锁の优雅姿势

  1. 临时顺序节点: 在ZK的/toilet_lock下创建临时节点,像排队取号。只有最小的号能进,其他人监听前一个号——相当于在厕所门口排号等叫号。
  2. 断线自动释放: 如果你突然断网(比如手机没电),ZK会自动删除你的号——防止你"占着茅坑不拉屎"。

Battle结果

对比项

Redis锁

ZooKeeper锁

速度

快如闪电(内存操作)

慢如龟速(要写磁盘)

一致性

"大概也许可能"一致

"死也要一致"(ZAB协议保证)

上手难度

简单如抢红包

复杂如IKEA说明书

适用场景

秒杀抢购(短平快)

银行转账(不能出错)


4. 高阶骚操作

技巧1:把大厕所拆成格子间(分段锁)

  • 把100个库存拆成16个格子间(stock_1到stock_16),分散竞争压力:
int segment = productId.hashCode() % 16; // 对产品ID取模分区
String lockKey = "厕所:坑位:" + productId + ":" + segment; 
// 现在可以16个人同时抢了!

技巧2:设置等待时间(防止憋死)

boolean res = lock.tryLock(10, 30, TimeUnit.SECONDS); 
// 最多等10秒,30秒后自动释放(建议带止泻药)

技巧3:抢不到就摆烂(退避算法)

int maxRetries = 3; // 最多试3次
long baseDelay = 100; // 初始等100ms
for (int i = 0; i < maxRetries; i++) {
    if (tryLock()) break;
    Thread.sleep(baseDelay * (1 << i) + random.nextInt(100)); 
    // 第一次等100ms,第二次200ms...越来越佛系
}

技巧4:读多写少?搞VIP通道(读写锁)

RReadWriteLock rwLock = redisson.getReadWriteLock("rwLock");
rwLock.readLock().lock();  // 读锁:多人可同时看库存
rwLock.writeLock().lock(); // 写锁:改库存时独占厕所

5. 翻车现场与救命指南

翻车场景

保命方案

锁过期了但还没擦完屁股

Watchdog续命 + 预估时间(少刷短视频)

手抖删了别人的锁

UUID标识 + Lua脚本(对暗号再删)

Redis集群脑裂多人进厕所

RedLock投票(虽然可能降低抢坑效率)

抢锁的人太多系统卡死

拆格子间 + 读写锁(增加坑位数量)


终极总结

  • 选型建议: 大多数情况用Redisson(自带电子狗、可重入、支持RedLock),就像选自动挡汽车。 非要强一致性?上ZooKeeper(但准备好接受它的龟速)。
  • 三大哲学
  • 锁要细:宁可锁10个小柜子,不锁整个仓库(减少排队)
  • 超时设:给自己留后路,防止社会性死亡
  • 幂等保:万一锁失效,业务操作要能重试(比如扣库存用CAS
  • 骚操作提醒: 极端场景下,可以试试不用锁——比如用Redis的DECR直接扣库存(但记得先SETNX初始化啊兄dei!)

最后友情提示:任何锁方案都要实测!不然上线后半夜接报警电话的就是你(别问我是怎么知道的)

Tags:opsforhash

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言