本篇文章小编给大家分享一下Spring Cloud Alibaba之Sentinel实现熔断限流功能代码示例,文章代码介绍的很详细,小编觉得挺不错的,现在分享给大家供大家参考,有需要的小伙伴们可以来看看。
sentinel功能概述
流量控制:将随机的请求调整为合适的形状。即限制请求数量;
熔断降级:当检测到调用链路中某个资源出现不稳定的表现,如请求响应时间长或者异常比例升高的时候,则对此资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。 采用的手段:1.并发线程数的限制;2.通过响应时间进行降级
系统负载保护:Sentinel提供系统维度的自适应保护能力。即在系统负载较高时,自动将流量转发到其它集群中的机器上去, 使系统的入口流量和系统的负载达到一个平衡,保护系统能力范围内处理最多的请求。
sentinel和Hystrix的区别
两者的原则是一致的,都是当一个资源出现问题时,让其快速失败,不波及到其它服务。
Hystrix采用的是线程池隔离的方式,优点是做到了资源之间的隔离,缺点是增加了线程切换的成本
Sentinel采用的是通过并发线程的数量和响应时间来对资源限制。
Sentinel规则
Sentinel默认定义如下规则:
流控规则
通过QPS或并发线程数来做限制,里面的针对来源可以对某个微服务做限制,默认是都限制。
流控模式:
直接:接口达到限流条件,开启限流;
关联:当关联的资源达到限流条件时,开启限流(适合做应用让步)
链路:当从某个接口过来的资源达到限流条件时,开启限流(限制更细致)
关于配置规则:可以直接使用url地址来配置,也可以通过自定义名称来配置(需要在方法上添加@SentinelResource("order")注解才能达到效果,可以重复)
链路限流不生效的问题:由于sentinel基于filter开发的拦截使用的链路收敛的模式,因此需要设置关闭链路收敛使链路收敛能够生效,
spring: cloud: sentinel: filter: # 关闭链路收敛使链路收敛能够生效 enabled: false
降级规则
当满足设置的条件,对服务进行降级。
根据平均响应时间:当资源的平均响应时间超过阀值(以ms为单位)之后,资源进入准降级状态。
如果接下来1秒持续进入的n个请求的RT都持续超过这个阀值,则在接下来的时间窗口(单位s)之内就会使这个方法进行服务降级。
注意Sentinel默认的最大时间为4900ms,超过这个时间将被默认设置为4900ms;可以通过启动配置 -Dcsp.sentinel.statistic.max.rt=xxx来修改。
异常降级:通过设置异常数或者异常比例来进行服务降级。
热点规则
必须使用@SentinelResource("order")注解来做标记,将限流做到参数级别上去,并且可以配置排除参数值等于某个值时不做限流。
授权规则
通过配置黑白名单来设置是否允许通过。
自定义来源获取规则:
import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** *sentinel自定义授权来源获取规则
*/ @Component public class RequestOriginParserDefinition implements RequestOriginParser { /** * 定义区分来源的规则:本质上是通过获取request域中获取来源标识,然后交给流控应用来进行匹配处理 * * @param request request域 * @return 返回区分来源的值 */ @Override public String parseOrigin(HttpServletRequest request) { String client = request.getHeader("client"); if(StringUtils.isNotBlank(client)){ return "NONE"; } return client; } }
系统规则
系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体Load、RT、入口QPS、CPU使用率和线程数五个维度来监控整个应用数据,让系统跑到最大吞吐量的同时保证系统稳定性。
Load(仅对 pnux/Unix-pke 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。
RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
CPU使用率:当单台机器上所有入口流量的 CPU使用率达到阈值即触发系统保护。
sentinel的使用
下面我们通过一些简单的示例来快速了解sentinel的使用。
安装控制台界面工具
在Sentinel的Github上下载安装包https://github.com/apbaba/Sentinel/releases;就是一个jar包直接使用命令启动即可。
java -Dserver.port=9080 -Dcsp.sentinel.dashboard.server=localhost:9080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
-Dserver.port是设置访问的端口号;
sentinel-dashboard.jar 就是刚刚下载的jar包名称;
为方便使用可以创建一个bat启动文件,在里面输入上面的命令行,后面启动直接点击这个bat文件即可。
从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel;启动成功后浏览器输入http://127.0.0.1:9080即可访问控制台。
注意这个控制台不是必须接入的,同时只有你的接口方法被访问过后控制台里面才会显示。
服务中使用
添加如下依赖包
com.alibaba.cloud spring-cloud-starter-alibaba-sentinel com.alibaba.csp sentinel-apache-dubbo-adapter
注意如果没有使用dubbo那么无需引入sentinel-apache-dubbo-adapter; 比如之前使用的是feign和Hystrix搭配的,只需要将Hystrix的相关配置和依赖去掉,然后加入sentinel的依赖即可。
代码中的使用示例1,如果我们只需对相关的http方法进行限流,直接引入依赖的包即可;下面是我们向对某个方法进行限流,因此使用使用@SentinelResource注解来配置。
@Service public class SentinelDemoServiceImpl implements SentinelDemoService { /** * sentinel 熔断限流示例1 */ @SentinelResource(value = "SentinelDemoService#sentinelDemo1", defaultFallback = "sentinelDemo1Fallback") @Override public String sentinelDemo1() { return "sentinel 示例1"; } /** * 失败的时候会调用此方法 */ public String sentinelDemo1Fallback(Throwable t) { if (BlockException.isBlockException(t)) { return "Blocked by Sentinel: " + t.getClass().getSimpleName(); } return "Oops, failed: " + t.getClass().getCanonicalName(); } }
然后在控制台配置相关的策略规则即可。
自定义Sentinel的异常返回
通过实现BlockExceptionHandler接口来自定义异常返回;注意之前的UrlBlockHandler视乎已经在新版中移除了。
@Component public class SentinelExceptionHandler implements BlockExceptionHandler { /** * 异常处理 * * @param request 请求 * @param response 响应 * @param e BlockException异常接口,包含Sentinel的五个异常 * FlowException 限流异常 * DegradeException 降级异常 * ParamFlowException 参数限流异常 * AuthorityException 授权异常 * SystemBlockException 系统负载异常 * @throws IOException IO异常 */ @Override public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { JSONObject responseData = new JSONObject(); if (e instanceof FlowException) { responseData.put("message", "限流异常"); responseData.put("code", "C5001"); } else if (e instanceof DegradeException) { responseData.put("message", "降级异常"); responseData.put("code", "C5002"); } else if (e instanceof ParamFlowException) { responseData.put("message", "参数限流异常"); responseData.put("code", "C5003"); } else if (e instanceof AuthorityException) { responseData.put("message", "授权异常"); responseData.put("code", "C5004"); } else if (e instanceof SystemBlockException) { responseData.put("message", "系统负载异常"); responseData.put("code", "C5005"); } response.setContentType("application/json;charset=utf-8"); response.getWriter().write(responseData.toJSONString()); } }
基于文件实现Sentinel规则的持久化
Sentinel 控制台通过 API 将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的规则保存到本地的文件中。
编写一个实现InitFunc接口的类,在里面定义持久化的方式,这里使用文件
public class FilePersistence implements InitFunc { @Value("spring.application.name") private String applicationName; @Override public void init() throws Exception { String ruleDir = System.getProperty("user.home") + "/sentinel-rules/" + applicationName; 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> flowRuleRDS = new FileRefreshableDataSource<>( flowRulePath, flowRuleListParser ); FlowRuleManager.register2Property(flowRuleRDS.getProperty()); WritableDataSource > flowRuleWDS = new FileWritableDataSource<>( flowRulePath, this::encodeJson ); WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS); // 降级规则 ReadableDataSource
> degradeRuleRDS = new FileRefreshableDataSource<>( degradeRulePath, degradeRuleListParser ); DegradeRuleManager.register2Property(degradeRuleRDS.getProperty()); WritableDataSource > degradeRuleWDS = new FileWritableDataSource<>( degradeRulePath, this::encodeJson ); WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS); // 系统规则 ReadableDataSource
> systemRuleRDS = new FileRefreshableDataSource<>( systemRulePath, systemRuleListParser ); SystemRuleManager.register2Property(systemRuleRDS.getProperty()); WritableDataSource > systemRuleWDS = new FileWritableDataSource<>( systemRulePath, this::encodeJson ); WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS); // 授权规则 ReadableDataSource
> authorityRuleRDS = new FileRefreshableDataSource<>( authorityRulePath, authorityRuleListParser ); AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty()); WritableDataSource > authorityRuleWDS = new FileWritableDataSource<>( authorityRulePath, this::encodeJson ); WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS); // 热点参数规则 ReadableDataSource
> paramFlowRuleRDS = new FileRefreshableDataSource<>( paramFlowRulePath, paramFlowRuleListParser ); ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty()); WritableDataSource > paramFlowRuleWDS = new FileWritableDataSource<>( paramFlowRulePath, this::encodeJson ); ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS); } private Converter
> flowRuleListParser = source -> JSON.parseObject( source, new TypeReference >() { } ); private Converter
> degradeRuleListParser = source -> JSON.parseObject( source, new TypeReference >() { } ); private Converter
> systemRuleListParser = source -> JSON.parseObject( source, new TypeReference >() { } ); private Converter
> authorityRuleListParser = source -> JSON.parseObject( source, new TypeReference >() { } ); private Converter
> paramFlowRuleListParser = source -> JSON.parseObject( source, new TypeReference >() { } ); 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
String encodeJson(T t) { return JSON.toJSONString(t); } }
在resources下创建配置目录META-INF/services,然后添加文件com.apbaba.csp.sentinel.init.InitFunc;在文件中添加上面写的配置类的全路径top.vchar.order.config.FilePersistence
使用Nacos实现动态规则配置
动态规则扩展文档
添加如下依赖
com.alibaba.csp sentinel-datasource-nacos
添加如下配置(具体可以参考SentinelProperties配置类):
spring: cloud: sentinel: datasource: flow: # 配置nacos的 nacos: rule-type: FLOW server-addr: 127.0.0.1:8848 namespace: public groupId: "DEFAULT_GROUP" dataId: dubbo-customer-demo-sentinel.rule username: nacos password: 123456
然后在nacos中创建一个配置文件 dubbo-customer-demo-sentinel.rule,类型为text; 具体配置参数见官网说明;下面是一个示例:
[ { "resource": "SentinelDemoService#sentinelDemo2", "count": 0, "grade": 1, "limitApp":"default", "strategy":0, "controlBehavior":0, "clusterMode":false } ]
实际使用不建议这样做,还是建议使用控制台的方式;因为使用官方提供的集成方式时,nacos的时候会疯狂的拉取数据,同时只支持一个规则的配置;因此要么自己去基于nacos实现,要么使用控制台的方式;
且配置项很多,因此还是建议使用控制台的方式来实现,或者是对接其rest api接口,在实际操作中还是建议使用界面化的操作。
关于熔断降级是如何实现自动调用我们配置的Fallback方法
sentinel使用了spring的AOP切面编程功能拦截有@SentinelResource注解的类,具体查看com.apbaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect类,在执行实际的方法时使用try-catch进行异常捕获,
如果异常是BlockException的时候会调用handleBlockException方法(注意我们也可以配置自己自定义的异常也走这个方法),通过反射执行配置的Fallback方法。