大促场景下热点数据写(库存扣减)技术难题解决方案

标签: 热点 数据 库存 | 发表时间:2017-11-17 17:31 | 作者:
出处:https://gao-xianglong.iteye.com

《大促场景下热点数据写(库存扣减)技术难题解决方案》

 

已经很久没有足够的时间让自己安静下来撰写一篇技术文章,确实近年来,大部分都花在了工作和2017年的新作品上。今天难得自己给自己打了瓶100ML的鸡血,出一篇前段时间针对交易系统大促场景下热点数据写优化的相关案例。当然,不同的企业有不同的解决方案和实现,但是万变不离其宗,还是那句话, 对于大型网站而言,其架构一定是简单和清晰的,而不是炫技般的复杂化,毕竟解决问题采用最直接的方式直击要害才是最见效的,否则事情只会变得越来越糟

 

在大部分情况下,商品库存都是直接在关系型数据库中进行扣减,那么在限时抢购活动正式开始后,那些单价比平时更给力、更具吸引力的热卖商品大家肯定都会积极踊跃的参与抢购,这必然会产生大量针对数据库同一行记录的并发更新操作。因此数据库为了保证原子性,InnoDB引擎缺省会对同一行数据记录进行加锁,把前端的并发请求变成串行操作来确保数据更新时的一致性。

 

一、在RDBMS中扣减商品库存

先来看看如果是直接在数据库中扣减库存,应该如何避免商品超卖呢?在生产环境中我们可以通过乐观锁机制来避免这个问题,所谓乐观锁,简单来说,就是在item表中建立一个version字段。假设某一个热卖商品的实际库存为n,处于性能考虑对于查询库存操作是不建议加for update的,那么在并发场景下,必然会导致多个用户拿到的stock和version都一样。因此当第1个用户成功扣减商品库存后,则需要将item表中的version加1,这样一来,当第2个用户扣减库存时,由于version不匹配,那么为了提升库存扣减的成功率,可以适当进行重试,如果库存不足,则说明商品已经售罄,反之扣减库存后version继续加1。关于在数据库中使用乐观锁扣减库存的伪代码,如下所示:

 

public void testStock(int num) {
if (version不一致时的重试次数阈值) {
    SELECT stock,version FROM item WHERE item_id=1;
if (如果查询的指定商品存在) {
if (判断stock是否够扣减) {
             UPDATE item SET version=version+1,stock=stock-1 WHERE 
                          item_id=1 AND version="+ version +";
if (扣减库存失败) {
/* version不一致时开始尝试重试 */
testStock(--num);
} else {
logger.info("扣减库存成功");
}
} else {
logger.warn("指定商品已售罄");
}
}
}
}

 

如果系统前端不配合做限流消峰等处理,随意放任大量的并发更新请求直接在数据库中扣减同一热卖商品的库存数据, 这将会导致线程之间相互竞争InnoDB的行锁,由于数据库中针对同一行数据的更新操作是串行执行的,那么某一个线程在未释放锁之前,其余的线程将会全部阻塞在队列中等待拿锁,并发越高时,等待的线程也就会越多,这会严重影响数据库的TPS,从而导致RT线性上升,最终可能引发系统出现雪崩。

 

二、在Redis中扣减库存
InnoDB的行锁特性其实是一把利与弊都同样明显的双刃剑,在保证一致性的同时却降低了可用性, 那么究竟应该如何保证大并发更新热点数据不会导致数据库沦为瓶颈这其实是秒杀、抢购场景下最核心的技术难题之一。可以尝试将热卖商品的库存扣减操作转移至数据库外,由于Redis的读/写能力要远胜过任何类型的关系型数据库,因此在Redis中实现库存扣减将会是一个不错的替代方案,这样一来,数据库中存储的商品库存可以理解为实际库存,而Redis中存储的商品库存则为实时库存。

 

