关于秒杀场景的全面分析总结

1.技术层面

1.高访问压力隔离
将秒杀系统独立部署,甚至使用独立域名,使其与网站完全隔离。

2.用户在秒杀开始前,通过不停刷新浏览器页面以保证不会错过秒杀.但是持续刷新商品页面详情页会对服务器造成压力
重新设计秒杀商品页面,不使用网站原来的商品详细页面,页面内容静态化(静态在CDN或客户端),用户请求不需要经过应用服务。而是静态页面,秒杀按钮在秒杀开始前无法点击。

3.突然增加的网络带宽:主要是很多商品的静态图片如果从某一个服务器去加载,网页必然在秒杀开始的那一刻崩溃。
秒杀商品页面缓存在CDN

4.如果下单的url可以直接通过html源码获取,那么就会有部分用户绕过网页前端直接在秒杀开始前下单。

隐藏下单的url,流程变为了:点击下单->获取秒杀url->通过url下单
在获取url层加入验证,判断是否有在下单时间内,如果可以下单就生成随机数,在url下单时就可以通过随机数判断了。

为了增大qps,获取秒杀url的时候可以单独隔离为其他服务,结果写入redis(前缀+userId+goodsId,随机数)即可。

5.如何只允许第一个提交的订单被发送到订单子系统
由于最终能够成功秒杀到商品的用户只有一个,因此需要在用户提交订单时,检查是否已经有订单提交。
如果已经有订单提交成功,则需要更新 JavaScript文件的请求秒杀url为空,更新秒杀开始标志为否,购买按钮变灰。

6.限流
在获取秒杀url这一层可以做限流操作,比如使用guava做令牌桶算法等等。某一个多次点击秒杀按钮,会生成验证码来限流。

7.减库存的时机
有两种选择,一种是拍下减库存 另外一种是付款减库存;目前采用的“拍下减库存”的方式,拍下就是一瞬间的事,对用户体验会好些。

8.库存会带来“超卖”的问题
redis分布式乐观锁,如果并发量不算极大也可以考虑zookeeper分布式锁

2.设计原则

1.尽量将请求拦截在系统上游
传统秒杀系统之所以挂,请求都压倒了后端数据层,数据读写锁冲突严重,并发高响应慢,几乎所有请求都超时,流量虽大,下单成功的有效流量甚小

2.读多写少的常用多使用缓存
这是一个典型的读多写少的应用场景【一趟火车其实只有2000张票,200w个人来买,最多2000个人下单成功,其他人都是查询库存,写比例只有0.1%,读比例占99.9%】,非常适合使用缓存。

3.架构

在秒杀开始的时候抢先进入下单页面,而不是商品详情等用户体验细节,因此秒杀系统的页面设计应尽可能简单。

下单表单也尽可能简单,购买数量只能是一个且不可以修改,送货地址和付款方式都使用用户默认设置,没有默认也可以不填,允许等订单提交后修改

1.前端页面
加上其他的css, js,图片等资源,如果同时有几千万人参与一个商品的抢购,一般机房带宽也就只有1G10G,所以必须前端页面CDN。

2.出于性能原因这个一般由js调用客户端本地时间,就有可能出现客户端时钟与服务器时钟不一致,另外服务器之间也是有可能出现时钟不一致。

客户端与服务器时钟不一致可以采用客户端定时和服务器同步时间。

这里考虑一下性能问题,用于同步时间的接口由于不涉及到后端逻辑,只需要将当前web服务器的时间发送给客户端就可以了,因此速度很快。

并且web服务器群是可以很容易的横向扩展的(LB+DNS轮询),这个接口可以只返回一小段json格式的数据

而且可以优化一下减少不必要cookie和其他http头的信息,所以数据量不会很大。

JS层面,限制用户在x秒之内只能提交一次请求;

3.站点设计
前端层的请求拦截,只能拦住小白用户(不过这是99%的用户哟),高端的程序员根本不吃这一套,写个for循环,直接调用你后端的http请求,怎么整?

同一个uid,限制访问频度,做页面缓存,x秒内到达站点层的请求,均返回同一页面(redis页面静态化)

同一个item的查询,例如手机车次,做页面缓存,x秒内到达站点层的请求,均返回同一页面(redis页面静态化)

如此限流,又有99%的流量会被拦截在站点层。

4.服务层设计
站点层的请求拦截,只能拦住普通程序员,高级黑客,假设他控制了10w台肉鸡(并且假设买票不需要实名认证),这下uid的限制不行了吧?怎么整?

大哥,我是服务层,我清楚的知道小米只有1万部手机,我清楚的知道一列火车只有2000张车票,我透10w个请求去数据库有什么意义呢?

访问数据库的过程:秒杀开始->redis->进入消息对列->mysql

对于写请求,做请求队列,每次只透过有限的写请求去数据层,如果均成功再放下一批,如果库存不够则队列里的写请求全部返回“已售完”(无需再次访问数据库);

