秒杀系统优化学习总结

归纳自W3Cschool的架构师之路系列文章

Posted by Hyuga on August 23, 2018

本文仅做个人学习知识点归纳,原文请前往W3Cschool-架构 秒杀系统优化思路查看

什么是秒杀系统

常见的秒杀系统很多,最著名莫过于12306,流量大,并发高,抢票请求远高于票数,多人读取同一份数据,相关业务功能必须加锁,但同时又会造成读写冲突,锁非常严重。

这种场景有什么办法可以优化呢?

  • 拦截过量的请求
  • 使用缓存

如何拦截过量请求

  • 系统上游拦截:过滤大量无效请求流入数据库,减轻数据库锁冲突压力,减轻并发,提高响应效率
  • 利用缓存:秒杀,是一个典型的读多写少的应用场景,查询请求远高于下单请求,非常适合利用缓存进行优化

常见秒杀架构

浏览器 -> 站点 -> 服务 -> 数据

  • 浏览器端,最上层,会执行到一些JS代码
  • 站点层,这一层会访问后端数据,拼html页面返回给浏览器
  • 服务层,向上游屏蔽底层数据细节,提供数据访问
  • 数据层,最终的库存是存在这里的,mysql是一个典型(当然还有会缓存)

各层级优化

客户端拦截请求

客户端js限制请求触发

  • 点击按钮后,按钮置灰,禁止用户重复提交请求,等响应后放开限制
  • js控制,限制用户x秒内只能提交一次请求

这就是所谓的“将请求尽量拦截在系统上游”,越上游越好,浏览器层,APP/WEB层就给拦住 这种方式可以拦截普通用户的大量无效请求,但对于编程人员来说,这是拦不住的,一个firebug模拟http请求,啥用没有。 那有什么其他方式拦截这种模拟请求呢?

站点拦截请求

用户id为key,时间轴控制触发次数+页面、数据缓存

  • uid限流:一个用户uid请求后,5秒只准透过1个请求
  • 页面缓存:5秒内的其他请求返回上一个有效请求的缓存数据、页面

这种方式能在站点层(例如:controller)限制程序猿的for循环批量刷票行为,也能保证普通用户体验良好 优点:有效限流,不会出现404,用户体验较好,保证了系统的健壮性(利用页面缓存,把请求拦截在站点层了) 缺点:不同站点页面缓存内容可能不一致(如果使用redis数据缓存是否可避免)

问题又来了,如果黄牛批量创建账号(不考虑实名),比如10w个id去刷票,那怎么办?上面这种方式也拦不住了。

服务层拦截请求

请求队列

这一层的拦截重点,就是不让请求流入数据库 前面拦截了大量无效请求后,剩下的请求量可能还是远大于票库存量,这时就需要请求队列的帮助了。

假如库存只有100,前面两层拦截后仍有1000个下单、支付请求到了服务层,此时创建一个请求队列,将符合库存数量的请求放入队列,每次只从队列中放有限的写请求(比如10个)到数据库。 如果10个请求都处理完了,再从请求队列中取10个去请求数据库,如果库存不够则队列里面的下单、支付等写请求全部返回已售完

数据库层

闲庭信步

上面三层已经拦截了大量请求,db基本就没什么压力了

其实库存是有限的,透太多无效的请求到数据库根本没有没有意义。

全部透到数据库,100w个下单,0个成功,请求有效率0%。透3k个到数据,全部成功,请求有效率100%。

总结

  • 尽量将请求拦截在系统上游(越上游越好)
  • 读多写少的常用多使用缓存(缓存抗读压力)
  • 结合业务做优化

至此,写请求的限流拦截就结束了,下面看看读请求是怎么优化的。

读请求优化

采用缓存优化

不管是memcached还是redis,单机抗个每秒10w应该都是没什么问题的。将同样的请求响应数据进行缓存一段时间,抗住大量读请求流入数据库。

  • 分时分段售票
    • 例如12306的分时分段售票,进行流量摊匀。
  • 数据粒度的优化
    • 流量大的时候,做一个粗粒度的“有票”“无票”缓存即可。
    • 1000w请求,只有100票,让100个请求到队列,其他的请求大部分可以直接返回无票响应并缓存
  • 业务逻辑异步
    • 例如下单业务与支付业务分离
    • 分布式服务,下单业务与支付业务分离,下单成功的用户后续请求流量将流入支付相关站点