架构思维:构建高并发扣减服务_利用缓存实现万级并发扣减

在这里插入图片描述

1. 引入:重申背景与性能需求

架构思维:构建高并发扣减服务_利用数据库实现并发扣减中我们基于关系型数据库实现了库存扣减,利用事务保证 ACID 特性以防止超卖/少卖。

数据库方案最大的问题,在于它必须使用事务(Transaction)来保证扣减的 原子性和一致性。然而,正是事务的 ACID 特性:

A:原子性(AtomicityC:一致性(ConsistencyI:隔离性(IsolationD:持久性(Durability

成了性能的掣肘者。

在批量扣减场景下,一条 SQL 对应一个 SKU,每次扣减都需要检查返回结果,一旦一个失败,就要整体回滚。而高并发下,多个用户争抢一个 SKU,又容易引发行级锁争用,甚至死锁,进一步导致性能雪崩。

所以说,数据库事务给了你一致性,也带走了你的吞吐率。在常规机器或 Docker 环境下,该方案难以支撑单机万级 TPS。


2. 回顾:纯数据库扣减方案瓶颈

  • 事务开销:每个 SKU 扣减需循环多次 UPDATE + 返回值检查。
  • 锁竞争:并发扣减同一 SKU 时,隔离级别导致锁等待或死锁。
  • 持久化不必要:库存扣减只需原子性,持久化仅为故障恢复的后置需求。

故单机万级并发遥不可及。

3. 纯缓存方案原理剖析

  • 拆分功能:将“原子性扣减”委托给单线程的 Redis,将“持久化”异步交由数据库。

  • 数据模型

    • 库存 KV:key = ss_{sku}, value = 剩余数量

      key为:sku_stock_{sku}。前缀sku_stock是固定不变,所有以此为前缀的均表示是库存。{sku}是占位符,在实际存储时被具体的skuid替代。
      value:库存数量。当前此key表示的sku剩余可购买的数量。

      在实际应用中,上述 key 的 sku_stock_ 前缀一般会简写成 ss_ 或者可以起到和其他 key 区分的较短形式。当我们存储的 SKU 有百万、千万级别时,此方式可极大地降低存储空间,从而降低成本,毕竟内存是比较昂贵的

    • 扣减流水 Hash:key = sx_{sku}, field = 流水ID, value = 数量

      key:sx_{sku}。前缀sx_是按上述缩短的形式设计的,只起到了区分的作用。{sku}为占位符
      hashField:此次扣减流水编号。
      hashValue: 此次扣减的数量

  • 原子保障:Redis 单线程事件循环天然串行执行命令,满足扣减原子要求。


4. 架构图解读:整体流程

在这里插入图片描述

  1. 扣减接口:接收批量 SKU+数量请求,不直接操作数据库,而调用 Redis。

  2. Redis 缓存层

    • 不设置过期,全量持久化 SKU 库存与流水。
    • 利用 Lua 脚本做批量原子操作(见下文)。
  3. 异步持久化:扣减成功后,通过消息或异步线程将结果写入数据库,用于容灾与对账。

  4. 运营后台:直接修改数据库,并同步写回 Redis,可通过 Binlog 或直接代码异步更新。


5. Lua 脚本执行流程详解

在这里插入图片描述

在 Redis 中部署并调用 Lua 脚本,保证以下操作的原子性:

  1. 防重检查GET uuid,已存在则拒绝;
  2. 批量读取:一次性读取所有 SKU 的剩余库存;
  3. 数量校验:如有任何 SKU 数量不足,立即返回失败;
  4. 循环扣减并记录流水:更新 KV 并在 Hash 中写入流水;
  5. 返回结果:全部成功则返回 OK,再触发异步持久化。

此流程利用 Redis 单线程特性,确保校验与扣减步骤之间无任何并发干扰。

6. 异常场景与应对策略

  • Redis 宕机中断在校验前:安全返回失败,无数据影响。
  • Redis 宕机中断在扣减后:部分库存被扣减但返回失败,需对账程序比对 Redis 与数据库日志,补回丢失库存。
  • 异步写库失败:Redis 数据准确但数据库滞后,通过日志比对将多余库存在库端扣减。

对账逻辑与自动化脚本是保证最终一致性的关键。


7. 升级与优化:主从分离与 Binlog 同步

在这里插入图片描述

  1. 读写分离

    • 增加 Redis 从节点,扣减走主节点,查询走从节点,互不干扰。
  2. Binlog 驱动同步

    • 运营后台修改数据库后,使用 MySQL Binlog 实时流式推送至 Redis,利用 ACK 机制保证消费可靠,简化分布式事务难题。

优化后读写性能和一致性保障更强。

8. 优劣与适用性分析

  • 优点

    • 性能提升超十倍,满足高并发场景。
    • 部署成本低,Docker 上轻松运行。
  • 缺点

    • 缺乏 ACID,极端故障下仍需对账补偿。
    • 系统复杂度增加,对异常流程要求高。
  • 适用场景

    • 大促、限购等对精度要求可降级的场景。
    • 通过完善对账与降级策略,也可用于库存、积分等精度较高的业务。

9. 在 Redis Cluster 上演化纯缓存扣减方案

已思考 4 秒

专家:后端架构 > Redis Cluster 高并发扣减演化专家
要求:V=2(详细),面向中高级后端开发者或架构师

计划

  1. 回顾单实例 Lua 扣减方案的原子依赖;
  2. 分析 Redis Cluster 分片模型对 Lua 原子性的影响;
  3. 提出演化策略——槽内多键原子、跨槽脚本分片执行、客户端路由与聚合;
  4. 运维自动化:脚本下发与版本管理;
  5. 容灾与对账;
  6. 适用场景与总结。

10. 在 Redis Cluster 上演化纯缓存扣减方案

纯缓存 + Lua 脚本方案中,我们利用单实例 Redis 的单线程执行保障了多 SKU 批量扣减的原子性。若将 Redis 换成 Cluster 模式,数据会根据 key 的哈希槽(0–16383)分布到多个节点上,单实例 Lua 脚本的“多 key 原子”假设将不再成立,需要进行方案演化。


1)、Redis Cluster 的多槽分片模型

  • 哈希槽分配:每个 key 会被分配到 0–16383 中的一个槽,槽再绑定到某个节点;
  • 跨槽操作限制:Cluster 下的 EVAL/EVALSHA 仅支持所有 key 位于同一槽,否则报错;
  • 节点扩容增减:可动态迁移槽到新节点,数据在线重分布。

这意味着,若一个 Lua 脚本操作了落在不同槽的多个 SKU key,将触发 CROSSSLOT Keys in request don’t hash to the same slot 错误。


2)、演化策略

2.1). 利用 Hash Tag 保证多键落槽

在设计 key 时,为所有同次批量扣减的 SKU key 添加相同的 Hash Tag,例如:

ss_{orderId}_{skuA}    → key="ss_{order42}_SKU_A"
ss_{orderId}_{skuB}    → key="ss_{order42}_SKU_B"

Redis 会只对 {order42} 部分计算槽,这样所有本次扣减相关 key 均落在同一槽,单次 EVAL 能覆盖多个 SKU,实现原子批量扣减。

优点
  • 零成本复用单实例脚本逻辑;
  • 保持原子性与高性能。
缺点
  • 破坏了槽的均衡,某些热点订单标签可能集中在单节点。

2.2). 跨槽脚本分组执行

