Redis事务全解析:从秒杀案例看原子操作实现

💡 一句话真相 :Redis事务就像"点套餐"🍱------把多个命令打包下单(MULTI),后厨(Redis)一次性烹饪(EXEC),但某个菜缺货(失败)不会退单!


🔧 一、为什么需要事务?转账案例的痛点

场景:A向B转账100元
客户端 Redis GET A_balance → 500 GET B_balance → 300 SET A_balance 400 SET B_balance 400 客户端 Redis

风险 :若第二步后Redis宕机 → A扣款成功,B未到账!


🛠️ 二、Redis事务实现:三阶段流程

1. 开启事务(点餐下单)
bash 复制代码
MULTI  # 开启事务 → 返回OK  

效果:后续命令进入队列,不立即执行

2. 命令入队(选菜品)
bash 复制代码
DECRBY A_balance 100  # A账户减100 → 进入队列  
INCRBY B_balance 100  # B账户加100 → 进入队列  

特性:

  • 语法错误(如INCRBY B 100)直接拒绝入队
  • 运行时错误(如对字符串DECRBY)不检查
3. 执行事务(后厨烹饪)
bash 复制代码
EXEC  # 执行队列中所有命令  

输出示例:

复制代码
1) (integer) 400  # DECRBY结果  
2) (integer) 400  # INCRBY结果  

🚨 三、原子性≠回滚:核心差异图解

事务开始 命令1成功 命令2失败 命令3执行 事务提交

与传统数据库对比:

特性 Redis事务 MySQL事务
原子性 命令全部执行 全部成功或全部回滚
隔离性 单线程天然隔离 需设置隔离级别
错误处理 继续执行后续命令 自动回滚
回滚支持 ❌ 无 ✅ 有

💥 关键结论 :Redis事务是 "部分原子性" ------ 保证命令打包执行,但不支持回滚!


🔒 四、乐观锁:Watch命令解决并发冲突

1. 秒杀库存超卖问题

客户端1 Redis 客户端2 GET stock → 10 GET stock → 10 DECR stock DECR stock 客户端1 Redis 客户端2

2. Watch实现乐观锁
bash 复制代码
WATCH stock  # 监控库存键  

MULTI  
DECR stock  
EXEC  # 若stock被其他客户端修改,EXEC返回(nil)  

执行效果 :

3. 重试机制(代码示例)
python 复制代码
import redis  
r = redis.Redis()  
  
def decr_stock():  
    while True:  
        try:  
            r.watch('stock')  
            stock = int(r.get('stock'))  
            if stock > 0:  
                pipe = r.pipeline()  
                pipe.multi()  
                pipe.decr('stock')  
                if pipe.execute():  # 执行成功返回非None  
                    break  
            else:  
                print("库存不足")  
                break  
        except Exception:  
            continue  # 冲突则重试  

⚠️ 五、事务四大坑点与解决方案

坑点 现象 解决方案
命令错误不回滚 SET失败后DECR仍执行 开发阶段用redis-cli --pipe检测语法
运行时错误继续执行 对字符串INCR → 继续执行事务 保证数据类型正确性
WATCH失效重试风暴 高并发时无限重试 设置最大重试次数/退避策略
事务内执行慢命令 阻塞其他客户端 避免KEYS、大数据操作

💎 六、事务 vs Lua脚本:终极选择

特性 事务 Lua脚本
原子性 命令级 脚本级(真正原子)
复杂度 简单 需学习Lua语法
性能 较高 超高(减少网络往返)
错误处理 强(可抛异常)

Lua脚本示例:

lua 复制代码
local stock = redis.call('GET', KEYS[1])  
if tonumber(stock) > 0 then  
    redis.call('DECR', KEYS[1])  
    return 1  -- 成功  
else  
    return 0  -- 失败  
end  

执行命令:

bash 复制代码
EVAL "脚本内容" 1 stock  

🔧 七、最佳实践:3类场景选型

  1. 简单命令组 → 事务(如:MULTI + SET A 1 + SET B 2 + EXEC
  2. 原子计数器 → Lua脚本(如:库存扣减)
  3. 读后写冲突WATCH + 事务(如:余额转账)

💡 八、总结:Redis事务三原则

  1. 非回滚设计:执行即提交,失败命令需业务补偿
  2. 隔离性保障:单线程执行天然隔离
  3. 乐观锁搭档 :WATCH解决并发冲突

🔥 黄金口诀:

  • 简单打包用MULTI
  • 并发控制靠WATCH
  • 真原子操作选Lua

#Redis事务 #高并发 #分布式系统
👉 关注我,下期揭秘《Redis+Lua实现分布式锁》!