常州武进网站建设网站建设主持词
常州武进网站建设,网站建设主持词,南昌网站建设机构,节能网站源码写在前面的话
作为一个长期和关系型数据库#xff08;RDBMS#xff09;打交道的开发者#xff0c;初次查阅 Redis 文档时#xff0c;看到 MULTI、EXEC、DISCARD 这些指令#xff0c;心中难免涌起一股由于熟悉而带来的安全感。
我们的大脑会自动建立映射#xff1a;MULTI …写在前面的话作为一个长期和关系型数据库RDBMS打交道的开发者初次查阅 Redis 文档时看到MULTI、EXEC、DISCARD这些指令心中难免涌起一股由于熟悉而带来的安全感。我们的大脑会自动建立映射MULTI就是BEGINEXEC就是COMMITDISCARD就是ROLLBACK。这套组合拳打下来所有的业务逻辑似乎都应该具备了“不成功便成仁”的原子性保障。但这恰恰是 Redis 给我上的第一课相似的命名背后往往藏着截然不同的灵魂。当你把 MySQL 的事务观生搬硬套到 Redis 身上时错付就已经开始了。这篇文章将带你剥开Redis 事务的外衣从“原子性”的定义偏差说起聊聊为什么在现代开发中我们越来越倾向于用 Lua 脚本来替代它。一、先把误会解开Redis 事务不是 ACID在关系型数据库的世界里“事务”二字重若千钧它几乎等同于ACID原子性、一致性、隔离性、持久性。我们习惯了“要么全有要么全无”的安全感。而在 Redis 的世界里MULTI和EXEC更像是一个批处理信号把一堆命令先放进队列里排队等到EXEC时一次性、按顺序地执行它们。这里有一个巨大的认知偏差。当我们谈论 Redis 的“原子性”时Redis 指的其实是隔离性Isolation而不是回滚Rollback。它保证的是我执行这段命令的时候别人不能插队独占执行。它不保证的是如果我执行到一半报错了我会帮你把前面的操作撤销失败回滚。为了更直观地理解我们可以对比一下 Redis 事务和标准 ACID 事务的区别特性关系型数据库 (MySQL)Redis 事务差异解读原子性 (Atomicity)All or Nothing失败即回滚如同未发生过All or Partial没得商量错了就错了剩下的接着干Redis 不支持 Rollback部分成功是常态一致性 (Consistency)强一致性约束必须满足弱一致性依赖业务代码保障Redis 不会校验业务约束如外键、非空等隔离性 (Isolation)有多种隔离级别 (RC/RR/Serializable)串行化执行执行期间不可被打断得益于单线程模型EXEC期间天然隔离持久性 (Durability)WAL 日志保障掉电不丢失取决于 AOF/RDB 配置默认配置下通常有数据丢失风险一句话总结Redis 事务是“命令队列 独占执行”绝不是“失败回滚 强一致”。二、残酷的真相它真的不包回滚为了把这个概念刻进 DNA我们看两种真实的错误场景。1. 入队时的“低级错误”全员连坐如果你在命令入队阶段就犯了语法错误比如参数写少了Redis 还是讲道理的它会直接拒绝整个事务。/* by yours.tools - online tools website : yours.tools/zh/jsonudview.html */ 127.0.0.1:6379 MULTI OK 127.0.0.1:6379 SET key1 value1 QUEUED 127.0.0.1:6379 SET key2 # --- 语法错误少了参数 (error) ERR wrong number of arguments for set command 127.0.0.1:6379 EXEC (error) EXECABORT Transaction discarded because of previous errors.这时候所有命令都不会执行。这符合我们对“事务”的预期。2. 执行时的“运行时错误”虽死犹进这才是真正的坑。假设语法没问题但在执行期间某条命令因为数据类型不匹配报错了/* by yours.tools - online tools website : yours.tools/zh/jsonudview.html */ 127.0.0.1:6379 MULTI OK 127.0.0.1:6379 SET user:A:points 100 QUEUED 127.0.0.1:6379 LPUSH user:A:points error_data # --- 对 String 类型做 List 操作注定运行报错 QUEUED 127.0.0.1:6379 INCR user:A:points # --- 后续命令 QUEUED 127.0.0.1:6379 EXEC 1) OK 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value --- 报错 3) (integer) 101 --- 依然成功了目瞪口呆了吗第二条命令报错了但第三条命令依然欢快地执行了。数据出现了中间态即所谓的“不一致”。Redis 官方对此的解释非常“直男”“只有语法错误才会被拦截运行时错误属于程序员的逻辑 Bug比如把 String 当 List 用。数据库不应该为了程序员的 Bug 买单去搞复杂的回滚机制。”三、进阶之路从原生批量到 Lua 脚本 预备知识RTT 是性能杀手一个 Redis 命令的执行可以简化为 4 步发送命令 → 命令排队 → 命令执行 → 返回结果。其中第 1 步和第 4 步的时间之和称为RTT (往返时间)。如果我有 100 个命令一个个发就需要 100 次 RTT大部分时间都浪费在网络传输上。批量操作的核心意义就是把 100 次 RTT 压缩成 1 次。既然MULTI/EXEC这么“头铁”那我们在实际开发中到底该怎么选我们可以把 Redis 的批量操作能力分为几个段位。Lv1. 原生批量命令 (MSET / MGET)这是最简单、最快的方式。特点原生的原子性。MSET key1 val1 key2 val2是一个原子操作要么都成功要么都失败在 Redis 层面。示例MSET key1 Hello key2 World局限只能处理同一种命令逻辑死板。Lv2. 管道 (Pipeline)当你需要批量执行几十个不同的命令且不需要它们之间有逻辑依赖时Pipeline 是首选。特点唯快不破。它把几十个命令打包一次网络请求RTT发给服务器服务器执行完再一次性返回。形象理解下 100 个单 - 一次性收 100 个快递 (1 次 RTT)。与事务的区别非原子性Pipeline 只是打包发送Redis 可能会在处理 Pipeline 中间穿插执行其他客户端的命令交错执行。效率更高不需要像事务那样每个命令都发一次只需要发送一次。Lv3. 事务 (MULTI / EXEC)比 Pipeline 多了一层保障独占执行。特点原子操作隔离性。两个不同的事务不会同时运行。在EXEC执行期间Redis 会“以此为尊”保证没有其他客户端能插队。缺点RTT 开销大事务中每个命令都需要单独发送到服务端入队请求次数并没有减少。不支持回滚不支持在事务中间做逻辑判断。Lv3.1 事务 WATCH (乐观锁)单纯的MULTI/EXEC往往比较鸡肋因为它无法感知中间状态。但这套机制唯一的“王牌”组合是配合WATCH命令实现乐观锁 (CAS)。场景秒杀扣减库存。在MULTI之前WATCH stock。如果在EXEC执行前stock被别人改了整个事务原地取消返回 nil。代码示例WATCH stock:001 # 1. 监视库存 GET stock:001 # 2. 读库存发现是 10 MULTI # 3. 开启事务 (开始排队) DECR stock:001 # 4. 减库存 EXEC # 5. 执行 # 如果在步骤 1-5 之间别人改了 stock:001这里会返回 (nil)事务回滚。致命弱点高并发下性能极差。就像一群人抢一个麦克风一个人抢到了其他人的CAS全部失败只能客户端重试自旋。竞争越激烈重试越频繁CPU 空转越严重。Lv4. 最终兵器 —— Lua 脚本从 Redis 2.6 开始Lua 脚本成为了解决复杂原子性问题的核心方案它完美替代了WATCH事务。为什么它比事务强逻辑原子性一段 Lua 脚本被视作一条命令。Redis 保证脚本执行期间不会有任何其他脚本或命令插入。效率更高不需要像WATCH那样反复重试。脚本在服务器端执行只有一次 RTT。示例安全的“先查后改”-- 判断 key 是否等于预期值如果是则删除 if redis.call(GET, KEYS[1]) ARGV[1] then return redis.call(DEL, KEYS[1]) else return 0 end⚠️ 必须警惕的缺陷Lua 也不回滚虽然 Lua 脚本被称为“原子操作”但请注意它的原子性依然指的是不被打扰而不是失败回滚。如果 Lua 脚本运行到中途出错比如调用了不存在的命令或显式报错退出脚本会停止执行但之前已经执行过的写操作是不会被撤销的这意味着即使是 Lua也不能给你带来 RDBMS 那种“回滚一切”的安全感。你依然需要在代码层面保证逻辑的严密性。四、总结选型决策表为了让你在实际业务中不再纠结我整理了一份简单的决策表需求场景推荐方案核心理由简单批量读写 (KV)MSET / MGET原生命令最快最省心。大量离散命令 (无关联)Pipeline网络开销最低吞吐量最高。需要 CAS (低并发)WATCH MULTI事务唯一的用武之地。适合低频竞争实现简单。复杂逻辑 / 高并发Lua 脚本行业标准。避免了 CAS 自旋的性能开销原子性强。即使报错也要回滚MySQL / RDBMS别为难 Redis。它没有 Undo Log做不到真正的回滚。写在最后回头看Redis 事务这套机制就像是一个“如果不仔细读说明书一定会用错”的半成品。但正是这个“半成品”折射出了 Redis 最底层的价值观为了性能可以牺牲一切“看起来很美”的抽象。它拒绝了沉重的 Undo Log拒绝了复杂的隔离级别只留下了一个最简单的“排队执行”逻辑。所以当我们下次再写下MULTI的时候心里要清楚如果只是为了快Pipeline才是那个不讲武德的“加速器”。如果只是为了防插队Transaction够用了但在高并发下它脆弱得像个易碎品。如果要处理真正的复杂逻辑请毫不犹豫地拥抱Lua—— 虽然它也不会回滚但至少在“执行原子性”上它是我们手里最稳的那张牌。真正的技术成熟不是背诵八股文里的 ACID 定义而是懂得在由于物理限制而满是遗憾的真实世界里做出那个最不坏的选择。文章的最后想和你多聊两句。技术之路常常是热闹与孤独并存。那些深夜的调试、灵光一闪的方案、还有踩坑爬起后的顿悟如果能有人一起聊聊该多好。为此我建了一个小花园——我的微信公众号「[努力的小郑]」。这里没有高深莫测的理论堆砌只有我对后端开发、系统设计和工程实践的持续思考与沉淀。它更像我的数字笔记本记录着那些值得被记住的解决方案和思维火花。如果你觉得今天的文章还有一点启发或者单纯想找一个同行者偶尔聊聊技术、谈谈思考那么欢迎你来坐坐。愿你前行路上总有代码可写有梦可追也有灯火可亲。