前言
这篇讲讲sentinel限流。
之前我们内部系统其实没有限流这个概念,我个人尝试过在网关层面直接做请求限制,没有在服务层尝试过。
正文
sentinel是阿里的产品,目的是为了限制服务的流量来达到保护应用的目的。
sentinel功能
流量控制
流量控制有以下几个角度:
- 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
- 运行指标,例如 QPS、线程池、系统负载等;
- 控制的效果,例如直接限流、冷启动、排队等。
熔断降级
当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。
Hystrix通过线程池的方式,来对依赖(在sentinel的概念中对应资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本,还需要预先给各个资源做线程池大小的分配。
Sentinel则是:
- 通过并发线程数进行限制
和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
- 通过响应时间对资源进行降级
除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
系统负载保护
Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。
针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
简单来说就是可以根据系统的某些指标,如CPU使用情况,RT,QPS等数据限制请求。
使用
sentinel的使用也是十分简单,sentinel提供dashboard服务供限流服务接入,我们可以通过dashboard快速下发规则给应用,实现界面化管理。
先下载dashboard,启动
1 | java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.8.jar |
这里采用springCloudAlibaba快速接入。
pom文件依赖如下
1 | <properties> |
使用start.aliyun.com
构建的springboot程序会自动生成demo文件,这里我们手动写下。
流控
先写配置,用于在触发sentinel流控时显示的返回。
1 |
|
定义服务
1 |
|
这里我们讲讲@SentinelResource
注解。
注意这个注解也采用aop,所以private方法不支持。
这个注解用于定义资源,并提供可选的异常处理和 fallback 配置项。
@SentinelResource
注解包含以下属性:
value
:资源名称,必需项(不能为空)entryType
:entry 类型,可选项(默认为EntryType.OUT
)blockHandler
/blockHandlerClass
:blockHandler
对应处理BlockException
的函数名称,可选项。blockHandler 函数访问范围需要是public
,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException
。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定blockHandlerClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。fallback
:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。- fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。defaultFallback
(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所以类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要为空,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。- defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。exceptionsToIgnore
(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出
BlockException
时只会进入blockHandler
处理逻辑。若未配置blockHandler
、fallback
和defaultFallback
,则被限流降级时会将BlockException
直接抛出。
其实核心参数就两个,value和defaultFallback,一个用于定义资源,另一个配置默认的失败方法。
注意这里getUserFallBack的方法参数为空或者接收一个Throwable参数。
接下来写controller
1 |
|
很简单的一个接口,我们调用看看。
1 | http://127.0.0.1:8081/user/get?id=1 |
打开dashboard,账号密码都是sentinel。
1 | http://127.0.0.1:8080 |
在左侧可以发现我们的应用,并且在实时监控中可以看到我们调用的接口和对应的资源。
1 | sentinel_spring_web_context |
我们可以对/user/get做限流,也可以对UserService#getUserNameById限流,因为他们都属于sentinel的资源。
这里我们对/user/get限流,qps单机阈值限制为1。新增完成后,多次访问接口,会出现Oops, blocked by Sentinel: FlowException
的错误提示。
对UserService#getUserNameById做qps为1的限流,可以发现限流请求默认返回了get user fall back。
熔断降级
在上面服务的基础上增加一个服务方法和接口。
1 |
|
1 |
|
在这个接口上我们等待了一秒延迟。
我们在控制台对这个资源进行降级。为了能更直观看出来,最大RT填为500,最小请求数为1,比例阈值0.5(这个无所谓),熔断时间10s。
保存之后我们请求接口,第一次请求成功,然后由于请求时间大于500ms,直接触发了熔断降级,此时再次请求就会执行默认的fallback方法。
集群限流
这个好理解,就是一组服务的最大请求量。配置起来也简单。
先copy一份idea的启动文件,然后加上vmoptions:-Dserver.port=8082
再启动一个服务,在dashboard上就能看见应用为2/2.
选择集群流控,添加token-server,这个服务的作用是用于接收其他客户端的请求,判断请求是否通过。
然后将另一个服务变为token-client,填写最大qps即可完成集群流控。
当然我们在配置普通流控规则时,也可以勾选是否集群,选择均摊模式,填写阈值即可应用流控规则。原理也是请求前向token-server获取令牌实现集群限流。
总结
今天试用了一下sentinel,很多年前在我还在大学的时候就初步使用过。
今天遇到的坑是defaultFallback不生效,我在源码中调试了很久,包括源码是如何寻找到方法,最后调试才发现name.equals(method.getName())
判断不通过,原来我在SentinelResource定义的是getUserFallback,而编写的方法是getUserFallBack,大小写错了!崩溃!
其他的话问题不是很大,还有一个就是规则的持久化了,这个的话得配合nacos做。官方demo和网上的案例我也看了,难度较小,只需要知道流控的规则json如何编写,然后引入对应的包监听nacos数据即可。