只有2000张火车票,即使10w个请求过来,也只透传2000个去访问数据库:
- 如果前一批请求均成功,再放下一批
- 如果前一批请求库存已经不足,则后续请求全部返回“已售罄”
cache抗,不管是memcached还是redis,单机抗个每秒10w应该都是没什么问题的 。
画外音:缓存做水平扩展,很容易线性扩容 。
如此削峰限流,只有非常少的写请求,和非常少的读缓存mis的请求会透到数据层去,又有99%的请求被拦住了 。
四、数据库层
经过前三层的优化:
- 浏览器拦截了80%请求
- 站点层拦截了99%请求,并做了页面缓存
- 服务层根据业务库存,以及数据库抗压能力,做了写请求队列与数据缓存
db基本就没什么压力了,闲庭信步 。
画外音:这类业务数据量不大,无需分库,数据库做一个高可用就行 。
此时,透2000个到数据库,全部成功,请求有效率100% 。
画外音:优化前,10w个请求0个成功,有效性0% 。
按照上面的优化方案,其实压力最大的反而是站点层,假设真实有效的请求数是每秒100w,这部分的压力怎么处理?
解决方向有两个:
(1)站点层水平扩展,通过加机器扩容,一台抗5000,200台搞定;
(2)服务降级,抛弃请求,例如抛弃50%;
原则是要保护系统,不能让所有用户都失败 。
站点层限速,是个每个uid的请求计数放到redis里么?吞吐量很大情况下,高并发访问redis,网络带宽会不会成为瓶颈?
同一个uid计数与限速,如果担心访问redis带宽成为瓶颈,可以这么优化:
(1)计数直接放在内存,这样就省去了网络请求;
(2)在Nginx层做7层均衡,让一个uid的请求落到同一个机器上;
画外音:这个计数对数据一致性、准确性要求不高,即使服务重启计数丢了,大不了重新开始计 。
除了系统上的优化,产品与业务还能够做一些折衷,降低架构难度 。
业务折衷一
一般来说,下单和支付放在同一个流程里,能够提高转化率 。对于秒杀场景,产品上,下单流程和支付流程异步,放在两个环节里,能够降低数据库写压力 。以12306为例,下单成功后,系统占住库存,45分钟之内支付即可 。
业务折衷二
一般来说,所有用户规则相同,体验会更好 。对于秒杀场景,产品上,不同地域分时售票,虽然不是所有用户规则相同,但能够极大降低系统压力 。北京9:00开始售票,上海9:30开始售票,广州XX开始售票,能够分担系统压力 。
业务折衷三
秒杀场景,由于短时间内并发较大,系统返回较慢,用户心情十分焦急,可能会频繁点击按钮,对系统造成压力 。产品上可以优化为,一旦点击,不管系统是否返回,按钮立刻置灰,不给用户机会频繁点击 。
业务折衷四
一般来说,显示具体的库存数量,能够加强用户体验 。对于秒杀场景,产品上,只显示有/无车票,而不是显示具体票数目,能够降低缓存淘汰率 。
画外音:显示库存会淘汰N次,显示有无只会淘汰1次 。更多的,用户关注是否有票,而不是票有几张 。
无论如何,产品技术运营一起,目标是一致的,把事情做好,不存在谁是甲方,谁是乙方的关系 。
总结
对于秒杀系统,除了产品和业务上的折衷,架构设计上主要有两大优化方向:
(1)尽量将请求拦截在系统上游;
(2)读多写少用缓存
推荐阅读
- 淘宝非法请求签名是什么意思 淘宝非法请求签名怎么办
- ajax——请求本地数据库
- 西北风每秒10米是几级风 东北风10米每秒是几级风
- Python服务器动态资源请求
- C#模拟HTTP请求发送二进制文件
- 达到物理网卡上限,突然几十万的请求访问Redis的某个key如何解决
- 每秒20W次并发分词检索,架构如何设计?
- C++内存池实现
- 一文彻底搞懂JavaScript异步请求
- PHP模拟POST/GET请求,以及http_build_query用法,好陌生?