推荐使用 GitCodeTree 插件浏览网页项目
Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来保障微服务的稳定性。
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,服务名称作为资源名来标示资源。
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。Sentinel 作为一个调配器,可以根据系统的处理能力把随机的请求调整成合适的形状。
流量控制调整角度
由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积。所以
Sentinel
同时也被设计用来降低调用链路中的不稳定资源。Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。
Hystrix: 采用线程池(benefits-of-thread-pools)的方式,来对依赖进行了隔离
Sentinel:
通过并发线程数进行限制
Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其他资源的影响。减少线程切换的损耗,也不需要您预先分配线程池的大小。
当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
通过响应时间对资源进行降级
Sentinel 可以通过响应时间来快速降级不稳定的资源
当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
为防止雪崩事件发生,Sentinel 提供了系统维度的系统自适应保护。
当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。这个时候,Sentinel 能让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
sudo mkdir -p /usr/local/soft/sentinel/
cd /usr/local/soft/sentinel/
wget https://github.com/alibaba/Sentinel/releases/download/v1.8.0/sentinel-dashboard-1.8.0.jar
# 指定 java 版本,最低 JDK 1.8
FROM java:8
# 挂载docker卷
VOLUME /tmp
# 前者主要操作的是 jar 包 后者定义的jar包名称
ADD sentinel-dashboard-1.8.0.jar sentinel-dashboard-1.8.0.jar
# 定义时区参数
ENV TZ=Asia/Shanghai
# 设置时区
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo '$TZ' > /etc/timezone
# 指定暴露端口
EXPOSE 8858
# username 和 password 写自己的账户密码,最后一个双引号内需要和上面自定义jar包名一致
ENTRYPOINT ["java","-Dserver.port=8858","-Dsentinel.dashboard.auth.username=root","-Dsentinel.dashboard.auth.password=123456","-Dcsp.sentinel.dashboard.server=localhost:8858","Dproject.name=sentinel-dashboard","-jar","/sentinel-dashboard-1.8.0.jar"]
具体启动参数配置,参考 sentinel 启动配置项
# docker构建 docker build -f Dockerfile -t 定义镜像名称:版本名 .
docker build -f Dockerfile -t xxx-sentinel:1.0 .
# 查看 docker 镜像是中存在的 sentinel 镜像
docker images
docker-compose-sentinel.yml
version: '3'
services:
sentinel-dashboard:
image: xxx-sentinel:1.0
container_name: sentinel-dashboard
restart: always
ports:
- "8858:8858"
运行 sentinel 镜像
docker-compose -f docker-compose-sentinel.yml up -d
获取最新 Sentinel Jar 资源包
或者使用 git clone 源码进行 mvn clean package
打包
# JDK 版本在 JDK 1.8 及以上
# 默认用户名和密码都是 sentinel
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
浏览器输入 http://localhost:8080/#/login
打开 Sentinel 控制台界面。输入 sentinel
账号和密码进行登录
Sentinel 采用懒加载项进行应用监控。
所以 确保客户端有访问量,Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包。
簇点链路(单机调用链路)页面实时的去拉取指定客户端资源的运行情况。它一共提供两种展示模式:一种用树状结构展示资源的调用链路,另外一种则不区分调用链路展示资源的运行情况。
注意: 簇点监控是内存态的信息,它仅展示启动后调用过的资源。
同时,同一个服务下的所有机器的簇点信息会被汇总,并且秒级地展示在"实时监控"下。
注意:
Sentinel 控制台同时提供简单的规则管理以及推送的功能。规则推送分为 3 种模式,包括 “原始模式”、“Pull 模式” 和"Push 模式"。
在控制台通过接入端暴露的 HTTP API 来查询规则。
目前控制台的规则推送也是通过 规则查询更改 HTTP API 来更改规则。这也意味着这些规则仅在内存态生效,应用重启之后,该规则会丢失。
以上是原始模式。当了解了原始模式之后,我们非常鼓励您通过 动态规则 并结合各种外部存储来定制自己的规则源。我们推荐通过动态配置源的控制台来进行规则写入和推送,而不是通过 Sentinel 客户端直接写入到动态配置源中。在生产环境中,我们推荐 push 模式,具体可以参考:在生产环境使用 Sentinel。
注:若要使用集群流控功能,则必须对接动态规则源,否则无法正常使用。
Sentinel 同时还提供应用维度规则推送的示例页面(流控规则页面,前端路由为 /v2/flow
),用户改造控制台对接配置中心后可直接通过 v2 页面推送规则至配置中心。Sentinel 抽取了通用接口用于向远程配置中心推送规则以及拉取规则:
DynamicRuleProvider
: 拉取规则(应用维度)DynamicRulePublisher
: 推送规则(应用维度)用户只需实现 DynamicRuleProvider
和 DynamicRulePublisher
接口,并在 v2 的 controller 中通过 @Qualifier
注解替换相应的 bean 即可实现应用维度推送。我们提供了 Nacos 和 Apollo 的示例,改造详情可参考 应用维度规则推送示例。
控制台的一些特性可以通过配置项来进行配置,配置项主要有两个来源:
System.getProperty()
和System.getenv()
,同时存在时后者可以覆盖前者。
通过环境变量进行配置时,因为不支持
.
所以需要将其更换为_
。
配置项 | 类型 | 默认值 | 最小值 | 描述 |
---|---|---|---|---|
auth.enabled | boolean | true | - | 是否开启登录鉴权,仅用于日常测试,生产上不建议关闭 |
sentinel.dashboard.auth.username | String | sentinel | - | 登录控制台的用户名,默认为 sentinel |
sentinel.dashboard.auth.password | String | sentinel | - | 登录控制台的密码,默认为 sentinel |
sentinel.dashboard.app.hideAppNoMachineMillis | Integer | 0 | 60000 | 是否隐藏无健康节点的应用,距离最近一次主机心跳时间的毫秒数,默认关闭 |
sentinel.dashboard.removeAppNoMachineMillis | Integer | 0 | 120000 | 是否自动删除无健康节点的应用,距离最近一次其下节点的心跳时间毫秒数,默认关闭 |
sentinel.dashboard.unhealthyMachineMillis | Integer | 60000 | 30000 | 主机失联判定,不可关闭 |
sentinel.dashboard.autoRemoveMachineMillis | Integer | 0 | 300000 | 距离最近心跳时间超过指定时间是否自动删除失联节点,默认关闭 |
sentinel.dashboard.unhealthyMachineMillis | Integer | 60000 | 30000 | 主机失联判定,不可关闭 |
server.servlet.session.cookie.name | String | sentinel_dashboard_cookie | - | 控制台应用的 cookie 名称,可单独设置避免同一域名下 cookie 名冲突 |
配置示例:
java -Dsentinel.dashboard.app.hideAppNoMachineMillis=60000
System.setProperty("sentinel.dashboard.app.hideAppNoMachineMillis", "60000");
sentinel_dashboard_app_hideAppNoMachineMillis=60000
sentinel
、-Dsentinel.dashboard.auth.username=sentinel
用于指定控制台登录用户名为 sentinel
-Dsentinel.dashboard.auth.password=123456
用于指定控制台的登录密码为 123456
;-Dserver.servlet.session.timeout=7200
用于指定 Spring Boot 服务端 session 的过期时间,默认单位为 s;指定 60m
则为60分钟。默认为30分钟;官方 Sentinel Demo 集合
Spring Cloud 整合 Damo 集合
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
SentinelSpringCloudApplication
@SpringBootApplication
public class SentinelSpringCloudApplication {
public static void main(String[] args) {
SpringApplication.run(SentinelSpringCloudApplication.class, args);
}
}
HelloService
public interface HelloService {
String hello(String name);
}
HelloServiceImpl
@Service
public class HelloServiceImpl implements HelloService {
//@SentinelResource 注解用来标识资源属性,资源名称、是否被限流、降级等。fallback 用于表示限流或降级的操作。若不配置 blockHandler、fallback 等函数,则被流控降级时方法会直接抛出对应的 BlockException;若方法未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException。
@SentinelResource(value = "hello")
@Override
public String hello(String name) {
return "hello " + name +"!";
}
}
HelloController
@RestController
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping(value = "/hello/{name}")
public String hello(@PathVariable(value = "name") String name){
return helloService.hello(name);
}
}
application 配置项
server:
port: 8081
spring:
application:
name: spring-cloud-alibaba-sentinel-springboot-example
cloud:
sentinel:
transport:
dashboard: localhost:8080
port: 8791
management:
endpoints:
web:
exposure:
exclude: '*'
@Bean
@SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class)
public RestTemplate restTemplate() {
return new RestTemplate();
}
@SentinelRestTemplate
注解的属性支持限流(blockHandler
, blockHandlerClass
)和降级(fallback
, fallbackClass
)的处理。其中 blockHandler
或 fallback
属性对应的方法必须是对应 blockHandlerClass
或 fallbackClass
属性中的静态方法。该方法的参数跟返回值跟 org.springframework.http.client.ClientHttpRequestInterceptor#interceptor
方法一致,其中参数多出了一个 BlockException
参数用于获取 Sentinel 捕获的异常。
定位为GraalVM和OpenJDK HotSpot量身定制的一个Kurbernetes Native Java框架。虽然开源时间较短,但是生态方面也已经达到可用的状态,自身包含扩展框架,已经支持像Netty、Undertow、Hibernate、JWT等框架,足以用于开发企业级应用,用户也可以基于扩展框架自行扩展。
Feign 对应的接口中的资源名策略定义:httpmethod:protocol:/requesturl。
@FeignClient
注解中的所有属性,Sentinel 都做了兼容。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
# 配置文件打开 Sentinel 对 Feign 的支持
feign.sentinel.enabled=true
@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";
}
}
示例
理念
Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。
- 查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。
- 监控 (单机和集群聚合):通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。
- 规则管理和推送:统一管理推送规则。
- 鉴权:生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。
现阶段 Sentinel 核心库和控制台(Dashboard)整合为一个控制端。
控制台的搭建参见 [Sentinel Server 端搭建]( # Sentinel_Server)
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-transport-simple-httpartifactId>
<version>x.y.zversion>
dependency>
JVM 启动参数配置:
-Dcsp.sentinel.dashboard.server=consoleIp:port
指定控制台地址和端口-Dcsp.sentinel.api.port=xxxx
指定客户度监控 API 的端口(默认为 8719),方便监控多个应用Sentinel 采用懒加载机制,当有流量访问时才会初始化监控
${user.home}/logs/csp/sentinel-record.log.xxx
查看日志来查看启动状态
在 Sentinel 里面,所有的资源都对应一个资源名称以及一个 Entry。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 API 显式创建;每一个 Entry 创建的时候,同时也会创建一系列功能插槽(slot chain)
Sentinel 主要插槽总览:
NodeSelectorSlot
负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;ClusterBuilderSlot
则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;StatisticSlot
则用于记录、统计不同纬度的 runtime 指标监控信息;FlowSlot
则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制;AuthoritySlot
则根据配置的黑白名单和调用来源信息,来做黑白名单控制;DegradeSlot
则通过统计信息以及预设的规则,来做熔断降级;SystemSlot
则通过系统的状态,例如 load1 等,来控制总的入口流量;主要负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级。
用于构建资源的
ClusterNode
以及调用来源节点。ClusterNode
保持资源运行统计信息(响应时间、QPS、block 数目、线程数、异常数等)以及原始调用者统计信息列表。来源调用者的名字由ContextUtil.enter(contextName,origin)
中的origin
标记。
用于统计实时的调用数据。
clusterNode
:资源唯一标识的 ClusterNode 的 runtime 统计origin
:根据来自不同调用者的统计信息defaultnode
: 根据上下文条目名称和资源 ID 的 runtime 统计- 入口的统计
底层采用高性能的滑动窗口数据结构 LeapArray
来统计实时的秒级指标数据,可以很好地支撑写多于读的高并发场景。
主要根据预设的资源的统计信息,按照固定的次序,依次生效。如果一个资源对应两条或者多条流控规则,则会根据如下次序依次检验,直到全部通过或者有一个规则生效为止:
主要针对资源的平均响应时间(RT)以及异常比率,来决定资源是否在接下来的时间被自动熔断掉。
会根据对于当前系统的整体情况,对入口资源的调用进行动态调配。其原理是让入口的流量和当前系统的预计容量达到一个动态平衡。注意系统规则只对入口流量起作用(调用类型为 EntryType.IN
),对出口流量无效。可通过 SphU.entry(res, entryType)
指定调用类型,如果不指定,默认是EntryType.OUT
。
Sentinel 将
ProcessorSlot
作为 SPI 接口进行扩展(1.7.2 版本以前SlotChainBuilder
作为 SPI),使得 Slot Chain 具备了扩展的能力。您可以自行加入自定义的 slot 并编排 slot 间的顺序,从而可以给 Sentinel 添加自定义的功能。
Sentinel 的核心骨架,将不同的 Slot 按照顺序串在一起(责任链模式),从而将不同的功能(限流、降级、系统保护)组合在一起。
slot chain 可以分为两个主要部分:
Context 代表调用链路上下文,贯穿一次调用链路中的所有
Entry
。Context 维持着入口节点(entranceNode
)、本次调用链路的 curNode、调用来源(origin
)等信息。Context 名称即为调用链路入口名称。Context 维持的方式:通过 ThreadLocal 传递,只有在入口
enter
的时候生效。由于 Context 是通过 ThreadLocal 传递的,因此对于异步调用链路,线程切换的时候会丢掉 Context,因此需要手动通过ContextUtil.runOnContext(context, f)
来变换 context
每一次资源调用都会创建一个
Entry
。Entry
包含了资源名、curNode(当前统计节点)、originNode(来源统计节点)等信息。
Sentinel 里面的各种种类的统计节点:
StatisticNode
:最为基础的统计节点,包含秒级和分钟级两个滑动窗口结构。DefaultNode
:链路节点,用于统计调用链路上某个资源的数据,维持树状结构。ClusterNode
:簇点,用于统计每个资源全局的数据(不区分调用链路),以及存放该资源的按来源区分的调用数据(类型为 StatisticNode
)。特别地,Constants.ENTRY_NODE
节点用于统计全局的入口资源数据。EntranceNode
:入口节点,特殊的链路节点,对应某个 Context 入口的所有调用数据。Constants.ROOT
节点也是入口节点。构建的时机:
EntranceNode
在 ContextUtil.enter(xxx)
的时候就创建了,然后塞到 Context 里面。NodeSelectorSlot
:根据 context 创建 DefaultNode
,然后 set curNode to context。ClusterBuilderSlot
:首先根据 resourceName 创建 ClusterNode
,并且 set clusterNode to defaultNode;然后再根据 origin 创建来源节点(类型为 StatisticNode
),并且 set originNode to curEntry。几种 Node 的维度(数目):
ClusterNode
的维度是 resourceDefaultNode
的维度是 resource * context,存在每个 NodeSelectorSlot 的 map
里面EntranceNode
的维度是 context,存在 ContextUtil 类的 contextNameNodeMap
里面StatisticNode
)的维度是 resource * origin,存在每个 ClusterNode 的 originCountMap
里面用于根据规则判断结果进行相应的统计操作。
entry 的时候:依次执行后面的判断 slot。每个 slot 触发流控的话会抛出异常(BlockException
的子类)。若有 BlockException
抛出,则记录 block 数据;若无异常抛出则算作可通过(pass),记录 pass 数据。
exit 的时候:若无 error(无论是业务异常还是流控异常),记录 complete(success)以及 RT,线程数-1。
记录数据的维度:线程数+1、记录当前 DefaultNode 数据、记录对应的 originNode 数据(若存在 origin)、累计 IN 统计数据(若流量类型为 IN)。
Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。
使用 Sentinel 进行资源保护,主要步骤:
参见: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)
。
// 1.5.0 之前的版本的示例:
Entry entry = null;
// 务必保证finally会被执行
try {
// 资源名可使用任意有业务语义的字符串
entry = SphU.entry("自定义资源名");
// 被保护的业务逻辑
// do something...
} catch (BlockException e1) {
// 资源访问阻止,被限流或被降级
// 进行相应的处理操作
} finally {
if (entry != null) {
// SphU.entry(xxx) 需要与 entry.exit() 方法成对出现,匹配调用,否则会导致调用链记录异常,抛出 ErrorEntryFreeException 异常。
entry.exit();
}
}
SphO
提供 if-else 风格的 API。用这种方式,当资源发生了限流之后会返回false
,这个时候可以根据返回值,进行限流之后的逻辑处理。
// 资源名可使用任意有业务语义的字符串
if (SphO.entry("自定义资源名")) {
// 务必保证finally会被执行
try {
/**
* 被保护的业务逻辑
*/
} finally {
SphO.exit();
}
} else {
// 资源访问阻止,被限流或被降级
// 进行相应的处理操作
}
@SentinelResource
注解定义资源并配置blockHandler
和fallback
函数来进行限流之后的处理。具体参见: Sentinel 注解埋点
// 原本的业务方法.
@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
函数会针对所有类型的异常。
在异步调用中,需要通过
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:
public class AsyncEntryDemo {
private void invoke(String arg, Consumer<String> handler) {
CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(3);
String resp = arg + ": " + System.currentTimeMillis();
handler.accept(resp);
} catch (Exception ex) {
ex.printStackTrace();
}
});
}
private void anotherAsync() {
try {
final AsyncEntry entry = SphU.asyncEntry("test-another-async");
CompletableFuture.runAsync(() -> {
ContextUtil.runOnContext(entry.getAsyncContext(), () -> {
try {
TimeUnit.SECONDS.sleep(2);
// Normal entry nested in asynchronous entry.
anotherSyncInAsync();
System.out.println("Async result: 666");
} catch (InterruptedException e) {
// Ignore.
} finally {
entry.exit();
}
});
});
} catch (BlockException ex) {
ex.printStackTrace();
}
}
private void fetchSync() {
Entry entry = null;
try {
entry = SphU.entry("test-sync");
} catch (BlockException ex) {
ex.printStackTrace();
} finally {
if (entry != null) {
entry.exit();
}
}
}
private void fetchSyncInAsync() {
Entry entry = null;
try {
entry = SphU.entry("test-sync-in-async");
} catch (BlockException ex) {
ex.printStackTrace();
} finally {
if (entry != null) {
entry.exit();
}
}
}
private void anotherSyncInAsync() {
Entry entry = null;
try {
entry = SphU.entry("test-another-sync-in-async");
} catch (BlockException ex) {
ex.printStackTrace();
} finally {
if (entry != null) {
entry.exit();
}
}
}
private void directlyAsync() {
try {
final AsyncEntry entry = SphU.asyncEntry("test-async-not-nested");
this.invoke("abc", result -> {
// If no nested entry later, we don't have to wrap in `ContextUtil.runOnContext()`.
try {
// Here to handle the async result (without other entry).
} finally {
// Exit the async entry.
entry.exit();
}
});
} catch (BlockException e) {
// Request blocked, handle the exception.
e.printStackTrace();
}
}
private void doAsyncThenSync() {
try {
// First we call an asynchronous resource.
final AsyncEntry entry = SphU.asyncEntry("test-async");
this.invoke("abc", resp -> {
// The thread is different from original caller thread for async entry.
// So we need to wrap in the async context so that nested invocation entry
// can be linked to the parent asynchronous entry.
ContextUtil.runOnContext(entry.getAsyncContext(), () -> {
try {
// In the callback, we do another async invocation several times under the async context.
for (int i = 0; i < 7; i++) {
anotherAsync();
}
System.out.println(resp);
// Then we do a sync (normal) entry under current async context.
fetchSyncInAsync();
} finally {
// Exit the async entry.
entry.exit();
}
});
});
// Then we call a sync resource.
fetchSync();
} catch (BlockException ex) {
// Request blocked, handle the exception.
ex.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
initFlowRule();
AsyncEntryDemo service = new AsyncEntryDemo();
// Expected invocation chain:
//
// EntranceNode: machine-root
// -EntranceNode: async-context
// --test-top
// ---test-sync
// ---test-async
// ----test-another-async
// -----test-another-sync-in-async
// ----test-sync-in-async
ContextUtil.enter("async-context", "originA");
Entry entry = null;
try {
entry = SphU.entry("test-top");
System.out.println("Do something...");
service.doAsyncThenSync();
} catch (BlockException ex) {
// Request blocked, handle the exception.
ex.printStackTrace();
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
TimeUnit.SECONDS.sleep(20);
}
private static void initFlowRule() {
// Rule 1 won't take effect as the limitApp doesn't match.
FlowRule rule1 = new FlowRule()
.setResource("test-another-sync-in-async")
.setLimitApp("originB")
.as(FlowRule.class)
.setCount(4)
.setGrade(RuleConstant.FLOW_GRADE_QPS);
// Rule 2 will take effect.
FlowRule rule2 = new FlowRule()
.setResource("test-another-async")
.setLimitApp("default")
.as(FlowRule.class)
.setCount(5)
.setGrade(RuleConstant.FLOW_GRADE_QPS);
List<FlowRule> ruleList = Arrays.asList(rule1, rule2);
FlowRuleManager.loadRules(ruleList);
}
}
Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效。同时 Sentinel 也提供相关 API,来快速的定制自己的规则策略
同一个资源可以同时有多个限流规则。
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS 或线程数模式 | QPS 模式 |
limitApp | 流控针对的调用来源 | default ,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 根据资源本身(直接) |
controlBehavior | 流控效果(直接拒绝 / 排队等待 / 慢启动模式),不支持按调用关系限流 | 直接拒绝 |
private static void initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule1 = new FlowRule();
rule1.setResource(resource);
// Set max qps to 20
rule1.setCount(20);
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
// FlowRuleManager.loadRules() 采用硬编码的方式定义流量控制规则
FlowRuleManager.loadRules(rules);
}
同一个资源可以同时有多个降级规则。
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 引入) |
private static void initDegradeRule() {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule(resource);
.setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType());
.setCount(0.7); // Threshold is 70% error ratio
.setMinRequestAmount(100)
.setStatIntervalMs(30000) // 30s
.setTimeWindow(10);
rules.add(rule);
// DegradeRuleManager.loadRules() 方法来用硬编码的方式定义熔断降级规则
DegradeRuleManager.loadRules(rules);
}
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
Field | 说明 | 默认值 |
---|---|---|
highestSystemLoad | load1 触发值,用于触发自适应控制阶段 |
-1 (不生效) |
avgRt | 所有入口流量的平均响应时间 | -1 (不生效) |
maxThread | 入口流量的最大并发数 | -1 (不生效) |
qps | 所有入口资源的 QPS | -1 (不生效) |
highestCpuUsage | 当前系统的 CPU 使用率(0.0-1.0) | -1 (不生效) |
private void initSystemProtectionRule() {
List<SystemRule> rules = new ArrayList<>();
SystemRule rule = new SystemRule();
rule.setHighestSystemLoad(10);
rules.add(rule);
// 系统保护规则
SystemRuleManager.loadRules(rules);
}
很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用 Sentinel 的访问控制(黑白名单)的功能。黑白名单根据资源的请求来源(
origin
)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。授权规则,即黑白名单规则(
AuthorityRule
):
resource
:资源名,即限流规则的作用对象limitApp
:对应的黑名单/白名单,不同 origin 用,
分隔,如appA,appB
strategy
:限制模式,AUTHORITY_WHITE
为白名单模式,AUTHORITY_BLACK
为黑名单模式,默认为白名单模式
调用方信息通过
ContextUtil.enter(resourceName, origin)
方法中的origin
参数传入。
AuthorityRule rule = new AuthorityRule();
// 设置 test 资源
rule.setResource("test");
rule.setStrategy(RuleConstant.AUTHORITY_WHITE);
// 设置资源访问规则 只有 appA 和 appB 的请求才能通过
rule.setLimitApp("appA,appB");
AuthorityRuleManager.loadRules(Collections.singletonList(rule));
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。
示例:
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-parameter-flow-controlartifactId>
<version>x.y.zversion>
dependency>
对应的资源配置热点参数限流规则,并在 entry
的时候传入相应的参数,即可使热点参数限流生效。
若自行扩展并注册了自己实现的
SlotChainBuilder
,并希望使用热点参数限流功能,则可以在 chain 里面合适的地方插入ParamFlowSlot
。
// 通过 SphU 类里面几个 entry 重载方法来传入对应的参数
public static Entry entry(String name, EntryType type, int count, Object... args) throws BlockException
public static Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException
注意:
若 entry 的时候传入了热点参数,那么 exit 的时候也一定要带上对应的参数(exit(count, args)
)。例如: entry.exit(1, paramA, paramB)
。
@SentinelResource
注解方式定义的资源,若注解作用的方法上有参数,Sentinel 会将它们作为参数传入 SphU.entry(res, args)
// uid 和 type 会分别作为第一个和第二个参数传入 Sentinel API,从而可以用于热点规则判断
@SentinelResource("myMethod")
public Result doSomething(String uid, int type) {
// some logic here...
}
属性 | 说明 | 默认值 |
---|---|---|
resource | 资源名,必填 | |
count | 限流阈值,必填 | |
grade | 限流模式 | QPS 模式 |
durationInSec | 统计窗口时间长度(单位为秒),1.6.0 版本开始支持 | 1s |
controlBehavior | 流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持 | 快速失败 |
maxQueueingTimeMs | 最大排队等待时长(仅在匀速排队模式生效),1.6.0 版本开始支持 | 0ms |
paramIdx | 热点参数的索引,必填,对应 SphU.entry(xxx, args) 中的参数索引位置 |
|
paramFlowItemList | 参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型和字符串类型 |
|
clusterMode | 是否是集群参数流控规则 | false |
clusterConfig | 集群流控相关配置 |
ParamFlowRule rule = new ParamFlowRule(resourceName)
.setParamIdx(0)
.setCount(5);
// 针对 int 类型的参数 PARAM_B,单独设置限流 QPS 阈值为 10,而不是全局的阈值 5.
ParamFlowItem item = new ParamFlowItem().setObject(String.valueOf(PARAM_B))
.setClassType(int.class.getName())
.setCount(10);
rule.setParamFlowItemList(Collections.singletonList(item));
// 通过 ParamFlowRuleManager 的 loadRules 方法更新热点参数规则
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
http://localhost:8719/getRules?type=
type=flow
以 JSON 格式返回现有的限流规则,degrade 返回现有生效的降级规则列表,system 则返回系统保护规则。
http://localhost:8719/getParamRules
其中,type 可以输入 flow
、degrade
等方式来制定更改的规则种类,data
则是对应的 JSON 格式的规则。
初始化时系统的规则配置,都是存在内存中的,如果应用重启,这个规则就会失效。可以通过实现外部开放的 DataSource
接口的方式,来自定义规则的存储数据源。实现规则的持久化存储:
具体使用参见:动态规则配置
// 代码层面判断 Sentinel 的流控降级异常
BlockException.isBlockException(Throwable t);
或者
curl http://localhost:8719/cnode?id=<资源名称>
,观察返回的数据。如果规则生效,在返回的数据栏中的 block
以及 block(m)
中会有显示通过 StatisticSlotCallbackRegistry
向 StatisticSlot
注册回调函数。利用这些回调接口来实现报警等功能,实时的监控信息可以从 ClusterNode
中实时获取:
ProcessorSlotEntryCallback
: callback when resource entry passed (onPass
) or blocked (onBlocked
)ProcessorSlotExitCallback
: callback when resource entry successfully completed (onExit
)业务异常记录类
Tracer
用于记录业务异常。
相关方法:
trace(Throwable e)
:记录业务异常(非 BlockException
异常),对应的资源为当前线程 context 下 entry 对应的资源。trace(Throwable e, int count)
:记录业务异常(非 BlockException
异常),异常数目为传入的 count
。traceEntry(Throwable, int, Entry)
:向传入 entry 对应的资源记录业务异常(非 BlockException
异常),异常数目为传入的 count
。如果用户通过 SphU
或 SphO
手动定义资源,则 Sentinel 不能感知上层业务的异常,需要手动调用 Tracer.trace(ex)
来记录业务异常,否则对应的异常不会统计到 Sentinel 异常计数中。注意不要在 try-with-resources 形式的 SphU.entry(xxx)
中使用,否则会统计不上。
Tips: 从 1.3.1 版本开始,注解方式定义资源支持自动统计业务异常,无需手动调用 Tracer.trace(ex)
来记录业务异常。Sentinel 1.3.1 以前的版本需要手动记录。
标识进入调用链入口(上下文):
以下静态方法用于标识调用链路入口,用于区分不同的调用链路:
public static Context enter(String contextName)
public static Context enter(String contextName, String origin)
其中 contextName
代表调用链路入口名称(上下文名称),origin
代表调用来源名称。默认调用来源为空。返回值类型为 Context
,即生成的调用链路上下文对象。
注意:ContextUtil.enter(xxx)
方法仅在调用链路入口处生效,即仅在当前线程的初次调用生效,后面再调用不会覆盖当前线程的调用链路,直到 exit。Context
存于 ThreadLocal 中,因此切换线程时可能会丢掉,如果需要跨线程使用可以结合 runOnContext
方法使用。
流控规则中若选择“流控方式”为“链路”方式,则入口资源名即为上面的 contextName
。
退出调用链(清空上下文):
public static void exit()
:该方法用于退出调用链,清理当前线程的上下文。获取当前线程的调用链上下文:
public static Context getContext()
:获取当前线程的调用链路上下文对象。在某个调用链上下文中执行代码:
public static void runOnContext(Context context, Runnable f)
:常用于异步调用链路中 context 的变换。Sentinel 底层采用高性能的滑动窗口数据结构来统计实时的秒级指标数据,并支持对滑动窗口进行配置。主要有以下两个配置:
windowIntervalMs
:滑动窗口的总的时间长度,默认为 1000 mssampleCount
:滑动窗口划分的格子数目,默认为 2;格子越多则精度越高,但是内存占用也会越多Tips: 我们可以通过 SampleCountProperty
来动态地变更滑动窗口的格子数目,通过 IntervalProperty
来动态地变更滑动窗口的总时间长度。注意这两个配置都是全局生效的,会影响所有资源的所有指标统计。
FlowSlot
会根据预设的规则,结合前面 NodeSelectorSlot
、ClusterNodeBuilderSlot
、StatistcSlot
统计出来的实时信息进行流量控制。
限流的直接表现是在执行 Entry nodeA = SphU.entry(资源名字)
的时候抛出 FlowException
异常。FlowException
是 BlockException
的子类,您可以捕捉 BlockException
来自定义被限流之后的处理逻辑。
同一个资源可以对应多条限流规则。FlowSlot
会对该资源的所有限流规则依次遍历,直到有规则触发限流或者所有规则遍历完毕。
一条限流规则主要由下面几个因素组成:
resource
:资源名,即限流规则的作用对象count
: 限流阈值grade
: 限流阈值类型,QPS 或线程数strategy
: 根据调用关系选择策略流量控制主要有两种统计类型,一种是统计线程数,另外一种则是统计 QPS。类型由
FlowRule.grade
字段来定义。其中,0 代表根据并发数量来限流,1 代表根据 QPS 来进行流量控制。其中线程数、QPS 值,都是由StatisticSlot
实时统计获取的。
curl http://localhost:8719/cnode?id=resourceName
输出内容格式:
idx id thread pass blocked success total Rt 1m-pass 1m-block 1m-all exeption
2 abc647 0 46 0 46 46 1 2763 0 2763 0
Sentinel线程数限流不负责创建和管理线程池,而是简单统计当前请求上下文的线程个数,如果超出阈值,新的请求会被立即拒绝。
线程数限流用于保护业务线程数不被耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对高线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离),或者使用信号量来控制同时请求的个数(信号量隔离)。这种隔离方案虽然能够控制线程数量,但无法控制请求排队时间。当请求过多时排队也是无益的,直接拒绝能够迅速降低系统压力。
public class FlowThreadDemo {
private static AtomicInteger pass = new AtomicInteger();
private static AtomicInteger block = new AtomicInteger();
private static AtomicInteger total = new AtomicInteger();
private static AtomicInteger activeThread = new AtomicInteger();
private static volatile boolean stop = false;
private static final int threadCount = 100;
private static int seconds = 60 + 40;
private static volatile int methodBRunningTime = 2000;
public static void main(String[] args) throws Exception {
System.out.println(
"MethodA will call methodB. After running for a while, methodB becomes fast, "
+ "which make methodA also become fast ");
tick();
initFlowRule();
for (int i = 0; i < threadCount; i++) {
Thread entryThread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
Entry methodA = null;
try {
TimeUnit.MILLISECONDS.sleep(5);
methodA = SphU.entry("methodA");
activeThread.incrementAndGet();
Entry methodB = SphU.entry("methodB");
TimeUnit.MILLISECONDS.sleep(methodBRunningTime);
methodB.exit();
pass.addAndGet(1);
} catch (BlockException e1) {
block.incrementAndGet();
} catch (Exception e2) {
// biz exception
} finally {
total.incrementAndGet();
if (methodA != null) {
methodA.exit();
activeThread.decrementAndGet();
}
}
}
}
});
entryThread.setName("working thread");
entryThread.start();
}
}
private static void initFlowRule() {
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
rule1.setResource("methodA");
// set limit concurrent thread for 'methodA' to 20
rule1.setCount(20);
rule1.setGrade(RuleConstant.FLOW_GRADE_THREAD);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
}
private static void tick() {
Thread timer = new Thread(new TimerTask());
timer.setName("sentinel-timer-task");
timer.start();
}
static class TimerTask implements Runnable {
@Override
public void run() {
long start = System.currentTimeMillis();
System.out.println("begin to statistic!!!");
long oldTotal = 0;
long oldPass = 0;
long oldBlock = 0;
while (!stop) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
long globalTotal = total.get();
long oneSecondTotal = globalTotal - oldTotal;
oldTotal = globalTotal;
long globalPass = pass.get();
long oneSecondPass = globalPass - oldPass;
oldPass = globalPass;
long globalBlock = block.get();
long oneSecondBlock = globalBlock - oldBlock;
oldBlock = globalBlock;
System.out.println(seconds + " total qps is: " + oneSecondTotal);
System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal
+ ", pass:" + oneSecondPass
+ ", block:" + oneSecondBlock
+ " activeThread:" + activeThread.get());
if (seconds-- <= 0) {
stop = true;
}
if (seconds == 40) {
System.out.println("method B is running much faster; more requests are allowed to pass");
methodBRunningTime = 20;
}
}
long cost = System.currentTimeMillis() - start;
System.out.println("time cost: " + cost + " ms");
System.out.println("total:" + total.get() + ", pass:" + pass.get()
+ ", block:" + block.get());
System.exit(0);
}
}
}
QPS:每秒访问量
当 QPS 超过某个阈值的时候,则采取措施进行流量控制。流量控制的手段包括下面 3 种,对应 FlowRule
中的 controlBehavior
字段:
直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT
)方式。该方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException
。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。具体的例子参见 FlowqpsDemo。
冷启动(RuleConstant.CONTROL_BEHAVIOR_WARM_UP
)方式。该方式主要用于系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮的情况。具体的例子参见 WarmUpFlowDemo。
匀速器(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER
)方式。这种方式严格控制了请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。具体的例子参见 PaceFlowDemo。
主要用于处理间隔性突发的流量,例如消息队列。在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
调用关系包括调用方、被调用方;方法又可能会调用其它方法,形成一个调用链路的层次关系。Sentinel 通过
NodeSelectorSlot
建立不同资源间的调用的关系,并且通过ClusterNodeBuilderSlot
记录每个资源的实时统计信息。
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
限流规则中的 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 允许只根据某个入口的统计信息对资源限流。比如我们可以设置 FlowRule.strategy
为 RuleConstant.CHAIN
,同时设置 FlowRule.ref_identity
为 Entrance1
来表示只有从入口 Entrance1
的调用才会记录到 NodeA
的限流统计当中,而对来自 Entrance2
的调用漠不关心。
调用链的入口是通过 API 方法 ContextUtil.enter(name)
定义的。
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db
和 write_db
这两个资源分别代表数据库读写,我们可以给 read_db
设置限流规则来达到写优先的目的:设置 FlowRule.strategy
为 RuleConstant.RELATE
同时设置 FlowRule.ref_identity
为 write_db
。这样当写库操作过于频繁时,读数据的请求会被限流。
,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
Sentinel 提供以下几种熔断策略:
SLOW_REQUEST_RATIO
):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。ERROR_RATIO
):当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0]
,代表 0% - 100%。ERROR_COUNT
):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。Tips: 异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException
)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex)
记录业务异常。
Entry entry = null;
try {
entry = SphU.entry(key, EntryType.IN, key);
// Write your biz code here.
// <>
} catch (Throwable t) {
if (!BlockException.isBlockException(t)) {
Tracer.trace(t);
}
} finally {
if (entry != null) {
entry.exit();
}
}
Sentinel 支持注册自定义的事件监听器监听熔断器状态变换事件(state change event)。
EventObserverRegistry.getInstance().addStateChangeObserver("logging",
(prevState, newState, rule, snapshotValue) -> {
if (newState == State.OPEN) {
// 变换至 OPEN state 时会携带触发时的值
System.err.println(String.format("%s -> OPEN at %d, snapshotValue=%.2f", prevState.name(),
TimeUtil.currentTimeMillis(), snapshotValue));
} else {
System.err.println(String.format("%s -> %s at %d", prevState.name(), newState.name(),
TimeUtil.currentTimeMillis()));
}
});
public class SlowRatioCircuitBreakerDemo {
private static final String KEY = "some_method";
private static volatile boolean stop = false;
private static int seconds = 120;
private static AtomicInteger total = new AtomicInteger();
private static AtomicInteger pass = new AtomicInteger();
private static AtomicInteger block = new AtomicInteger();
public static void main(String[] args) throws Exception {
initDegradeRule();
registerStateChangeObserver();
startTick();
int concurrency = 8;
for (int i = 0; i < concurrency; i++) {
Thread entryThread = new Thread(() -> {
while (true) {
Entry entry = null;
try {
entry = SphU.entry(KEY);
pass.incrementAndGet();
// RT: [40ms, 60ms)
sleep(ThreadLocalRandom.current().nextInt(40, 60));
} catch (BlockException e) {
block.incrementAndGet();
sleep(ThreadLocalRandom.current().nextInt(5, 10));
} finally {
total.incrementAndGet();
if (entry != null) {
entry.exit();
}
}
}
});
entryThread.setName("sentinel-simulate-traffic-task-" + i);
entryThread.start();
}
}
private static void registerStateChangeObserver() {
EventObserverRegistry.getInstance().addStateChangeObserver("logging",
(prevState, newState, rule, snapshotValue) -> {
if (newState == State.OPEN) {
System.err.println(String.format("%s -> OPEN at %d, snapshotValue=%.2f", prevState.name(),
TimeUtil.currentTimeMillis(), snapshotValue));
} else {
System.err.println(String.format("%s -> %s at %d", prevState.name(), newState.name(),
TimeUtil.currentTimeMillis()));
}
});
}
private static void initDegradeRule() {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule(KEY)
.setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
// Max allowed response time
.setCount(50)
// Retry timeout (in second)
.setTimeWindow(10)
// Circuit breaker opens when slow request ratio > 60%
.setSlowRatioThreshold(0.6)
.setMinRequestAmount(100)
.setStatIntervalMs(20000);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
System.out.println("Degrade rule loaded: " + rules);
}
private static void sleep(int timeMs) {
try {
TimeUnit.MILLISECONDS.sleep(timeMs);
} catch (InterruptedException e) {
// ignore
}
}
private static void startTick() {
Thread timer = new Thread(new TimerTask());
timer.setName("sentinel-timer-tick-task");
timer.start();
}
static class TimerTask implements Runnable {
@Override
public void run() {
long start = System.currentTimeMillis();
System.out.println("Begin to run! Go go go!");
System.out.println("See corresponding metrics.log for accurate statistic data");
long oldTotal = 0;
long oldPass = 0;
long oldBlock = 0;
while (!stop) {
sleep(1000);
long globalTotal = total.get();
long oneSecondTotal = globalTotal - oldTotal;
oldTotal = globalTotal;
long globalPass = pass.get();
long oneSecondPass = globalPass - oldPass;
oldPass = globalPass;
long globalBlock = block.get();
long oneSecondBlock = globalBlock - oldBlock;
oldBlock = globalBlock;
System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal
+ ", pass:" + oneSecondPass + ", block:" + oneSecondBlock);
if (seconds-- <= 0) {
stop = true;
}
}
long cost = System.currentTimeMillis() - start;
System.out.println("time cost: " + cost + " ms");
System.out.println("total: " + total.get() + ", pass:" + pass.get()
+ ", block:" + block.get());
System.exit(0);
}
}
}
Sentinel 系统自适应保护从整体维度对应用入口流量进行控制,结合应用的 Load、总体平均 RT、入口 QPS 和线程数等几个维度的监控指标,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统自适应保护的思路是根据硬指标,即系统的负载 (load1) 来做系统过载保护。当系统负载高于某个阈值,就禁止或者减少流量的进入;当 load 开始好转,则恢复流量的进入。这个思路给我们带来了不可避免的两个问题:
根据 TCP BBR 的思想,根据系统能够处理的请求,和允许进来的请求,来做平衡,而不是根据一个间接的指标(系统 load)来做限流。最终实现的是 在系统不被拖垮的情况下,提高系统的吞吐率,而不是 load 一定要到低于某个阈值。
如果我们还是按照固有的思维,超过特定的 load 就禁止流量进入,系统 load 恢复就放开流量,这样做的结果是无论我们怎么调参数,调比例,都是按照果来调节因,都无法取得良好的效果。
Sentinel 在系统自适应保护的做法是,用 load1 作为启动控制流量的值,而允许通过的流量由处理请求的能力,即请求的响应时间以及当前系统正在处理的请求速率来决定。
系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 和线程数四个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN
),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统规则支持以下的阈值类型:
maxQps * minRt
计算得出。设定参考值一般是 CPU cores * 2.5
。我们把系统处理请求的过程想象为一个水管,到来的请求是往这个水管灌水,当系统处理顺畅的时候,请求不需要排队,直接从水管中穿过,这个请求的RT是最短的;反之,当请求堆积的时候,那么处理请求的时间则会变为:排队时间 + 最短处理时间。
我们用 T 来表示(水管内部的水量),用RT来表示请求的处理时间,用P来表示进来的请求数,那么一个请求从进入水管道到从水管出来,这个水管会存在 P * RT
个请求。换一句话来说,当 T ≈ QPS * Avg(RT)
的时候,我们可以认为系统的处理能力和允许进入的请求个数达到了平衡,系统的负载不会进一步恶化。
接下来的问题是,水管的水位是可以达到了一个平衡点,但是这个平衡点只能保证水管的水位不再继续增高,但是还面临一个问题,就是在达到平衡点之前,这个水管里已经堆积了多少水。如果之前水管的水已经在一个量级了,那么这个时候系统允许通过的水量可能只能缓慢通过,RT会大,之前堆积在水管里的水会滞留;反之,如果之前的水管水位偏低,那么又会浪费了系统的处理能力。
然而,和 TCP BBR 的不一样的地方在于,还需要用一个系统负载的值(load1)来激发这套机制启动。
注:这种系统自适应算法对于低 load 的请求,它的效果是一个“兜底”的角色。对于不是应用本身造成的 load 高的情况(如其它进程导致的不稳定的情况),效果不明显。
public class SystemGuardDemo {
private static AtomicInteger pass = new AtomicInteger();
private static AtomicInteger block = new AtomicInteger();
private static AtomicInteger total = new AtomicInteger();
private static volatile boolean stop = false;
private static final int threadCount = 100;
private static int seconds = 60 + 40;
public static void main(String[] args) throws Exception {
tick();
initSystemRule();
for (int i = 0; i < threadCount; i++) {
Thread entryThread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
Entry entry = null;
try {
entry = SphU.entry("methodA", EntryType.IN);
pass.incrementAndGet();
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
// ignore
}
} catch (BlockException e1) {
block.incrementAndGet();
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
// ignore
}
} catch (Exception e2) {
// biz exception
} finally {
total.incrementAndGet();
if (entry != null) {
entry.exit();
}
}
}
}
});
entryThread.setName("working-thread");
entryThread.start();
}
}
private static void initSystemRule() {
List<SystemRule> rules = new ArrayList<SystemRule>();
SystemRule rule = new SystemRule();
// max load is 3
rule.setHighestSystemLoad(3.0);
// max cpu usage is 60%
rule.setHighestCpuUsage(0.6);
// max avg rt of all request is 10 ms
rule.setAvgRt(10);
// max total qps is 20
rule.setQps(20);
// max parallel working thread is 10
rule.setMaxThread(10);
rules.add(rule);
SystemRuleManager.loadRules(Collections.singletonList(rule));
}
private static void tick() {
Thread timer = new Thread(new TimerTask());
timer.setName("sentinel-timer-task");
timer.start();
}
static class TimerTask implements Runnable {
@Override
public void run() {
System.out.println("begin to statistic!!!");
long oldTotal = 0;
long oldPass = 0;
long oldBlock = 0;
while (!stop) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
long globalTotal = total.get();
long oneSecondTotal = globalTotal - oldTotal;
oldTotal = globalTotal;
long globalPass = pass.get();
long oneSecondPass = globalPass - oldPass;
oldPass = globalPass;
long globalBlock = block.get();
long oneSecondBlock = globalBlock - oldBlock;
oldBlock = globalBlock;
System.out.println(seconds + ", " + TimeUtil.currentTimeMillis() + ", total:"
+ oneSecondTotal + ", pass:"
+ oneSecondPass + ", block:" + oneSecondBlock);
if (seconds-- <= 0) {
stop = true;
}
}
System.exit(0);
}
}
}
**集群流控最基础的方式:**假设我们希望给某个用户限制调用某个 API 的总 QPS 为 50,但机器数可能很多(比如有 100 台)。这时候我们很自然地就想到,找一个 server 来专门来统计总的调用量,其它的实例都与这台 server 通信来判断是否可以调用。
另外集群流控还可以解决流量不均匀导致总体限流效果不佳的问题。假设集群中有 10 台机器,我们给每台机器设置单机限流阈值为 10 QPS,理想情况下整个集群的限流阈值就为 100 QPS。不过实际情况下流量到每台机器可能会不均匀,会导致总量没有到的情况下某些机器就开始限流。因此仅靠单机维度去限制的话会无法精确地限制总体流量。而集群流控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果。
集群流控两种身份:
集群流控模块需求的版本为 JDK 1.7+
sentinel-cluster-common-default
: 公共模块,包含公共接口和实体sentinel-cluster-client-default
: 默认集群流控 client 模块,使用 Netty 进行通信,提供接口方便序列化协议扩展sentinel-cluster-server-default
: 默认集群流控 server 模块,使用 Netty 进行通信,提供接口方便序列化协议扩展;同时提供扩展接口对接规则判断的具体实现(TokenService
),默认实现是复用 sentinel-core
的相关逻辑FlowRule
添加了两个字段用于集群限流相关配置:
private boolean clusterMode; // 标识是否为集群限流配置
private ClusterFlowConfig clusterConfig; // 集群限流相关配置项
其中 用一个专门的 ClusterFlowConfig
代表集群限流相关配置项,以与现有规则配置项分开:
// 全局唯一的规则 ID,由集群限流管控端分配.
private Long flowId;
// 阈值模式,默认(0)为单机均摊,1 为全局阈值.
private int thresholdType = ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL;
private int strategy = ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL;
// 在 client 连接失败或通信失败时,是否退化到本地的限流模式
private boolean fallbackToLocalWhenFail = true;
flowId
代表全局唯一的规则 ID,Sentinel 集群限流服务端通过此 ID 来区分各个规则,因此务必保持全局唯一。一般 flowId 由统一的管控端进行分配,或写入至 DB 时生成。thresholdType
代表集群限流阈值模式。其中单机均摊模式下配置的阈值等同于单机能够承受的限额,token server 会根据客户端对应的 namespace(默认为 project.name
定义的应用名)下的连接数来计算总的阈值(比如独立模式下有 3 个 client 连接到了 token server,然后配的单机均摊阈值为 10,则计算出的集群总量就为 30);而全局模式下配置的阈值等同于整个集群的总阈值。建议使用动态规则源来动态地管理规则。
TokenClient
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId, parser);
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
TokenServer
由于集群限流服务端有作用域(namespace)的概念,因此我们需要注册一个自动根据 namespace 生成动态规则源的 PropertySupplier
// Supplier 类型:接受 namespace,返回生成的动态规则源,类型为 SentinelProperty>
// ClusterFlowRuleManager 针对集群限流规则,ClusterParamFlowRuleManager 针对集群热点规则,配置方式类似
ClusterFlowRuleManager.setPropertySupplier(namespace -> {
return new SomeDataSource(namespace).getProperty();
});
每当集群限流服务端 namespace set 产生变更时,Sentinel 会自动针对新加入的 namespace 生成动态规则源并进行自动监听,并删除旧的不需要的规则源。
引入 jar 包
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-cluster-client-defaultartifactId>
<version>1.7.1version>
dependency>
可以通过 API 将当前模式置为客户端模式:http://
mode
为 0 代表 client
,1 代表 server
。设置成功后,若已有客户端的配置,集群限流客户端将会开启并连接远程的 token server。我们可以在 sentinel-record.log
日志中查看连接的相关日志。
若集群限流客户端未进行配置,则用户需要对客户端进行基本的配置,比如指定集群限流 token server
。http://
其中 data 是 JSON 格式的 ClusterClientConfig
,对应的配置项:
serverHost
: token server hostserverPort
: token server 端口requestTimeout
: 请求的超时时间(默认为 20 ms)也可以通过 ClusterClientConfigManager
的 register2Property
方法注册动态配置源。配置源注册的相关逻辑可以置于 InitFunc
实现类中,并通过 SPI 注册,在 Sentinel 初始化时即可自动进行配置源加载监听。
若用户未引入集群限流 client 相关依赖,或者 client 未开启/连接失败/通信失败,则对于开启了集群模式的规则:
当 token client 与 server 之间的连接意外断开时,token client 会不断进行重试,每次重试的间隔时间以 n * 2000 ms
的形式递增。
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-cluster-server-defaultartifactId>
<version>1.7.1version>
dependency>
Sentinel 集群限流服务端有两种启动方式
转换集群流控身份: http://
其中 mode 为 0
代表 client,1
代表 server,-1
代表关闭。注意应用端需要引入集群限流客户端或服务端的相应依赖。
在独立模式下,我们可以直接创建对应的 ClusterTokenServer
实例并在 main 函数中通过 start
方法启动 Token Server。
推荐给集群限流服务端注册动态配置源来动态地进行配置.
配置类型:
project.name
配置的应用名),token server 会根据上报的命名空间名称统计连接数。Tips:
ClusterServerConfigManager
的各个 registerXxxProperty
方法来注册相关的配置源。maxAllowedQps
),来对 token server 的资源使用进行限制,防止在嵌入模式下影响应用本身。public class ClusterServerDemo {
public static void main(String[] args) throws Exception {
// Not embedded mode by default (alone mode).
ClusterTokenServer tokenServer = new SentinelDefaultTokenServer();
// A sample for manually load config for cluster server.
// It's recommended to use dynamic data source to cluster manage config and rules.
// See the sample in DemoClusterServerInitFunc for detail.
ClusterServerConfigManager.loadGlobalTransportConfig(new ServerTransportConfig()
.setIdleSeconds(600)
.setPort(11111));
ClusterServerConfigManager.loadServerNamespaceSet(Collections.singleton(DemoConstants.APP_NAME));
// Start the server.
tokenServer.start();
}
}
注意:若在本地启动多个 Demo 示例,需要加上
-Dcsp.sentinel.log.use.pid=true
参数,否则控制台显示监控会不准确。
sentinel-demo-cluster-embedded
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-coreartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-transport-simple-httpartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-parameter-flow-controlartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-cluster-client-defaultartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-cluster-server-defaultartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-annotation-aspectjartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
<version>${spring.boot.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>${spring.boot.version}version>
dependency>
dependencies>
开源版本控制台 。使用集群限流功能需要对 Sentinel 控制台进行相关的改造,推送规则时直接推送至配置中心,接入端引入 push 模式的动态数据源。
以下通用接口位于 sentinel-core
中:
TokenService
: 集群限流功能接口,server / client 均可复用ClusterTokenClient
: 集群限流功能客户端ClusterTokenServer
: 集群限流服务端接口EmbeddedClusterTokenServer
: 集群限流服务端接口(embedded 模式)以下通用接口位于 sentinel-cluster-common-default
:
EntityWriter
EntityDecoder
集群流控 Client 端通信相关扩展接口:
ClusterTransportClient
:集群限流通信客户端RequestEntityWriter
ResponseEntityDecoder
集群流控 Server 端通信相关扩展接口:
ResponseEntityWriter
RequestEntityDecoder
集群流控 Server 端请求处理扩展接口:
RequestProcessor
: 请求处理接口 (request -> response)Sentinel 1.6.0 引入了 Sentinel API Gateway Adapter Common 模块,此模块中包含网关限流的规则和自定义 API 的实体和管理逻辑:
GatewayFlowRule
:网关限流规则,针对 API Gateway 的场景定制的限流规则,可以针对不同 route 或自定义的 API 分组进行限流,支持针对请求中的参数、Header、来源 IP 等进行定制化的限流。ApiDefinition
:用户自定义的 API 定义分组,可以看做是一些 URL 匹配的组合。比如我们可以定义一个 API 叫 my_api
,请求 path 模式为 /foo/**
和 /baz/**
的都归到 my_api
这个 API 分组下面。限流的时候可以针对这个自定义的 API 分组维度进行限流。其中网关限流规则 GatewayFlowRule
的字段解释如下:
resource
:资源名称,可以是网关中的 route 名称或者用户自定义的 API 分组名称。
resourceMode
:规则是针对 API Gateway 的 route(RESOURCE_MODE_ROUTE_ID
)还是用户在 Sentinel 中定义的 API 分组(RESOURCE_MODE_CUSTOM_API_NAME
),默认是 route。
grade
:限流指标维度,同限流规则的 grade
字段。
count
:限流阈值
intervalSec
:统计时间窗口,单位是秒,默认是 1 秒。
controlBehavior
:流量整形的控制效果,同限流规则的 controlBehavior
字段,目前支持快速失败和匀速排队两种模式,默认是快速失败。
burst
:应对突发请求时额外允许的请求数目。
maxQueueingTimeoutMs
:匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。
paramItem
:参数限流配置。若不提供,则代表不针对参数进行限流,该网关规则将会被转换成普通流控规则;否则会转换成热点规则。其中的字段:
parseStrategy
:从请求中提取参数的策略,目前支持提取来源 IP(PARAM_PARSE_STRATEGY_CLIENT_IP
)、Host(PARAM_PARSE_STRATEGY_HOST
)、任意 Header(PARAM_PARSE_STRATEGY_HEADER
)和任意 URL 参数(PARAM_PARSE_STRATEGY_URL_PARAM
)四种模式。fieldName
:若提取策略选择 Header 模式或 URL 参数模式,则需要指定对应的 header 名称或 URL 参数名称。pattern
:参数值的匹配模式,只有匹配该模式的请求属性值会纳入统计和流控;若为空则统计该请求属性的所有值。(1.6.2 版本开始支持)matchStrategy
:参数值的匹配策略,目前支持精确匹配(PARAM_MATCH_STRATEGY_EXACT
)、子串匹配(PARAM_MATCH_STRATEGY_CONTAINS
)和正则匹配(PARAM_MATCH_STRATEGY_REGEX
)。(1.6.2 版本开始支持)用户可以通过 GatewayRuleManager.loadRules(rules)
手动加载网关规则,或通过 GatewayRuleManager.register2Property(property)
注册动态规则源动态推送(推荐方式)。
Sentinel 提供了 Spring Cloud Gateway 的适配模块,提供两种资源维度的限流:
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-spring-cloud-gateway-adapterartifactId>
<version>x.y.zversion>
dependency>
使用时只需注入对应的
SentinelGatewayFilter
实例以及SentinelGatewayBlockExceptionHandler
实例即可。示例:
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
}
server:
port: 8090
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
enabled: true
discovery:
locator:
lower-case-service-id: true
routes:
# Add your routes here.
- id: product_route
uri: lb://product
predicates:
- Path=/product/**
- id: httpbin_route
uri: https://httpbin.org
predicates:
- Path=/httpbin/**
filters:
- RewritePath=/httpbin/(?>.*), /$\{segment}
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("some_customized_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/product/baz"));
add(new ApiPathPredicateItem().setPattern("/product/foo/**")
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("another_customized_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/ahas"));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
如上:那么这里面的 route ID(如 product_route
)和 API name(如 some_customized_api
)都会被标识为 Sentinel 的资源。比如访问网关的 URL 为 http://localhost:8090/product/foo/22
的时候,对应的统计会加到 product_route
和 some_customized_api
这两个资源上面,而 http://localhost:8090/httpbin/json
只会对应到 httpbin_route
资源上面。:
您可以在 GatewayCallbackManager
注册回调进行定制:
setBlockHandler
:注册函数用于实现自定义的逻辑处理被限流的请求,对应接口为 BlockRequestHandler
。默认实现为 DefaultBlockRequestHandler
,当被限流时会返回类似于下面的错误信息:Blocked by Sentinel: FlowException
。GatewayRuleManager
加载网关流控规则(GatewayFlowRule
)时,无论是否针对请求属性进行限流,Sentinel 底层都会将网关流控规则转化为热点参数规则(ParamFlowRule
),存储在 GatewayRuleManager
中,与正常的热点参数规则相隔离。转换时 Sentinel 会根据请求属性配置,为网关流控规则设置参数索引(idx
),并同步到生成的热点参数规则中。SphU.entry(res, args)
中。Sentinel API Gateway Adapter Common 模块向 Slot Chain 中添加了一个 GatewayFlowSlot
,专门用来做网关规则的检查。GatewayFlowSlot
会从 GatewayRuleManager
中提取生成的热点参数规则,根据传入的参数依次进行规则检查。若某条规则不针对请求属性,则会在参数最后一个位置置入预设的常量,达到普通流控的效果。用户可以直接在 Sentinel 控制台上查看 API Gateway 实时的 route 和自定义 API 分组监控,管理网关规则和 API 分组配置。
在 API Gateway 端,用户只需要在原有启动参数的基础上添加如下启动参数即可标记应用为 API Gateway 类型:
# 注:通过 Spring Cloud Alibaba Sentinel 自动接入的 API Gateway 整合则无需此参数
-Dcsp.sentinel.app.type=1
Sentinel 提供了
@SentinelResource
注解用于定义资源,并提供了 AspectJ 的扩展用于自动定义资源、处理BlockException
等。使用 Sentinel Annotation AspectJ Extension 的时候需要引入以下依赖>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-annotation-aspectjartifactId>
<version>x.y.zversion>
dependency>
注意:注解方式埋点不支持 private 方法。
@SentinelResource
用于定义资源,并提供可选的异常处理和 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
类型的参数用于接收对应的异常。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
直接抛出。
示例:
public class TestService {
// 对应的 `handleException` 函数需要位于 `ExceptionUtil` 类中,并且必须为 static 函数.
@SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
public void test() {
System.out.println("Test");
}
// 原函数
@SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
public String hello(long s) {
return String.format("Hello at %d", s);
}
// Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
public String helloFallback(long s) {
return String.format("Halooooo %d", s);
}
// Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
public String exceptionHandler(long s, BlockException ex) {
// Do some log here.
ex.printStackTrace();
return "Oops, error occurred at " + s;
}
}
aop.xml
文件中引入对应的 Aspect
<aspects>
<aspect name="com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect"/>
aspects>
将 SentinelResourceAspect
注册为一个 Spring Bean 进行初始化:
示例: sentinel-demo-annotation-spring-aop
@Configuration
public class SentinelAspectConfiguration {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
Sentinel 的理念是开发者只需要关注资源的定义,当资源定义成功后可以动态增加各种流控降级规则。Sentinel 提供两种方式修改规则:
loadRules
)DataSource
适配不同数据源修改例如:
FlowRuleManager.loadRules(List<FlowRule> rules); // 修改流控规则
DegradeRuleManager.loadRules(List<DegradeRule> rules); // 修改降级规则
开发的时候规则一般存储在文件、数据库或者配置中心当中。
DataSource
接口给我们提供了对接任意配置源的能力。相比直接通过 API 修改规则,实现DataSource
接口是更加可靠的做法。
推荐通过控制台设置规则后将规则推送到统一的规则中心,客户端实现 ReadableDataSource
接口端监听规则中心实时获取变更,流程如下:
DataSource
扩展常见的实现方式有:
Sentinel 目前支持以下数据源扩展:
实现拉模式的数据源最简单的方式是继承
AutoRefreshDataSource
抽象类,然后实现readSource()
方法,在该方法里从指定数据源读取字符串格式的配置数据。比如 基于文件的数据源。
实现推模式的数据源最简单的方式是继承
AbstractDataSource
抽象类,在其构造方法中添加监听器,并实现readSource()
从指定数据源读取字符串格式的配置数据。比如 基于 Nacos 的数据源。
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId, parser);
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
InitFunc
SPI 扩展接口。只需要实现自己的 InitFunc
接口,在 init
方法中编写注册数据源的逻辑。package com.test.init;
public class DataSourceInitFunc implements InitFunc {
@Override
public void init() throws Exception {
final String remoteAddress = "localhost";
final String groupId = "Sentinel:Demo";
final String dataId = "com.alibaba.csp.sentinel.demo.flow.rule";
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId,
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
}
}
resource
目录)下的 META-INF/services
目录下的 com.alibaba.csp.sentinel.init.InitFunc
文件中。com.test.init.DataSourceInitFunc
Sentinel Dashboard 通过 Sentinel 客户端自带的规则 API 来实时查询和更改内存中的规则。
这个示例展示 Sentinel 是如何从文件获取规则信息的。
FileRefreshableDataSource
会周期性的读取文件以获取规则,当文件有更新时会及时发现,并将规则更新到内存中。使用时只需添加以下依赖:
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-extensionartifactId>
<version>x.y.zversion>
dependency>
Nacos 是阿里中间件团队开源的服务发现和动态配置中心。Sentinel 针对 Nacos 作了适配,底层可以采用 Nacos 作为规则配置数据源。使用时只需添加以下依赖:
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
<version>x.y.zversion>
dependency>
然后创建 NacosDataSource
并将其注册至对应的 RuleManager 上即可。比如:
// remoteAddress 代表 Nacos 服务端的地址
// groupId 和 dataId 对应 Nacos 中相应配置
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId,
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
详细示例可以参见 sentinel-demo-nacos-datasource。
Sentinel 针对 ZooKeeper 作了相应适配,底层可以采用 ZooKeeper 作为规则配置数据源。使用时只需添加以下依赖:
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-zookeeperartifactId>
<version>x.y.zversion>
dependency>
然后创建 ZookeeperDataSource
并将其注册至对应的 RuleManager 上即可。比如:
// remoteAddress 代表 ZooKeeper 服务端的地址
// path 对应 ZK 中的数据路径
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new ZookeeperDataSource<>(remoteAddress, path, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
详细示例可以参见 sentinel-demo-zookeeper-datasource。
Sentinel 针对 Apollo 作了相应适配,底层可以采用 Apollo 作为规则配置数据源。使用时只需添加以下依赖:
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-apolloartifactId>
<version>x.y.zversion>
dependency>
然后创建 ApolloDataSource
并将其注册至对应的 RuleManager 上即可。比如:
// namespaceName 对应 Apollo 的命名空间名称
// ruleKey 对应规则存储的 key
// defaultRules 对应连接不上 Apollo 时的默认规则
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new ApolloDataSource<>(namespaceName, ruleKey, defaultRules, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
详细示例可以参见 sentinel-demo-apollo-datasource。
Sentinel 针对 Redis 作了相应适配,底层可以采用 Redis 作为规则配置数据源。使用时只需添加以下依赖:
com.alibaba.csp
sentinel-datasource-redis
x.y.z
Redis 动态配置源采用 Redis PUB-SUB 机制实现,详细文档参考:https://github.com/alibaba/Sentinel/tree/master/sentinel-extension/sentinel-datasource-redis
无论触发了限流、熔断降级还是系统保护,它们的秒级拦截详情日志都在
${user_home}/logs/csp/sentinel-block.log
里。如果没有发生拦截,则该日志不会出现。
日志格式如下:
2014-06-20 16:35:10|1|sayHello(java.lang.String,long),FlowException,default,origin|61,0
2014-06-20 16:35:11|1|sayHello(java.lang.String,long),FlowException,default,origin|1,0
日志含义:
index | 例子 | 说明 |
---|---|---|
1 | 2014-06-20 16:35:10 |
时间戳 |
2 | 1 |
该秒发生的第一个资源 |
3 | sayHello(java.lang.String,long) |
资源名称 |
4 | XXXException |
拦截的原因, 通常 FlowException 代表是被限流规则拦截,DegradeException 则表示被降级,SystemBlockException 则表示被系统保护拦截 |
5 | default |
生效规则的调用来源(参数限流中代表生效的参数) |
6 | origin |
被拦截资源的调用者,可以为空 |
7 | 61,0 |
61 被拦截的数量,0无意义可忽略 |
所有的资源都会产生秒级日志,它在 ${user_home}/logs/csp/${app_name}-${pid}-metrics.log
里。格式如下:
1532415661000|2018-07-24 15:01:01|sayHello(java.lang.String)|12|3|4|2|295
1532415661000
:时间戳2018-07-24 15:01:01
:格式化之后的时间戳sayHello(java.lang.String)
:资源名12
:表示到来的数量,即此刻通过 Sentinel 规则 check 的数量(passed QPS)3
:实际该资源被拦截的数量(blocked QPS)4
:每秒结束的资源个数(完成调用),包括正常结束和异常结束的情况(exit QPS)2
:异常的数量295
:资源的平均响应时间(RT)其它的日志在 ${user_home}/logs/csp/sentinel-record.log.xxx
里。该日志包含规则的推送、接收、处理等记录,排查问题的时候会非常方便。
${log_dir}/sentinel-cluster-client.log
:Token Client 日志,会记录请求失败的信息Sentinel 提供对所有资源的实时监控。如果需要实时监控,客户端需引入以下依赖(以 Maven 为例):
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-transport-simple-httpartifactId>
<version>x.y.zversion>
dependency>
引入上述依赖后,客户端便会主动连接 Sentinel 控制台。通过 Sentinel 控制台 即可查看客户端的实时监控。
相关 API: GET /clusterNode
当应用启动之后,可以运行下列命令,获得当前所有簇点(ClusterNode
)的列表(JSON 格式):
curl http://localhost:8719/clusterNode
结果示例:
[
{"avgRt":0.0, //平均响应时间
"blockRequest":0, //每分钟拦截的请求个数
"blockedQps":0.0, //每秒拦截个数
"curThreadNum":0, //并发个数
"passQps":1.0, // 每秒成功通过请求
"passReqQps":1.0, //每秒到来的请求
"resourceName":"/registry/machine", 资源名称
"timeStamp":1529905824134, //时间戳
"totalQps":1.0, // 每分钟请求数
"totalRequest":193},
....
]
模糊查询该簇点的具体信息,其中 id
对应 resource name,支持模糊查询:
curl http://localhost:8719/cnode?id=xxxx
结果示例:
idx id thread pass blocked success total aRt 1m-pass 1m-block 1m-all exeption
6 /app/aliswitch2/machines.json 0 0 0 0 0 0 0 0 0 0
7 /app/sentinel-admin/machines.json 0 1 0 1 1 6 0 0 0 0
8 /identity/machine.json 0 0 0 0 0 0 0 0 0 0
9 /registry/machine 0 2 0 2 2 1 192 0 192 0
10 /app/views/machine.html 0 1 0 1 1 2 0 0 0 0
查询该簇点的调用者统计信息:
curl http://localhost:8719/origin?id=xxxx
结果示例:
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
其中的 origin 由 ContextUtil.enter(resourceName,origin)
方法中的 origin
指定。
通过命令 curl http://localhost:8719/tree
来查询链路入口的链路树形结构:
EntranceNode: machine-root(t:0 pq:1 bq:0 tq:1 rt:0 prq:1 1mp:0 1mb:0 1mt:0)
-EntranceNode1: Entrance1(t:0 pq:1 bq:0 tq:1 rt:0 prq:1 1mp:0 1mb:0 1mt:0)
--nodeA(t:0 pq:1 bq:0 tq:1 rt:0 prq:1 1mp:0 1mb:0 1mt:0)
-EntranceNode2: Entrance1(t:0 pq:1 bq:0 tq:1 rt:0 prq:1 1mp:0 1mb:0 1mt:0)
--nodeA(t:0 pq:1 bq:0 tq:1 rt:0 prq:1 1mp:0 1mb:0 1mt:0)
t:threadNum pq:passQps bq:blockedQps tq:totalQps rt:averageRt prq: passRequestQps 1mp:1m-passed 1mb:1m-blocked 1mt:1m-total
所有资源的秒级日志在 ${home}/logs/csp/${appName}-${pid}-metrics.log.${date}.xx
。例如,该日志的名字可能为 app-3518-metrics.log.2018-06-22.1
1529573107000|2018-06-21 17:25:07|sayHello(java.lang.String,long)|10|3601|10|0|2
index | 例子 | 说明 |
---|---|---|
1 | 1529573107000 |
时间戳 |
2 | 2018-06-21 17:25:07 |
日期 |
3 | sayHello(java.lang.String,long) |
资源名称 |
4 | 10 |
每秒通过的资源请求个数 |
5 | 3601 |
每秒资源被拦截的个数 |
6 | 10 |
每秒结束的资源个数,包括正常结束和异常结束的情况 |
7 | 0 |
每秒资源的异常个数 |
8 | 2 |
资源平均响应时间 |
每秒的拦截日志也会出现在 <用户目录>/logs/csp/sentinel-block.log
文件下。如果没有发生拦截,则该日志不会出现。
2014-06-20 16:35:10|1|sayHello(java.lang.String,long),FlowException,default,origin|61,0
2014-06-20 16:35:11|1|sayHello(java.lang.String,long),FlowException,default,origin|1,0
index | 例子 | 说明 |
---|---|---|
1 | 2014-06-20 16:35:10 |
时间戳 |
2 | 1 |
该秒发生的第一个资源 |
3 | sayHello(java.lang.String,long) |
资源名称 |
4 | XXXException |
拦截的原因, 通常 FlowException 代表是被限流规则拦截, DegradeException 则表示被降级,SystemException 则表示被系统保护拦截 |
5 | default |
生效规则的调用应用 |
6 | origin |
被拦截资源的调用者。可以为空 |
7 | 61,0 |
61 被拦截的数量,0则代表可以忽略 |
相关 API: GET /metric
curl http://localhost:8719/metric?identity=XXX&startTime=XXXX&endTime=XXXX&maxLines=XXXX
需指定以下 URL 参数:
identity
:资源名称startTime
:开始时间(时间戳)endTime
:结束时间maxLines
:监控数据最大行数返回和 资源的秒级日志 格式一样的内容。例如:
1529998904000|2018-06-26 15:41:44|abc|100|0|0|0|0
1529998905000|2018-06-26 15:41:45|abc|4|5579|104|0|728
1529998906000|2018-06-26 15:41:46|abc|0|15698|0|0|0
1529998907000|2018-06-26 15:41:47|abc|0|19262|0|0|0
1529998908000|2018-06-26 15:41:48|abc|0|19502|0|0|0
1529998909000|2018-06-26 15:41:49|abc|0|18386|0|0|0
1529998910000|2018-06-26 15:41:50|abc|0|19189|0|0|0
1529998911000|2018-06-26 15:41:51|abc|0|16543|0|0|0
1529998912000|2018-06-26 15:41:52|abc|0|18471|0|0|0
1529998913000|2018-06-26 15:41:53|abc|0|19405|0|0|0
若您的应用为 Spring Boot 或 Spring Cloud 应用,您可以使用 Spring Cloud Alibaba,通过 Spring 配置文件来指定配置,详情请参考 Spring Cloud Alibaba Sentinel 文档。
Sentinel 提供如下的配置方式:
其中,project.name
参数只能通过 JVM -D 参数方式配置(since 1.8.0 取消该限制),其它参数支持所有的配置方式。
优先级顺序:JVM -D 参数的优先级最高。若 properties 和 JVM 参数中有相同项的配置,以 JVM 参数配置的为准。
用户可以通过 -Dcsp.sentinel.config.file
参数配置 properties 文件的路径,支持 classpath 路径配置(如 classpath:sentinel.properties
)。默认 Sentinel 会尝试从 classpath:sentinel.properties
文件读取配置,读取编码默认为 UTF-8。
名称 | 含义 | 类型 | 默认值 | 是否必需 | 备注 |
---|---|---|---|---|---|
project.name |
指定应用的名称 | String |
null |
否 | |
csp.sentinel.app.type |
指定应用的类型 | int |
0 (APP_TYPE_COMMON ) |
否 | 1.6.0 引入 |
csp.sentinel.metric.file.single.size |
单个监控日志文件的大小 | long |
52428800 (50MB) | 否 | |
csp.sentinel.metric.file.total.count |
监控日志文件的总数上限 | int |
6 | 否 | |
csp.sentinel.statistic.max.rt |
最大的有效响应时长(ms),超出此值则按照此值记录 | int |
4900 | 否 | 1.4.1 引入 |
csp.sentinel.spi.classloader |
SPI 加载时使用的 ClassLoader,默认为给定类的 ClassLoader | String |
default |
否 | 若配置 context 则使用 thread context ClassLoader。1.7.0 引入 |
roject.name
项用于指定应用名(appName)。若未指定,则默认解析 main 函数的类名作为应用名。实际项目使用中建议手动指定应用名。
名称 | 含义 | 类型 | 默认值 | 是否必需 | 备注 |
---|---|---|---|---|---|
csp.sentinel.log.dir |
Sentinel 日志文件目录 | String |
${user.home}/logs/csp/ |
否 | 1.3.0 引入 |
csp.sentinel.log.use.pid |
日志文件名中是否加入进程号,用于单机部署多个应用的情况 | boolean |
false |
否 | 1.3.0 引入 |
csp.sentinel.log.output.type |
Record 日志输出的类型,file 代表输出至文件,console 代表输出至终端 |
String |
file |
否 | 1.6.2 引入 |
若需要在单台机器上运行相同服务的多个实例,则需要加入
-Dcsp.sentinel.log.use.pid=true
来保证不同实例日志的独立性。
名称 | 含义 | 类型 | 默认值 | 是否必需 |
---|---|---|---|---|
csp.sentinel.dashboard.server |
控制台的地址,指定控制台后客户端会自动向该地址发送心跳包。地址格式为:hostIp:port |
String |
null |
是 |
csp.sentinel.heartbeat.interval.ms |
心跳包发送周期,单位毫秒 | long |
null |
非必需,若不进行配置,则会从相应的 HeartbeatSender 中提取默认值 |
csp.sentinel.api.port |
本地启动 HTTP API Server 的端口号 | int |
8719 | 否 |
csp.sentinel.heartbeat.client.ip |
指定心跳包中本机的 IP | String |
- | 若不指定则通过 HostNameUtil 解析;该配置项多用于多网卡环境 |
csp.sentinel.api.port
可不提供,默认为 8719,若端口冲突会自动向下探测可用的端口。