限流规则的定义包括以下维度:
- 限流类型
- 运行模式
- 限流算法
- 限流后置操作
- 限流条件阈值
- 限流对象
限流类型
- 接口方法:支持对某个具体的 RPC 接口或普通 Bean 的方法限流。要求在限流对象中配置接口路径名称和方法签名。
- Web 页面:对基于 Spring MVC 的 Web 请求进行限流。要求在限流对象中配置请求 URI。
运行模式
- 拦截模式:限流生效的模式,若匹配上规则,会将方法调用进行限制,调用配置的“限流后操作”。
- 监控模式:仅打印限流记录日志,不实际产生限流效果。
限流算法
常见的限流方式有:
- 通过限制单位时间段内调用量来限流(QPS 限流算法)
- 通过限制系统的并发调用程度来限流
- 使用漏桶(Leaky Bucket)算法来进行限流
- 使用令牌桶(Token Bucket)算法来进行限
限流熔断中主要使用了其中两种:QPS 限流算法 和 令牌桶算法。
QPS 限流算法
QPS 限流算法通过限制单位时间内允许放过的请求数来限流。
优点:
- 计算简单,是否限流只跟请求数相关,放过的请求数是可预知的(令牌桶算法放过的请求数还依赖于流量是否均匀)。比较符合用户直觉和预期。
- 可以通过拉长限流周期来应对突发流量。如 1 秒限流 10 个,想要放过瞬间 20 个请求,可以把限流配置改成 3 秒限流 30 个。拉长限流周期会有一定风险,用户可以自主决定承担多少风险。
缺点:
- 没有很好的处理单位时间的边界。比如在前一秒的最后一毫秒里和下一秒的第一毫秒都触发了最大的请求数,就看到在两毫秒内发生了两倍的 QPS。
- 放过的请求不均匀,突发流量时,请求总在限流周期的前一部分放过。如 10 秒限 100个,高流量时放过的请求总是在限流周期的第一秒。
令牌桶算法
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。
优点:
- 放过的流量比较均匀,有利于保护系统。
- 存量令牌能应对突发流量,很多时候,我们希望能放过脉冲流量。而对于持续的高流量,后面又能均匀地放过不超过限流值的请求数。
缺点:
- 存量令牌没有过期时间,突发流量时第一个周期会多放过一些请求,可解释性差。即在突发流量的第一个周期,默认最多会放过 2 倍限流值的请求数。
- 实际限流数难以预知,跟请求数和流量分布有关。
存量桶系数
令牌桶算法中,多余的令牌会放到桶里,这个桶的容量是有上限的,决定这个容量的就是存量桶系数,默认为 1.0,即默认存量桶的容量是 1.0 倍的限流值。推荐设置 0.6~1.5 之间。
存量桶系数的影响有两方面:
- 突发流量第一个周期放过的请求数。如存量桶系数等于 0.6,第一个周期最多放过 1.6 倍限流值的请求数。
- 影响误杀率。存量桶系数越大,越能容忍流量不均衡问题。
误杀率:限流熔断是对单机进行限流,线上场景经常会用单机限流模拟集群限流。由于机器之间的秒级流量不够均衡,所以很容易出现误限。例如两台服务器,总限流值 20,每台限流 10,某一秒两台服务器的流量分别是 5、15,这时其中一台就限流了 5 个请求。减小误杀率的两个办法:
- 拉长限流周期。
- 使用令牌桶算法,并且调出较好的存量桶系数。
限流后置操作
限流操作 | 适用于接口方法限流 | 适用于Web 页面限流 | 解释 |
空处理 | Y | Y | 不做任何处理,返回空值。 |
抛出异常 | Y | N | 异常信息为填写的输入框内容。 |
跳转到指定页面 | N | Y | 跳转到指定的页面地址。 |
页面 JSON 报文 | N | Y | 直接将指定的 JSON 字符串在 HTML response 中返回。默认返回内容为: {success:false,error:"MAX_VISIT_LIMIT"} |
页面 XML 报文 | N | Y | 直接将特定的 XML 字符串在 HTML response中返回。默认返回内容为:
|
限流条件阈值
- 条件模型
条件模型 | 限流阈值 | 说明 |
---|---|---|
单位时间内服务访问次数或 Web 页面访问次数 | QPS 计数值 | 根据单位时间内的请求数进行限流。 |
堆内存使用量 | 最大堆内存使用量(单位为兆MB) | 根据当前堆内存使用量进行限流。 |
CPU 负载 | 100 * CPU 负载百分比 | 根据过去一分钟内的 CPU 平均负载进行限流。 |
并发线程数 | 最大并发线程数 | 根据单台机器上并发的线程数进行限流。 |
- 单位时间:打印限流日志的周期。对于单位时间内访问次数的限流条件,也表示统计周期。单位为毫秒(ms)。最小值为 1000 ms。
- 限流阈值:见上表。
- 流量类型:
- 所有流量:对正常流量和压测流量均限流。
- 正常流量:仅对正常流量限流。
- 压测流量:仅对压测流量线路。
限流对象
限流熔断可以对方法的参数进行过滤,可实现对某个特定的参数进行限流。
- 对于接口方法,配置要限流的接口路径名称和方法签名。
- 对于 Web 页面方法,配置要限流的请求 URI。
配置接口方法类型的限流对象
接口方法类型的限流对象的参数配置包括以下内容:
参数 | 说明 |
限流对象名 | 包括要限流的接口与方法名:
|
键值 | 限流参数及属性名称,用 MVEL 表达式 表示,获取用于比较的键值。 |
比较关系 | 等于 或 不等于 |
比较值 | 用于比较的属性值 |
下图给出了一个参数配置的样例:
- 上图中的配置表示限流对象为:
com.alipay.antcloud.dsrconsole.core.service.guardian.facade.GuardianAppFacade.queryAppNames
方法的第一个参数中instanceId
属性值为000001
的请求。 ARGS
是限流熔断内部定义的一个变量,表示方法的所有参数,ARGS[0]
表示第一个参数。
配置 Web 请求中的限流对象
Web 类型的限流对象的参数配置包括以下内容:
参数 | 说明 |
---|---|
限流对象名 | Web 请求中的 URI,不包括域名和参数部分。 |
键值 | Web 请求 URL 中的参数键值,不支持 MVEL 表达式。 |
比较关系 | 等于 或 不等于 |
比较值 | 用于比较的属性值 |
说明:
- Web 类型的限流参数键值不支持 MVEL 表达式。
- Web 类型的限流参数键值不支持 ARGS 变量。例如请求:
/queryAllNames?instanceId=00001&name=cloudinc
,参数之间没有顺序关系,所以对于 Web 请求的参数过滤不能使用 ARGS 变量。
下图给出了针对请求 URL http://xxx.domain//webapi/guardian/history/search?instanceId=000001
限流对象配置样例:
其中,instanceId
是请求 URL 中的参数的键值,00001
是参数中的属性值。
接口方法中的 MVEL 表达式
配置方式
- 方式一:左侧键值计算结果是 true/false,右侧比较值中也填写 true/false,例如:
- 方式二:左侧键值计算结果是个普通字符串,右侧比较值中也填写一个字符串,例如:
上述两种方式的效果一样,推荐用第一种方式,第一种方式支持更多的运算符,例如:&&、||、>、<=
等,表达能力更丰富。
配置样例
-
使用 MVEL 表达式获取参数的属性值:
可使用
obj.field
的格式获取参数的属性值。属性必须有 public 的getter
方法,或是本身是 public 的。若没有属性值,只有 public 的getter
方法也可以。获取到的属性值可以和特定的值比较,例如:ARGS[0].field == 'loull'
。 - 使用 MVEL 表达式的基础运算符:
!=
,例如:ARGS[0].id != 100
==
,例如:ARGS[1].uid == 'test'
>=
,例如:ARGS[0].number >= 200
>
,例如:ARGS[0].number > 100
<=
,例如:ARGS[0].number <= 101
<
,例如:ARGS[0].number < 200
+ - * /
,例如:ARGS[0].num1 + ARGS[1].num2 > ARGS[2].num3
&& ||
,例如:ARGS[0].number > 100 && ARGS[0].number < 200
-
使用 MVEL 表达式获取 Date 类型:
例如:
ARGS[0].getTime()<123123123
。 -
使用 MVEL 表达式获取 Enum 枚举值:
例如:
AccountTypeEnum
类型AccountTypeEnum type = AccountTypeEnum.CORPORATE_ACCOUNT;
匹配名字属性可以配置为:
ARGS[0].name == 'CORPORATE_ACCOUNT'
。 -
使用 MVEL 表达式获取数组元素:
例如:
ARGS[0][0]=='2017080200077000000022076255'
,表示第一个参数是数组,数组的第一个元素是2017080200077000000022076255
。 -
使用 MVEL 表达式操作集合:
-
List 类型:
例如:
ARGS[0].get(1)=='test2'
。 -
Map 类型:
例如:
Map<String,Object> dataMap = new HashMap<String, Object>(); dataMap.put("testInteger",new Integer(20)); dataMap.put("testDouble",new Double(30));
匹配表达式可以表示为:
ARGS[0].get('testDouble') == 30.0
,或者ARGS[0].testDouble == 30.0
。
-
-
使用关键字 contains 做范围匹配:
-
是否包含在集合内:
['aa', 'bb', 'Xin'].contains([0].last)
或者[0].namelist contians ('Xi')
,其中[0].namelist
是数组,不是字符串。 -
是否包含在字符串内:
[0].last contains 'in'
,其中[0].last
是字符串,判断是否包含'in'
。
-
-
使用关键字 IN 做范围匹配:
-
用于比较一个参数是否在一个白名单/黑名单范围内的场景。限制:白名单/黑名单列表的元素,不能超过 100 个。
-
IN
表示在名单范围内,则匹配成功,NOT_IN
相反。例如:ARGS[0].id IN 1,2,3,100
。
-
-
使用 MVEL 表达式执行参数的 public 方法:
可以调用某个参数的 public 方法,用返回的结果和特定的值比较,例如:
[0].publicMethod == 'xxxx'
。
原创文章,作者:网友投稿,如若转载,请注明出处:https://www.cloudads.cn/archives/34022.html