什么是“秒杀”?为什么传统项目中也有“秒杀”的概念?一起来分析一下.
发布日期:2021-06-29 13:15:40 浏览次数:2 分类:技术文章

本文共 5436 字,大约阅读时间需要 18 分钟。

什么是“秒杀”?为什么传统项目中也有“秒杀”的概念?一起来分析一下.

 

如题所述,到底什么是“秒杀”,为什么我不是做电商的,还和秒杀扯上关系了?

或者说“秒杀”一定是电商项目的一个关键字吗?

 

当然,笔者这样反问了,那么当然,秒杀就不是特定电商项目的事情了。可以将其理解为这一类的业务的一个代称。

哪一类业务呢?高并发项目。

 

 

举个行业的例子来说;(这里还是以电商项目为例,因为电商项目的并发场景比较便于理解)

双十一,是每个人都热血沸腾的时刻,为什么这里说是时刻呢?因为从我们技术人眼中,双十一不是一天的概念,而是那么一瞬间的概念,通常来说,我们比较关心双十一 0点的时候的一个流量情况,而不是关心一天的流量情况(当然,一天也得关心,此处相对而论);

从产品角度、用户角度而言,双十一是一个活动,一个通过各种红包、优惠价等方式形成的一种促销活动,这种活动,将会吸引用户大量购物,产生大量交易订单,从而带动经济流动,产生各种经济利益。

从技术角度而言,双十一产生的数据量也是庞大的,此处通过去年双十一一个图片表达。

这是历年的双十一当天的订单数量产生量,以2019年为例,一天的时间产生了2684亿元的成交量

,这当天的并发量是怎么评估的。

 

再看另一张图,这里统计的是突破1000亿耗时,用了64分钟成交金额1000亿,这并发量又是多少?

.

2秒不到产生了100亿的交易额

 

这种数据量的产生的流量高峰是什么样的高度,相信这都是行业能标杆的表现。

这里的并发量是真的不敢想象,我们简单的计算一下,1.36秒,产生了100亿的交易额。

假设1个订单平均是100块钱,那么1.36秒就是产生了 1亿的订单数

假设1个订单平均是1000块钱,那么1.36秒就是产生了 10000000的订单数

那么就可以认为 并发为 7,352,941/s

一秒 700多万的并发,试问这什么数据库,什么服务能承受得了?

 

此处再强调,700多万的并发量是站在有效订单生成的基础上计算而来,并且是估算1000元一个订单的平均成交额。

要知道,我们平时的抢购经验来说,基本上是一件商品,上千上万的用户在抢购,这个 700多万 应当 * 1000 都不为过。

 

那么给各位先分析了一个数据上的概念,我们对后面的分析可能会更加理解一些。

那么在这种情况,上万人抢购一件商品的库存,对于系统而言,如何承担这样大的压力,并且有多少这种抢购存在,对系统的负载又是一大考验。

从数据库层面,我们需要生成订单,减库存,这么大的并发请求,我们如何能保证一件商品,最终成交只会有一个订单,其他请求都抢购失败。我们如何保证我们能够保证系统能够正常处理这么大的并发抢购量,而不是导致此次抢购活动瘫痪,导致就连一个订单都没有生成?这些都是我们此次需要思考的问题。

对于数据而言,我们需要将库存 -1,然后生成一个订单数;这样才是正确的流程,并且库存只能是>=0,切不能成为负数。我们系统如何控制此种规则严格执行?在很多场景,很多顾客在抢购成功后,生成订单了,然后不想要了,就会取消订单,这个时候,我们需要将库存+1,吧这个名额给其他顾客继续抢购,那么这如何实现?是直接将库存加回去吗?

站在数据库层面,我们对于一件商品的库存数据,如果多个个顾客同时请求下单,这个时候,我们怎么处理这种多线程环境下,保证不会超出库存量?

每个用户都会去不停的刷新商品,不停地关注商品的库存量,想看看现在库存是否有,我是否能下单?这种高请求量的读请求和其他用户的高请求量的下单操作并发出现,我们如何控制一条数据读写操作并发环境下正常?那这种情况下,我们既要保证有序进行,又要保证系统的高吞吐性能?

 

上面抛出来了一些问题,我们接下来都会一一分析,一一思考解决方案;

 

对于上述问题,我们需要考虑整体的架构设计,因为对于一个系统的健壮性,不能单单考虑一个点,依赖于某个技术栈就能解决问题;

对于系统的健壮性,高可用,业务应用集群部署是少不了的,通常来说,一般的互联网项目,我们都是线上部署多个应用集群,通过负载均衡规则,将流量均摊给各个服务。保证整体业务的高可用。这是一种思路,但是我们今天从另外一个角度来分析一下系统优化。

很多后端程序员都会片面思考服务端如何优化,怎么保证系统的健壮,但是我认为这是不好的思维方式(虽然笔者侧重后端开发);作为一个优秀的程序员,应当有全局设计的思考能力。

