授权规则
授权规则能够根据调用来源判断还否允许执行本次请求。
授权规则概述
在某些场景下,需要根据调用接口的来源判断是否允许执行本次请求。此时就可以使用Sentinel提供的授权规则来实现,Sentinel的授权规则能够根据请求的来源判断是否允许本次请求通过。
在Sentinel的授权规则中,提供了 白名单与黑名单 两种授权类型。
演示授权规则
(1)在订单微服务shop-order中新建io.binghe.shop.order.parser
包,并创建MyRequestOriginParser类,实现com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser
接口,用来处理请求的来源。代码如下所示。
/** * @author binghe * @version 1.0.0 * @description Sentinel授权规则,用来处理请求的来源 */ @Component public class MyRequestOriginParser implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest httpServletRequest) { return httpServletRequest.getParameter("serverName"); } }
(2)首先在浏览器中访问
http://localhost:8080/order/request_sentinel4
,在Sentinel的簇点链路里找到/request_sentinel4。
(3)点击授权按钮,进入授权规则配置框,按照如下方式进行配置。
其中,流控应用填写的是test,授权类型为黑名单。这里要结合新建的MyRequestOriginParser类进行理解,MyRequestOriginParser类的parseOrigin()方法如下所示。
public String parseOrigin(HttpServletRequest httpServletRequest) { return httpServletRequest.getParameter("serverName"); }
parseOrigin()方法中直接返回了从HttpServletRequest中获取的serverName参数,而在上图中的流控应用中输出的是test,授权类型为黑名单。
所以,如果我们访问http://localhost:8080/order/request_sentinel4?serverName=test
的话,是处于黑名单的状态,无法访问。
(4)点击新增按钮后,不断在浏览器中刷新http://localhost:8080/order/request_sentinel4?serverName=test
,会发现无法访问,被Sentinel限流了。
系统规则
系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、 RT、入口 QPS 、 CPU使用率和线程数五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统规则概述
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量 (进入应用的流量) 生效。
- Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。
- RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
- CPU使用率:当单台机器上所有入口流量的 CPU使用率达到阈值即触发系统保护
演示系统规则
(1)在订单微服务中新建io.binghe.shop.order.handler
包,并创建MyUrlBlockHandler类,实现com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler
接口,用来捕获系统级Sentinel异常,代码如下所示。
/** * @author binghe * @version 1.0.0 * @description 处理Sentinel系统规则,返回自定义异常 */ @Component public class MyUrlBlockHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { String msg = null; if (e instanceof FlowException) { msg = "限流了"; } else if (e instanceof DegradeException) { msg = "降级了"; } else if (e instanceof ParamFlowException) { msg = "热点参数限流"; } else if (e instanceof SystemBlockException) { msg = "系统规则(负载/...不满足要求)"; } else if (e instanceof AuthorityException) { msg = "授权规则不通过"; } // http状态码 response.setStatus(500); response.setCharacterEncoding("utf-8"); response.setHeader("Content-Type", "application/json;charset=utf-8"); response.setContentType("application/json;charset=utf-8"); JSONObject jsonObject = new JSONObject(); jsonObject.put("code", 500); jsonObject.put("codeMsg", msg); response.getWriter().write(jsonObject.toJSONString()); } }
(2)在订单微服务的io.binghe.shop.order.controller.SentinelController
类中新增requestSentinel5()方法,如下所示。
@GetMapping(value = "/request_sentinel5") @SentinelResource("request_sentinel5") public String requestSentinel5(){ log.info("测试Sentinel5"); return "sentinel5"; }
(3)首先在浏览器中访问
http://localhost:8080/order/request_sentinel5
,在Sentinel的簇点链路里找到/request_sentinel5。
(4)点击流控按钮,进入流控规则配置框,按照如下方式进行配置。
(5)在浏览器中不断刷新
http://localhost:8080/order/request_sentinel5
,会显示如下信息。
返回的原始数据如下所示。
{"code":500,"codeMsg":"限流了"}
说明触发了系统规则,捕获到了Sentinel全局异常。
@SentinelResource注解
使用Sentinel时,可以使用@SentinelResource注解来指定异常处理策略。
@SentinelResource注解概述
在Sentinel中,指定发生异常时的处理策略非常简单,只需要使用@SentinelResource注解即可,@SentinelResource注解的源码如下所示。
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface SentinelResource { //资源名称 String value() default ""; //entry类型,标记流量的方向,取值IN/OUT,默认是OUT EntryType entryType() default EntryType.OUT; int resourceType() default 0; //处理BlockException的函数名称,函数要求: //1. 必须是 public //2.返回类型 参数与原方法一致 //3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 //blockHandlerClass ,并指定blockHandlerClass里面的方法。 String blockHandler() default ""; //存放blockHandler的类,对应的处理函数必须static修饰。 Class<?>[] blockHandlerClass() default {}; //用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所 //有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。函数要求: //1. 返回类型与原方法一致 //2. 参数类型需要和原方法相匹配 //3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定fallbackClass里面的方法。 String fallback() default ""; //存放fallback的类。对应的处理函数必须static修饰。 String defaultFallback() default ""; //用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常进 //行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函数要求: //1. 返回类型与原方法一致 //2. 方法参数列表为空,或者有一个 Throwable 类型的参数。 //3. 默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定 fallbackClass 里面的方法。 Class<?>[] fallbackClass() default {}; //指定排除掉哪些异常。排除的异常不会计入异常统计,也不会进入fallback逻辑,而是原样抛出。 Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class}; //需要trace的异常 Class<? extends Throwable>[] exceptionsToIgnore() default {}; }
演示@SentinelResource注解
1.定义限流和降级后的处理方法
(1)在订单微服务的io.binghe.shop.order.service.SentinelService
接口中新增sendMessage2()方法,如下所示。
String sendMessage2();
(2)在订单微服务的io.binghe.shop.order.service.impl.SentinelServiceImpl
方法中,实现sendMessage2()方法,并且定义一个成员变量count,用来记录请求sendMessage2()方法的次数,同时定义25%的异常率。在sendMessage2()方法上使用@SentinelResource指定了资源的名称、发生BlockException时进入的方法和发生异常时进入的方法,代码如下所示。
private int count = 0; @Override @SentinelResource( value = "sendMessage2", blockHandler = "blockHandler", fallback = "fallback") public String sendMessage2() { count ++; //25%的异常率 if (count % 4 == 0){ throw new RuntimeException("25%的异常率"); } return "sendMessage2"; } public String blockHandler(BlockException e){ log.error("限流了:{}", e); return "限流了"; } public String fallback(Throwable e){ log.error("异常了:{}", e); return "异常了"; }
(3)在订单微服务的io.binghe.shop.order.controller.SentinelController
类中新增requestSentinel6()方法,在方法中调用io.binghe.shop.order.service.SentinelService
接口中的sendMessage2()方法,如下所示。
@GetMapping(value = "/request_sentinel6") public String requestSentinel6(){ log.info("测试Sentinel6"); return sentinelService.sendMessage2(); }
(4)首先在浏览器中访问
http://localhost:8080/order/request_sentinel6
,在Sentinel的簇点链路里找到/request_sentinel6。
(5)点击流控按钮进入流控规则页面,按照下图方式进行配置。
(6)点击新增按钮后在浏览器中刷新http://localhost:8080/order/request_sentinel6
,当刷新的频率超过每秒2次时,浏览器会显示如下信息。
当刷新的次数是4的倍数时,浏览器会显示如下信息。
2.在外部类中指定限流和异常调用的方法
(1)在订单微服务的io.binghe.shop.order.handler
包下新建
MyBlockHandlerClass类,用于定义被Sentinel限流时的方法,源码如下所示。
/** * @author binghe * @version 1.0.0 * @description 定义被Sentinel限流时调用的方法 */ @Slf4j public class MyBlockHandlerClass { public static String blockHandler(BlockException e){ log.error("限流了:{}", e); return "限流了"; } }
(2)在订单微服务的io.binghe.shop.order.handler
包下新建MyFallbackClass类,用于定义抛出异常时调用的方法,源码如下所示。
/** * @author binghe * @version 1.0.0 * @description 定义异常时调用的方法 */ @Slf4j public class MyFallbackClass { public static String fallback(Throwable e){ log.error("异常了:{}", e); return "异常了"; } }
(3)修改io.binghe.shop.order.service.impl.SentinelServiceImpl#sendMessage2()
方法上的注解,修改后的代码如下所示。
@Override @SentinelResource( value = "sendMessage2", blockHandlerClass = MyBlockHandlerClass.class, blockHandler = "blockHandler", fallbackClass = MyFallbackClass.class, fallback = "fallback") public String sendMessage2() { count ++; System.out.println(count); //25%的异常率 if (count % 4 == 0){ throw new RuntimeException("25%的异常率"); } return "sendMessage2"; }
(4)首先在浏览器中访问
http://localhost:8080/order/request_sentinel6
,在Sentinel的簇点链路里找到/request_sentinel6。
(5)点击流控按钮进入流控规则页面,按照下图方式进行配置。
(6)点击新增按钮后在浏览器中刷新http://localhost:8080/order/request_sentinel6
,当刷新的频率超过每秒2次时,浏览器会显示如下信息。
当刷新的次数是4的倍数时,浏览器会显示如下信息。
Sentinel持久化
Sentinel中可以自定义配置的持久化来将Sentinel的配置规则持久化到服务器磁盘,使得重启应用或者Sentinel后,Sentinel的配置规则不丢失。
Sentinel持久化概述
细心的小伙伴会发现,我们之前配置的Sentinel规则在程序重启或者Sentinel重启后就会消失不见,此时就需要我们重新配置。如果这发生在高并发、大流量的场景下是不可接受的。那有没有什么办法让程序或Sentinel重启后配置不丢失呢?其实,Sentinel中可以自定义配置的持久化来解决这个问题。
实现Sentinel的持久化
(1)在订单微服务shop-order中新建io.binghe.shop.order.persistence
包,并创建SentinelPersistenceRule类,实现com.alibaba.csp.sentinel.init.InitFunc
接口,并在SentinelPersistenceRule类中获取应用的名称,覆写init()方法,源码如下所示。
/** * @author binghe * @version 1.0.0 * @description Sentinel规则持久化 */ public class SentinelPersistenceRule implements InitFunc { //实际可以从外部配置读取 private String appcationName = "server-order"; @Override public void init() throws Exception { String ruleDir = System.getProperty("user.home") + "/sentinel-rules/" + appcationName; 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); } }
(2)在订单微服务的resources目录下新建META-INF目录,并在META-INF目录下新建services目录,在services目录下新建名称为com.alibaba.csp.sentinel.init.InitFunc
的文件,如下所示。
(3)在com.alibaba.csp.sentinel.init.InitFunc
文件中添加io.binghe.shop.order.persistence.SentinelPersistenceRule
类的全类名,如下所示。
io.binghe.shop.order.persistence.SentinelPersistenceRule
(4)首先在浏览器中访问
http://localhost:8080/order/request_sentinel6
,在Sentinel的簇点链路里找到/request_sentinel6。
(5)点击流控按钮进入流控规则页面,按照下图方式进行配置。
(6)点击新增按钮,此时打开电脑的user.home
目录,我电脑的目录为C:\Users\binghe
,可以发现C:\Users\binghe
目录中多了一个sentinel-rules目录。
(7)打开sentinel-rules目录,发现里面存在一个server-order目录,如下所示。
(8)打开server-order目录后,会发现生成了Sentinel的配置文件,并持久化到了磁盘上,如下所示。
(9)打开flow-rule.json文件,内容如下所示。
[ { "clusterConfig": { "acquireRefuseStrategy": 0, "clientOfflineTime": 2000, "fallbackToLocalWhenFail": true, "resourceTimeout": 2000, "resourceTimeoutStrategy": 0, "sampleCount": 10, "strategy": 0, "thresholdType": 0, "windowIntervalMs": 1000 }, "clusterMode": false, "controlBehavior": 0, "count": 2, "grade": 1, "limitApp": "default", "maxQueueingTimeMs": 500, "resource": "/request_sentinel6", "strategy": 0, "warmUpPeriodSec": 10 } ]
可以看到,flow-rule.json文件中持久化了对于/request_sentinel6
接口的配置。
至此,我们完成了Sentinel规则的持久化。