1、概述
1.1、背景
第三方接口有限流,需要根据第三方的限流提前控制流量,防止调用第三方接口被限流。
1.2、目的
研究sentinel中间件的限流是否符合我们需要的业务场景。主要从以下几个方面考虑:
1、限流需要支持集群(分布式服务下,需要支持多节点的限流)
2、限流规则的持久化(不同三方接口限流规则不一致,需要限流的方法繁多且规则不一致,规则最好支持数据库的持久化)
3、限流参数的灵活性(限流的方法可以通过参数来自定义,比如大部分三方接口是根据应用id来进行限流,另外关注限流的时间窗口可配置)
4、限流之后的异常处理(异常处理要支持自定义,比如对于三方接口的异常处理,我们需要进行休眠操作,而不是直接失败)
针对以上四点,着重看一下sentinel这这些方面是否有比较友好的支持:
项目地址:https://gitee.com/ershuai8614/sentinel.git
2、嵌入式集群流控
2.1 、通过控制台添加规则
2.1.1、项目配置启动
sentinel本身就是支持集群的,由于官网文档已经对集群流控已经介绍的比较详细(官方文档:https://sentinelguard.io/zh-cn/docs/cluster-flow-control.html),这里不再赘述,下面主要讲解集群限流在项目中的应用。
down下来上面的项目地址,我们需要启动两个项目,一个是sentinel的控制台(控制台中可以手动对限流的规则进行自定义,只不过这些规则只在内存中,不能持久化),另一个是sentinel的嵌入式集群测试demo。
1)找到sentinel-dashboard中的DashboardApplication,启动的jvm参数:-Dserver.port=8088
2)找到sentinel-demo中 sentinel-demo-cluster中 sentinel-demo-cluster-embedded项目中的ClusterDemoApplication
同一个项目分别启动三个项目,jvm启动参数分别如下:
-Dcsp.sentinel.log.use.pid=true -Dproject.name=sentinle.cluster.demo.embedded -Dserver.port=8083 -Dcsp.sentinel.dashboard.server=localhost:8088 -Dcsp.sentinel.api.port=8283
-Dcsp.sentinel.log.use.pid=true -Dproject.name=sentinle.cluster.demo.embedded -Dserver.port=8084 -Dcsp.sentinel.dashboard.server=localhost:8088 -Dcsp.sentinel.api.port=8284
-Dcsp.sentinel.log.use.pid=true -Dproject.name=sentinle.cluster.demo.embedded -Dserver.port=8085 -Dcsp.sentinel.dashboard.server=localhost:8088 -Dcsp.sentinel.api.port=8285
启动参数解释如下:
-Dcsp.sentinel.log.use.pid=true --若在本地启动多个 Demo 示例,需要加上此启动参数,否则控制台显示监控会不准确
-Dproject.name --指定项目名称
-Dserver.port=8083 --指定启动项目端口号
-Dcsp.sentinel.dashboard.server=localhost:8088 --指定seneinel控制台的地址和端口(与sentinel控制台启动的端口保持一致)
-Dcsp.sentinel.api.port=8283 --指定控制台监控客户端API 的端口
两个项目启动之后,我们打开控制台(默认账号密码为sentinel/sentinel),发现此时控制台没有相关的项目配置,我们需要先分别调用一下配置了限流的接口,localhost:8083/hello/world ,localhost:8084/hello/world ,localhost:8085/hello/world (hello的接口在项目中加了限流的注解)
sentinel的接口限流相关配置实现类是懒加载的,在第一次调用后,限流相关的资源以及配置信息才会在控制台显示。
控制台的机器列表(我们分别启动了三个服务,刚好对应上)
2.1.2、集群配置
因为我们研究的是集群留空,所以在给接口配置流控规则之前,需要先配置集群信息
1、应用内机器指的就是嵌入模式,嵌入模式下(token和client均可以分摊流量,而外部指定机器指的是独立模式,具体看官方集群流控文档:https://sentinelguard.io/zh-cn/docs/cluster-flow-control.html)
2、选择token和client机器组成流控的集群,最大允许qps为1
配置完成之后我们可以点击tokenClient列表查看全部client客户端
这里需要注意的是,我们需要手动编辑一下client客户端的配置,默认客户端和服务端请求超时时间是20ms,这里改成2000ms,不然后面测试流控可能会出问题。
2.1.3、流控规则配置
在配置完集群信息之后我们在簇点链路列表给sayHello资源添加流控的规则(需要给三台服务均添加流控规则)
1、资源名指的就是需要进行流量限制的接口名
2、针对来源是可以对调用该接口的来源进行限流配置,默认default全部
3、阈值类型有 qps和并发线程数,我们这里选qps,集群阈值填1,表示1s只能有一个请求,集群阈值模式可以选单机均摊和总体阈值,单机均摊会根据服务的总连接数计算总的阈值,而总体阈值是针对于整个集群而言,这里我们选总体阈值,失败退化是指token服务不可用之后自动转换成单机限流。
规则添加完成之后,找到com.alibaba.csp.sentinel.demo.cluster.ClusterDemoTest,分别调用者三个接口,返现在1s之内,只有第一个接口可以响应成功,后面两个服务响应会失败。
初步来看,sentinel集群限流是支持的。但是上面我们是通过控制台手动添加规则,在文章的开始我们也说明了,我们的业务场景需要支持规则持久化,最好是能通过db的方式来读取我们的规则。下面我们来看sentinel的规则持久化相关的支持。
2.2、sentinel流控规则持久化
官方文档说明:https://sentinelguard.io/zh-cn/docs/dynamic-rule-configuration.html
从官方文档上可以看出,sentinel对规则的持久化只适配了nacos,我们可以通过nacos来定义需要限流的资源和具体限流的规则。同样是以嵌入集群模式下为例,我们来尝试不再使用sentinel控制台实现相关的规则定义,而是采用读取nacos中的配置来动态加载资源规则。
同样是在sentinel-demo中 sentinel-demo-cluster中 sentinel-demo-cluster-embedded项目中,官方已经为我们适配了nacos的限流规则。采用spi的机制,代码入口在 com.alibaba.csp.sentinel.demo.cluster.init.DemoClusterInitFunc
以上是主要的代码逻辑,每个方法点进去都可以看到,基本就是从nacos中读取相关配置然后注入到扩展的接口点中去。我们着重看一下nacos都有哪些配置属性
我这里建了一个nacos的命名空间sentinle.cluster.demo.embedded ,id为 0fa80e91-7804-446a-99a4-ae5a82f841cb(代码里需要对应)
主要是以上四条配置,Data id 都是项目启动时指定的项目名称 project.name 加上指定的后缀组成,分别是流控规则、集群相关的配置、热点规则以及客户端配置。Group也指定的SENTINEL_GROUP.
下面分别来看一下 这四个配置的内容、
sentinle.cluster.demo.embedded-flow-rules
[
{
"resource":"sayHello", --需要限流的资源名称
"grade":1, --限流阈值类型,QPS 或线程数模式,默认为QPS
"count":60, --限流阈值
"strategy":0, --调用关系限流策略:直接、链路、关联,默认直接
"controlBehavior":0, --流控效果(直接拒绝 / 排队等待 / 慢启动模式),不支持按调用关系限流,默认直接拒绝
"clusterMode":true, --标识是否为集群限流配置
"clusterConfig":{
"flowId":9959231232121334, --全局唯一的规则 ID,由集群限流管控端分配
"thresholdType":1, --阈值模式,默认(0)为单机均摊,1 为全局阈值
"fallbackToLocalWhenFail":true, --在 client 连接失败或通信失败时,是否退化到本地的限流模式
"strategy":0, --调用关系限流策略:直接、链路、关联,默认直接
"windowIntervalMs":1000 --滑动窗口时间,默认1s
}
}
]
sentinle.cluster.demo.embedded-param-rules
[
{
"resource":"sayHello",
"grade":1,
"paramIdx":0, --参数索引位置
"count":1, --阈值
"durationInSec":60, --统计窗口时间长度(单位为秒)
"controlBehavior":0,
"clusterMode":true,
"clusterConfig":{
"flowId":9959231435454,
"thresholdType":1,
"fallbackToLocalWhenFail":true,
"strategy":0,
"windowIntervalMs":1000
}
}
]
sentinle.cluster.demo.embedded-cluster-map
[
{
"machineId":"192.168.244.168@8283", --token服务器地址@api监控端口
"ip":"192.168.244.168", --token服务器ip
"port":18730, --token服务器暴露给sentinel控制台的端口
"clientSet":[
"192.168.244.168@8284", --client 端地址、端口
"192.168.244.168@8285"
]
}
]
sentinle.cluster.demo.embedded-client-config
{
"requestTimeout":2000 --client请求服务端超时时间,单位ms
}
配置好以上nacos配置后,指定nacos的命名空间和地址即可。项目启动之后,我们发现,在sentinel控制台中已经存在了和我们之前手动添加的规则一样的配置,而这些配置就是我们在nacos中的配置(具体规则的正确性可自行测试)
通过以上分析,我们可以看出sentinel已经适配了nacos的规则持久化, 如果我们需要,完全可以自己通过spi机制实现一个基于db的规则持久化,和nacos不同的仅仅只是规则来源的不同而已。
2.3、热点参数限流
上面我们分析了sentinel对集群、规则持久化的支持,下面来看一下规则定义的灵活性,文章开始已经提到,最好是能能够根据接口参数来限流,这一部分属于sentinel的热点参数限流,官方文档:https://sentinelguard.io/zh-cn/docs/parameter-flow-control.html
对比以上两种设置发现,对于单机的热点规则是可以支持设置统计窗口时长的,但是对于集群模式下的热点规则,则设置不了统计窗口时长的。
我们可以通过上面的测试用来进行验证,之前我们再nacos上配置了流控规则是1s60的qps,热点参数限制的是第一个参数,60s内qps为1。
以下测试用例,在每次请求完成之后,线程休眠1s,发现并没有抛出限流的异常,说明我们设置的60s 的时间统计窗口是无效的,对于集群热点参数流控来说,只能默认统计时间窗口是1s。
3、独立集群模式
3.1、nacos配置
这里就不在演示通过控制台来配置规则,我们直接通过nacos来进行限流规则的配置。
创建一个 sentinle.cluster.demo.alone 的nacos命名空间,我这里创建的id为:f412be2f-04f1-4a50-9bb2-c60bf8593776(后面在代码中用到),分别有以下配置:
1、appA-flow-rules:
[
{
"resource":"sayHello", --需要限流的资源名称
"grade":1, --限流阈值类型,QPS 或线程数模式,默认为QPS
"count":60, --限流阈值
"strategy":0, --调用关系限流策略:直接、链路、关联,默认直接
"controlBehavior":0, --流控效果(直接拒绝 / 排队等待 / 慢启动模式),不支持按调用关系限流,默认直接拒绝
"clusterMode":true, --标识是否为集群限流配置
"clusterConfig":{
"flowId":9959231232121334, --全局唯一的规则 ID,由集群限流管控端分配
"thresholdType":1, --阈值模式,默认(0)为单机均摊,1 为全局阈值
"fallbackToLocalWhenFail":true, --在 client 连接失败或通信失败时,是否退化到本地的限流模式
"strategy":0, --调用关系限流策略:直接、链路、关联,默认直接
"windowIntervalMs":1000 --滑动窗口时间,默认1s
}
}
]
2、cluster-server-namespace-set:
[
"appA"
]
3、appA-cluster-map:
[
{
"machineId":"192.168.244.168@8788", --token服务器地址@api监控端口
"ip":"192.168.244.168", --token服务器ip
"port":11111, --token服务器暴露给sentinel控制台的端口
"clientSet":[
"192.168.244.168@8281", --client 端地址、端口
"192.168.244.168@8282"
]
}
]
4、cluster-server-transport-config:
{
"port":11111,
"idleSeconds":600
}
5、appA-cluster-client-config:
{
"requestTimeout":2000
}
6、appA-param-rules
[
{
"resource":"sayHello",
"grade":1,
"paramIdx":0, --参数索引位置
"count":1, --阈值
"durationInSec":60, --统计窗口时间长度(单位为秒)
"controlBehavior":0,
"clusterMode":true,
"clusterConfig":{
"flowId":9959231435454,
"thresholdType":1,
"fallbackToLocalWhenFail":true,
"strategy":0,
"windowIntervalMs":1000
}
}
]
3.2、项目启动:
1)token服务端启动
找到 com.alibaba.csp.sentinel.demo.cluster.ClusterServerDemo 启动,注释掉传输配置,因为已经在nacos中配置过。
jvm启动参数如下:-Dserver.port=8720 -Dproject.name=appA -Dcsp.sentinel.dashboard.server=127.0.0.1:8088 -Dcsp.sentinel.api.port=8788 -Dcsp.sentinel.log.use.pid=true
2)客户端启动:(此客户端是本人基本嵌入式模式copy过来的,为了方便测试两种集群模式,sentinel官网项目没有独立模式的客户端)
找到com.alibaba.csp.sentinel.demo.cluster.ClusterClientDemo 同一个项目启动两次,jvm启动参数如下:
-Dserver.port=8721 -Dproject.name=appA -Dcsp.sentinel.dashboard.server=127.0.0.1:8088 -Dcsp.sentinel.api.port=8281 -Dcsp.sentinel.log.use.pid=true
-Dserver.port=8722 -Dproject.name=appA -Dcsp.sentinel.dashboard.server=127.0.0.1:8088 -Dcsp.sentinel.api.port=8282 -Dcsp.sentinel.log.use.pid=true
启动完成之后,先调用接口 http://localhost:8721/hello/world http://localhost:8721/hello/world
后打开控制台,查看机器列表,分别有三台服务已连接,一个为token服务器,另外两台为客户端。
簇点链路资源如下:
流控规则如下(两个客户端均有此流控规则):
热点规则如下(两个客户端均有此配置):
找到com.alibaba.csp.sentinel.demo.cluster.ClusterDemoTest 测试类,验证以上规则是否生效。
通过独立模式集群限流发现,集群限流生效,但是同样对于热点参数中配置的时间窗口没有作用。
对比sentinel的嵌入式集群和独立模式集群,我们发现。两种集群的区别是:
同样两种集群都有共同的缺陷,就是token server不支持高可用,用于生产环境需要注意此点。
嵌入式集群:作为内置的 token server 与服务在同一进程中启动。在此模式下,集群中各个实例都是对等的,token server 和 client 可以随时进行转变,因此无需单独部署,灵活性比较好。但是隔离性不佳,需要限制 token server 的总 QPS,防止影响应用本身。嵌入模式适合某个应用集群内部的流控。
独立集群:token服务器不参与资源请求的限流,它只负责向客户端发放token,独立部署,隔离性好,但是需要额外的部署操作。独立模式适合作为 Global Rate Limiter 给集群提供流控服务。
4、限流之后的异常处理
sentinel对于限流之后的自异常处理也是支持的,可以直接定义限流异常处理的方法,和异常处理的类
5、总结
通过以上对sentinel的学习,我们发现sentinel对以下4点的支持如下
1、限流需要支持集群(分布式服务下,需要支持多节点的限流) ---支持
2、限流规则的持久化(不同三方接口限流规则不一致,需要限流的方法繁多且规则不一致,规则最好支持数据库的持久化) --支持
3、限流参数的灵活性(限流的方法可以通过参数来自定义,比如大部分外三方接口是根据应用id来进行限流,另外关注限流的时间窗口可配置) --集群限流时时间窗口不可配置
4、限流之后的异常处理(异常处理要支持自定义,比如对于三方接口的异常处理,我们需要进行休眠操作,而不是直接失败) --支持