电商秒杀系统设计分析

1,乐视秒杀,每秒钟10万的订单更新(insert/update),以用户ID分库分表,二叉树分库扩容,表级同步,DB1 - DB8, order1 - order10, DB编号 = (uid/10)%8,表编号=uid%10,这样单库基本上可以保持1万左右的并发,可以业务层分库分表,也可以使用mycat之类的中间件。

订单ID结构:分库分表信息+时间戳+机器号+自增序号,分信息:1bit数据库编号如5,第2bit为表编号如3,但是为了保证最终扩容,分库信息=(uid / 10) %64 +1,扩容到16 32 64个库都不是问题 。但是还需要根据商户ID来查询订单,所以又引入对商户维度的order表集群,将uid维度的order集群冗余一份到商户ID维度的order表集群,这样可以支持多维度查询,保持2个维度集群使用MQ异步同步,MQ也会异常导致数据不一致,引入实时监控服务,实时计算2个维度集群差异,做一致性同步。

1级,订单、支付流水,对实时和准确性要求高,不加入任何缓存,读写直接DB。2级,用户数据相关,读多写少,采用redis缓存。3级,支付配置,量少读多,用内存本地缓存,使用高可用消息推送平台,修改配置则推送更新并反馈。

采用nginx的nginx_max_conns限流修改DB并发为有限并发异步写,创建1个TaskQueue,所有DB写入放入并发度16的TaskQueue,通过queue异步更新DB。抢购库采用redis的nosql,属于业务层不属于数据层,然后在抢购库前再加上redis list队列,如产品A配置了20件,来了N个人,均会查询redis队列的长度,<=0则有权限减抢购库库存,队列长度<20则等待对头位置获取抢购权限,太长则直接返回。


2,同一商品并发读放到缓存中,采用一致性hash,同一个key在同一台机器,也采用应用层内存,应用上缓存相关数据(也有动静态),如不变属性(标题),在描述

前推到秒杀机器并缓存到描述结束,库存动态采用被动缓存方式一定时间(数秒),失效再去tair缓存拉取最新数据,真正写数据时仍然会一致性。

热点商品放在单独热点库,应用层做排队,DB做限流队列,在InnoDB做patch,单行做并发排队,可对多条SQL做合并。


3,request进来可以采用redis的inc/desc,扣完则直接返回

可以采用事务控制流程,也可以先去一个表insert之类拿一个资格,然后处理流程,实际库存扣减失败要更新到缓存中,拦截+数据一致性。

扫描二维码关注公众号,回复: 3431683 查看本文章

也可以将num保存到redis,多个用户对num进行desc,并且返回值,大于0时才生成订单,redis为单线程处理数据。


4,引入队列,所有写DB在队列中排队,串行,达到阈值就不再消费队列,关闭购买。解决超卖,但是多种商品则需要多条队列。

写操作到memcache中,利用memcache的cas锁减库存,同一时刻只有1个写入成功,解决建库存问题。


5,京东提交订单,1次订单要写10多张表,当订单数达到上万时,系统就无法承受,就将订单转化为xml,这样只写一张表,后面再去做异步,先将整个xml订单

的信息存储下来,然后再通过状态机异步写原来的10多张表数据,针对商品维度、商品ID等所有数据会异构一份到库存、促销、商品,又将数据分为A,B,C包,

A可能为基础数据,B为扩展数据例如。

装1个程序承载异步MYSQL的数据落地,再通过work写到MYSQL服务里面,正常双写MYSQL,redis,购物车服务端存储,GZIP压缩。


6,将提交操作变成两段式,先申请后确认。然后利用Redis的原子自增操作,同时利用Redis的事务特性来发号,保证拿到小于等于库存阀值的号的人都可以成功提交订单。然后数据异步更新到DB中。

优点:解决超卖问题,库存读写都在内存中,故同时解决性能问题。