通常来说,我们一个B/S应用结构,是由用户电脑的浏览器发起请求,然后由统一网关接收,然后将其路由至对应的服务应用上处理,然后服务应用访问数据库的数据。一个由上至下的层次访问方式。(应该不会有人问直接浏览器访问数据库吧?)画个图

 

很简单的一个架构方式,我们就需要对这图上的各个节点进行系统优化思考

 

用户的浏览器

对于顶层,直接和用户打交道的,通常交互应用是通过应用客户端,和浏览器网页等形式,在这层上,我们通常都会有一定的处理逻辑。H5上会有js脚本,控制整个网页的规则运行,APP上通常有类似java等语言处理逻辑。我们都是通过这些逻辑再与服务器进行交互请求,回调的。那么在此处,我们就需要限制用户的无意或者恶意操作了。例如,用户狂刷新商品,看看商品的最新库存(本质来说,这是很常见的用户操作行为,但是在笔者看来,这无意义人工压测了)。那么这种操作,我们就需要控制真正请求到后端的请求量了。如果客户端是一个吃瓜群众,用户操作多少下,就请求多少下,那么这个客户端开发就会被服务端打死。

我们会看到很多类似的场景,例如,一个下单按钮,我们会在正常发起了一个下单请求后,当服务端还未返回结果前,这个按钮就是置灰的,不允许点击的。这也是降频操作。

但是对于一些操作场景,读的场景,我们不可能说,用户去刷新商品的库存,一秒钟只能刷新一次(至少不能让用户这样感知),这个时候,我们就需要做一些特殊处理了。例如,采用客户端本地缓存,举例说明,对于某个商品的库存数据,客户端采用缓存,缓存的过期时间为2s,当缓存过期后,才再次真实的向服务端发起请求,那么原本用户在10s中共刷新了200下商品缓存,那么这个时候,其实只会发起5个请求到服务端,原本需要200个请求,拦截了大量的恶意用户操作请求。并且我们并不限制用户的操作,提高的用户的体验感。

平时我们也会看到很多抢购活动中,明明有库存啊,就是下单的时候,就告诉我,没货了。这其实就是缓存一致性问题导致的。这种问题都是我们故意制造出来的,因为在这种业务场景下,我们是不太特别严格要求数据的一致性的。

 

网关

上面关于普通的用户操作,我们是可以处理大部分的用户的恶意操作的。但是在一个合格的程序员面前,我们所考虑的用户并不都是那种"良好"操作习惯的用户。对于系统安全来说,我们还需要考虑“同行”的行为。

我们的12306抢票软件,都知道,我们很多人大多时候并不会真的在这个软件平台上进行购票操作,因为很多时候我们在上面看,基本上都是没票。

那么这个时候就衍生出来了很多“同行”的产品,例如“智行12306”、“美团抢票”等等,那么对于这种第三方平台所制作出来的应用,目的是什么?是为了保护"官方12306购票系统"吗?当然不是,他们的目的就是保证他们的付费用户能够高效的购买的心意的票。那么这个时候,就是不是用户与服务之间的较量了。这就是服务与服务之间的较量了。并且这种第三方应用很多,这个时候又变成“多个打一个”了。你觉得"官方12306购票系统"难不难?这也是为什么早期"官方12306购票系统"上线的时候,直接崩了的原因了。

那么"官方12306购票系统"怎么处理呢?不管你还是“智行12306”、“美团抢票”,你都需要登录吧?不登录,你还想抢票?当游客吗?那么这个时候,我们就可以有效的过滤一批请求了。我们的 12306账户都是实名制的,一个真实的合法公民只能有一个账号,绑定了我们的身份证,那么这个时候,我们就能确定一个用户发起的所有请求是否只是一个用户发起的,我们就可以针对这个用户进行精准限流了,在用户发起下单请求的时候,都需要携带用户ID,那么这个时候,我们在站点层面就可以控制一个用户ID只路由一个请求到后端中,其他请求,站点都可以抛弃,直接返回false了。这样,同一个用户不管1秒发起的10000个下单请求还是10个下单请求,只会有一个请求被放行。这样即使有些用户使用了第三方抢票软件,我们也可以控制每个用户之间的请求是公平的。

甚至我们还可以做一些恶意攻击的处理方案,例如当1s发起了1000个请求,将其用户ID放入小黑屋待一段时间(布隆过滤器),或者将这个主机ip加进去,这样的话,就又控制了一批非人为操作发起的请求。