在Redis中扣减热卖商品的库存,或许有同学会有疑问,Redis如何保证一致性呢?如何才能做到不超卖和少买呢?答案就是 Redis提供的Watch命令来实现乐观锁,和基于MySQL的乐观锁机制一样,并发环境下,通过Watch命令对目标Key进行标记后,当事务提交时,如果监控到目标Key对应的值已经发生了改变,那么也就则意味着版本号发生了改变,因此这一次的事务提交操作就失败,如图1所示:

图1 利用Redis乐观锁扣减商品库存 

 

在Redis中扣减热卖商品的库存主要是出于以下2个目的:

1、首先是为了避免在RDBMS中,多线程之间相互竞争InnoDB引擎的行锁导致RT上升,TPS下降,最终引发雪崩的问题;

2、其次是能够利用Redis与生俱来的高效读/写能力来提升系统的整体吞吐量。

 

三、利用“分裂”技巧巧妙地提升库存扣减成功率

这里跟大家分享一个笔者公司的业务场景,由于特务特点,我们整点的限时抢购往往是爆款+大库存(几万至十几万不等的库存数),我们都知道限时抢购的峰值其实就是秒杀,并且还伴随的大库存。相对于普通的秒杀场景而言,由于库存并不多,如果上游系统配合交易系统做好扩容、限流保护、隔离(业务隔离、数据隔离,以及系统隔离)、动静分离、localCache等措施,秒杀场景下就能够将绝大多数流量挡在系统上游,让用户流量像漏斗模型一样逐层减少,让流量始终保持在系统可处理的容量范围之内。

 

由于“变态”的业务特点,业务系统除了要承受亿级流量的冲击,交易系统还要想办法提升下单时的库存扣减成功率,这对于我们来说确实是一次挑战,因为在生产环境中,一次的不小心,将会带来灾难性的后果。我们都知道架构的意义是有序的对系统进行重构,不断减少系统的“熵”,让其不断进步,但架构调整的失误,将会是不可逆的,尤其是那些成熟且用户规模较大的网站。

 

我们都知道,秒杀活动开始后,能够抢购到心仪的产品,是非常不容的一件事情,因为在同一个单位时间内,除了你之外,还有别的用户也在下单,那么针对同一个爆款的WATCH碰撞概率将会被无情放大,成功率自然降低。如果是小库存,直接返回商品已经售罄即可,但是多大十几万的库存,让用户看得到,买不到,心里痒痒的似乎不太友好,并且运营策略上也希望能够快速消完这些库存好制造噱头。

 

你不用指望能够利用某一种数据库就能够即提升吞吐量又提升成功率,首先你需要搞明白的是,这是一个实打实的单点问题,要保证一致性,就必然会牺牲成功率,这个矛盾点,该怎么解决呢? 我们目前采用的做法是在Redis中,将某一个SKU的Key,拆分成N个对应的subKeys,库存服务在扣减库存的时候,通过轮询路由策略路由到不同的subKey上来降低WATCH碰撞概率,达到大幅度提升下单成功率的目的,如图2所示:

图2 将parentKey分裂为n个subKeys

 

分裂的概念相信大家都已经清楚了,接下来笔者再跟大家分享关于分裂操作的具体细节和一些注意事项。支撑分裂操作的主要由2部分构成,首先是嵌入在库存服务中的路由组件,其次是分裂管理服务,路由组件的任务很简单,订阅配置中心的分裂规则,然后轮询路由到不同的subKeys上做扣减即可。而分裂管理服务则相对复杂,parentKey的分裂操作就由它负责,并且它还需要处理一些相关的库存聚合(subKeys库存聚合)和下拉(重新划分库存给subKeys)任务。

 

分裂了,必然需要对分裂信息进行管理,比如:运营后台对某一个parentKey进行大库存扣减、调整某一个parentKey的分裂数量,以及删除某一个parentKey的分裂规则。这些操作全都包含着以下2个动作:
1、库存聚合(subKeys库存聚合),并将subKey库存设置为0;
2、然后将聚合后的库存归还给目标parentKey;

 

