采用了 读写分离 方式,新增加了一套从库,借助mysql自带的数据同步能力 。 库存校验 时读取从数据库 。
当然,数据同步有一定的时间延迟,从库的数据新鲜度有一定的滞后性,所以这个 库存校验 结果并不一定准确,但却能拦截大部分的 无效流量 。最终能不能成功购买,由主库的 乐观扣减SQL来控制,并不会影响最终扣减的准确性 。大大减轻主库的查询压力 。
palm_tree: 【数据库扣减方案】第二次升级引入了从库,确实能分摊主库很大一部分压力,但是面对秒杀这种万级QPS流量,mysql的 千级TPS 根本支撑不了,需要进一步升级读取的性能 。

文章插图
- 此时引入缓存中间件(如Redis),将mysql的数据定时同步到缓存中
- 库存校验 模块,从redis中查询剩余的库存数据 。由于缓存基于内存操作,性能比数据库高出几个数量级,单台redis实例可以达到10W QPS的读性能
补充说明:如果并发量还是很高的话,可以考虑引入 缓存集群 ,将不同的 秒杀商品sku 尽量均匀分布在多个redis节点中,从而分摊掉整体的峰值QPS压力 。(参考缓存热点的解决方案)
数据库方案的优点:
- ACID 超卖 少买
- 实现简单,如果项目工期紧张,或者开发资源不足情况下非常适用
- 如果参与秒杀的SKU非常多,最后的写操作都是基于 库存主库 ,性能压力会比较大 。
库存扣减为了保证数据并发安全,要求原子性,而 Redis 正好满足扣减类的特殊性要求,是个不错的技术选型 。
下面,我们简单来看看基于 Redis 如何来设计库存扣减?

文章插图
首先,设计Redis的数据模型:
剩余库存(k-v结构):key:sku_leaved_amount_{sku_id}value:剩余的库存数值流水(hash结构):key:inventory_flow_{sku_id}hash—key:订单明细id(不同业务场景的全局性id,用来做幂等控制)hash—value:本次购买的数量对于购物车下单,多个sku批量扣减,我们需要按单个sku循环发起Redis调用 。但是多个Redis命令无法保证原子性 。我们可以采用 lua脚本 形式,将这些命令打包到一个脚本中,作为一个命令发送给Redis执行,从而保证了原子性 。lua 是一个类似 JAVAScript、Shell 等的解释性语言,它可以完成 Redis 已有命令不支持的功能 。用户在编写完 lua 脚本之后,将此脚本上传至 Redis 服务端,服务端会返回一个标识码代表此脚本 。在实际执行具体请求时,将数据和此标识码发送至 Redis 即可 。Redis 会和执行普通命令一样,采用单线程执行此 lua 脚本和对应数据 。
Lua 脚本执行流程:批量扣减是对单个扣减的循环调用,所以这里介绍的流程只讲单次扣减的处理步骤 。
- 首先根据 订单明细id 查询扣减流水,是否已经操作过,做幂等性校验
- 然后查询sku的剩余库存,并根据 下单购买数 做校验,只要有一个sku 数量不足,则返回失败
- 修改所有sku的缓存中的剩余库存数
- 缓存中插入扣减流水记录
缓存方案利弊分析:
- Redis 高性能 ACID 少卖
- 为了避免 少卖 情况发生, 纯缓存方案 需要做大量的对账、异常处理的设计,系统复杂度增加很多 。
- 纯缓存方案 适合一些高并发、大流量场景,但对数据准确度要求不是特别苛刻的业务场景 。
推荐阅读
- Java并发工具类的简单使用
- 音乐|网易云音乐上线Hi-Res音质:百万级曲库 比无损更大
- 电商网站开发前期需要准备哪些资料?
- 使用多线程编程来实现并发时,需要考虑并发所带来的哪些风险呢?
- 电商小程序的运营干货,都快来get下吧!
- 并发插入引发的死锁问题排查
- linux的TCP连接数量最大不能超过65535个吗,那服务器是如何应对百万千万的并发的?
- 实例Python并发编程
- 常用的并发工具类
- 学并发编程,透彻理解这三个核心是关键