上面说的是写请求,那么获取请求呢?返回false?那这个时候也是不合理的,我们这个时候还是需要考虑缓存,站点层面的数据缓存,例如,用户获取当前商品的库存,这个时候,我们不以用户ID作为缓存键,而是以用户请求的商品的ID为缓存键,这样,即使两个不同的用户发起的请求,我们也可以实现缓存共用了。当用户1查询火车A的票余量的时候,生成的缓存,用户2也去查询火车B的票余量的时候,当超出限额流量的时候,也返回这个缓存数据,那么真实的请求就不会直接路由到业务服务上去。这也是一种保障用户友好体验的行为了。如果我们直接抛“请求过快,请稍后再重试”,你烦吗?我挺烦的。

对于此处,我们可以考虑“七层负载均衡”,通过nginx实现。如果想提高系统的吞吐率,我们可以使用增加节点方式,增加系统的吞吐率性能。

 

业务应用

存在一种这样的情况,服务已用户的账户进行限流,那么这个时候,如果我用1000个账户去抢票呢?其他用户只能用自己的1个账户去抢票,那么我的抢票的成功的概率是不是相对高一些呢?那这个时候,系统的并发也会有1000,再结合真实的人为产生的并发,也是有很高的并发量,那这个时候对于系统而言,怎么控制顺序读写?

mysql官方宣称写操作,单机并发2000写操作极限。再多就爆了。

那么redis官方宣称单机并发80000写操作,那这就可以解决很多流量问题了。

然后我们需要真实的控制可控的请求到数据库层面,那么如何控制呢?也就是缓存限流。

票的余量有1000张,10000个请求如果全部请求到数据库中?后面的9000个请求都直接返回票已卖光,这样不就可以吗?可以通过缓存记录票余量,直接在缓存中操作数量的读写操作,对于缓存的数据我们采用异步入库操作。定时将票的余量同步至数据库中。这样也保证了数据的持久性。 那么怎么控制买票的有序执行呢?队列,我们可以通过有序队列,消费下单请求,这样也保护了数据库的安全性。不使用队列,直接通过一个状态进行记录当前售票的情况,当我们记录到第1100个请求后,就直接将“票是否售完”状态变为true,那么这个时候,后面的请求都直接返回“票已售光”,那么就降低了系统逻辑的处理负载。

当然,对于业务应用,我们可以通过产品规则去解决,通过一些案例去解决。

例如:分时段操作,将用户从产品设计上进行拆分,这也是一种分散流量的操作。

对于"用户取消订单"的这种操作,我们可以将缓存中的票的余量进行重建,将票的余量+1,然后再将“票是否售完”状态变为false,这个时候我们会惊奇的发现,过了1个小时了,突然发现又有票了,奇怪哈!

在双十一的时候,淘宝也做了一个操作,就是“双十一当天禁止取消订单”操作,只能通过拦截方式。这是属于产品设计上进行系统保护。

 

数据库

从数据库层面而言,我们只需要考虑几点:保证数据库的可用性、保证数据的持久性、保证业务数据的正确性(不能卖票卖到还剩-10张吧?)

对于数据库的特性而言,存在行锁,多条请求,我们在多个请求的时候,当一个请求在修改数据库的数据的时候,就会出现排他锁,其他操作都是阻塞的,那么我们如果解决呢?排他锁,能够保证业务的正常,事务的隔离性,数据的脏读、幻读、重复读等异常情况,显然,我们不能直接不用innodb,对于互联网项目而言,读多写少,我们还是需要考虑事务问题,那么这个时候,我们就可以对数据进行拆分了。当票的余量的表的数据过大,我们可以考虑水平分表,分库,通过地域进行拆分。用售票规则而定,假如一张“杭州东” ~ “南昌西”的车次的票,我们可以将其放在“浙江库的杭州表”中,那么杭州始发地的票的余额,我们都可以知道去哪里查询,那么对于这趟车次的用户假设也很多呢?我们怎么解决一条数据的并发写操作呢?这里我们可以考虑将始发地进行拆分。都知道,我们12306放票规则是根据始发地分配的。

“杭州东” ~ “南昌西”

会途径 “金华”/"上饶" ,那么这个时候,我们假设这趟车一共有1000张票,那么“杭州东” 可以卖500张,“金华”可以卖 300张 “上饶”可以卖200张(当然,卖火车票不是这么简单的,我们需要考虑出发地,目的地,才能知道区间的票的购买余量,并且还需要控制始发地的放票量,才能给中间的站点买票数量)。那么这样我们就可以将一个车次的票拆分为3条数据,这样,3条数据就便于我们写操作了。

对于上述的业务而言,存在不严谨的数据假设,此处也只是为了举例思考。欢迎各位举例批评。

 

 

总结:

以上为个人通过个人学习,网络大佬的分享,总结的一些思考,希望各位bu she li jiao (没打出来这个成语,尴尬了)

转载地址:https://changemax.blog.csdn.net/article/details/113742012 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:三级缓存(不是CPU的概念,而是一种技术上逻辑容错处理方案)
下一篇:explain 关键字分析(第一次发)

发表评论

最新留言

不错!
[***.144.177.141]2024年04月08日 20时10分01秒