大家好,我是你们的技术月老,今天要教大家如何在分布式系统中"锁"住你的资源,就像锁住你男/女朋友的心一样牢固!(单身狗别走,学完这个你就能锁住双11的购物车了!)
分布式锁的"人生三问"
- 为什么需要锁? - 就像厕所门要上锁一样,防止多人同时操作引发"社会性死亡"事故
- 为什么是分布式锁? - 因为你的服务可能像渣男一样同时在多个服务器上"养鱼"
- 为什么用Redis? - 因为它快得像闪电侠,比MySQL这种"老爷爷"更适合做锁服务
Redis实现分布式锁的"段位排行"
青铜方案:SETNX + DEL(基础版)
# 加锁
def acquire_lock(conn, lockname, acquire_timeout=10):
identifier = str(uuid.uuid4())
end = time.time() + acquire_timeout
while time.time() < end:
if conn.setnx(lockname, identifier): # SETNX:SET if Not eXists
return identifier
time.sleep(0.001)
return False
# 解锁
def release_lock(conn, lockname, identifier):
if conn.get(lockname) == identifier:
conn.delete(lockname)
底层数据结构:简单字符串(就是普通的key-value)
使用场景:适合临时用用,就像临时停车锁,被拖走了也不心疼
致命缺陷:
- 如果客户端崩溃,锁就永远不释放(比死锁还可怕)
- 非原子性操作(get和delete不是原子的)
白银方案:SETNX + EXPIRE(进阶版)
def acquire_lock_with_timeout(conn, lockname, acquire_timeout=10, lock_timeout=10):
identifier = str(uuid.uuid4())
lockname = "lock:" + lockname
lock_timeout = int(math.ceil(lock_timeout))
end = time.time() + acquire_timeout
while time.time() < end:
if conn.setnx(lockname, identifier):
conn.expire(lockname, lock_timeout) # 设置过期时间
return identifier
elif not conn.ttl(lockname): # 检查是否设置了过期时间
conn.expire(lockname, lock_timeout)
time.sleep(0.001)
return False
底层数据结构:还是字符串,但多了过期时间属性
使用场景:适合短期任务,就像租房子签合同,到期自动退租
改进点:
- 加了过期时间,防止死锁
- 双重检查过期时间
新问题:
SETNX和EXPIRE不是原子操作!可能在中间服务器挂了,还是会出现死锁
黄金方案:SET扩展命令(专业版)
python
复制
def acquire_lock_atomic(conn, lockname, acquire_timeout=10, lock_timeout=10):
identifier = str(uuid.uuid4())
lockname = "lock:" + lockname
end = time.time() + acquire_timeout
while time.time() < end:
# 一条命令原子性完成setnx和expire
if conn.set(lockname, identifier, ex=lock_timeout, nx=True):
return identifier
time.sleep(0.001)
return False
底层数据结构:依然是字符串,但用了Redis的扩展参数
使用场景:正式生产环境推荐,就像结婚领证,安全可靠
优势:
- 原子性操作
- 自动过期
- 代码简洁
依然存在的问题:
- 业务执行时间超过锁超时时间怎么办?(就像你约会迟到,女朋友已经走了)
- 锁被其他客户端误删怎么办?(就像你老婆删了你游戏存档)
王者方案:Redlock算法(终极版)
# 需要多个Redis实例
def acquire_redlock(redis_servers, lockname, ttl):
identifier = str(uuid.uuid4())
quorum = len(redis_servers) // 2 + 1
locked = 0
for server in redis_servers:
if server.set(lockname, identifier, ex=ttl, nx=True):
locked += 1
if locked >= quorum and (time.time() - start_time) < ttl:
return identifier
# 如果没获取足够锁,释放已经获取的
for server in redis_servers:
if server.get(lockname) == identifier:
server.delete(lockname)
return False
底层数据结构:多个Redis实例的字符串
使用场景:金融级应用,就像结婚还要公证处公证
优势:
- 高可用性(N/2+1机制)
- 更安全
代价:
- 性能开销大
- 实现复杂
灵魂拷问:你真的需要分布式锁吗?
- 乐观锁不香吗?(用版本号控制)
- CAS操作不行吗?(Compare and Swap)
- Zookeeper不好吗?(临时顺序节点)
终极选择指南
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| SETNX | 临时测试 | 简单 | 问题多 |
| SETNX+EXPIRE | 短期任务 | 防死锁 | 非原子性 |
| SET扩展 | 生产环境 | 原子性 | 时钟问题 |
| Redlock | 金融级 | 高可用 | 复杂慢 |
总结
选择分布式锁就像选择对象:
- 短期关系用SETNX(约个会)
- 长期关系用SET扩展(稳定恋爱)
- 结婚必须Redlock(领证公证)
记住:没有最好的锁,只有最合适的锁!你的业务场景决定你的选择~
互动时间:你们项目中用的是什么分布式锁方案?遇到过什么坑?评论区见!