每秒100W请求,架构如何优化

如《同样是高并发,QQ/微博/12306的架构难度一样吗?》一文所述,同样是高并发场景,三类业务的架构挑战不一样:

  • QQ类业务,用户主要读写自己的数据,访问基本带有uid属性,数据访问锁冲突较小
  • 微博类业务,用户的feed主页由别人发布的消息构成,数据读写有一定锁冲突
  • 12306类业务,并发量很高,几乎所有的读写锁冲突都集中在少量数据上,难度最大
那么对于秒杀类业务,系统上和业务上分别能如何优化呢,这是本文要讨论的问题 。
系统层面,秒杀业务的优化方向如何?
主要有两项:
(1)将请求尽量拦截在系统上游,而不要让锁冲突落到数据库 。
传统秒杀系统之所以挂,是因为请求都压到了后端数据层,数据读写锁冲突严重,并发高响应慢,几乎所有请求都超时,访问流量大,下单成功的有效流量小 。
一趟火车2000张票,200w个人同时来买,没有人能买成功,请求有效率为0 。
画外音:此时系统的效率,还不如线下售票窗口 。
(2)充分利用缓存 。
秒杀买票,这是一个典型的读多写少的业务场景:
  • 车次查询,读,量大
  • 余票查询,读,量大
  • 下单和支付,写,量小
一趟火车2000张票,200w个人同时来买,最多2000个人下单成功,其他人都是查询库存,写比例只有0.1%,读比例占99.9%,非常适合使用缓存来优化 。
秒杀业务,常见的系统分层架构如何?
每秒100W请求,架构如何优化

文章插图
秒杀业务,可以使用典型的服务化分层架构:
  • 端(浏览器/App),最上层,面向用户
  • 站点层,访问后端数据,拼装html/json返回
  • 服务层,屏蔽底层数据细节,提供数据访问
  • 数据层,DB存储库存,当然也有缓存
这四层分别应该如何优化呢?
一、端上的请求拦截(浏览器/APP)
想必春节大家都玩过微信的摇一摇抢红包,用户每摇一次,真的就会往后端发送一次请求么?
回顾抢票的场景,用户点击“查询”按钮之后,系统卡顿,用户着急,会不自觉的再去频繁点击“查询”,不但没用,反而平白无故增加系统负载,平均一个用户点5次,80%的请求是这么多出来的 。
JS层面,可以限制用户在x秒之内只能提交一次请求,从而降低系统负载 。
画外音:频繁提交,可以友好提示“频率过快” 。
APP层面,可以做类似的事情,虽然用户疯狂的在摇微信抢红包,但其实x秒才向后端发起一次请求 。
画外音:这就是所谓的“将请求尽量拦截在系统上游”,浏览器/APP层就能拦截80%+的请求 。
不过,端上的拦截只能挡住普通用户(99%的用户是普通用户),程序员firebug一抓包,写个for循环直接调用后端http接口,js拦截根本不起作用,这下怎么办?
二、站点层的请求拦截
如何抗住程序员写for循环调用http接口,首先要确定用户的唯一标识,对于频繁访问的用户予以拦截 。
用什么来做用户的唯一标识?
ip?cookie-id?别想得太复杂,购票类业务都需要登录,用uid就能标识用户 。
在站点层,对同一个uid的请求进行计数和限速,例如:一个uid,5秒只准透过1个请求,这样又能拦住99%的for循环请求 。
一个uid,5s只透过一个请求,其余的请求怎么办?
缓存,页面缓存,5秒内到达站点层的其他请求,均返回上次返回的页面 。
画外音:车次查询和余票查询都能够这么做,既能保证用户体验(至少没有返回404页面),又能保证系统的健壮性(利用页面缓存,把请求拦截在站点层了) 。
OK,通过计数、限速、页面缓存拦住了99%的普通程序员,但仍有些高端程序员,例如黑客,控制了10w个肉鸡,手里有10w个uid,同时发请求,这下怎么办?
三、服务层的请求拦截
并发的请求已经到了服务层,如何进拦截?
服务层非常清楚业务的库存,非常清楚数据库的抗压能力,可以根据这两者进行削峰限速 。
例如,业务服务很清楚的知道,一列火车只有2000张车票,此时透传10w个请求去数据库,是没有意义的 。
画外音:假如数据库每秒只能抗500个写请求,就只透传500个 。
【每秒100W请求,架构如何优化】用什么削峰?
请求队列 。
对于写请求,做请求队列,每次只透传有限的写请求去数据层(下订单,支付这样的写业务) 。


推荐阅读