由于聚合和归还并不在同一个事物中,如果因为某些原因导致执行异常,那就悲剧了。比如聚合库存的时候成功了,这时subKeys的库存已经被设置为0,用户是无法正常下单的,但还库存给parentKey这个动作失败了,将会导致商品少卖,所以需要依靠以下2点来尽量保证商品不少卖:
1、业务上增大Redis的重试次数;
2、如果Redis故障,告警后人工介入归还库存;

 

为什么要区分普通用户扣减库存和运营后台扣减库存?因为这是2个截然不同的概念,因为用户扣减库存,往往会受限于业务(比如限制1个用户1次能够购买的商品数量),但运营后台则不同,有时候可能因为人为原因导致库存设超,因此需要扣减大量的库存,但是如果扣减的库存数量大于每一个subKey持有的有效库存数,则无法完成扣减操作,所以针对运营后台的扣减我们提供有单独的扣减方法,首先会聚合subKeys的库存并将subKey持有的库存数设置为0,将扣减后的库存还给parentKey,再等待重新下拉分配库存给subKeys。在此大家需要注意,如果一个商品特别爆,用户并发越大,聚合再分配的时间窗口期就会越长。

 

有时候,subKeys之间的库存数可能存在不均匀的情况,那么 当某一个subKey持有的库存被扣减完,且无回流库存以便下拉重新分配时,只要路由到这个subKey的库存扣减动作都会是失败的,用户就会存在看得到,买不到的不友好体验,因此可以在路由组件上做动作,当某一个subKey的库存已经消完后,本地需要做 剔除动作,下次不路由到这个subKey上。

 

最后给大家一点建议,如果parentKey的分裂数量越多,库存扣减的成功率就会越大,当然分裂数量也不是越多越好,一般来说一个parentKey分裂为10-20个subKey就够了,相对以前已经拥有了10-20倍的下单扣减成功率提升。

转发前,请询问我,3Q!



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [热点 数据 库存] 推荐:

大促场景下热点数据写(库存扣减)技术难题解决方案

- - gao_xianglong的自言自语
《大促场景下热点数据写(库存扣减)技术难题解决方案》. 已经很久没有足够的时间让自己安静下来撰写一篇技术文章,确实近年来,大部分都花在了工作和2017年的新作品上. 今天难得自己给自己打了瓶100ML的鸡血,出一篇前段时间针对交易系统大促场景下热点数据写优化的相关案例. 当然,不同的企业有不同的解决方案和实现,但是万变不离其宗,还是那句话, 对于大型网站而言,其架构一定是简单和清晰的,而不是炫技般的复杂化,毕竟解决问题采用最直接的方式直击要害才是最见效的,否则事情只会变得越来越糟.

SQLite数据库存储引擎设计

- - searchdatabase
  SQLite是一个嵌入式库并且实现了零配置、无服务端和事务功能的SQL数据库引擎. 它在广泛领域内被使用,而且单线程读写性能与MySQL比肩,并且保证ACID性.   SQLite的存储后端是采用Btree实现,多个连接可以并发操作,但是同一时间只允许一个写着存在.   SQLite在硬盘上一个数据库一个文件,每个数据库文件头部保存有这个数据库的元信息,包括版本,大小,Btree根节点位置等等.

瞎扯三个热点: UV / 移动 / 数据

- - 数据战略 Data strategy
瞎扯三个热点: UV / 移动 / 数据. 品觉每天都看大量的数据,以及坚持早上阅读各种关于电商额报道,今天来跟大家瞎扯几个观点. 和前两年相比,今年电商热度急剧下降. 基本每家都说生意不好做了,甚至一些过去很风光的电商都传出资金链断流等着被收购. 这其中的原因很多,各种分析都有人写过. 而我从数据的角度看到了一个有意思的现象,今年电子商务的UV增长跟业务增长是不匹配的.

转:MySQL数据库存储引擎和分支现状