对于读请求,还用说么?cache来抗,不管是memcached还是redis,单机抗个每秒10w应该都是没什么问题的;

如此限流,只有非常少的写请求,和非常少的读缓存mis的请求会透到数据层去,又有99.9%的请求被拦住了。

5.请求分发
用户请求分发模块:使用Nginx或Apache将用户的请求分发到不同的机器上

用户请求预处理模块:判断商品是不是还有剩余来决定是不是要处理该请求。

用户请求处理模块:把通过预处理的请求封装成事务提交给数据库,并返回是否成功。

数据库接口模块:该模块是数据库的唯一接口,负责与数据库交互,提供RPC接口供查询是否秒杀结束、剩余数量等信息。

4.数据库设计

1.数据库分片
在这里插入图片描述
分片解决的是“数据量太大”的问题,也就是通常说的“水平切分”。

一旦引入分片,势必有“数据路由”的概念,哪个数据访问哪个库。

hash分库:
优点:简单,数据均衡,负载均匀
缺点:迁移麻烦(2库扩3库数据要迁移)

ps:此时不能使用id自增的模式
两个写库使用不同的初始值,相同的步长来增加id:1写库的id为0,2,4,6…;2写库的id为1,3,5,7…;

不使用数据的id,业务层自己生成唯一的id(雪花算法),保证数据不冲突;

2.分组
在这里插入图片描述
也就是组从分离模式,一主多从实现读写分离

实际互联网公司都是:分片又分组
在这里插入图片描述
3.高可用
在这里插入图片描述
仍是双主,但只有一个主提供服务(读+写),另一个主是“shadow-master”,只用来保证高可用,平时不提供服务。

4.读性能
添加索引,主库不加索引,从库加索引
在这里插入图片描述

常见的是下面两种缓存增加读性能的结构:
在这里插入图片描述
在这里插入图片描述
5.数据一致性
**主从一致性:**常用中间件同步主从库,某一个操作会被发送给所有db。
在这里插入图片描述

cache和数据库的一致性的问题
在这里插入图片描述
常见的缓存架构如上,此时写操作的顺序是:

(1)淘汰cache;
(2)写数据库;

读操作的顺序是:

(1)读cache,如果cache hit则返回;
(2)如果cache miss,则读从库;
(3)读从库后,将数据放回cache;

在一些异常时序情况下,有可能从【从库读到旧数据(同步还没有完成),旧数据入cache后】,数据会长期不一致。解决办法是“缓存双淘汰”,写操作时序升级为:

(1)淘汰cache;
(2)写数据库;
(3)在经过“主从同步延时窗口时间”后,再次发起一个异步淘汰cache的请求;

这样,即使有脏数据如cache,一个小的时间窗口之后,脏数据还是会被淘汰。带来的代价是,多引入一次读miss(成本可以忽略)。

除此之外,最佳实践之一是:建议为所有cache中的item设置一个超时时间。

数据库扩容保证高效扩容:数据库节点加倍(解决hash扩容的缺点)

原始结构:两个master,一个作为另一个的备份,原来只有模0和模1的区别。
在这里插入图片描述
开始扩容:现在变成了模0到模3,并且原来的备用的master生效开始工作,承担四分之一的工作。
在这里插入图片描述
最后给四个master分配一个“备用master”。
在这里插入图片描述

5.一些总结

万一负载过大,可以选择拒绝请求来保护服务的可用性
在这里插入图片描述
多次重复的用户请求生效一次
在这里插入图片描述
万一流量多大以可使用验证码来削峰
在这里插入图片描述
超发和超卖问题可以使用redis和zookeeper的分布式锁解决:
友情链接https://blog.csdn.net/qq_35688140/article/details/100983288

精简至(加入了一些个人对秒杀的理解):https://mp.weixin.qq.com/s?__biz=MzAwOTE3NDY5OA==&mid=2647908611&idx=1&sn=2bd05e30ed8c1e22bf7928b49360b3d6&chksm=8344eac6b43363d04e5525cea543349d4a324b281bb3d19ce46abd2be43e451b67f3051998ee&mpshare=1&scene=1&srcid=&sharer_sharetime=1569457291296&sharer_shareid=7df625ffbcd223e8cf2361bf11b95c68&key=cd483184e931ac1c2f8fa02a37056991cd712f8b7c08f2857dd2c3a700b17889fdd6e5a1ddc2bcf7d266e6c91266d55c80a358045be32d7f04b0939cc06f381bf2ded19470080d417de790f88ee1202a&ascene=1&uin=MjIyODYyOTkwNQ%3D%3D&devicetype=Windows+10&version=62060833&lang=zh_CN&pass_ticket=TEvIJN2kxnjksbeVWbir%2FnVt3556HgCsJsI4MJHacOlounyrYAq%2B14supvhBdLxB

发布了275 篇原创文章 · 获赞 42 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qq_35688140/article/details/101434668