需要注意的问题

1、服务单一职责+独立部署

image.png

秒杀服务需要单独提取成一个微服务,这样即使它自己出问题,也不会影响别人。并且方便独立部署。

2、秒杀链接加密

image.png

可以对链接进行 MD5 加密,也可以使用随机码机制,就是在真正开始秒杀的时候,用户才会得知随机码,其它时间都不会知道。

3、* 库存预热+快速扣减

image.png

如果秒杀要走正常的加入购物车流程,然后去来锁库存,最终去支付,这样呢整个流程太慢了,在高并发系统里边肯定会出现整个级联崩溃的情况。

我们应该先做到预热库存,比如现在要秒杀的商品,数量有400件,我们给 redis 里面存一个 400 的信号量,想要秒杀的人进来之后,必须要先拿到信号量,这一块我们会对 redis 的信号量进行快速扣减,直接扣减1个数,所以无论有多少请求进来,即使有百万请求,最终也只有 400个人能拿到这个信号量的值。然后我们会将这 400 个人放行给我们后台的集群系统,这些请求即使走正常的下单逻辑,系统也不会出现什么问题。

当然这一块最大问题就是,redis 扛不住

一台 redis 单机并发可能就在两万到三万左右。所以如果我们想让它扛得住,我们可以做 redis 集群,可以做上十几台的集群,让它能扛住百万的并发。

4、* 动静分离

image.png

目前使用的是 nginx ,可以复制多个 nginx,组件 nginx 集群

上线以后更好的条件就是使用 CDN 来做压力承担。我们将现有的静态资源,全部分享给这个 CDN 网络,比如我们使用阿里云,我们让阿里云来保存这个静态资源,阿里云会将这些静态资源放进各个服务节点,比如有一个上海节点,还有北京节点,还有杭州节点。那接下来如果我们访问我们的静态资源,阿里云会就近选择一个最快的节点,给我们返回这个静态资源。

做好动静分离之后,放到后台的请求就很少了,以首页为例,5、60个请求,只有1个是动态请求,静态请求全过滤掉了,这样服务器的压力就小很多了。

5、* 恶意请求拦截

image.png

对于高并发系统来说,恶意请求也有很多种。

第一种情况,恶意脚本。

它会向服务器每秒发1000次秒杀请求,或者每秒发1000次请求商品数据的请求,但我们会发现,其实按照正用户正常的流量访问,刷的再快每秒可能也就五六次,所以每秒1000次请求肯定是有别的脚本在模拟这个访问。我们应该把这些恶意请求拦截过去,

还有一些伪造的请求。

比如我们有很多请求需要带一些令牌,它不带令牌,直接发请求,我们也应该直接拦截下来,

一句话,只要一经过网关以后,放给我们后台的整个请求,应该是一个具有正常行为的请求

恶意请求的拦截,我们最好在网关层拦截,最终经过网关层拦截之后,放给后台集群的又是一些正常请求

6、* 流量错峰

image.png

在我们的秒杀系统中,假设现在有100万个人来进行秒杀,同时点了立即抢购,那么瞬间流量就会达到一百万,

此时我们就需要处理这些流量,比如可以将流量分散到之后的几秒

方案一

用户点完立即抢购之后,让其输入一个验证码,验证码有两个好处

第一个好处,这个验证码来区分是机器还是人,人识别了验证码,然后输完了,点击提交,我们这个请求才是正常的,机器识别不了,所以它的这个请求就是非法的。

第二个好处,人输验证码,输的速度有快有慢,输完点击提交。可能就会出现十万个人在一秒内输完,又有十万个人在两秒内输完,
这样我们就相当于把整个流量分散开了。不是说一秒直接集中一百万,而是变成了波浪式的峰值活动。

最终这一百万的请求就会分散到各个时间点上

方案二

我们使用的购物车机制,用户选中了这个商品,点了加入购物车,再结账、锁库存,这都需要时间,大家的操作快慢都不一样。所以在这里又把流量给错开了,最终流量就会分摊到各个时间轴上。

7、* 限流&熔断&降级

image.png

我们的秒杀系统,可能要远程调用其它的服务,包括秒杀服务自己进来的流量也很大。

所以首先要做的第一个就是限流。限流有前端限流,有后端限流。

前端限流最典型的方式给按钮设置点击频率,比如点了第一次抢购之后,一秒以后才能点第二次、第三次,或者点一下就不能再点了。通过前端的限流,可以限制一部分的流量。

当然,如果用户知道点了这个按钮发什么请求,用恶意脚本去无限制的访问,前端也限不住。

所以前端限流先限制一些请求,请求来到后台时,后台来识别哪些请求是用户的正常行为,哪些是恶意行为,然后再给它进行过滤,包括请求一进来,即使是用户的正常行为,服务器也可以对其限流,比如1秒点了十几次,我们只放行一两次,