缺点:由于异步写入DB,可能存在数据不一致。另可能存在少买,也就是如果拿到号的人不真正下订单,可能库存减为0,但是订单数并没有达到库存阀值。


7,一号店做法

排队模块:负责接收用户的抢购请求,将请求以先入先出的方式保存下来。每一个参加秒杀活动的商品保存一个队列,队列的大小可以根据参与秒杀的商品数量(或加点余量)自行定义。排队模块还负责提供一系列接口,如:给已进入队列的用户查询下单状态的接口,给调度模块拉取请求的接口,服务模块回写业务处理状态的接口等。

调度模块:负责排队模块到服务模块的动态调度,不断检查服务模块,一旦处理能力有空闲,就从排队队列头上把用户访问请求调入服务模块。并负责向服务模块分发请求。这里调度模块扮演一个中介的角色,但不只是传递请求而已。它还担负着调节系统处理能力的重任。我们可以根据服务模块的实际处理能力,动态调节向排队系统拉取请求的速度。作用有点类似水坝的闸门,当下游干旱时就打开闸门多放些水,当下游洪涝时,就放下闸门少放些水。

服务模块:是负责调用真正业务处理服务,并返回处理结果,并调用排队模块的接口回写业务处理结果。我们设计这个模块,是为了和后面真正的业务处理服务解耦。目前我们的系统不只支持秒杀抢购这种业务场景,后续有其他适用于排队系统的业务都可以接入,如:领取抵用券等等。同时我们也可以针对后面业务系统的处理能力,动态调节服务模块调用后面业务处理服务的速度。


8,站点层是可以通过加机器扩容的,最不济1k台机器来呗。如果机器不够,抛弃请求,抛弃50%(50%直接返回稍后再试),原则是要保护系统,不能让所有用户都失败。同一个uid,限制访问频度,做页面缓存,x秒内到达站点层的请求,均返回同一页面。

小米只有1万部手机,我清楚的知道一列火车只有2000张车票,我透10w个请求去数据库有什么意义呢?没错, 请求队列!,对于写请求,做请求队列,每次只透有限的写请求去数据层(下订单,支付这样的写业务)1w部手机,只透1w个下单请求去db3k张火车票,只透3k个下单请求去db,如果均成功再放下一批,如果库存不够则队列里的写请求全部返回“已售完”。
例如A 用户搜索了“手机”,B 用户搜索“手机”,优先使用A 搜索后生成的缓存页面?这个方法也经常用在“动态”运营活动页,例如短时间推送4kw用户app-push运营活动,做页面缓存。

处理失败返回下单失败,让用户再试。队列成本很低,爆了很难吧。最坏的情况下,缓存了若干请求之后,后续请求都直接返回“无票”(队列里已经有100w请求了,都等着,再接受请求也没有意义了)


9,A:查询模块(查询库存)并发查询,库存数存在缓存中,(商品信息和图片信息等等)静态化处理和库存剩余数量缓存化处理。

 B: 下订单模块(秒杀关键部分),队列控制异步化处理,首先判断这个队列是否已满, 如果没满就将请求放入队列中排队,队列满以后的所有请求直接返回秒杀失败。
C: 支付模块,异步付款,等待付款成功结果。(付款成功更新库存,也可下单的时候扣库存)。
A: 各个系统的业务数据分库存储,主从读写分离,单个子系统的数据,可以通过分库/分表的方法。
B: 构建合理数据库索引,如对购买记录添加唯一索引,数据更新检测库存,如update number set x=x-1 where (x -1 ) >= 0。

10, 提交订单, 每个订单task 队列, 订单队列超量提前拒绝其他请求

首先判断这个队列是否已满, 如果没满就将请求放入队列中排队,队列满以后的所有请求直接返回秒杀失败。中间件在处理web服务器请求的同时,转发队列中的请求给中心商品服务器(/支付中心)(这里可以使用生产者-消费者模型)。

