为了账号安全,请及时绑定邮箱和手机立即绑定
慕课专栏

目录

索引目录

如何设计一个Java秒杀系统

原价 ¥ 58.00

立即订阅
04 多层限流:抵御秒杀的必备策略
更新时间:2020-05-28 12:26:07
读书而不思考,等于吃饭而不消化。——波尔克

在前两节中,我们从业务和技术的角度分析了秒杀活动的特点,以及面临的技术挑战。从本节开始,将从设计的角度分析秒杀系统应该注意的事项,以及具体的解决方案。本小节主要介绍的是“限流”。

秒杀会造成高并发,而在高并发环境下,最直接的就是使用限流技术来抗衡海量流量的冲击。

秒杀期间的流量会依次触达系统的前台、后台及数据库服务器。如果将全部的请求流量比喻成“入侵的敌军”,前台就是我们正面敌军的第一道防线。因此,第一道防线必须想尽办法削弱敌军,尽力守卫我们的后方阵地。当第一道防线失守,敌军抵达后台服务器时,后台就是第二道防线。同理,后台会继续承担起防守的职责,为数据库做好防卫。只有先后冲破前台和后台守卫的流量,才能最终访问数据库,将抢购的订单入库。不难发现,前面防守的越好,后面遇到的敌军就越少。

因此,在设计秒杀系统时,既要层层限流,又要尽早限流。

1.第一层限流:合法性限流

秒杀抢购时,用户执行的第一个动作就是点击“抢购”按钮,因此“抢购”按钮就是第一次在前台限流的时机。我们可以先验证用户操作的合法性:是否存在刷单行为(同一 IP 在某一时间段内多次提交相同订单)、抢购前是否已登录、抢购渠道是否合法等。

  • 如果检测到刷单行为,可以想办法延长用户操作时间,避开秒杀的流量峰值。常用的手段如“加入验证码”、“提示繁忙,并倒计时5秒后再跳回下单页面”。同理,如果检测到用户未登录,就直接提示用户登录,从而推迟下单时间。即,可以通过延长下单时间来降低秒杀的流量峰值;

    如果检测到的刷单频率过快(例如每 100 毫秒抢单一次),就可以判断是“抢单机器人”在恶意刷单,可以将此客户端的请求直接拒绝,并将此 IP 加入到黑名单中。

  • 正常情况下,用户只有浏览了商品的介绍页面之后才能点击“抢购”按钮,而对于直接访问"抢购商品 URL "的作弊行为应予以禁止(例如,用户可能提前复制了抢购的 URL ,然后粘贴到地址栏访问,或者通过 ajax 等程序直接调用)。例如,可以先通过技术手段隐藏抢购商品的 URL ,只有当用户进行了合理的操作之后,再开放抢购 URL ,并且限制每次生成的 URL 仅一次有效。即,可以通过对抢购渠道的合法性校验,来限制非法请求。

2.第二层限流:负载限流

在古时候,人们用牛来拉重物。当一头牛拉不动一根圆木时,人们从来没有考虑过要培育更强壮的牛。同理,我们也不该想方设法打造超级计算机,而应该千方百计综合利用更多计算机来解决问题。
——格蕾斯·霍珀( Grace Hopper )

合法性限流只能限制无效、非法的请求。但对于秒杀抢购来说,仍然会有大量的合法有效请求进入系统。解决这些合法请求的第一个手段就是集群:搭建后台服务集群,然后通过 nginx 等反向代理服务器将请求分流到不同的集群节点上,即用多个服务器共同来负载请求压力,如图所示。

图片描述

假设某一秒内有 10 万个请求,那么三个后台服务器平均每台只需要处理 3.3 万个。因此,可以通过增加服务器的个数,轻松降低单台服务器压力。此外,使用集群做负载均衡还有一个好处是,可以实现失败迁移。三个服务器即使挂掉一个,还可以由其余两个顶着,可以避免单节点故障问题。

思考:集群就一定会增加经济成本吗?

如果是小规模集群,购买一台超级计算机,和购买多台普通计算机,哪个更贵?

还要注意的是, nginx 只是负载均衡工具中常用的一个,它是在网络模型的第七层(应用层)进行负载。类似的,也可以在网络模型中的其它层做负载,例如也可以在数据链路层生成一个虚拟的 MAC 地址,然后再映射到多个真实的 MAC 地址上,即在网络模型的第二层进行负载。同样的道理,也可以在第三层通过虚拟 IP 与真实 IP 之间的映射实现负载,或者在第四层通过端口号进行负载(我们经常使用的 LVS 就是基于第四层的负载)。

此外, nginx 是通过软件进行的负载均衡,我们也可以使用 F5 、 Array 等硬件负载均衡产品来实现分流的功能。当然,使用硬件必然会增加开发的成本。

在设计负载均衡时,需要考虑以下几点:

1.多个负载均衡工具可以结合起来同时使用,常见的做法如“使用 LVS + Nginx ”实现多级负载。但要注意的是,并不是负载的级别越多越好,因为每增加一级负载就会增加一条请求的转发路径,转发路径越多就越容易出现网络延迟、失效等异常情况,从而为系统埋下隐患。此外,负载均衡的后方是后台服务器,因此负载的次数越多(例如使用了多个 nginx 组成的负载集群)、级别越多(例如使用了 LVS + Nginx )就必然需要搭建更多的后台服务器;

