对于很多电商系统而言,在诸如双十一这样的大流量的迅猛冲击下,都曾经或多或少发生过宕机的情况。当一个系统面临持续的大流量时,它其实很难单靠自身调整来恢复状态,你必须等待流量自然下降或者人为地把流量切走才行,这无疑会严重影响用户的购物体验。
我们可以在系统达到不可用状态之前就做好流量限制,防止最坏情况的发生。针对电商系统,在遇到大流量时,更多考虑的是运行阶段如何保障系统的稳定运行,常用的手段:限流、降级、拒绝服务。
限流相对降级是一种更极端的保存措施,限流就是当系统容量达到瓶颈时,我们需要通过限制一部分流量来保护系统,并做到既可以人工执行开关,也支持自动化保护的措施。
限流既可以是在客户端限流,也可以是在服务端限流。限流的实现方式既要支持 URL 以及方法级别的限流,也要支持基于 QPS 和线程的限流。
(1)客户端限流
好处:可以限制请求的发出,通过减少发出无用请求从而减少对系统的消耗。
缺点:当客户端比较分散时,没法设置合理的限流阈值。如果阈值设的太小,会导致服务端没有达到瓶颈时客户端已经被限制;而如果设的太大,则起不到限制的作用。
(2)服务端限流
好处:可以根据服务端的性能设置合理的阈值。
缺点:被限制的请求都是无效的请求,处理这些无效的请求本身也会消耗服务器资源。
在限流的实现手段上来讲,基于 QPS 和线程数的限流应用最多,最大 QPS 很容易通过压测提前获取,例如我们的系统最高支持 1w QPS 时,可以设置 8000 来进行限流保护。线程数限流在客户端比较有效,例如在远程调用时我们设置连接池的线程数,超出这个并发线程请求,就将线程进行排队或者直接超时丢弃。
限流必然会导致一部分用户请求失败,因此在系统处理这种异常时一定要设置超时时间,防止因被限流的请求不能 fast fail(快速失败)而拖垮系统。
(3)限流的方案
业务场景:
商品详情页入口流量防护:黑白名单,限制同一个 ip 访问频率,限制查询商品接口调用频率。
方案一:基于 redis+lua 脚本限流
gateway 官方提供了 RequestRateLimiter 过滤器工厂,基于 redis+lua 脚本方式采用令牌桶算法实现了限流。
方案二:整合 sentinel 限流
利用 Sentinel 的网关流控特性,在网关入口处进行流量防护,或限制 API 的调用频率。
Spring Cloud Gateway 接入 Sentinel 实现限流的原理:
1)引入依赖
<!‐‐添加Sentinel的依赖‐‐>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring‐cloud‐starter‐alibaba‐sentinel</artifactId>
</dependency>
<!‐‐ gateway接入sentinel ‐‐>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring‐cloud‐alibaba‐sentinel‐gateway</artifactId>
</dependency>
2)接入 sentinel 制台,修改 application.yml 配置
spring:
application:
name: gateway
main:
allow‐bean‐definition‐overriding: true
cloud:
sentinel:
transport:
dashboard: 192.168.65.103:8000
3)启动 sentinel 控制台
下载地址: https://github.com/alibaba/Sentinel/releases/download/1.8.4/sentineldashboard-1.8.4.jar
java ‐Dserver.port=8000 ‐jar sentinel‐dashboard‐1.8.4.jar
访问[http://192.168.65.103:8000/#/login](http://192.168.65.103:8000/#/login)
,默认用户名密码: sentinel/sentinel
。
4)测试
启动网关服务,商品服务,访问商品详情接口[http://localhost:8888/pms/productInfo/27](http://localhost:8888/pms/productInfo/27)
。
从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:
route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId;
自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组;
route 维度限流
需求:对商品详情接口进行流控
1)配置流控规则
2)jemeter 压测配置
压测接口:[http://localhost:8888/pms/productInfo/27](http://localhost:8888/pms/productInfo/27)
。
3)查看jemeter压测结果
4)查看 sentinel 控制台实时监控效果
微服务接入 sentinel。
场景:对秒杀下单接口进行流控。
Sentinel 匀速排队方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。该方式的作用如下图所示:
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
注意:匀速排队模式暂时不支持 QPS > 1000 的场景。
场景:商品详情接口热点参数限流。
很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制;
用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制;
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
注意:
@SentinelResource("resourceName")
注解,否则不生效;降级就是当系统的容量达到一定程度时,限制或者关闭系统的某些非核心功能,从而把有限的资源保留给更核心的业务。
比如降级方案可以这样设计:当秒杀流量达到 5w/s 时,把成交记录的获取从展示 20 条降级到只展示 5 条。“从 20 改到 5”这个操作由一个开关来实现,也就是设置一个能够从开关系统动态获取的系统参数。
降级的核心目标是牺牲次要的功能和用户体验来保证核心业务流程的稳定,是一个不得已而为之的举措。例如在双 11 零点时,如果优惠券系统扛不住,可能会临时降级商品详情的优惠信息展示,把有限的系统资源用在保障交易系统正确展示优惠信息上,即保障用户真正下单时的价格是正确的。
场景:用户下单接口
拒绝服务可以说是一种不得已的兜底方案,用以防止最坏情况发生,防止因把服务器压跨而长时间彻底无法提供服务。当系统负载达到一定阈值时,例如 CPU 使用率达到 90% 或者系统 load 值达到 2*CPU 核数时,系统直接拒绝所有请求,这种方式是最暴力但也最有效的系统保护方式。
例如秒杀系统,我们可以在以下环节设计过载保护:
在最前端的 Nginx 上设置过载保护,当机器负载达到某个值时直接拒绝 HTTP 请求并返回 503 错误码。
阿里针对 nginx 开发的过载保护扩展插件 sysguard:
在 Java 层同样也可以设计过载保护。 比如 Sentinel 提供了系统规则限流。
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
maxQps * minRt
估算得出。设定参考值一般是 CPU cores * 2.5
。