实时监控
监控接口通过的QPS和拒绝的QPS
簇点链路
用来显示微服务的所监控的API
流控规则
流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。同一个资源可以创建多条限流规则。FlowSlot 会对该资源的所有限流规则依次遍历,直到有规则触发限流或者所有规则遍历完毕。一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果。
Field |
说明 |
默认值 |
resource |
资源名,资源名是限流规则的作用对象 |
|
count |
限流阈值 |
|
grade |
限流阈值类型,QPS 模式(1)或并发线程数模式(0) |
QPS 模式 |
limitApp |
流控针对的调用来源 |
default,代表不区分调用来源 |
strategy |
调用关系限流策略:直接、链路、关联 |
根据资源本身(直接) |
controlBehavior |
流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 |
直接拒绝 |
clusterMode |
是否集群限流 |
否 |
限流阈值类型 --> QPS 和 并发线程数
流量控制主要有两种统计类型,一种是统计并发线程数,另外一种则是统计 QPS。类型由 FlowRule 的 grade 字段来定义。其中,==0 代表根据并发数量来限流,1 代表根据 QPS 来进行流量控制==。
并发线程数
并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。
流控模式 --> (直接 关联 链路
基于调用关系的流量控制。调用关系包括调用方、被调用方;一个方法可能会调用其它方法,形成一个调用链路的层次关系。
直接
资源调用达到设置的阈值后直接被流控抛出异常
关联
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写的速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 strategy 为 RuleConstant.STRATEGY_RELATE 同时设置 refResource 为 write_db。这样当写库操作过于频繁时,读数据的请求会被限流。
链路 (注意,高版本此功能直接使用不生效,如何解决?)
根据调用链路入口限流。
从1.6.3版本开始,Sentinel Web filter默认收敛所有URL的入口context,导致链路限流不生效。
从1.7.0版本开始,官方在CommonFilter引入了WEBCONTEXTUNIFY参数,用于控制是否收敛context,将其配置为false即可根据不同的URL进行链路限流。
1.8.0 需要引入sentinel-web-servlet依赖
<!--- 解决流控链路不生效的问题--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-web-servlet</artifactId> </dependency>
添加配置类,配置CommonFilter过滤器,指定WEB_CONTEXT_UNIFY=false,禁止收敛URL的入口context
@Configuration public class SentinelConfig { @Bean public FilterRegistrationBean sentinelFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new CommonFilter()); registration.addUrlPatterns("/*"); // 入口资源关闭聚合 解决流控链路不生效的问题 registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false"); registration.setName("sentinelFilter"); registration.setOrder(1); return registration; } }
@Bean public FilterRegistrationBean sentinelFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new CommonFilter()); registration.addUrlPatterns("/*"); // 入口资源关闭聚合 解决流控链路不生效的问题 registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false"); registration.setName("sentinelFilter"); registration.setOrder(1); //CommonFilter的BlockException自定义处理逻辑 WebCallbackManager.setUrlBlockHandler(new MyUrlBlockHandler()); return registration; } @Slf4j public class MyUrlBlockHandler implements UrlBlockHandler { @Override public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException e) throws IOException { log.info("UrlBlockHandler BlockException================"+e.getMessage()); R r = null; if (e instanceof FlowException) { r = R.error(100,"接口限流了"); } else if (e instanceof DegradeException) { r = R.error(101,"服务降级了"); } else if (e instanceof ParamFlowException) { r = R.error(102,"热点参数限流了"); } else if (e instanceof SystemBlockException) { r = R.error(103,"触发系统保护规则了"); } else if (e instanceof AuthorityException) { r = R.error(104,"授权规则不通过"); } //返回json数据 response.setStatus(500); response.setCharacterEncoding("utf-8"); response.setContentType(MediaType.APPLICATION_JSON_VALUE); new ObjectMapper().writeValue(response.getWriter(), r); } }
流控效果 --> 快速失败,预热(Warm Up),匀速排队
快速失败
(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
预热(Warm Up)
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
冷加载因子: codeFactor 默认是3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
匀速排队
匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
注意:匀速排队模式暂时不支持 QPS > 1000 的场景。
降级规则
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
熔断降级规则(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 引入) |
熔断策略
慢调用策略
慢调用比例 (`SLOW_REQUEST_RATIO`):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(`statIntervalMs`)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
异常比例
慢调用比例 (`SLOW_REQUEST_RATIO`):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(`statIntervalMs`)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
异常数
异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
注意 :异常降级仅针对业务异常,对sentinel限流降级本身的异常(BlockException)不生效
热点参数限流
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
- 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
注意:1.热点规则需要使用@SentinelResource("resourceName")注解,否则不生效
2.参数必须是7种基本数据类型才会生效
系统规则
Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
- CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
- 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
授权控制
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
规则配置
来源访问控制规则(AuthorityRule)非常简单,主要有以下配置项:
- resource:资源名,即限流规则的作用对象。
- limitApp:对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB。
- strategy:限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式。
没有引入CommonFilter的实现
实现com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser接口,在parseOrigin方法中区分来源, 并交给spring管理
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** * @author jiuge */ @Component public class MyRequestOriginParser implements RequestOriginParser { /** * 通过request获取来源标识,交给授权规则进行匹配 * @param request * @return */ @Override public String parseOrigin(HttpServletRequest request) { // 标识字段名称可以自定义 String origin = request.getParameter("serviceName"); // if (StringUtil.isBlank(origin)){ // throw new IllegalArgumentException("serviceName参数未指定"); // } return origin; } }
引入CommonFilter的实现
第一步:实现com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser接口,在接口方法中实现区分来源
import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser; import com.alibaba.csp.sentinel.util.StringUtil; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** * @author jiuge */ public class MyRequestOriginParser implements RequestOriginParser { /** * 通过request获取来源标识,交给授权规则进行匹配 * @param request * @return */ @Override public String parseOrigin(HttpServletRequest request) { // 标识字段名称可以自定义 String origin = request.getParameter("serviceName"); if (StringUtil.isBlank(origin)){ throw new IllegalArgumentException("serviceName参数未指定"); } return origin; } }
第二步:将RequestOriginParser 实现类交给WebCallbackManager 管理
@Bean public FilterRegistrationBean sentinelFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new CommonFilter()); registration.addUrlPatterns("/*"); // 入口资源关闭聚合 解决流控链路不生效的问题 registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false"); registration.setName("sentinelFilter"); registration.setOrder(1); //CommonFilter的BlockException自定义处理逻辑 WebCallbackManager.setUrlBlockHandler(new MyUrlBlockHandler()); //解决授权规则不生效的问题 //com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser WebCallbackManager.setRequestOriginParser(new MyRequestOriginParser()); return registration; }
sentinel 动态规则拓展
Sentinel 的理念是开发者只需要关注资源的定义,当资源定义成功后可以动态增加各种流控降级规则。Sentinel 提供两种方式修改规则:
- 通过 API 直接修改 (loadRules)
- 通过 DataSource 适配不同数据源修改
1、通过API直接修改(loadRules)
- FlowRuleManager.loadRules(List<FlowRule> rules); // 修改流控规则 DegradeRuleManager.loadRules(List<DegradeRule> rules); // 修改降级规则
手动修改规则(硬编码方式)一般仅用于测试和演示,生产上一般通过动态规则源的方式来动态管理规则。
2、通过 DataSource 适配不同数据源修改 (推模式,拉模式)
官方推荐通过控制台设置规则后将规则推送到统一的规则中心,客户端实现ReadableDataSource接口端监听规则中心实时获取变更
Sentinel 目前支持以下数据源扩展:
- Pull-based: 动态文件数据源、Consul, Eureka
- Push-based: ZooKeeper, Redis, Nacos, Apollo, etcd
拉模式
客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件,甚至是 VCS 等。这样做的方式是简单,缺点是无法及时获取变更;实现拉模式的数据源最简单的方式是继承 AutoRefreshDataSource 抽象类,然后实现 readSource() 方法,在该方法里从指定数据源读取字符串格式的配置数据。
基于文件的数据源实现 【官方demo: sentinel-demo-dynamic-file-rule】
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-extension</artifactId> <version>1.8.0</version> </dependency>
FileRefreshableDataSource 会周期性的读取文件以获取规则,当文件有更新时会及时发现,并将规则更新到内存中。
private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}); String flowRulePath = URLDecoder.decode(classLoader.getResource("FlowRule.json").getFile(), "UTF-8"); // Data source for FlowRule FileRefreshableDataSource<List<FlowRule>> flowRuleDataSource = new FileRefreshableDataSource<>( flowRulePath, flowRuleListParser); // 将数据源注册至流控规则管理器中 FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
FlowRule.json 数据
[ { "resource": "abc", "controlBehavior": 0, "count": 20.0, "grade": 1, "limitApp": "default", "strategy": 0 }, { "resource": "abc1", "controlBehavior": 0, "count": 20.0, "grade": 1, "limitApp": "default", "strategy": 0 } ]
推模式
规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。实现推模式的数据源最简单的方式是继承 AbstractDataSource 抽象类,在其构造方法中添加监听器,并实现 readSource() 从指定数据源读取字符串格式的配置数据。
基于nacos 配置实现 【官方demo: sentinel-demo-nacos-datasource】
Java API
pom 文件
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.8.0</version> </dependency>
核心代码
// nacos server ip private static final String remoteAddress = "localhost:8848"; // nacos group private static final String groupId = "Sentinel:Demo"; // nacos dataId private static 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());
yml配置方式
pom
<!--sentinel持久化 采用 Nacos 作为规则配置数据源--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
yml中配置
spring: application: name: mall-user-sentinel-demo cloud: nacos: discovery: server-addr: 127.0.0.1:8848 sentinel: transport: # 添加sentinel的控制台地址 dashboard: 127.0.0.1:8080 # 指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer port: 8719 datasource: ds1: nacos: server-addr: 127.0.0.1:8848 dataId: ${spring.application.name} groupId: DEFAULT_GROUP data-type: json rule-type: flow
nacos 配置中心添加
[ { "resource": "userinfo", "limitApp": "default", "grade": 1, "count": 1, "strategy": 0, "controlBehavior": 0, "clusterMode": false } ]
sentinel 规则持久化
拉模式改造
首先 Sentinel 控制台通过 API 将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的规则保存到本地的文件中。
实现InitFunc接口,在init中处理DataSource初始化逻辑,并利用spi机制实现加载。
推模式改造
基于nacos 配置中心控制台实现推送
配置中心控制台 --> 配置中心 --> sentinel 数据源 --> Sentinel
基于Sentinel 控制台实现推送
Sentinel 控制台 --> 配置中心 --> Sentinel 数据源 --> Sentinel
改造 Sentinel dashboard 控制台
至此 sentinel 的控制台使用介绍到此结束。