03如何设计一个秒杀系统???

设计一个秒杀系统剩余部分

3.8减库存可能存在的问题

漏洞

购物过程分多步操作,因此在不同的操作步骤中减库存,就会存在一些可能被恶意买家利用的漏洞。

例如发生恶意下单的情况, 假如我们采用“下单减库存”的方式,即用户下单后就减去库存,正常情况下,买家下单后付款的概率会很高,所以不会有太大问题。

但是有一种场景例外,就是当卖家参加 某个活动时,此时活动的有效时间是商品的黄金售卖时间,如果有竞争对手通过恶意下 单的方式将该卖家的商品全部下单,让这款商品的库存减为零,那么这款商品就不能正 常售卖了。

这些恶意下单的人是不会真正付款的,这正是“下单减库存”方式 的不足之处。

既然“下单减库存”可能导致恶意下单,从而影响卖家的商品销售,那么有没有办法解决呢?

你可能会想,采用“付款减库存”的方式是不是就可以了?的确可以,但是,“付 款减库存”又会导致另外一个问题:库存超卖。 假如有 100 件商品,就可能出现 300 人下单成功的情况,因为下单时不会减库存,所以也就可能出现下单成功数远远超过真正库存数的情况,这尤其会发生在做活动的热门 商品上。这样一来,就会导致很多买家下单成功但是付不了款,买家的购物体验自然比 较差。 可以看到,不管是“下单减库存”还是“付款减库存”,都会导致商品库存不能完全和 实际售卖情况对应起来的情况,看来要把商品准确地卖出去还真是不容易啊! 那么,既然“下单减库存”和“付款减库存”都有缺点,我们能否把两者相结合,将两 次操作进行前后关联起来,下单时先预扣,在规定时间内不付款再释放库存,即采用“预 扣库存”这种方式呢? 这种方案确实可以在一定程度上缓解上面的问题。但是否就彻底解决了呢?其实没有!

针对恶意下单这种情况,虽然把有效的付款时间设置为 10 分钟,但是恶意买家完全可 以在 10 分钟后再次下单,或者采用一次下单很多件的方式把库存减完。针对这种情况, 解决办法还是要结合安全和反作弊的措施来制止。 例如,给经常下单不付款的买家进行识别打标(可以在被打标的买家下单时不减库存)、 给某些类目设置最大购买件数(例如,参加活动的商品一人最多只能买 3 件),以及对 重复下单不付款的操作进行次数限制等。 针对“库存超卖”这种情况,在 10 分钟时间内下单的数量仍然有可能超过库存数量, 遇到这种情况我们只能区别对待:对普通的商品下单数量超过库存数量的情况,可以通 过补货来解决;但是有些卖家完全不允许库存为负数的情况,那只能在买家付款时提示 库存不足。

3.9大型秒杀中如何减

业务系统中最常见的就是预扣库存方案,像你在买机票、买电影票时,下单 后一般都有个“有效付款时间”,超过这个时间订单自动释放,这都是典型的预扣库存 方案。而具体到秒杀这个场景,应该采用哪种方案比较好呢? 由于参加秒杀的商品,一般都是“抢到就是赚到”,所以成功下单后却不付款的情况比 较少,再加上卖家对秒杀商品的库存有严格限制,所以秒杀商品采用“下单减库存”更 加合理。另外,理论上由于“下单减库存”比“预扣库存”以及涉及第三方支付的“付 款减库存”在逻辑上更为简单,所以性能上更占优势。 “下单减库存”在数据一致性上,主要就是保证大并发请求时库存数据不能为负数,也 就是要保证数据库中的库存字段值不能为负数,一般我们有多种解决方案:一种是在应 用程序中通过事务来判断,即保证减后库存不能为负数,否则就回滚;另一种办法是直 接设置数据库的字段数据为无符号整数,这样减后库存字段值小于零时会直接执行 SQL 语句来报错;再有一种就是使用 CASE WHEN 判断语句。

