上一篇
场景引入:
凌晨三点,你被报警电话惊醒——线上订单系统出现了重复扣款,排查日志发现,两个请求同时通过了余额校验,结果各自扣了用户一笔钱,你揉着太阳穴想:"明明加了Redis分布式锁啊..."
这就是分布式锁的典型翻车现场,很多人以为锁的封装无非就是lock()
和unlock()
,但真正高并发的生产环境会教你做人,今天我们就掰开揉碎聊聊这里面的门道。
用Redis的SETNX
实现锁时,新手常犯的错误:
# 错误示范 def deduct_balance(): if redis.setnx("order_lock", 1): # 获取锁 try: # 业务处理 time.sleep(10) # 模拟耗时操作 finally: redis.delete("order_lock") # 释放锁
问题在于:如果业务处理到一半机器宕机,这个锁就永远无法释放!正确的做法必须设置过期时间:
# 正确姿势 if redis.set("order_lock", 1, nx=True, ex=30): # 原子性设置值+过期时间 try: process_business() finally: if redis.get("order_lock") == 1: # 确保只删除自己的锁 redis.delete("order_lock")
更隐蔽的场景是:客户端A获取锁后因GC暂停,锁过期被释放,客户端B获取锁,此时A恢复执行又手动释放了B的锁,解决方案是给锁绑定唯一标识:
// Java示例 String lockId = UUID.randomUUID().toString(); if (redisTemplate.opsForValue().setIfAbsent("lock", lockId, 30, TimeUnit.SECONDS)) { try { // 业务代码 } finally { // 使用Lua脚本保证原子性检查+删除 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; redisTemplate.execute(script, Collections.singletonList("lock"), lockId); } }
对于长时间任务,需要动态延长锁有效期,像Redisson的实现逻辑:
// Go语言伪代码 func autoRenew() { ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: if !isBusinessDone { redis.Expire("lock", 30*time.Second) } else { return } case <-stopChan: return } } }
同一个线程多次获取锁时需要特殊处理:
class RedisLock: def __init__(self, client, name): self.client = client self.name = name self._count = 0 # 重入计数器 def acquire(self): if self._count > 0: self._count += 1 return True if self.client.set(self.name, threading.get_ident(), nx=True, ex=30): self._count = 1 return True return False def release(self): if self._count <= 0: raise RuntimeError("锁未持有") self._count -= 1 if self._count == 0: self.client.delete(self.name)
场景特征 | 推荐方案 | 注意事项 |
---|---|---|
临界区耗时短 | Redis单机锁 | 注意网络抖动影响 |
强一致性要求 | Zookeeper顺序节点 | 性能较低(通常1000QPS以下) |
高可用需求 | RedLock多Redis实例 | 需要至少3个独立主节点 |
大量锁竞争 | 分段锁+本地缓存 | 类似ConcurrentHashMap设计 |
某电商平台在秒杀活动中采用的分层锁方案:
第一层:本地缓存标记
// 使用Guava Cache LoadingCache<String, Boolean> hotItemCache = CacheBuilder.newBuilder() .expireAfterWrite(1, TimeUnit.SECONDS) .build(key -> redis.exists("stock_lock_" + key));
第二层:Redis集群锁
def reduce_stock(item_id): if not hotItemCache.get(item_id): return False # 快速失败 lock_key = f"cluster_lock_{item_id}" with redis.lock(lock_key, timeout=500, blocking_timeout=50): stock = db.query("SELECT stock FROM items WHERE id=?", item_id) if stock > 0: db.execute("UPDATE items SET stock=stock-1 WHERE id=?", item_id)
最终兜底:数据库乐观锁
UPDATE items SET stock = stock - 1 WHERE id = ? AND stock = original_stock -- 通过版本号或CAS机制
分布式锁就像分布式系统的安全带,用错了比不用更危险,2025年最新的《分布式系统故障分析报告》显示,35%的分布式事务问题源于锁使用不当,记住三个黄金原则:
下次实现分布式锁时,不妨多问自己:如果此时网络分区怎么办?如果Redis主从切换怎么办?如果客户端进程突然被杀怎么办?想清楚这些问题,你的锁才能真正护住业务的安全线。
本文由 后玥 于2025-08-02发表在【云服务器提供商】,文中图片由(后玥)上传,本平台仅提供信息存储服务;作者观点、意见不代表本站立场,如有侵权,请联系我们删除;若有图片侵权,请您准备原始证明材料和公证书后联系我方删除!
本文链接:https://vps.7tqx.com/wenda/512694.html
发表评论