如《同样是高并发,QQ/微博/12306的架构难度一样吗?》一文所述,同样是高并发场景,三类业务的架构挑战不一样:
- QQ类业务,用户主要读写自己的数据,访问基本带有uid属性,数据访问锁冲突较小
- 微博类业务,用户的feed主页由别人发布的消息构成,数据读写有一定锁冲突
- 12306类业务,并发量很高,几乎所有的读写锁冲突都集中在少量数据上 ,难度最大
系统层面,秒杀业务的优化方向如何?
主要有两项:
(1)将请求尽量拦截在系统上游,而不要让锁冲突落到数据库。 传统秒杀系统之所以挂,是因为请求都压到了后端数据层,数据读写锁冲突严重,并发高响应慢,几乎所有请求都超时,访问流量大,下单成功的有效流量小。 一趟火车2000张票,200w个人同时来买,没有人能买成功,请求有效率为0。 画外音:此时系统的效率,还不如线下售票窗口。 (2)充分利用缓存。 秒杀买票,这是一个典型的读多写少的业务场景:-
车次查询,读,量大
-
余票查询,读,量大
-
下单和支付,写,量小
秒杀业务,常见的系统分层架构如何?
-
端(浏览器/APP),最上层,面向用户
-
站点层,访问后端数据,拼装html/json返回
-
服务层,屏蔽底层数据细节,提供数据访问
-
数据层,DB存储库存,当然也有缓存
-
如果前一批请求均成功,再放下一批
-
如果前一批请求库存已经不足,则后续请求全部返回“已售罄”
-
浏览器拦截了80%请求
-
站点层拦截了99%请求,并做了页面缓存
-
服务层根据业务库存,以及数据库抗压能力,做了写请求队列与数据缓存
按照上面的优化方案,其实压力最大的反而是站点层,假设真实有效的请求数是每秒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次。更多的,用户关注是否有票,而不是票有几张。
无论如何,产品技术运营一起,目标是一致的,把事情做好,不存在谁是甲方,谁是乙方的关系。