中心商品服务器(/支付中心)将前面N个成功秒杀的结果返回后, 返回秒杀结束。

中间件收到商品服务器返回的秒杀结束,给队列中的所有请求返回失败, 以后进来的请求也全部返回失败, 这样就大大降低了中心商品服务器的请求数, 甚至可以在候选人中间件和中心商品服务器之间再加一层候选人中间件


11, 为了保护秒杀系统后端的关键服务,需要进行并发限制的处理。比如调风控的服务,当每个tomcat实例并发连接数超过10个就放弃请求风控服务等类似的并发保护措施。秒杀系统为了便于用户下单,流程设计会尽量精简,因此不利于阻挡利用软件参与抢购的机器人,下面的几种实践方法,可以有效限制机器人刷单:为了防止这种构造数据避过流程的软件,把秒杀活动分为几个环节:活动入口,验证码页,验证码交互,订单详情页,提交订单。几个环节环环相扣,以保障任何人都必须从入口一步步完成抢购活动,这种限定软件的行为轨迹,基本拉到和普通用户同样的水平,以增加普通用户成功抢购的几率。为了保证所有用户必须一步步访问,利用一个nginx的模块,来校验用户当前访问的步骤是否合法,并且把当前用户相关状态以加密串的形式种植在cookie中。Lua配合nginx模块的时候,可以进行复杂一些的业务逻辑方面的校验工作。
可以想像一个比较简单,快速的通道,即在本地进行一些数据的分析,比如当一个手机号1分钟内下单超过5次就认为这个下单行为是异常的,这条途径是比较快速,但是精准度稍有欠佳。可以配合使用调用远程风控服务的方式,提高精准度。风控服务可以通过大量的数据判断,这个用户是否是恶意订单。 目前多数以加密cookie的形式记录用户的访问行为,当用户删除cookie并从入口页重新进入后,将无法获取用户以前的行为,只能当做新用户处理。因此建议在服务器端实时存储用户的行为,这样不依赖cookie的校验,用户避开规则可能性就更小。并且减少加密cookie,每个请求都会减少一百多字节的数据,在高峰期减少cookie也可以起到节省带宽的作用。

12,海量的用户秒杀请求,本质上是一个排序,先到先得.但是如此之多的请求,完全响应,难度又很大.所以在Web服务器集群,可以考虑卸载流量.比如每十个请求,随机抛弃九个,只放行一个请求到后续处理环节.把秒杀的排序模式,变为随机抽奖的模式.每次秒杀活动开始之前.先计算活动推出的商品数量,然后分配一个限额到每个Web服务器.比如一个活动推出秒杀商品电视,手机,衣服各1000件,那么每台服务器的限额就是125件.将这个限额写入ZooKeeper,Web服务器监听到限额的变化,就会重新初始化各自的商品剩余数量.
假设用户请求秒杀电视机,它只是锁了该Web服务器电视机的数量。(该Web服务器手机和衣服还可以继续并发处理,当然其他的Web服务器也在同时处理电视机的秒杀请求)这样缩小了锁定的范围,增加了系统处理的吞吐量.如果这个剩余数量大于零,则将用户ID放入电视机购买队列,然后告知用户秒杀成功如果这个剩余数量等于零,则告知用户秒杀失败.即便别的Web服务器还有电视机的剩余配额.
假设活动期间,需要修改库存信息。 两种可能, 第一种,该商品已经卖了500件,电商不想继续卖了.第二种,从仓库中又找到了一些积压库存..两种情况,都直接修改ZooKeeper中相应商品的配额.Web服务器会监听变化,并重新初始化全局容器.
消费者主要是从队列获取购买请求,发送至数据库扣减数据库库存写订单明细记录

数据库层,使用存储过程代替JDBC调用,数据库层,库存单行更新,增加多个槽位.

猜你喜欢

转载自blog.csdn.net/zhanjianshinian/article/details/53342730