一、概念
1.总结
1.调用方设置controller资源限流,会返回UrlBlockSentinelHandler处理信息(当前流量请求过大)【url资源】
2.调用方设置feign资源降级,会触发熔断fallback
3.被调用方宕机,会触发熔断fallback
4.提供方设置controller资源降级,会触发UrlBlockSentinelHandler处理信息(当前流量请求过大)【url资源】
5.自定义资源限流,会执行catch【当资源时一段代码时,使用自定义资源try catch】
6.注解方式定义资源限流,会触发指定blockHandler【当资源时方法时使用注解】
url资源统一使用UrlBlockSentinelHandler处理,自定义资源、注解资源需要单独处理(blockHandler和catch)
2.熔断
什么是熔断?
A服务调用B服务的某个功能,由于网络不稳定问题,或者B服务卡机,导致功能时间超长。如果这样子的次数太多。我们就可以直接将B断路了(A不再请求B接口),凡是调用B的直接返回降级数据,不必等待B的超长执行。这样B的故障问题,就不会级联影响到A。(是调用方的主动规则)一句话解释:不可用的资源请求、和降级的资源请求会触发熔断,执行fallback(不用等待超时异常直接返回)
3.降级
什么是降级?
整个网站处于流量高峰期,服务器压力剧增,根据当前业务情况及流量,对一些服务和页面进行有策略的降级[停止服务,所有的调用直接返回降级数据]。以此缓解服务器资源的的压力,以保证核心业务的正常运行,同时也保持了客户和大部分客户的得到正确的相应。(也是调用方的主动设置)一句话解释:符合降级规则的资源请求,在接下来的窗口期都会触发熔断,而不会请求资源
1.宕机的feign请求会触发熔断fallback
2.调用方设置feign请求降级规则,降级后触发熔断fallback
3.调用方不设置feign降级,而提供方设置controller资源降级,降级后触发提供方的限流handle处理(UrlBlockSentinelHandler)一般用作高并发场景下让出资源
4.熔断与降级异同点
相同点:
1、为了保证集群大部分服务的可用性和可靠性,防止崩溃,牺牲小我2、用户最终都是体验到某个功能不可用不同点:
1、熔断是被调用方故障,调用方主动触发
2、降级是基于全局考虑,停止部分资源调用,触发熔断快速返回
5.限流
什么是限流?【一定要实现限流】
对打入服务的请求流量进行控制,使服务能够承担不超过自己能力的流量压力【丢弃超出的请求】
二、实现方案
1.Hystrix
注:已经不更新了
- 隔离策略:线程池;为每一个请求新增一个线程池,每个请求过来分配一个线程(资源大、性能低)
- 熔断降级策略:基于异常比例;
- 实时统计实现:滑动窗口
- 动态规则配置:支持多种数据源(之前的配置需要持久化)
- 扩展性:插件形式
- 基于注解的支持:支持
- 限流:有限的支持
- 流量整形:不支持
- 系统自适应保护:不支持;系统判断当前是否处于高峰期,判断是否放行请求
- 控制台:简单的监控查看
2.Sentinel
springcloud alibaba
3.Hystrix、Sentinel对比图
三、Sentinel
1.文档
适配spring-cloud
适配spring-cloud详细步骤
2.简介
Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。
3.使用步骤
使用 Sentinel 来进行资源保护,主要分为几个步骤:
- 定义资源
- 定义规则
- 检验规则是否生效
3.1.定义资源
注意:
资源需要定义,例如使用注解定义资源
但是springboot默认给所以方法设置成了资源,所以可以省略
方式一:主流框架的默认适配
主流的框架默认配置成了资源,例如所有controller的api,feign的Api
为了减少开发的复杂程度,我们对大部分的主流框架,例如 Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux、Reactor 等都做了适配。您只需要引入对应的依赖即可方便地整合 Sentinel。可以参见: 主流框架的适配。
方式二:抛出异常的方式定义资源
SphU
包含了 try-catch 风格的 API。用这种方式,当资源发生了限流之后会抛出 BlockException
。这个时候可以捕捉异常,进行限流之后的逻辑处理。示例代码如下:
// 1.5.0 版本开始可以利用 try-with-resources 特性(使用有限制)
// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
try (Entry entry = SphU.entry("resourceName")) {
// 被保护的业务逻辑
// do something here...
} catch (BlockException ex) {
// 资源访问阻止,被限流或被降级
// 在此处进行相应的处理操作
}
特别地,若 entry 的时候传入了热点参数,那么 exit 的时候也一定要带上对应的参数(exit(count, args)
),否则可能会有统计错误。这个时候不能使用 try-with-resources 的方式。另外通过 Tracer.trace(ex)
来统计异常信息时,由于 try-with-resources 语法中 catch 调用顺序的问题,会导致无法正确统计异常数,因此统计异常信息时也不能在 try-with-resources 的 catch 块中调用 Tracer.trace(ex)
。
手动 exit 示例:
Entry entry = null;
// 务必保证 finally 会被执行
try {
// 资源名可使用任意有业务语义的字符串,注意数目不能太多(超过 1K),超出几千请作为参数传入而不要直接作为资源名
// EntryType 代表流量类型(inbound/outbound),其中系统规则只对 IN 类型的埋点生效
entry = SphU.entry("自定义资源名");
// 被保护的业务逻辑
// do something...
} catch (BlockException ex) {
// 资源访问阻止,被限流或被降级
// 进行相应的处理操作
} catch (Exception ex) {
// 若需要配置降级规则,需要通过这种方式记录业务异常
Tracer.traceEntry(ex, entry);
} finally {
// 务必保证 exit,务必保证每个 entry 与 exit 配对
if (entry != null) {
entry.exit();
}
}
热点参数埋点示例:
Entry entry = null;
try {
// 若需要配置例外项,则传入的参数只支持基本类型。
// EntryType 代表流量类型,其中系统规则只对 IN 类型的埋点生效
// count 大多数情况都填 1,代表统计为一次调用。
entry = SphU.entry(resourceName, EntryType.IN, 1, paramA, paramB);
// Your logic here.
} catch (BlockException ex) {
// Handle request rejection.
} finally {
// 注意:exit 的时候也一定要带上对应的参数,否则可能会有统计错误。
if (entry != null) {
entry.exit(1, paramA, paramB);
}
}
SphU.entry()
的参数描述:
参数名 | 类型 | 解释 | 默认值 |
---|---|---|---|
entryType | EntryType | 资源调用的流量类型,是入口流量(EntryType.IN )还是出口流量(EntryType.OUT ),注意系统规则只对 IN 生效 | EntryType.OUT |
count | int | 本次资源调用请求的 token 数目 | 1 |
args | Object[] | 传入的参数,用于热点参数限流 | 无 |
注意:SphU.entry(xxx)
需要与 entry.exit()
方法成对出现,匹配调用,否则会导致调用链记录异常,抛出 ErrorEntryFreeException
异常。常见的错误:
- 自定义埋点只调用
SphU.entry()
,没有调用entry.exit()
- 顺序错误,比如:
entry1 -> entry2 -> exit1 -> exit2
,应该为entry1 -> entry2 -> exit2 -> exit1
方式三:返回布尔值方式定义资源
SphO
提供 if-else 风格的 API。用这种方式,当资源发生了限流之后会返回 false
,这个时候可以根据返回值,进行限流之后的逻辑处理。示例代码如下:
// 资源名可使用任意有业务语义的字符串
if (SphO.entry("自定义资源名")) {
// 务必保证finally会被执行
try {
/**
* 被保护的业务逻辑
*/
} finally {
SphO.exit();
}
} else {
// 资源访问阻止,被限流或被降级
// 进行相应的处理操作
}
注意:SphO.entry(xxx)
需要与 SphO.exit()方法成对出现,匹配调用,位置正确,否则会导致调用链记录异常,抛出
ErrorEntryFreeException` 异常。
方式四:注解方式定义资源
Sentinel 支持通过 @SentinelResource
注解定义资源并配置 blockHandler
和 fallback
函数来进行限流之后的处理。示例:
// 原本的业务方法.
@SentinelResource(blockHandler = "blockHandlerForGetUser")
public User getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}
// blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用
public User blockHandlerForGetUser(String id, BlockException ex) {
return new User("admin");
}
注意 blockHandler
函数会在原方法被限流/降级/系统保护的时候调用,而 fallback
函数会针对所有类型的异常。请注意 blockHandler
和 fallback
函数的形式要求,更多指引可以参见 Sentinel 注解支持文档。
方式五:异步调用支持
Sentinel 支持异步调用链路的统计。在异步调用中,需要通过 SphU.asyncEntry(xxx)
方法定义资源,并通常需要在异步的回调函数中调用 exit
方法。以下是一个简单的示例:
try {
AsyncEntry entry = SphU.asyncEntry(resourceName);
// 异步调用.
doAsync(userId, result -> {
try {
// 在此处处理异步调用的结果.
} finally {
// 在回调结束后 exit.
entry.exit();
}
});
} catch (BlockException ex) {
// Request blocked.
// Handle the exception (e.g. retry or fallback).
}
SphU.asyncEntry(xxx)
不会影响当前(调用线程)的 Context,因此以下两个 entry 在调用链上是平级关系(处于同一层),而不是嵌套关系:
// 调用链类似于:
// -parent
// ---asyncResource
// ---syncResource
asyncEntry = SphU.asyncEntry(asyncResource);
entry = SphU.entry(normalResource);
若在异步回调中需要嵌套其它的资源调用(无论是 entry
还是 asyncEntry
),只需要借助 Sentinel 提供的上下文切换功能,在对应的地方通过 ContextUtil.runOnContext(context, f)
进行 Context 变换,将对应资源调用处的 Context 切换为生成的异步 Context,即可维持正确的调用链路关系。示例如下:
public void handleResult(String result) {
Entry entry = null;
try {
entry = SphU.entry("handleResultForAsync");
// Handle your result here.
} catch (BlockException ex) {
// Blocked for the result handler.
} finally {
if (entry != null) {
entry.exit();
}
}
}
public void someAsync() {
try {
AsyncEntry entry = SphU.asyncEntry(resourceName);
// Asynchronous invocation.
doAsync(userId, result -> {
// 在异步回调中进行上下文变换,通过 AsyncEntry 的 getAsyncContext 方法获取异步 Context
ContextUtil.runOnContext(entry.getAsyncContext(), () -> {
try {
// 此处嵌套正常的资源调用.
handleResult(result);
} finally {
entry.exit();
}
});
});
} catch (BlockException ex) {
// Request blocked.
// Handle the exception (e.g. retry or fallback).
}
}
此时的调用链就类似于:
-parent
---asyncInvocation
-----handleResultForAsync
更详细的示例可以参考 Demo 中的 AsyncEntryDemo,里面包含了普通资源与异步资源之间的各种嵌套示例。
3.2.定义规则
3.2.1.规则的种类
Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效。同时 Sentinel 也提供相关 API,供您来定制自己的规则策略。
Sentinel 支持以下几种规则:流量控制规则、熔断降级规则、系统保护规则、来源访问控制规则 和 热点参数规则。
流量控制规则 (FlowRule)
流量规则的定义
重要属性:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS 模式(1)或并发线程数模式(0) | QPS 模式 |
limitApp | 流控针对的调用来源 | default ,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 根据资源本身(直接) |
controlBehavior | 流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 | 直接拒绝 |
clusterMode | 是否集群限流 | 否 |
同一个资源可以同时有多个限流规则,检查规则时会依次检查。
通过代码定义流量控制规则
理解上面规则的定义之后,我们可以通过调用 FlowRuleManager.loadRules()
方法来用硬编码的方式定义流量控制规则,比如:
private void initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule(resourceName);
// set limit qps to 20
rule.setCount(20);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
更多详细内容可以参考 流量控制。
熔断降级规则 (DegradeRule)
熔断降级规则包含下面几个重要的属性:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即规则的作用对象 | |
grade | 熔断策略,支持慢调用比例/异常比例/异常数策略 | 慢调用比例 |
count | 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 | |
timeWindow | 熔断时长,单位为 s | |
minRequestAmount | 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) | 5 |
statIntervalMs | 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) | 1000 ms |
slowRatioThreshold | 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入) |
同一个资源可以同时有多个降级规则。
理解上面规则的定义之后,我们可以通过调用 DegradeRuleManager.loadRules()
方法来用硬编码的方式定义流量控制规则。
private void initDegradeRule() {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource(KEY);
// set threshold RT, 10 ms
rule.setCount(10);
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
rule.setTimeWindow(10);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
更多详情可以参考 熔断降级。
系统保护规则 (SystemRule)
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统规则包含下面几个重要的属性:
Field | 说明 | 默认值 |
---|---|---|
highestSystemLoad | load1 触发值,用于触发自适应控制阶段 | -1 (不生效) |
avgRt | 所有入口流量的平均响应时间 | -1 (不生效) |
maxThread | 入口流量的最大并发数 | -1 (不生效) |
qps | 所有入口资源的 QPS | -1 (不生效) |
highestCpuUsage | 当前系统的 CPU 使用率(0.0-1.0) | -1 (不生效) |
理解上面规则的定义之后,我们可以通过调用 SystemRuleManager.loadRules()
方法来用硬编码的方式定义流量控制规则。
private void initSystemRule() {
List<SystemRule> rules = new ArrayList<>();
SystemRule rule = new SystemRule();
rule.setHighestSystemLoad(10);
rules.add(rule);
SystemRuleManager.loadRules(rules);
}
注意系统规则只针对入口资源(EntryType=IN)生效。更多详情可以参考 系统自适应保护文档。
访问控制规则 (AuthorityRule)
很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用 Sentinel 的访问控制(黑白名单)的功能。黑白名单根据资源的请求来源(origin
)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
授权规则,即黑白名单规则(AuthorityRule
)非常简单,主要有以下配置项:
resource
:资源名,即规则的作用对象limitApp
:对应的黑名单/白名单,不同 origin 用,
分隔,如appA,appB
strategy
:限制模式,AUTHORITY_WHITE
为白名单模式,AUTHORITY_BLACK
为黑名单模式,默认为白名单模式
更多详情可以参考 来源访问控制。
热点规则 (ParamFlowRule)
详情可以参考 热点参数限流。
查询更改规则
引入了 transport 模块后,可以通过以下的 HTTP API 来获取所有已加载的规则:
http://localhost:8719/getRules?type=<XXXX>
其中,type=flow
以 JSON 格式返回现有的限流规则,degrade 返回现有生效的降级规则列表,system 则返回系统保护规则。
获取所有热点规则:
http://localhost:8719/getParamRules
定制自己的持久化规则
上面的规则配置,都是存在内存中的。即如果应用重启,这个规则就会失效。因此我们提供了开放的接口,您可以通过实现 DataSource
接口的方式,来自定义规则的存储数据源。通常我们的建议有:
- 整合动态配置系统,如 ZooKeeper、Nacos、Apollo 等,动态地实时刷新配置规则
- 结合 RDBMS、NoSQL、VCS 等来实现该规则
- 配合 Sentinel Dashboard 使用
更多详情请参考 动态规则配置。
3.3.规则生效的效果
判断限流降级异常
在 Sentinel 中所有流控降级相关的异常都是异常类 BlockException
的子类:
- 流控异常:
FlowException
- 熔断降级异常:
DegradeException
- 系统保护异常:
SystemBlockException
- 热点参数限流异常:
ParamFlowException
我们可以通过以下函数判断是否为 Sentinel 的流控降级异常:
BlockException.isBlockException(Throwable t);
除了在业务代码逻辑上看到规则生效,我们也可以通过下面简单的方法,来校验规则生效的效果:
- 暴露的 HTTP 接口:通过运行下面命令
curl http://localhost:8719/cnode?id=<资源名称>
,观察返回的数据。如果规则生效,在返回的数据栏中的block
以及block(m)
中会有显示 - 日志:Sentinel 提供秒级的资源运行日志以及限流日志,详情可以参考: 日志
block 事件
Sentinel 提供以下扩展接口,可以通过 StatisticSlotCallbackRegistry
向 StatisticSlot
注册回调函数:
ProcessorSlotEntryCallback
: callback when resource entry passed (onPass
) or blocked (onBlocked
)ProcessorSlotExitCallback
: callback when resource entry successfully completed (onExit
)
可以利用这些回调接口来实现报警等功能,实时的监控信息可以从 ClusterNode
中实时获取。
4.其它 API
4.1.业务异常统计 Tracer
业务异常记录类 Tracer
用于记录业务异常。相关方法:
traceEntry(Throwable, Entry)
:向传入 entry 对应的资源记录业务异常(非BlockException
异常),异常数目为传入的count
。
如果用户通过 SphU
或 SphO
手动定义资源,则 Sentinel 不能感知上层业务的异常,需要手动调用 Tracer.trace(ex)
来记录业务异常,否则对应的异常不会统计到 Sentinel 异常计数中。注意不要在 try-with-resources 形式的 SphU.entry(xxx)
中使用,否则会统计不上。
从 1.3.1 版本开始,注解方式定义资源支持自动统计业务异常,无需手动调用 Tracer.trace(ex)
来记录业务异常。Sentinel 1.3.1 以前的版本需要手动记录。
4.2.上下文工具类 ContextUtil
相关静态方法:
标识进入调用链入口(上下文):
以下静态方法用于标识调用链路入口,用于区分不同的调用链路:
public static Context enter(String contextName)
public static Context enter(String contextName, String origin)
其中 contextName
代表调用链路入口名称(上下文名称),origin
代表调用来源名称。默认调用来源为空。返回值类型为 Context
,即生成的调用链路上下文对象。
流控规则中若选择“流控方式”为“链路”方式,则入口资源名即为上面的 contextName
。
注意:
ContextUtil.enter(xxx)
方法仅在调用链路入口处生效,即仅在当前线程的初次调用生效,后面再调用不会覆盖当前线程的调用链路,直到 exit。Context
存于 ThreadLocal 中,因此切换线程时可能会丢掉,如果需要跨线程使用可以结合runOnContext
方法使用。- origin 数量不要太多,否则内存占用会比较大。
退出调用链(清空上下文):
public static void exit()
:该方法用于退出调用链,清理当前线程的上下文。
获取当前线程的调用链上下文:
public static Context getContext()
:获取当前线程的调用链路上下文对象。
在某个调用链上下文中执行代码:
public static void runOnContext(Context context, Runnable f)
:常用于异步调用链路中 context 的变换。
5.springcloud整合sentinel
5.1.sentinel依赖
<!--sentinel:熔断、降级、限流-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
5.2.下载控制台Dashboard
查看sentinel-core的版本,当前使用1.7.1
下载sentinel-dashboard-1.7.1.jar启动:
java -Dserver.port=8333 -Dcsp.sentinel.dashboard.server=127.0.0.1:8333 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.1.jar
5.3.配置dashboard属性
控制台工作流程:
1.控制台配置限流规则,然后将限流规则推送给应用(所以应用所在机器需要启动一个server port接收控制台消息)
2.应用接收到限流规则后,再将规则注册到sentinel中(所以还需要配置sentinel的服务地址:port)
# 每个模块都要配置【如果本机启动端口会冲突,编排好端口】
spring:
cloud:
sentinel:
transport:
# 应用开启端口,接收dashboard限流规则
port: 8719
# 控制台信息
dashboard: 127.0.0.1:8333
5.4.在dashboard操作
规则
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS 模式(1)或并发线程数模式(0) | QPS 模式 |
limitApp | 流控针对的调用来源 | default ,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 根据资源本身(直接) |
controlBehavior | 流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 | 直接拒绝 |
clusterMode | 是否集群限流 | 否 |
5.4.1.限流
限流效果:
5.4.1.1 流控模式
调用关系包括调用方、被调用方;一个方法又可能会调用其它方法,形成一个调用链路的层次关系。Sentinel 通过 NodeSelectorSlot 建立不同资源间的调用的关系,并且通过 ClusterBuilderSlot 记录每个资源的实时统计信息。
有了调用链路的统计信息,我们可以衍生出多种流量控制手段。
直接:
所有调用此资源的请求都受该规则控制
关联:
资源A和资源B关联限流,如果A流量大则限流B
链路:
指定请求调用入口,只有从入口过来的请求受该规则控制
根据调用方限流
ContextUtil.enter(resourceName, origin)
方法中的 origin
参数标明了调用方身份。这些信息会在 ClusterBuilderSlot
中被统计。可通过以下命令来展示不同的调用方对同一个资源的调用数据:
curl http://localhost:8719/origin?id=nodeA
调用数据示例:
id: nodeA
idx origin threadNum passedQps blockedQps totalQps aRt 1m-passed 1m-blocked 1m-total
1 caller1 0 0 0 0 0 0 0 0
2 caller2 0 0 0 0 0 0 0 0
上面这个命令展示了资源名为 nodeA
的资源被两个不同的调用方调用的统计。
流控规则中的 limitApp
字段用于根据调用来源进行流量控制。该字段的值有以下三种选项,分别对应不同的场景:
default
:表示不区分调用者,来自任何调用者的请求都将进行限流统计。如果这个资源名的调用总和超过了这条规则定义的阈值,则触发限流。{some_origin_name}
:表示针对特定的调用者,只有来自这个调用者的请求才会进行流量控制。例如NodeA
配置了一条针对调用者caller1
的规则,那么当且仅当来自caller1
对NodeA
的请求才会触发流量控制。other
:表示针对除{some_origin_name}
以外的其余调用方的流量进行流量控制。例如,资源NodeA
配置了一条针对调用者caller1
的限流规则,同时又配置了一条调用者为other
的规则,那么任意来自非caller1
对NodeA
的调用,都不能超过other
这条规则定义的阈值。
同一个资源名可以配置多条规则,规则的生效顺序为:{some_origin_name} > other > default
注意:调用来源的数目不要太多(一般不要超过几百个),否则内存占用会非常多(调用来源的统计节点最大数目=资源数目*来源数目)。
根据调用链路入口限流:链路限流
NodeSelectorSlot
中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为 machine-root
的虚拟节点,调用链的入口都是这个虚节点的子节点。
一棵典型的调用树如下图所示:
machine-root
/ \
/ \
Entrance1 Entrance2
/ \
/ \
DefaultNode(nodeA) DefaultNode(nodeA)
上图中来自入口 Entrance1
和 Entrance2
的请求都调用到了资源 NodeA
,Sentinel 允许只根据某个入口的统计信息对资源限流。比如我们可以设置 strategy
为 RuleConstant.STRATEGY_CHAIN
,同时设置 refResource
为 Entrance1
来表示只有从入口 Entrance1
的调用才会记录到 NodeA
的限流统计当中,而不关心经 Entrance2
到来的调用。
调用链的入口(上下文)是通过 API 方法 ContextUtil.enter(contextName)
定义的,其中 contextName 即对应调用链路入口名称。详情可以参考 ContextUtil 文档。
具有关系的资源流量控制:关联流量控制
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db
和 write_db
这两个资源分别代表数据库读写,我们可以给 read_db
设置限流规则来达到写优先的目的:设置 strategy
为 RuleConstant.STRATEGY_RELATE
同时设置 refResource
为 write_db
。这样当写库操作过于频繁时,读数据的请求会被限流。
5.4.1.2 流控效果
快速失败
超出阈值的直接丢弃
Warm Up
在一段时间内将请求数放行到阈值
排队等待
超出阈值的请求排队,如果超时仍未处理的请求被丢弃
5.4.2.熔断
熔断适配feign,远程调用快速失败
注意:
熔断是在调用方配置,以下在调用方product测试
熔断可以看做一种回调机制,调用方设置feign降级,会触发回调
Feign 支持
Sentinel 适配了 Feign 组件。如果想使用,除了引入 spring-cloud-starter-alibaba-sentinel
的依赖外还需要 2 个步骤:
- 配置文件打开 Sentinel 对 Feign 的支持:
feign.sentinel.enabled=true
- 加入
spring-cloud-starter-openfeign
依赖使 Sentinel starter 中的自动化配置类生效:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
这是一个 FeignClient
的简单使用示例:
// 指定配置类,熔断回调类
@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class)
public interface EchoService {
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
String echo(@PathVariable("str") String str);
}
// 配置类
class FeignConfiguration {
@Bean
public EchoServiceFallback echoServiceFallback() {
return new EchoServiceFallback();
}
}
// 熔断回调类
class EchoServiceFallback implements EchoService {
// 熔断回调方法
@Override
public String echo(@PathVariable("str") String str) {
return "echo fallback";
}
}
Note | Feign 对应的接口中的资源名策略定义:httpmethod:protocol://requesturl。@FeignClient 注解中的所有属性,Sentinel 都做了兼容。 |
---|---|
EchoService
接口中方法 echo
对应的资源名为 GET:http://service-provider/echo/{str}
。
5.4.3.降级
1.宕机的feign请求会触发熔断fallback
2.调用方设置feign请求降级规则,降级后触发熔断fallback
3.调用方不设置feign降级,而提供方设置controller资源降级,降级后触发提供方的限流handle处理(UrlBlockSentinelHandler)一般用作高并发场景下让出资源
熔断方法一般是服务的调用方提供 降级方法一般由服务的提供方提供。
例:A调用B:1、调用不到,得到A自己设置的熔断数据 2、能调用B,但是B自己设置了降级。那么A得到的是B降级数据
5.5.dashboard实时监控【Endpoint 支持】
在使用 Endpoint 特性之前需要在 Maven 中添加 spring-boot-starter-actuator 依赖,并在配置中允许 Endpoints 的访问。
Spring Boot 1.x 中添加配置 management.security.enabled=false。暴露的 endpoint 路径为 /sentinel
Spring Boot 2.x 中添加配置 management.endpoints.web.exposure.include=*。暴露的 endpoint 路径为 /actuator/sentinel注意:
spring-boot-starter-actuator是审计框架,计算springboot应用健康状况信息、请求的调用信息
dashboard可以拿到actuator数据作实时监控统计
<!--审计模块,监控应用的健康情况、调用信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.暴露端口,属性配置
# 审计模块暴露
management:
endpoints:
web:
exposure:
exclude: '*'
5.6.url资源限流
默认限流返回Blocked by Sentinel
可以实现自定义限流的返回页面,实现 请求限制处理器
默认返回:
自定义返回:
新版本:【在每个模块添加该配置】
/**
* @Description: 限流请求自定义返回
**/
@Component
public class UrlBlockHandler implements BlockExceptionHandler {
/**
* 自定义限流返回信息
* @param request
* @param response
* @param ex
* @throws IOException
*/
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {
R error = R.error(BizCodeEnume.TO_MANY_REQUEST.getCode(), BizCodeEnume.TO_MANY_REQUEST.getMsg());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().write(JSON.toJSONString(error));
}
}
5.7.自定义资源限流
文档:自定义资源
资源可以是一个方法、一个请求、甚至是一段代码
5.7.1 抛出异常的方式定义资源
- 使用try catch包含资源代码
- 指定这段代码的资源名
- 在dashboard上根据资源名设置限流规则、降级规则
SphU
包含了 try-catch 风格的 API。用这种方式,当资源发生了限流之后会抛出BlockException
。这个时候可以捕捉异常,进行限流之后的逻辑处理。示例代码如下:// 1.5.0 版本开始可以利用 try-with-resources 特性(使用有限制) // 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。 try (Entry entry = SphU.entry("resourceName")) { // 被保护的业务逻辑 // do something here... } catch (BlockException ex) { // 资源访问阻止,被限流或被降级 // 在此处进行相应的处理操作 }
特别地,若 entry 的时候传入了热点参数,那么 exit 的时候也一定要带上对应的参数(
exit(count, args)
),否则可能会有统计错误。这个时候不能使用 try-with-resources 的方式。另外通过Tracer.trace(ex)
来统计异常信息时,由于 try-with-resources 语法中 catch 调用顺序的问题,会导致无法正确统计异常数,因此统计异常信息时也不能在 try-with-resources 的 catch 块中调用Tracer.trace(ex)
。手动 exit 示例:
Entry entry = null; // 务必保证 finally 会被执行 try { // 资源名可使用任意有业务语义的字符串,注意数目不能太多(超过 1K),超出几千请作为参数传入而不要直接作为资源名 // EntryType 代表流量类型(inbound/outbound),其中系统规则只对 IN 类型的埋点生效 entry = SphU.entry("自定义资源名"); // 被保护的业务逻辑 // do something... } catch (BlockException ex) { // 资源访问阻止,被限流或被降级 // 进行相应的处理操作 } catch (Exception ex) { // 若需要配置降级规则,需要通过这种方式记录业务异常 Tracer.traceEntry(ex, entry); } finally { // 务必保证 exit,务必保证每个 entry 与 exit 配对 if (entry != null) { entry.exit(); } }
热点参数埋点示例:
Entry entry = null; try { // 若需要配置例外项,则传入的参数只支持基本类型。 // EntryType 代表流量类型,其中系统规则只对 IN 类型的埋点生效 // count 大多数情况都填 1,代表统计为一次调用。 entry = SphU.entry(resourceName, EntryType.IN, 1, paramA, paramB); // Your logic here. } catch (BlockException ex) { // Handle request rejection. } finally { // 注意:exit 的时候也一定要带上对应的参数,否则可能会有统计错误。 if (entry != null) { entry.exit(1, paramA, paramB); } }
SphU.entry()
的参数描述:
参数名 类型 解释 默认值 entryType EntryType
资源调用的流量类型,是入口流量( EntryType.IN
)还是出口流量(EntryType.OUT
),注意系统规则只对 IN 生效EntryType.OUT
count int
本次资源调用请求的 token 数目 1 args Object[]
传入的参数,用于热点参数限流 无 注意:
SphU.entry(xxx)
需要与entry.exit()
方法成对出现,匹配调用,否则会导致调用链记录异常,抛出ErrorEntryFreeException
异常。常见的错误:
- 自定义埋点只调用
SphU.entry()
,没有调用entry.exit()
- 顺序错误,比如:
entry1 -> entry2 -> exit1 -> exit2
,应该为entry1 -> entry2 -> exit2 -> exit1
5.7.2 注解资源限流
Sentinel 支持通过 @SentinelResource
注解定义资源并配置 blockHandler
和 fallback
函数来进行限流之后的处理。示例:
// 原本的业务方法.
@SentinelResource(blockHandler = "blockHandlerForGetUser")
public User getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}
// blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用
public User blockHandlerForGetUser(String id, BlockException ex) {
return new User("admin");
}
注意 blockHandler
函数会在原方法被限流/降级/系统保护的时候调用,而 fallback
函数会针对所有类型的异常。请注意 blockHandler
和 fallback
函数的形式要求,更多指引可以参见 Sentinel 注解支持文档。
5.8 网关限流
文档:网关限流
Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流。
使用流程:
- 引入依赖
<!--网关限流适配,版本号要与spring-cloud-alibaba-dependencies一致-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>x.y.z</version>
</dependency>
- Sentinel 控制台会自动显示网关对应的菜单,可以查看实时的 route 和自定义 API 分组的监控和调用信息,并针对其配置规则:
网关规则动态配置及网关集群流控可以参考 AHAS Sentinel 网关流控。
自定义回调
/**
* @author : ctc
* @createTime : 2023/1/14 19:35
*/
@Configuration
public class SentinelGetewayConfig {
public SentinelGetewayConfig(){
GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() {
// 网关限流了请求,就会调用此回调
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
R r = R.error(BizCodeEnume.TO_MANY_REQUEST.getCode(), BizCodeEnume.TO_MANY_REQUEST.getMsg());
String errJson = JSON.toJSONString(r);
return ServerResponse.ok().body(Mono.just(errJson), String.class);
}
});
}
}
Comments NOTHING