Redis分布式锁:从青铜到王者,教你用代码"锁"住爱情!

134 阅读3分钟

大家好,我是你们的技术月老,今天要教大家如何在分布式系统中"锁"住你的资源,就像锁住你男/女朋友的心一样牢固!(单身狗别走,学完这个你就能锁住双11的购物车了!)

 分布式锁的"人生三问"

  1. 为什么需要锁?  - 就像厕所门要上锁一样,防止多人同时操作引发"社会性死亡"事故
  2. 为什么是分布式锁?  - 因为你的服务可能像渣男一样同时在多个服务器上"养鱼"
  3. 为什么用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)

使用场景:适合临时用用,就像临时停车锁,被拖走了也不心疼

致命缺陷

  1. 如果客户端崩溃,锁就永远不释放(比死锁还可怕)
  2. 非原子性操作(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

底层数据结构:还是字符串,但多了过期时间属性

使用场景:适合短期任务,就像租房子签合同,到期自动退租

改进点

  1. 加了过期时间,防止死锁
  2. 双重检查过期时间

新问题
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的扩展参数

使用场景:正式生产环境推荐,就像结婚领证,安全可靠

优势

  1. 原子性操作
  2. 自动过期
  3. 代码简洁

依然存在的问题

  1. 业务执行时间超过锁超时时间怎么办?(就像你约会迟到,女朋友已经走了)
  2. 锁被其他客户端误删怎么办?(就像你老婆删了你游戏存档)

 王者方案: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实例的字符串

使用场景:金融级应用,就像结婚还要公证处公证

优势

  1. 高可用性(N/2+1机制)
  2. 更安全

代价

  1. 性能开销大
  2. 实现复杂

 灵魂拷问:你真的需要分布式锁吗?

  1. 乐观锁不香吗?(用版本号控制)
  2. CAS操作不行吗?(Compare and Swap)
  3. Zookeeper不好吗?(临时顺序节点)

 终极选择指南

方案适用场景优点缺点
SETNX临时测试简单问题多
SETNX+EXPIRE短期任务防死锁非原子性
SET扩展生产环境原子性时钟问题
Redlock金融级高可用复杂慢

 总结

选择分布式锁就像选择对象:

  • 短期关系用SETNX(约个会)
  • 长期关系用SET扩展(稳定恋爱)
  • 结婚必须Redlock(领证公证)

记住:没有最好的锁,只有最合适的锁!你的业务场景决定你的选择~

互动时间:你们项目中用的是什么分布式锁方案?遇到过什么坑?评论区见!