2.各个负载均衡工具的特点各不相同,例如Nginx工作在网络模型的第 7 层,适用于更多的场合; LVS 是直接对流量进行的转发,因此效率非常高。

在实际开发时,需要根据具体的业务体量、财务预算和压力测试的结果等,选取最适合本系统的负载架构。

提示:本段讲解的是请求从前台抵达后台期间的负载限流。除此以外,在设计数据库时,也可以通过虚拟IP等手段进行数据库层面的负载限流。简单地讲,就是将流入数据库的请求再次负载到多个数据库服务器上。这些会在后面的章节里详细讲解。

3.第三层限流:服务限流

当海量请求经过了负载层的限流后,就会抵达后台服务。后台可以通过Web服务器、消息队列、实时监控等方式进行限流。

3.1Web服务器限流

单台 Web 服务器的处理能力有限,因此可以给 Tomcat 等服务器设置线程池,并设置线程池中的最大线程数。或者也可以设置服务器的最大连接数,从而限制处理请求的速度,保证服务器的稳定性。在实际设置这些服务器参数时,一定要结合具体的业务场景。例如,如果某个服务主要用于数据运算,就要相对减少此服务器的线程数,提高 CPU 的运算效率;如果服务的核心业务是 IO 操作,就要增大线程数,提高吞吐量。

3.2队列限流

水库可以抵御洪水的冲击,防止泛滥的水资源快速涌向下游;堡垒可以抵御敌人的攻击,防止敌军快速入侵。同样的,当秒杀带来了海量的请求时,我们也可以给它设置一道门槛,防止其快速冲向后端和服务器。这道门槛就可以是消息队列(当然也可以是缓存等其他技术,这里主要讲解消息队列)。

顾名思义,消息队列(简称 MQ )是一个由消息组成的队列,有着 FIFO 的特性。我们可以把秒杀时的用户请求当做“消息”存储到消息队列之中,之后秒杀系统的各个子系统就可以根据自身的性能,从消息队列中平稳的拉取固定数目的请求数,如图所示:

图片描述
举个例子,假设某一时刻秒杀的并发请求量是 10 万,“子系统-A“的处理极限是 2 万、“子系统-B“是 3 万、“子系统-C“是 4 万,显然 A、B、C 三个子系统是无法同时承受 10 万的请求量。此时,就可以先将 10 万个请求存储到消息队列中(即入队),然后A、B、C子系统根据自身的能力,选择性地从消息队列中拉取一定数额的请求(即出队),具体就是“子系统-A”仅从消息队列中拉取 2 万个请求并处理、“子系统-B”拉取 3 万条、“子系统-C”拉取 4 万条,然后将剩余的 1 万条请求直接拒绝,或者等待A、B、C的第二轮出队操作。这样一来,各个子系统既可以充分的发挥自己的性能,也能防止由于超负荷的并发量造成的系统崩溃。

3.3监控限流

网络带宽、CPU、内存、服务器的并发量等都是秒杀系统的重要性能指标,这些中的任何一个超负荷都可能造成系统的延迟或崩溃。因此,可以通过程序对它们进行实时监控,并设置自动开关。例如,当某个组件的利用率超过 90% 时就自动进入保护模式:在利用率降到 60 %以前无条件的拒绝一切其它请求。

除了上述的限流方法以外,还可以使用多线程、令牌桶算法、数据库调优等多种手段在不同的维度限制并发访问量。甚至也可以直接使用 RateLimiter 等工具类,在 API 层面直接调用三方 JAR 已经封装好的限流方法。

小结

本节先讲解了如何使用“限流”拦截秒杀时的海量请求,或将其分散到多个服务节点上。当请求到来时,先通过合法性校验拦截一部分请求,然后通过负载均衡组件将请求分流到多台服务器上。之后再对到达各个服务器上的请求通过 Web 服务器、消息队列和监控等手段进行逐步的限流,使请求数最终被控制在可被服务器处理的范围内。这就好比一次气势磅礴的洪水,被我们通过建高墙、挖渠道等手段,最终削弱成可控制的流水。无招胜有招,建议读者不用刻意的去记忆本节介绍的各个限流手段,而是以请求的“时间线”为线索,分析请求经历的每一个阶段,然后思考每个阶段可以采取什么样的方法进行限流,也许你能想到的方法比本文介绍的还要多。

但要注意,每采用一次限流手段,就会给整个秒杀系统增加一道工序,因此会多增加一层风险。例如,如果增加了“消息队列”进行限流,那么消息队列自身的故障也会导致系统问题。也就是说,不要盲目地将所有限流手段进行堆积,避免带来不确定的风险,在实际开发时,可以借助于两点进行权衡:1.经验;2.测试报告。后面讲解的缓存等技术也是如此,一定要实际考量每层缓存的具体作用,切勿盲目堆积。

}
立即订阅 ¥ 58.00

你正在阅读课程试读内容,订阅后解锁课程全部内容

千学不如一看,千看不如一练

手机
阅读

扫一扫 手机阅读

如何设计一个Java秒杀系统
立即订阅 ¥ 58.00

举报

0/150
提交
取消