Spring Cloud Alibaba Sentinel 高可用防护
1.导言
服务级联调用:当服务A故障时,服务B调用失败,但还会一直重试。高并发的情况下,会有很多服务B的线程调用服务A,每个线程都会阻塞等待,直到超时,积累到一定程度后,服务B也会崩溃,变为不可用。同样的道理,上游的服务C也会奔溃,变为不可用,这样产生了服务级联调用的雪崩,导致系统整体不可用。所以我们需要一套完善的机制,来保护系统,例如限流,熔断降级,防止服务被压垮,防止发生系统雪崩,阿里的Sentinel就是一套成熟的防护体系,就像一个哨兵一样,时刻保护我们的系统。
Sentinel 的用法很简单,服务只需要添加 Sentinel 的依赖,就具有限流、降级等防护功能了。但具体如何防护呢?,例如希望此服务的某个接口的 QPS 达到 5 时就限流,这就需要设定一个 Sentinel 规则。有了规则之后,Sentinel 便会根据规则来保护服务。那么规则怎么设置呢?手写肯定不现实,Sentinel 提供了一个管理控制台,界面化设置规则,规则会被自动推送给服务。
2.服务整合
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
<!--sentinel 自己的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
开启属性配置
management: endpoints: web: exposure: include: '*'
http://localhost:8001/actuator/sentinel
3.搭建 Sentinel 控制台
下载编译源码
cd sentinel-dashboard
mvn clean package
java -jar sentinel-dashboard-1.7.0.jar
spring: application: name: service-provider cloud: sentinel: transport: dashboard: localhost:8080
4.URL限流与资源限流
流控效果验证
快速多几次访问 /hello 这个接口。 因为设置的限流是 QPS 阈值为 1,所以很容易触发限流规则。
流控规则项说明:
资源名:这里默认使用的接口地址
针对来源:可以指定来自哪个服务的请求才适用这条规则,默认是default,也就相当于不区分来源
阈值类型:可以根据 QPS,也就是每秒请求数,进行设置,也可以根据已经使用的线程数来设置
单机阈值:设置此限流规则的阈值数。例如阈值类型选择了 QPS,阈值设置为1,意思就是当每秒请求数达到 1 以后就限流,阈值类型如果选择了线程数,意思就是处理请求的线程数达到 1 以上就限流。
流控模式:直接,就是针对这个接口本身
流控效果:快速失败,直接失败,抛异常
根据资源进行限流
对一个测试接口设置为资源,在 Sentinel Console 中对其设置限流规则,验证限流生效。
@GetMapping("/hello") @SentinelResource(value = "res_hello") public String hello(@RequestParam String name) { return "hello " + name + "!"; }
在 Console 中添加测试接口的限流规则
与之前的对 URL 添加流控规则的界面一致。只是“资源名”这项不再是接口的 URL,变成了我们定义的资源名。验证方式和被限流后的效果都与之前URL方式一致。
5.关联限流与链路限流
假设一个 Service 中有2个接口:查询、修改。当查询接口访问量特别大时,必然要影响修改接口,可能会导致修改接口无法处理请求。如果业务上,修改功能优先于查询功能,这就出现了一种限流的需求:“当修改接口的访问量达到一定程度时,就对查询接口限流”。这就是“关联限流”,限流规则不是自己设置的,而是被别人影响的。
持续大量访问 /hi 接口,使其触发阈值例如通过脚本持续访问 /hi :
while true; \ do curl -X GET "http://localhost:8001/hi?name=a" ;\ done;
访问接口 /hello,应已经被限流,相当是hi接口访问量特别高的时候,hello接口被限流了。
这个微服务中,2个API都需要调用这个Service资源,需要做限流保护。如果查询接口请求多,导致这个资源被限流,那么修改接口也就用不了了,很冤枉。这就出现了一种限流的需求:“这个Service资源只对查询接口限流,不影响修改接口”。这就是“链路限流”,执行限流时要判断流量是从哪儿来的。
创建2个测试接口(/testa,/testb),都调用同一个Service(设置为 SentinelResource)。在 Sentinel Console 中对Service设置限流规则,指定入口资源为 /testa。验证 /testa 被限流时,/testb 正常。
package com.example.demo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { @Autowired private CommonService commonService; @GetMapping("/testa") public String testa() { return "test a : " + commonService.getName(); } @GetMapping("/testb") public String testb() { return "test b : " + commonService.getName(); } }
构建2个测试接口,1个Service,指定其为 SentinelResource,2个接口都调用此Service
package com.example.demo; import com.alibaba.csp.sentinel.annotation.SentinelResource; import org.springframework.stereotype.Service; @Service public class CommonService { @SentinelResource("getName") public String getName(){ return "dell"; } }
Sentinel Console 中对Service设置链路限流,入口资源指定 /testa
注意点版本有问题。
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR5</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.1.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
6.预热与排队等待
预热限流
秒杀开始!海量用户同时请求购买接口。这一瞬间冲击非常大,即使接口设置了限流阈值,一瞬间打满也是很危险的。在秒杀开始之前,这个接口的请求量是比较少的,由空闲状态切换为繁忙状态,我们希望这个过程是逐步的,而不是突然的。这种场景就需要使用“预热 Warm Up”的流控方式。
假设有个接口的特点是间歇性突发流量,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,属于时忙时闲。正常限流方式会导致大量请求被丢弃。我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝超出阈值的请求。这种场景就需要使用“排队等待”的流控方式。
package com.example.demo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { @GetMapping("/hi") public String hi(){ return "hi\n"; } }
排队等待的处理机制:
此方式会严格控制请求通过的间隔时间,让请求以均匀的速度通过,对应的是漏桶算法。例如阈值 QPS = 2,那么就是 500ms 才允许通过下一个请求。
持续访问 /hello 接口,会看到匀速通过的效果,例如通过脚本持续访问 /hello :
while true; \ do curl -X GET "http://localhost:8001/hello?name=a" ;\ done;
7.热点限流与系统限流
/product/query?id=1&name=phone&price=100
这个API中包括3个参数,假设其中“name”参数是使用最多的,这个接口的绝大部分请求都含有“name”参数,可以称其为“热点参数”。我们可以针对这个热点参数设置接口的限流,当请求带有此参数时,如果达到阈值就触发限流,否则不限流。
热点规则机制
@GetMapping("hotparam") @SentinelResource(value = "hotparam") public String hotparam(String type, String name){ ... }
参数索引从0开始,按照接口中声明的顺序,此例子中“0”指“type”,“1”指“name”。可以对资源 hotparam 设置热点限流,例如参数 0 的阈值为 1,窗口时长 1秒,一旦在时间窗口内,带有指定索引的参数的 QPS 达到阈值后,会触发限流。还可以对此参数的值进行特殊控制,例如参数值为“5”或者“10”时的阈值,根据值独立控制阈值。
点击新增热点规则,选择高级选项
注意:
热点限流只支持“QPS 限流模式”
针对参数值时,参数类型必须是基本类型(byte int long float double boolean char)或者 String
系统限流
之前的限流规则都是针对接口资源的,如果每个资源的阈值都没有达到,但 系统能力不足了怎么办?所以,我们需要针对系统情况来设置一定的规则,系统保护规则是应用整体维度的,而不是资源维度的。
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统规则模式
Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为限流指标,进行自适应系统保护。当
系统 load1 超过设定的阈值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护。
系统容量由系统的 maxQps * minRt 估算得出。
设定参考值: CPU cores * 2.5。
CPU 使用率:当系统 CPU 使用率超过阈值 即触发系统保护(取值范围 0.0-1.0),比较灵敏。
平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值 即触发系统保护,单位是毫秒。
并发线程数:当单台机器上所有入口流量的并发线程数达到阈值 即触发系统保护。
入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值 即触发系统保护。
系统规则设置界面
RT 效果验证
1. 阈值设置为 10
2. 持续访问接口 /test-a(其中有睡眠时间),应看到限流的效果。例如通过脚本持续访问 /test-a :
while true; \ do curl -X GET "http://localhost:8001/test-a";\ done;
入口QPS 效果验证:
1. 阈值设置为 3
2. 批量持续访问3个测试接口,应看到限流的效果
并发线程数 效果验证:
阈值设置为 3
编写多线程测试代码访问接口,应看到限流效果
package com.example.demo; import org.springframework.web.client.RestTemplate; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestThread { public static void main(String[] args) { // 定义一个线程池 // 创建一个 RestTemplate // 循环启动线程发送请求 RestTemplate restTemplate = new RestTemplate(); ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i= 0; i< 50; i++){ executorService.submit(() -> { try{ System.out.println(restTemplate.getForObject("http://localhost:8081/hi", String.class)); }catch (Exception e){ System.out.println("exception: " + e.getMessage()); } }); } } }
8.降级规则
除了流量控制以外,对调用链路中不稳定的资源进行 熔断降级 也是保障高可用的重要措施之一。Sentinel 熔断降级会在调用链路中某个资源不正常时,对这个资源的调用进行限制,让请求快速失败,避免导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断。
RT 降级策略
创建测试接口
@GetMapping("/degrade-rt") public String test_degrade_rt(){ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } return "ok"; }
设置降级规则,阈值 1ms,时间窗口 3秒
持续请求“/degrade-rt”,应显示降级效果
while true; \ do curl -X GET "http://localhost:8081/degrade-rt";\ done;
异常比例降级策略
降级规则设置界面 0.1就是百分之十 ,时间窗口60秒,每秒错误百分之10 就会开始降级策略直到时间窗口结束。
创建测试接口
@GetMapping("/degrade-exception-rate") public String test_degrade_exception_rate() throws Exception { throw new Exception("rate"); }
持续请求“/degrade-exception-rate”,应显示降级效果
while true; \ do curl -X GET "http://localhost:8081/degrade-exception-rate";\ done;
异常数降级策略
降级规则设置界面 在一分钟内异常数大于三次,就会被熔断降级直到时间窗口结束
创建测试接口
@GetMapping("/degrade-exception-num") public String test_degrade_exception_num() throws Exception { throw new Exception("rate"); }
持续请求“/degrade-exception-num”,应显示降级效果
while true; \ do curl -X GET "http://localhost:8001/degrade-exception-num";\ done;
9.RestTemplate 与 Feign 整合 Sentinel
服务会调用其他服务,调用方式常用的是 RestTemplate 与 Feign,对于 Sentinel 来讲他们都是可以被保护的资源,所以我们需要学习一下 RestTemplate 与 Feign 如何与 Sentinel 整合,还有如何处理限流与降级后的异常。
整合流程
服务消费者 添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
添加配置
server: port: 8082 spring: application: name: service-consumer cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 management: endpoints: web: exposure: include: '*'
@Configuration public class ConsumerConfig { @Bean @SentinelRestTemplate public RestTemplate restTemplate() { return new RestTemplate(); } }
RestTemplate 限流与降级异常处理
定义异常处理类
package com.example.demo; import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; public class ExceptionUtil { public static SentinelClientHttpResponse handleBlock( HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex ){ System.out.println("handleblock ex class: " + ex.getClass().getCanonicalName()); return new SentinelClientHttpResponse("my block info"); } public static SentinelClientHttpResponse handleFallback( HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException ex ){ System.out.println("handle fallback ex class: " + ex.getClass().getCanonicalName()); return new SentinelClientHttpResponse("my fallback info"); } }
在@SentinelRestTemplate 注解中添加异常处理的配置 fallbackClass降级异常用什么类 fallback处理方法。blockHandlerClass限流异常的类, blockHandler处理方法
@SentinelRestTemplate(fallbackClass = ExceptionUtil.class, fallback = "handleFallback", blockHandlerClass = ExceptionUtil.class, blockHandler = "handleException")
验证步骤
Sentinel Console 中对此 RestTemplate 调用添加限流,阈值设为 1
快速多次访问“/resttemplate_sentinel”,应看到自定义的限流信息
Sentinel Console 中对此 RestTemplate 调用添加 RT 类型的降级规则,RT 设为 1,时间窗口设为 1
快速多次访问“/resttemplate_sentinel”,应看到自定义的降级信息
Feign 整合 Sentinel
属性配置 Feign Sentinel 开关
feign: sentinel: enabled: true
创建 service-provider 与 service-consumer,整合 nacos、Sentinel、Feign
Sentinel Console 中针对此 Feign 调用添加限流,阈值设为 1
快速多刷几次接口 “/hellofeign”,应显示限流效果
定义异常处理类
package com.example.demo; import feign.hystrix.FallbackFactory; import org.springframework.stereotype.Component; @Component public class FeignClientServiceFallbackFactory implements FallbackFactory<FeignClientService> { @Override public FeignClientService create(Throwable throwable) { return new FeignClientService() { @Override public String hello(String name) { System.out.println(throwable); return "my exception info"; } }; } }
@FeignClient 注解中添加异常处理的配置
package com.example.demo; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @FeignClient(name = "service-provider", fallbackFactory = FeignClientServiceFallbackFactory.class) public interface FeignClientService { @GetMapping("/hello") public String hello(@RequestParam("name") String name); }
10.错误信息自定义与区分来源
之前我们添加限流规则时,有一项“针对来源”一直都是使用的默认“default”,就是不区分来源。假设一个场景,service A 的API“/query”的主要调用者是 service B,如果不区分来源,肯定会对 Service C 不公平。需要对 Service B 特殊关照,这就可以使用“针对来源”。
之前我们被限流或者降级后,看到的提示信息都是:
Blocked by Sentinel (flow limiting)
效果演示时没问题,但实际环境下这种信息会让我们比较困扰,到底是被限流了,还是被降级了呢?如果能针
对不同的情况给出不同的信息就好了,这个需求比较好实现。
如何判别来源
Sentinel 提供了“RequestOriginParser ”,我们只需要实现其中的“parseOrigin”方法,提供获取来源的方法即可,例如
package com.example.demo; import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; @Component public class MyRequestOriginParser implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest httpServletRequest) { //根据业务自定义 return httpServletRequest.getParameter("from"); } }
Sentinel Console 中为“/hellofeign”设置限流,针对来源设置为“chrome”,阈值设为 1
访问接口 “/hellofeign?name=a&from=chrome”,应显示限流效果
授权规则
“针对来源”还有一个用处:用于实现 授权规则。“流控应用”项与“针对来源”是一样的,“白名单”表示允许访问,“黑名单”表示不允许访问。例如:“流控应用”设为“service-user”,“授权类型”选择“黑名单”,表示“service-user”这个应用不允许访问此资源。
访问接口 “/hellofeign?name=a&from=“service-user” 成功,其他失败。
根据错误类型自定义错误信息
Sentinel 已经定义了不同类型的异常,包括 FlowException、DegradeException、ParamFlowException、SystemBlockException、AuthorityException。我们只需要定义一个异常处理类,针对不同的异常做不同的处理即可,例如:
package com.example.demo; import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException; import com.alibaba.csp.sentinel.slots.system.SystemBlockException; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class MyUrlBlockHandler implements UrlBlockHandler { @Override public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException { // 1. 根据不同的异常类型,定义不同的错误信息 String errmsg = ""; if(e instanceof FlowException){ errmsg = "限流"; }else if(e instanceof DegradeException){ errmsg = "降级"; }else if(e instanceof ParamFlowException){ errmsg = "热点参数"; }else if(e instanceof SystemBlockException){ errmsg = "系统规则"; }else if(e instanceof AuthorityException){ errmsg = "授权规则"; } // 2. 输出错误信息 httpServletResponse.setStatus(500); httpServletResponse.setCharacterEncoding("utf-8"); httpServletResponse.setContentType("application/json;charset=utf-8"); new ObjectMapper().writeValue( httpServletResponse.getWriter(), errmsg ); } }
11.规则持久化
之前我们实践 Sentinel 时,每次我们重启应用 Sentinel 控制台中的规则就都没有了,需要重新设置,这是为什么?因为应用的 Sentinel 规则是保存在内存中的。生产环境中,我们需要做好规则持久化。Sentinel 为我们提供了丰富的数据源工具包,便于集成各类数据源,我们需要自己开发代码进行对接。
生成环境建议推模式
改造流程
从sentinel 源码中找到sentinel-dashboard并引入
新建service-sentinel-nacos 模块 pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>service-sentinel-nacos</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>service-sentinel-nacos</name> <description>Demo project for Spring Cloud alibaba</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR5</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.1.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
改配置
server: port: 8081 spring: application: name: service-sentinel-nacos cloud: sentinel: transport: dashboard: localhost:8080 datasource: flow: nacos: serverAddr: localhost:8848 dataId: ${spring.application.name}-flow-rules groupId: SENTINEL_GROUP ruleType: FLOW dataType: json management: endpoints: web: exposure: include: '*'
nacos 新建配置
改造sentinel 控制台
添加依赖
<!-- for Nacos rule publisher sample -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<!-- <scope>test</scope>-->
</dependency>
全pom.xml如下
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-parent</artifactId> <version>1.7.0</version> </parent> <artifactId>sentinel-dashboard</artifactId> <packaging>jar</packaging> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <spring.boot.version>2.0.5.RELEASE</spring.boot.version> <curator.version>4.0.1</curator.version> </properties> <dependencies> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-web-servlet</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-transport-simple-http</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-parameter-flow-control</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-api-gateway-adapter-common</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring.boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> <version>${spring.boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>${spring.boot.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.3</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> <version>4.4.5</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpasyncclient</artifactId> <version>4.1.3</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore-nio</artifactId> <version>4.4.6</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> </dependency> <!-- for Nacos rule publisher sample --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <!-- <scope>test</scope>--> </dependency> <!-- for Apollo rule publisher sample --> <dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-openapi</artifactId> <version>1.2.0</version> <scope>test</scope> </dependency> <!--for Zookeeper rule publisher sample--> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>${curator.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.github.stefanbirkner</groupId> <artifactId>system-rules</artifactId> <version>1.16.1</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>sentinel-dashboard</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring.boot.version}</version> <configuration> <fork>true</fork> <mainClass>com.alibaba.csp.sentinel.dashboard.DashboardApplication</mainClass> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <version>${maven.deploy.version}</version> <configuration> <skip>true</skip> </configuration> </plugin> </plugins> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> <resource> <directory>src/main/webapp/</directory> <excludes> <exclude>resources/node_modules/**</exclude> </excludes> </resource> </resources> </build> </project>
将test包下 com.alibaba.csp.sentinel.dashboard.rule 文件夹复制到 main 下。
修改 FlowControllerV2
@Autowired @Qualifier("flowRuleNacosProvider") private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired @Qualifier("flowRuleNacosPublisher") private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
修改前端源码
webapp => resources => app => scripts => directives => sidebar => sidebar.html
打开注释
<li ui-sref-active="active" ng-if="entry.appType==0"> <a ui-sref="dashboard.flow({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则 v1</a> </li>
全html 如下
<div class="navbar-default sidebar" role="navigation" style="overflow-y: auto;"> <div class="sidebar-nav navbar-collapse"> <ul class="nav in" id="side-menu"> <li class="sidebar-search"> <div class="input-group" style=""> <input type="text" class="form-control highlight-border" placeholder="应用名" ng-model="searchApp"> <span class="input-group-btn"> <button class="btn btn-secondary btn-default-inverse" type="button">搜索</button> </span> </div> </li> <li ui-sref-active="active"> <a ui-sref="dashboard.home" style="font-size:16px;"> <span class="glyphicon glyphicon-dashboard"></span> 首页</a> </li> <li ng-class="{active: true}" ng-repeat="entry in apps | filter: { app: searchApp }">{{dropDown}} <a href="javascript:void(0);" ng-click="click($event)" collapse="{{collpaseall == 1}}" style="font-size: 16px;word-break: break-word;"> {{entry.app}} <span class="fa arrow"></span> <span class="arrow">({{entry.healthyCount}}/{{entry.machines.length}})</span> </a> <!--<ul class="nav nav-second-level" collapse="{{entry.active}}" style="display: none;">--> <ul class="nav nav-second-level" ng-show="entry.active"> <li ui-sref-active="active"> <a ui-sref="dashboard.metric({app: entry.app})"> <i class="fa fa-bar-chart"></i> 实时监控</a> </li> <li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.identity({app: entry.app})"> <i class="glyphicon glyphicon-list-alt"></i> 簇点链路</a> </li> <li ui-sref-active="active" ng-if="entry.isGateway"> <a ui-sref="dashboard.gatewayIdentity({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 请求链路</a> </li> <li ui-sref-active="active" ng-if="entry.appType==0"> <a ui-sref="dashboard.flow({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则 Nacos</a> </li> <li ui-sref-active="active" ng-if="entry.isGateway"> <a ui-sref="dashboard.gatewayApi({app: entry.app})"> <i class="glyphicon glyphicon-tags"></i> API 管理</a> </li> <li ui-sref-active="active" ng-if="entry.isGateway"> <a ui-sref="dashboard.gatewayFlow({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则</a> </li> <li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.flowV1({app: entry.app})"> <i class="glyphicon glyphicon-filter"></i> 流控规则 Memory</a> </li> <li ui-sref-active="active"> <a ui-sref="dashboard.degrade({app: entry.app})"> <i class="glyphicon glyphicon-flash"></i> 降级规则</a> </li> <li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.paramFlow({app: entry.app})"> <i class="glyphicon glyphicon-fire"></i> 热点规则</a> </li> <li ui-sref-active="active"> <a ui-sref="dashboard.system({app: entry.app})"> <i class="glyphicon glyphicon-lock"></i> 系统规则</a> </li> <li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.authority({app: entry.app})"> <i class="glyphicon glyphicon-check"></i> 授权规则</a> </li> <li ui-sref-active="active" ng-if="!entry.isGateway"> <a ui-sref="dashboard.clusterAppServerList({app: entry.app})"> <i class="glyphicon glyphicon-cloud"></i> 集群流控</a> </li> <li ui-sref-active="active"> <a ui-sref="dashboard.machine({app: entry.app})"> <i class="glyphicon glyphicon-th-list"></i> 机器列表</a> </li> </ul> <!-- /.nav-second-level --> </li> </ul> </div> </div>
修改 js webapp => resources => app => scripts => controllers=> identity.js
FlowServiceV1 改成 FlowServiceV2
rule 添加 降级规则
DegradeRuleNacosProvider.java
/* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.nacos; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.RuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.nacos.api.config.ConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; /** * @author Eric Zhao * @since 1.4.0 */ @Component("degradeRuleNacosProvider") public class DegradeRuleNacosProvider implements DynamicRuleProvider<List<DegradeRuleEntity>> { @Autowired private ConfigService configService; @Autowired private Converter<String, List<DegradeRuleEntity>> converter; @Override public List<DegradeRuleEntity> getRules(String appName) throws Exception { String rules = configService.getConfig(appName + NacosConfigUtil.DEGRADE_DATA_ID_POSTFIX, NacosConfigUtil.GROUP_ID, 3000); if (StringUtil.isEmpty(rules)) { return new ArrayList<>(); } return converter.convert(rules); } }
DegradeRuleNacosPublisher.java
/* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.nacos; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.nacos.api.config.ConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; /** * @author Eric Zhao * @since 1.4.0 */ @Component("degradeRuleNacosPublisher") public class DegradeRuleNacosPublisher implements DynamicRulePublisher<List<DegradeRuleEntity>> { @Autowired private ConfigService configService; @Autowired private Converter<List<DegradeRuleEntity>, String> converter; @Override public void publish(String app, List<DegradeRuleEntity> rules) throws Exception { AssertUtil.notEmpty(app, "app name cannot be empty"); if (rules == null) { return; } configService.publishConfig(app + NacosConfigUtil.DEGRADE_DATA_ID_POSTFIX, NacosConfigUtil.GROUP_ID, converter.convert(rules)); } }
FlowRuleNacosProvider.java
/* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.nacos; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.nacos.api.config.ConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; /** * @author Eric Zhao * @since 1.4.0 */ @Component("flowRuleNacosProvider") public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>> { @Autowired private ConfigService configService; @Autowired private Converter<String, List<FlowRuleEntity>> converter; @Override public List<FlowRuleEntity> getRules(String appName) throws Exception { String rules = configService.getConfig(appName + NacosConfigUtil.FLOW_DATA_ID_POSTFIX, NacosConfigUtil.GROUP_ID, 3000); if (StringUtil.isEmpty(rules)) { return new ArrayList<>(); } return converter.convert(rules); } }
FlowRuleNacosPublisher.java
/* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.nacos; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.nacos.api.config.ConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; /** * @author Eric Zhao * @since 1.4.0 */ @Component("flowRuleNacosPublisher") public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> { @Autowired private ConfigService configService; @Autowired private Converter<List<FlowRuleEntity>, String> converter; @Override public void publish(String app, List<FlowRuleEntity> rules) throws Exception { AssertUtil.notEmpty(app, "app name cannot be empty"); if (rules == null) { return; } configService.publishConfig(app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX, NacosConfigUtil.GROUP_ID, converter.convert(rules)); } }
NacosConfig.java
/* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.nacos; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.RuleEntity; import com.alibaba.csp.sentinel.datasource.Converter; import com.alibaba.fastjson.JSON; import com.alibaba.nacos.api.config.ConfigFactory; import com.alibaba.nacos.api.config.ConfigService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; /** * @author Eric Zhao * @since 1.4.0 */ @Configuration public class NacosConfig { @Bean public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() { return JSON::toJSONString; } @Bean public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() { return s -> JSON.parseArray(s, FlowRuleEntity.class); } @Bean public Converter<List<DegradeRuleEntity>, String> degradeRuleEntityEncoder() { return JSON::toJSONString; } @Bean public Converter<String, List<DegradeRuleEntity>> degradeRuleEntityDecoder() { return s -> JSON.parseArray(s, DegradeRuleEntity.class); } @Bean public ConfigService nacosConfigService() throws Exception { return ConfigFactory.createConfigService("localhost"); } }
NacosConfigUtil.java
/* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.rule.nacos; /** * @author Eric Zhao * @since 1.4.0 */ public final class NacosConfigUtil { public static final String GROUP_ID = "SENTINEL_GROUP"; public static final String FLOW_DATA_ID_POSTFIX = "-flow-rules"; public static final String DEGRADE_DATA_ID_POSTFIX = "-degrade-rules"; public static final String PARAM_FLOW_DATA_ID_POSTFIX = "-param-rules"; public static final String CLUSTER_MAP_DATA_ID_POSTFIX = "-cluster-map"; /** * cc for `cluster-client` */ public static final String CLIENT_CONFIG_DATA_ID_POSTFIX = "-cc-config"; /** * cs for `cluster-server` */ public static final String SERVER_TRANSPORT_CONFIG_DATA_ID_POSTFIX = "-cs-transport-config"; public static final String SERVER_FLOW_CONFIG_DATA_ID_POSTFIX = "-cs-flow-config"; public static final String SERVER_NAMESPACE_SET_DATA_ID_POSTFIX = "-cs-namespace-set"; private NacosConfigUtil() {} }
DegradeController 添加注入
@Autowired @Qualifier("degradeRuleNacosPublisher") private DegradeRuleNacosPublisher degradeRuleNacosPublisher; @Autowired @Qualifier("degradeRuleNacosProvider") private DegradeRuleNacosProvider degradeRuleNacosProvider;
DegradeController 修改接口 如下全文
/* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.csp.sentinel.dashboard.controller; import java.util.Date; import java.util.List; import javax.servlet.http.HttpServletRequest; import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.dashboard.auth.AuthService; import com.alibaba.csp.sentinel.dashboard.auth.AuthService.AuthUser; import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType; import com.alibaba.csp.sentinel.dashboard.rule.nacos.DegradeRuleNacosProvider; import com.alibaba.csp.sentinel.dashboard.rule.nacos.DegradeRuleNacosPublisher; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity; import com.alibaba.csp.sentinel.dashboard.domain.Result; import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemDegradeRuleStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; /** * @author leyou */ @Controller @RequestMapping(value = "/degrade", produces = MediaType.APPLICATION_JSON_VALUE) public class DegradeController { private final Logger logger = LoggerFactory.getLogger(DegradeController.class); @Autowired private InMemDegradeRuleStore repository; @Autowired private SentinelApiClient sentinelApiClient; @Autowired private AuthService<HttpServletRequest> authService; @Autowired @Qualifier("degradeRuleNacosPublisher") private DegradeRuleNacosPublisher degradeRuleNacosPublisher; @Autowired @Qualifier("degradeRuleNacosProvider") private DegradeRuleNacosProvider degradeRuleNacosProvider; @ResponseBody @RequestMapping("/rules.json") public Result<List<DegradeRuleEntity>> queryMachineRules(HttpServletRequest request, String app, String ip, Integer port) { AuthUser authUser = authService.getAuthUser(request); authUser.authTarget(app, PrivilegeType.READ_RULE); if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app can't be null or empty"); } if (StringUtil.isEmpty(ip)) { return Result.ofFail(-1, "ip can't be null or empty"); } if (port == null) { return Result.ofFail(-1, "port can't be null"); } try { List<DegradeRuleEntity> rules = degradeRuleNacosProvider.getRules(app); rules = repository.saveAll(rules); return Result.ofSuccess(rules); } catch (Throwable throwable) { logger.error("queryApps error:", throwable); return Result.ofThrowable(-1, throwable); } } @ResponseBody @RequestMapping("/new.json") public Result<DegradeRuleEntity> add(HttpServletRequest request, String app, String ip, Integer port, String limitApp, String resource, Double count, Integer timeWindow, Integer grade) { AuthUser authUser = authService.getAuthUser(request); authUser.authTarget(app, PrivilegeType.WRITE_RULE); if (StringUtil.isBlank(app)) { return Result.ofFail(-1, "app can't be null or empty"); } if (StringUtil.isBlank(ip)) { return Result.ofFail(-1, "ip can't be null or empty"); } if (port == null) { return Result.ofFail(-1, "port can't be null"); } if (StringUtil.isBlank(limitApp)) { return Result.ofFail(-1, "limitApp can't be null or empty"); } if (StringUtil.isBlank(resource)) { return Result.ofFail(-1, "resource can't be null or empty"); } if (count == null) { return Result.ofFail(-1, "count can't be null"); } if (timeWindow == null) { return Result.ofFail(-1, "timeWindow can't be null"); } if (grade == null) { return Result.ofFail(-1, "grade can't be null"); } if (grade < RuleConstant.DEGRADE_GRADE_RT || grade > RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) { return Result.ofFail(-1, "Invalid grade: " + grade); } DegradeRuleEntity entity = new DegradeRuleEntity(); entity.setApp(app.trim()); entity.setIp(ip.trim()); entity.setPort(port); entity.setLimitApp(limitApp.trim()); entity.setResource(resource.trim()); entity.setCount(count); entity.setTimeWindow(timeWindow); entity.setGrade(grade); Date date = new Date(); entity.setGmtCreate(date); entity.setGmtModified(date); try { entity = repository.save(entity); publishRules(entity.getApp()); } catch (Throwable throwable) { logger.error("add error:", throwable); return Result.ofThrowable(-1, throwable); } return Result.ofSuccess(entity); } @ResponseBody @RequestMapping("/save.json") public Result<DegradeRuleEntity> updateIfNotNull(HttpServletRequest request, Long id, String app, String limitApp, String resource, Double count, Integer timeWindow, Integer grade) { AuthUser authUser = authService.getAuthUser(request); if (id == null) { return Result.ofFail(-1, "id can't be null"); } if (grade != null) { if (grade < RuleConstant.DEGRADE_GRADE_RT || grade > RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) { return Result.ofFail(-1, "Invalid grade: " + grade); } } DegradeRuleEntity entity = repository.findById(id); if (entity == null) { return Result.ofFail(-1, "id " + id + " dose not exist"); } authUser.authTarget(entity.getApp(), PrivilegeType.WRITE_RULE); if (StringUtil.isNotBlank(app)) { entity.setApp(app.trim()); } if (StringUtil.isNotBlank(limitApp)) { entity.setLimitApp(limitApp.trim()); } if (StringUtil.isNotBlank(resource)) { entity.setResource(resource.trim()); } if (count != null) { entity.setCount(count); } if (timeWindow != null) { entity.setTimeWindow(timeWindow); } if (grade != null) { entity.setGrade(grade); } Date date = new Date(); entity.setGmtModified(date); try { entity = repository.save(entity); publishRules(entity.getApp()); } catch (Throwable throwable) { logger.error("save error:", throwable); return Result.ofThrowable(-1, throwable); } return Result.ofSuccess(entity); } @ResponseBody @RequestMapping("/delete.json") public Result<Long> delete(HttpServletRequest request, Long id) { AuthUser authUser = authService.getAuthUser(request); if (id == null) { return Result.ofFail(-1, "id can't be null"); } DegradeRuleEntity oldEntity = repository.findById(id); if (oldEntity == null) { return Result.ofSuccess(null); } authUser.authTarget(oldEntity.getApp(), PrivilegeType.DELETE_RULE); try { repository.delete(id); publishRules(oldEntity.getApp()); } catch (Throwable throwable) { logger.error("delete error:", throwable); return Result.ofThrowable(-1, throwable); } return Result.ofSuccess(id); } private void publishRules(String app) throws Exception{ List<DegradeRuleEntity> degradeRuleEntities = repository.findAllByApp(app); degradeRuleNacosPublisher.publish(app, degradeRuleEntities); } }
service-sentinel-nacos 修改配置文件 添加降级配置
server: port: 8081 spring: application: name: service-sentinel-nacos cloud: sentinel: transport: dashboard: localhost:8080 datasource: flow: nacos: serverAddr: localhost:8848 dataId: ${spring.application.name}-flow-rules groupId: SENTINEL_GROUP ruleType: FLOW dataType: json degrade: nacos: serverAddr: localhost:8848 dataId: ${spring.application.name}-degrade-rules groupId: SENTINEL_GROUP ruleType: DEGRADE dataType: json management: endpoints: web: exposure: include: '*'