- xcv58 - 唐福林-博客雨
在MySQL经历了2008年Sun的收购和2009年Oracle收购Sun的过程中,基本处于停滞发展的情况,在可以预见的未来,MySQL是肯定会被Oracle搁置并且逐步雪藏消灭掉的. MySQL随着相应的各主创和内部开发人员的离去,缔造了各个不同的引擎和分支,让MySQL有希望继续发扬光大起来. 本文大致讲解一下MySQL目前除了主要的 MyISAM、InnoDB、Heap(Memory)、NDB 等引擎之外的其他引擎的发展和现状,以及MySQL主干以外的分支的状况,为了我们未来更好的使用MySQL或者其他分支建立一个了解基础.

库存销售规划分析之番外篇——也谈数据系统

- - i天下网商
零售公司发展进入成熟期之后,需要考虑盈利的问题,而不是全力利用资本市场进行融资,这时就需要实现成熟的库存销售规划分析体系,以保证利润率、库存率、销售库存比等因素. 随着对国内零售电商后台的库存销售规划分析系统的进一步了解,我觉得目前零售电商应该向国外同行学习的不是如何做数据分析,而是首先应该考虑,在公司日益发展、营收日益稳定的情况下,怎样建立一个成熟的库存销售规划分析系统,由最合适的团队来正确解读分析数据,从而更精确地指导未来的产品销售、库存和市场策略.

数据分析在服装库存管理中的几个应用

- - i天下网商
服装企业库存是一个永恒的话题,代理商和厂家一直在斗智斗勇. 本文不谈大的道理,只是从数据分析的角度告诉大家一些库存管理的办法.  文/特邀作者 数据化管理. 2011年是服装企业库存大增的一年,笔者在今年4月特意根据财报分析了几家上市的体育用品公司李宁、安踏、特步、匹克、动向的经营状况,库存金额平均增长41.2%,库存天数(DOS)由28天上升到41天.

分布式数据库存储透析:B-TREE 和 LSM-TREE 的性能差别

- - IT瘾-dev
宇文湛泉,现任金融行业核心业务系统DBA,主要涉及Oracle、DB2、Cassandra、MySQL、GoldenDB、TiDB等数据库开发工作. 最近一两年里,每次做分布式数据库的内容分享活动时,总是会提及现在数据库的两个重要的存储结构,B-TREE和LSM-TREE. 因为,我觉得作为数据库的存储根基,无论是要选型,或者是用好一个数据库,清楚这两的差别和各自特点,都特别重要.

泄露的库存管理数据表明Amazon平板Kindle Fire可能超过iPad成为史上最畅销平板

- SotongDJ - 36氪
虽然还有6周才能正式上市,但是Amazon 定价199美元的平板Kindle Fire正在发力成为史上最畅销的平板. 来自Amazon内部的可靠消息来源提供了一张内部库存管理图:. 从该图表中可以看出,Kindle Fire的预定数量每小时平均超过2000台(等于每天超过50000台). 仅仅开始预定5天,该平板的订单便已经超过了25万台.

高并发库存控制

- - 企业架构 - ITeye博客
1、在秒杀的情况下,肯定不能如此高频率的去读写数据库,会严重造成性能问题的. 必须使用缓存,将需要秒杀的商品放入缓存中,并使用锁来处理其并发情况. 当接到用户秒杀提交订单的情况下,先将商品数量递减(加锁/解锁)后再进行其他方面的处理,处理失败在将数据递增1(加锁/解锁),否则表示交易成功. 当商品数量递减到0时,表示商品秒杀完毕,拒绝其他用户的请求.

如何巧抓热点赚钱?

- stone - 封起De日子
由此进入→10.14实盘日志. 博客进行了一次调查,这是《7月中旬到现在盈利情况》的调查,从资金盈利情况看,大部分资金的盈利比例都在50%以下,而盈利比例超过50%以上的则非常少,更有20%的资金出现了亏损. 再仔细观察,《目前的持仓情况》显示,这些盈利比较少的博友突出体现在“没有抓住市场热点”,这个比例在22%.