例如这样的 SQL 语句:

UPDATE item SET inventory = CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END

3.10秒杀减库存的极致优化

在交易环节中,“库存”是个关键数据,也是个热点数据,因为交易的各个环节中都可能涉及对库存的查询。但是,秒杀中并不需要对库存 有精确的一致性读,把库存数据放到缓存(Cache)中,可以大大提升读性能。 解决大并发读问题,可以采用 LocalCache(即在秒杀系统的单机上缓存商品相关的数 据)和对数据进行分层过滤的方式,但是像减库存这种大并发写无论如何还是避免不了, 这也是秒杀场景下最为核心的一个技术难题。 因此,这里我想专门来说一下秒杀场景下减库存的极致优化思路,包括如何在缓存中减 库存以及如何在数据库中减库存。 秒杀商品和普通商品的减库存还是有些差异的,例如商品数量比较少,交易时间段也比 较短,因此这里有一个大胆的假设,即能否把秒杀商品减库存直接放到缓存系统中实现, 也就是直接在缓存中减库存或者在一个带有持久化功能的缓存系统(如 Redis)中完成。

如果你的秒杀商品的减库存逻辑非常单一,比如没有复杂的 SKU 库存和总库存这种联 动关系的话,完全可以。但是如果有比较复杂的减库存逻辑,或者需要使用事务, 你还是必须在数据库中完成减库存。 由于 MySQL 存储数据的特点,同一数据在数据库里肯定是一行存储(MySQL),因 此会有大量线程来竞争 InnoDB 行锁,而并发度越高时等待线程会越多,TPS (Transaction Per Second,即每秒处理的消息数)会下降,响应时间(RT)会上升, 数据库的吞吐量就会严重受影响。 这就可能引发一个问题,就是单个热点商品会影响整个数据库的性能, 导致 0.01%的 商品影响 99.99%的商品的售卖,这是我们不愿意看到的情况。一个解决思路是遵循前 面介绍的原则进行隔离,把热点商品放到单独的热点库中。但是这无疑会带来维护上的 麻烦,比如要做热点数据的动态迁移以及单独的数据库等。 而分离热点商品到单独的数据库还是没有解决并发锁的问题,我们应该怎么办呢?要解 决并发锁的问题,有两种办法:  应用层做排队。按照商品维度设置队列顺序执行,这样能减少同一台机器对数据库 同一行记录进行操作的并发度,同时也能控制单个商品占用数据库连接的数量,防 止热点商品占用太多的数据库连接。  数据库层做排队。应用层只能做到单机的排队,但是应用机器数本身很多,这种排 队方式控制并发的能力仍然有限,所以如果能在数据库层做全局排队是最理想的。 阿里的数据库团队开发了针对这种 MySQL 的 InnoDB 层上的补丁程序(patch), 可以在数据库层上对单行记录做到并发排队。 你可能有疑问了,排队和锁竞争不都是要等待吗,有啥区别? 如果熟悉 MySQL 的话,你会知道 InnoDB 内部的死锁检测,以及 MySQL Server 和 InnoDB 的切换会比较消耗性能,淘宝的 MySQL 核心团队还做了很多其他方面的优化, 如 COMMIT_ON_SUCCESS 和 ROLLBACK_ON_FAIL 的补丁程序,配合在 SQL 里面 加提示(hint),在事务里不需要等待应用层提交(COMMIT),而在数据执行完最后 一条 SQL 后,直接根据 TARGET_AFFECT_ROW 的结果进行提交或回滚,可以减少网 络等待时间(平均约 0.7ms)。据我所知,目前阿里 MySQL 团队已经将包含这些补丁 程序的 MySQL 开源。 另外,数据更新问题除了前面介绍的热点隔离和排队处理之外,还有些场景(如对商品 的 lastmodifytime 字段的)更新会非常频繁,在某些场景下这些多条 SQL 是可以合并 的,一定时间内只要执行最后一条 SQL 就行了,以便减少对数据库的更新操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值