假设有100万请求,我们把不合理的去除掉,然后剩六十万,这六十万中,有些用户一秒点了十次,我们只给它放过一次,这样相当于十个请求我们只放过一个,请求就剩了六万。所以通过一步一步的操作,每到达一层,无论是前端还是后端,我们都给它来做一个限流的操作,把不合理的过滤掉,哪怕请求是合理的,点的次数太多了,也将其限制起来,最终后台集群里边收到的流量就会少很多。

我们还可以限制次数、限制总量

比如我们的秒杀服务现在部署了五个机器,峰值处理能力是十万,我们就可以在网关层做一个总限流,限制给秒杀服务的所有流量不能超过十万,超过十万了,就让用户等上两秒,等上两秒以后,再把请求传到秒杀服务。

还有熔断,也非常重要,

假设我们的秒杀系统中要调用其它业务,假设这个其它业务还有别的调用关系,比如 A 先调 B,B 再调 C, C 再调 D,如果这个 B 经常调用失败,我们就要给 B 做一个断路保护。比如我们知道 B 经常调用失败,那我们下一次就不尝试调 B 了,要不然就会一直在 B 那里阻塞等待。本来我们的请求按照正常调用0.1秒就得到释放了,可以处理下一个请求,但现在一直阻塞了3、5秒后才释放,告诉我们失败,这样就很不合理。

所以我们就加入熔断机制,只要调用链的任何一个服务出现问题,比如 D 出现问题,我们就给它返回快速失败。C 调用 D 快速的返回我们 R 对象,比如它的 code,改成 1,那就是失败。这样一失败以后,就能保证我们整个调用链是一个快速返回的,而不是阻塞的。

所以我们的熔断先保证快速失败,如果哪个服务出现问题,我们把它断路之后,相当于就把它隔离了,它也不会影响别的服务

再加上如果我们自己的服务出现问题,我们也可以让它降级运行,比如流量太大了,我们这个秒杀服务快被压垮了,我们可以将一部分的流量直接引导到一个降级页面,告知用户当前服务太忙,请稍后再访问。这也是种手段。

8、* 队列削峰

image.png

比如要秒杀的商品有一百个库存,有一百万个人来抢这一百个库存,我们在 redis 中存储了信号量,所以这一百人去抢信号量,最终这一百万个请求只有一百个人能抢到信号量,信号量的扣减非常快,就是给一个数值减个一,所有用户都在统一的 redis 里边操作,也不可能出现信号量超扣的问题,因为它最多扣到零,这是一个原子操作。

接下来把拿到信号量的人,放行给我们的后台,后台可以直接将请求发给一个队列,然后订单服务就来监听这个秒杀队列,订单服务会针对队列里面的数据来创建订单等操作,即使花费5秒,甚至10秒都行,此时我们就可以告诉用户秒杀成功了,五分钟以后看我的订单去支付这个订单即可,所以我们可以引入队列。

特别是对于我们这个场景,如果是单品秒杀无所谓,比如有一个 iphone x,然后它有一百件可以秒杀,因为我们能放进来的请求其实就一百个,我们通过信号量控制以后,放进来一百个请求,这一百请求个走正常流程没问题。

但如果现在是淘宝的双十一,淘宝全网的所有商品,可能有几百万件商品,假设每一个商品都有一百个库存,然后现在用户都来秒杀这些商品,每一个商品放进来一百个请求,一百万个商品就会产生一亿的流量。所以这个时候队列的作用就特别明显,所有请求一进来,只要你能秒杀成功,那我就把这个请求的相关数据放到队列里边,整个后台的订单集群就来监听这个队列,队列里边做好整个集群化,存上几万亿的数据都没任何问题。放进队列里边的那些数据,则交给我们后边的集群慢慢按照自己的能力来进行消费。

反正无论怎么消费,一分钟以后肯定能见到结果。所以最终抢到商品的用户可能会出现订单延迟,但是最终都可以支付成功。

所以这就是我们的杀手锏,队列削峰

目前已有的功能

  1. 服务单一职责+独立部署
    • ✅ 我们单独创建了秒杀服务
  2. 秒杀链接加密
    • ✅我们采用随机码的机制,只有在真正秒杀的时候,用户发送秒杀请求,才可以得到随机码
  3. 库存预热+快速扣减
    • ✅我们采用了 redis 信号量的方式,来存储秒杀商品的库存,并且我们可以对信号量进行快速扣减,我们使用分布式锁 redisson 进行原子减量,最终能放过去的请求数,就是它的秒杀量
  4. 动静分离
    • ✅我们一开始就使用 nginx 进行了动静分离
  5. 恶意请求拦截
    • 🔲waiting…
  6. 流量错峰
    • ✅我们使用的是添加购物车,然后去购物车创建订单,然后再支付的方式,所以这个我们也做到了
  7. 限流&熔断&降级
    • 🔲waiting…
  8. 流量削峰
    • 🔲waiting…

1 人点赞

  • 秒杀系统设计 - 图9

1