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之类拿一个资格,然后处理流程,实际库存扣减失败要更新到缓存中,拦截+数据一致性。
也可以将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个下单请求去db,3k张火车票,只透3k个下单请求去db,如果均成功再放下一批,如果库存不够则队列里的写请求全部返回“已售完”。
例如A 用户搜索了“手机”,B 用户搜索“手机”,优先使用A 搜索后生成的缓存页面?这个方法也经常用在“动态”运营活动页,例如短时间推送4kw用户app-push运营活动,做页面缓存。
处理失败返回下单失败,让用户再试。队列成本很低,爆了很难吧。最坏的情况下,缓存了若干请求之后,后续请求都直接返回“无票”(队列里已经有100w请求了,都等着,再接受请求也没有意义了)
9,A:查询模块(查询库存)并发查询,库存数存在缓存中,(商品信息和图片信息等等)静态化处理和库存剩余数量缓存化处理。
首先判断这个队列是否已满, 如果没满就将请求放入队列中排队,队列满以后的所有请求直接返回秒杀失败。中间件在处理web服务器请求的同时,转发队列中的请求给中心商品服务器(/支付中心)(这里可以使用生产者-消费者模型)。
中心商品服务器(/支付中心)将前面N个成功秒杀的结果返回后, 返回秒杀结束。
中间件收到商品服务器返回的秒杀结束,给队列中的所有请求返回失败, 以后进来的请求也全部返回失败, 这样就大大降低了中心商品服务器的请求数, 甚至可以在候选人中间件和中心商品服务器之间再加一层候选人中间件
数据库层,使用存储过程代替JDBC调用,数据库层,库存单行更新,增加多个槽位.