当前位置:首页 > 问答 > 正文

分布式锁 原子操作 Redis锁结合Lua脚本实现原子性机制,解析redis锁和lua的应用

🔒 分布式锁实战:用Redis+Lua脚本打造原子操作神器

场景引入
半夜12点,你的秒杀系统突然报警——10台服务器同时抢购同一件商品,库存竟然变成了-100!😱 检查代码发现:if (库存>0) { 扣减库存 } 这个逻辑在分布式环境下被多个进程同时执行,瞬间击穿了防线...

别慌!今天我们就用 Redis锁 + Lua脚本 这对黄金组合,彻底解决这类分布式原子操作难题!


为什么需要分布式锁?

在单机时代,我们用synchronizedReentrantLock就能保证线程安全,但在分布式系统中:

  • 多台服务器上的进程共享同一资源(比如库存)
  • 网络延迟、服务宕机等不可控因素频发
  • 传统锁只能控制单进程,跨机器就失效了

这时候就需要一个全局可见的锁服务,而Redis凭借高性能丰富的数据结构成为首选。


Redis锁的朴素实现(和它的坑)

基础版:SETNX + EXPIRE

# 加锁(如果key不存在则设置成功)
SETNX lock:order_123 1
# 设置过期时间防止死锁
EXPIRE lock:order_123 30

问题来了

分布式锁 原子操作 Redis锁结合Lua脚本实现原子性机制,解析redis锁和lua的应用

  1. SETNX和EXPIRE是两条命令,如果中间Redis宕机... 锁永远不释放!💀
  2. 释放锁时可能误删其他客户端的锁(比如A处理超时后锁自动释放,B拿到锁,这时A又手动删了锁)

进阶版:SET扩展参数

SET lock:order_123 1 NX PX 30000  # 原子性设置锁+过期时间

解决了原子性问题,但锁误删依然存在。


Lua脚本:Redis的原子性大招

Redis的单线程模型保证了Lua脚本的原子执行——就像数据库的事务一样!

完美锁实现方案

-- 加锁脚本(参数:锁key, 客户端ID, 过期时间)
local key = KEYS[1]
local clientId = ARGV[1]
local expire = ARGV[2]
if redis.call('SETNX', key, clientId) == 1 then
    redis.call('PEXPIRE', key, expire)
    return 1
else
    return 0
end
-- 解锁脚本(确保只有锁持有者能删除)
local key = KEYS[1]
local clientId = ARGV[1]
if redis.call('GET', key) == clientId then
    return redis.call('DEL', key)
else
    return 0
end

优势
✅ 加锁和设置过期时间原子完成
✅ 解锁时校验客户端唯一标识(可用UUID)
✅ 脚本执行期间其他命令必须等待


实战中的优化技巧

锁重试机制

def acquire_lock():
    while timeout_not_reached:
        if redis.eval(加锁脚本) == 1:
            return True
        time.sleep(50ms)  # 避免疯狂重试
    raise LockTimeout()

锁续约(WatchDog)

对于长任务,启动一个后台线程定期延长锁过期时间

if redis.call('GET', KEYS[1]) == ARGV[1] then
    return redis.call('PEXPIRE', KEYS[1], ARGV[2])
end

避免锁风暴

  • 不同客户端设置随机等待时间
  • 可采用RedLock算法(多Redis实例部署)

经典应用场景

  1. 秒杀库存扣减

    local stock = tonumber(redis.call('GET', 'stock'))
    if stock > 0 then
        redis.call('DECR', 'stock')
        return "SUCCESS"
    end
  2. 防止重复订单

    分布式锁 原子操作 Redis锁结合Lua脚本实现原子性机制,解析redis锁和lua的应用

    if redis.call('SETNX', 'order:'..orderId, 1) == 1 then
        redis.call('EXPIRE', 'order:'..orderId, 3600)
        -- 处理订单逻辑
    end
  3. 全局配置更新

    -- 保证配置更新期间其他服务读取到一致状态
    if redis.call('SETNX', 'config_lock', 1) == 1 then
        redis.call('MSET', 'config1', newValue1, 'config2', newValue2)
    end

注意事项 ⚠️

  1. Redis集群问题:主从切换可能导致锁丢失,生产环境建议用RedLock
  2. 锁粒度:太细(影响性能)vs 太粗(降低并发)
  3. 超时时间:设置过短会导致任务未完成锁释放,过长会影响故障恢复

2025-07技术动态:Redis 7.4新增了SET命令的EXAT参数,支持绝对时间戳过期,比PX更精确!


分布式锁就像十字路口的红绿灯🚦,而Lua脚本则是那个绝对权威的交警,掌握这套组合拳,你就能在分布式世界里安全、高效地控制共享资源!下次遇到并发问题,不妨大喊一声:"Lua,给我锁它!" 🔐

(完)

发表评论