若无法统一 Hash Tag(如多次请求复用同一 SKU 导致频繁热点),则需要在客户端按槽分组,对每个槽调用一次 Lua 脚本,再在客户端聚合返回结果:

  1. 分组:将所有待扣减的 SKU key 按照 keyslot(key) 分成若干组;
  2. 并行 EVAL:对每个分组调用 Lua 脚本,局部判空与扣减;
  3. 聚合:若任一槽组返回失败,则对所有已执行成功的槽组进行补偿(使用反向 Lua 脚本回滚);

可在客户端或专用网关层实现此两阶段执行与补偿逻辑。


2.3) 引入 Redisson / Proxy 层

使用成熟的 Redis Cluster 客户端(如 Redisson)或代理层(如 Redis-Proxy/Twemproxy):

  • 客户端 自动管理跨槽分组与脚本下发;
  • Proxy 可在前端做一层路由与聚合,屏蔽应用层负担;

这样,开发者只需调用“扣减(list_of_keys, list_of_amounts)”高阶接口,底层框架负责分槽、并行、补偿和重试。


3)、运维与自动化

  1. Lua 脚本下发

    • 在 CI/CD 流水线中,对所有 Cluster 节点推送并加载最新脚本(SCRIPT LOAD);
    • 维护脚本版本和 SHA1 校验,确保客户端执行时可命中 EVALSHA
  2. 监控与报警

    • 监控各槽节点的脚本调用延迟与错误率;
    • 针对热点槽(同一 Hash Tag 的过载)预警,并可动态调整 Hash Tag 设计。

4)、容灾与对账

  • 故障切换:Cluster 自动主从切换后,数据在新主节点继续可用;
  • 对账机制:同单实例方案,定时比对 Redis 与下游持久库,将日志中的失败/部分成功事务通过补偿脚本回写 Redis。

5)、适用场景与建议

方案适用场景
Hash Tag 锁槽批量扣减且订单维度稳定、不担心单节点热点
跨槽分组执行SKU 数量大、Hash Tag 难以覆盖多样业务场景
Proxy/Redisson期望快速落地,借助成熟中间件屏蔽复杂性

在 Redis Cluster 环境下,保持扣减的原子性关键在于如何将多 key 操作限定在同一槽,或者以分组+补偿的方式模拟全局事务。同时,借助成熟客户端或代理层能显著降低应用复杂度。无论哪种策略,都需配合自动化脚本下发、监控报警与对账补偿,才能在万级甚至更高并发场景中,保障缓存扣减的正确性与可用性。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值