赞
踩
Sentinel Dashboard
System(uname -a)
: Linux VM-0-15-centos 3.10.0-1127.19.1.el7.x86_64 #1 SMP Tue Aug 25 17:23:54 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
Sentinel Dashboard Version
: sentinel-dashboard-1.8.2
Demo Application:
System
: macOS 10.14.6
JDK
: 1.8.0_251
Spring Boot
:2.2.13.RELEASE
Spring Cloud
:Hoxton.SR12
Spring Cloud Alibaba
: 2.2.5.RELEASE
单机版
:点这里
java -Dserver.port=10000 -Dcsp.sentinel.dashboard.server=localhost:10000 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.2.jar
集群版
:需二次开发,见:Sentinel Dash board 多控制台部署和主从问题 #1567 或使用企业级的 Sentinel 控制台 (AHAS Sentinel)
项目:wfbi-sentinel
<!-- sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
spring:
application:
name: sentinel-service
cloud:
# sentinel config
sentinel:
transport:
dashboard: localhost:10000
port: 8719
sentinel自动集成Spring Cloud,自动生成一些默认资源。
@RestController @RequestMapping("/flow") @Slf4j public class FlowController { /** * 默认接口,主流框架的默认适配 * * @return */ @GetMapping("") public String defaultResource() { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } return "sentinel defaultResource"; } }
访问:curl http://localhost:10013/flow
,查看dashboard簇点链路。
我们说的资源,可以是任何东西,服务,服务里的方法,甚至是一段代码。使用 Sentinel 来进行资源保护,主要分为几个步骤:
流量控制,其原理是监控应用流量的QPS(每秒查询率) 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
分为QPS限流
和线程数限流
定义资源
/**
* 注解方式定义资源
*
* @return
*/
@RequestMapping("/annotation")
@SentinelResource(value = "annotationResource", blockHandler = "blockHandlerForAnnotationResource")
public String annotationResource() {
return "annotationResource: 正常处理结果";
}
// blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用
public String blockHandlerForAnnotationResource(BlockException ex) {
return "annotationResource: 限流或被降级处理结果";
}
定义规则
上面是对资源annotationResource
进行QPS限流,针对所有来源,且阈值为2。当接口请求的QPS>=2时进行限流。
检验规则是否生效
执行:
ab -n 100000 -c 3 http://localhost:10013/flow/annotation
另开终端执行:
wfbi$ curl http://localhost:10013/flow/annotation
annotationResource: 限流或被降级处理结果
上面是对资源annotationResource
进行线程数限流,针对所有来源,且阈值为2。当接口请求的线程数>=2时进行限流。
有三种流控模式:
关联流控模式指的是,当指定接口关联的接口达到限流条件时,开启对指定接口开启限流。
比如:当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢。
资源1:exceptionResource
:
/** * 抛出异常的方式定义资源 * * @return */ @RequestMapping("/exception") public String exceptionResource() { // 1.5.0 版本开始可以利用 try-with-resources 特性 // 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。 try (Entry entry = SphU.entry("exceptionResource")) { // 被保护的业务逻辑 log.info("exceptionResource: 执行业务逻辑"); } catch (BlockException ex) { // 资源访问阻止,被限流或被降级 log.error("exceptionResource: 资源访问阻止,被限流或被降级"); // 在此处进行相应的处理操作 return "exceptionResource: 限流或被降级异常处理结果"; } return "exceptionResource: 正常处理结果"; }
资源2:boolResource
:
/** * 返回布尔值方式定义资源 * * @return */ @RequestMapping("/bool") public String boolResource() { // 资源名可使用任意有业务语义的字符串 if (SphO.entry("boolResource")) { // 务必保证finally会被执行 try { /** * 被保护的业务逻辑 */ log.info("boolResource: 执行业务逻辑"); } finally { SphO.exit(); } } else { // 资源访问阻止,被限流或被降级 log.error("boolResource: 资源访问阻止,被限流或被降级"); // 在此处进行相应的处理操作 return "boolResource: 限流或被降级处理结果"; } return "boolResource: 正常处理结果"; }
定义规则:
被限流的对象是boolResource
,被限流的条件是当关联资源exceptionResource
的QPS>=2。
检验规则是否生效
执行:
ab -n 100000 -t 1000 http://localhost:10013/flow/exception
另开终端执行:
wfbi$ curl http://localhost:10013/flow/bool
boolResource: 限流或被降级处理结果
链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。它的功能有点类似于针对来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度更细。
增加配置禁止收敛所有URL入口Context
:
spring:
cloud:
sentinel:
web-context-unify: false
注意: 从1.6.3 版本开始,Sentinel Web filter默认收敛所有URL的入口context,因此链路限流不生效。本文使用的SCA版本通过上述配置禁止收敛没有问题,网上看到有使用老版本的需要添加配置类,自己构建CommonFilter实例来禁止收敛所有URL的入口context
ChainService::trace定义为资源traceResource
@SentinelResource(value = "traceResource", blockHandler = "traceHandler")
public String trace(String path){
log.info("Invoke ChainService::trace, path=" + path);
return "traceResource: 正常处理结果, path=" + path;
}
// blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用
public String traceHandler(String path, BlockException ex) {
log.error("traceResource: 限流或被降级处理结果, path=" +path);
return "traceResource: 限流或被降级处理结果, path=" + path;
}
FlowController中定义两个接口方法都调用ChainService::trace
private final ChainService chainService; /** * 链路流控模式测试方法1 * * @return */ @RequestMapping("/trace1") public String tracePath1() { String path = "/flow/trace1"; return chainService.trace(path); } /** * 链路流控模式测试方法2 * * @return */ @RequestMapping("/trace2") public String tracePath2() { String path = "/flow/trace2"; return chainService.trace(path); }
Sentinel Dashboard定义规则
以上含义是当资源traceResource的QPS>=2时出发流控,注意被限制的资源是入口资源即/flow/trace1
,也就是说其他入口如/flow/trace2
不会被限制,可以正常执行业务逻辑。
检验规则是否生效
ab test开启开启对接口/flow/trace2
的不间断访问,QPS要大于等于2。此时通过浏览器去访问/flow/trace1
,返回如下:
说明trace1接口已经被限流了,说明其他入口的访问资源traceResource的QPS已满足>=2,从而触发针对trace1的限制。
降级规则就是设置当满足什么条件的时候,对服务进行降级。Sentinel提供了三个衡量条件:
定义FallBackController::fallback1为资源
/** * 慢调用比例熔断测试 * * @return */ @RequestMapping("/1") @SentinelResource(blockHandler = "blockHandlerForfallback1") public String fallback1() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.info("fallback1: 正常处理结果"); return "fallback1: 正常处理结果"; } // blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用 public String blockHandlerForfallback1(BlockException ex) { log.error("fallback1: 限流或被降级处理结果"); return "fallback1: 限流或被降级处理结果"; }
通过TimeUnit.SECONDS.sleep(1)模拟耗时。
定义降级熔断规则
上面配置表示,如果在S(统计时长)之内,有超过1个的请求且这些请求中响应时间>最大RT的请求数量比例>10%,就会触发熔断,在接下来的10s之内都不会调用真实方法,直接走降级方法。
检验规则是否生效
定义FallBackController::fallback2为资源
/** * 异常数熔断测试 * * @return */ @RequestMapping("/2") @SentinelResource(blockHandler = "blockHandlerForfallback2") public String fallback2() { log.error("fallback2: 抛出业务异常"); throw new RuntimeException("fallback2: 抛出业务异常"); } // blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用 public String blockHandlerForfallback2(BlockException ex) { log.error("fallback2: 限流或被降级处理结果"); return "fallback2: 限流或被降级处理结果"; }
定义降级熔断规则
上面配置表示,如果在1s之内,有超过1个的请求,请求中超过1个请求出现异常就会触发熔断,在接下来的10s之内都不会调用真实方法,直接走降级方法。
检验规则是否生效
热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
定义HotSpotController::hostspot1为资源
/** * 热点参数测试 * * @return */ @RequestMapping("/1") @SentinelResource(blockHandler = "blockHandlerForhostspot1") public String hostspot1(String param) { log.info("hostspot1: 正常处理结果, param=" + param); return "hostspot1: 正常处理结果"; } // blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用 public String blockHandlerForhostspot1(String param, BlockException ex) { log.error("hostspot1: 限流或被降级处理结果, param=" + param); return "hostspot1: 限流或被降级处理结果"; }
定义热点参数规则
首先新增热点规则
然后编辑热点规则
这里应该有个bug,在指定java.lang.String类型时一直不生效。后来先设置成int类型保存下来,再去修改编辑才可以
上面参数索引是指参数的位置,从0开始。对应到方法上就是参数param。也就是param值为a时且QPS>=2时触发限制。当参数为其它值比如b时不管QPS是多少都会被限制。
检验规则是否生效
通过ab test或其它工具使请求的QPS>=2,此时访问/hotspot/1?param=a
,如下:
此时访问/hotspot/1?param=b
,如下:
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过:
定义资源(默认生成)
@RequestMapping("/1")
public String auth1(String origin){
log.info("auth1: 正常处理结果, origin=" + origin);
return "auth1: 正常处理结果";
}
来源参数解析
@Component
public class RequestOriginParserDefinition implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
/**
* 定义从请求的什么地方获取来源信息
* 比如我们可以要求所有的客户端需要在请求头中携带来源信息
*/
String origin = request.getParameter("origin");
return origin;
}
}
定义黑名单规则
以上代表针对资源/auth/1
进行黑名单限制,当来源为aservcie进行限制,其它类型如bservice时不做限制。
检验规则是否生效
请求url:http://localhost:10013/auth/1?origin=aservice
请求url:http://localhost:10013/auth/1?origin=bservice
系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 、CPU使用率和线程数五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量 (进入应用的流量) 生效。
两种方式:
@ControllerAdvice
结合@ExceptionHandler
进行全局异常处理BlockExceptionHandler
接口并实现handle
方法进行统一异常处理Sentinel 规则默认是存放在内存中,极不稳定,所以需要将其持久化。可通过DataSource 扩展
将规则存储在文件、数据库或者配置中心当中实现持久化。
DataSource 扩展常见的实现方式有:
本地文件数据源会定时轮询文件的变更,读取规则。这样我们既可以在应用本地直接修改文件来更新规则,也可以通过 Sentinel 控制台推送规则。以本地文件数据源为例,推送过程如下图所示:
首先 Sentinel 控制台通过 API 将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的规则保存到本地的文件中。
持久化处理类
// InitFunc SPI 扩展接口 public class FilePersistence implements InitFunc { @Override public void init() throws Exception { String ruleDir = System.getProperty("user.home") + "/sentinel-rules/" + "wfbi-sentinel"; String flowRulePath = ruleDir + "/flow-rule.json"; String degradeRulePath = ruleDir + "/degrade-rule.json"; String systemRulePath = ruleDir + "/system-rule.json"; String authorityRulePath = ruleDir + "/authority-rule.json"; String paramFlowRulePath = ruleDir + "/param-flow-rule.json"; this.mkdirIfNotExits(ruleDir); this.createFileIfNotExits(flowRulePath); this.createFileIfNotExits(degradeRulePath); this.createFileIfNotExits(systemRulePath); this.createFileIfNotExits(authorityRulePath); this.createFileIfNotExits(paramFlowRulePath); // 流控规则 ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>( flowRulePath, flowRuleListParser ); FlowRuleManager.register2Property(flowRuleRDS.getProperty()); WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>( flowRulePath, this::encodeJson ); WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS); // 降级规则 ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>( degradeRulePath, degradeRuleListParser ); DegradeRuleManager.register2Property(degradeRuleRDS.getProperty()); WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>( degradeRulePath, this::encodeJson ); WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS); // 系统规则 ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<>( systemRulePath, systemRuleListParser ); SystemRuleManager.register2Property(systemRuleRDS.getProperty()); WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>( systemRulePath, this::encodeJson ); WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS); // 授权规则 ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<>( authorityRulePath, authorityRuleListParser ); AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty()); WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<>( authorityRulePath, this::encodeJson ); WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS); // 热点参数规则 ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<>( paramFlowRulePath, paramFlowRuleListParser ); ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty()); WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>( paramFlowRulePath, this::encodeJson ); ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS); } private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject( source, new TypeReference<List<FlowRule>>() { } ); private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject( source, new TypeReference<List<DegradeRule>>() { } ); private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject( source, new TypeReference<List<SystemRule>>() { } ); private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject( source, new TypeReference<List<AuthorityRule>>() { } ); private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject( source, new TypeReference<List<ParamFlowRule>>() { } ); private void mkdirIfNotExits(String filePath) throws IOException { File file = new File(filePath); if (!file.exists()) { file.mkdirs(); } } private void createFileIfNotExits(String filePath) throws IOException { File file = new File(filePath); if (!file.exists()) { file.createNewFile(); } } private <T> String encodeJson(T t) { return JSON.toJSONString(t); } }
要下载Nacos Dashboard源代码进行改造后才可以支持Sentinel和Nacos之间的双向读写。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。