SpringCloud Alibaba:Nacos统一配置,Sentinel限流熔断

1,Nacos统一配置管理

1.1,Nacos配置中心

随着单体架构向服务化架构及微服务架构的演进,各个应用自己独立维护本地配置的方式开始显露出它的不足之处:

  • 配置的动态更新:在实际应用中会有动态更新配置的需求,比如修改服务连接地址、限流的配置等。在传统模式下,需要手动修改配置文件并且重启应用才能生效,这种方式效率太低,重启也会导致服务暂时不可用。
  • 配置集中式管理:在微服务架构中,某些核心服务为了保证高性能会部署上百个节点,如果在每个节点中都维护一个配置文件,一旦配置文件中的某个属性需要修改,可想而知,工作量是巨大的。
  • 配置内容的安全性和权限:配置文件随着源代码统一提交到代码库中,容易造成生产环境配置信息的数据泄露
  • 不同部署环境下配置的管理:前面提到过通过profile机制来管理不同环境下的配置,这种方式对于日常维护来说比较烦琐。

统一配置管理就是弥补上述不足的方法,最基本的方法是把各个应用系统中的某些配置放在一个第三方中间件上进行统一维护。然后,对于统一配置中心上的数据的变更需要推送到相应的服务节点实现动态更新。

【Nacos集成Spring Boot实现统一配置管理:springboot-nacos-config】

  • pom.xml & application.properties
<?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>
    <groupId>com.nudt</groupId>
    <artifactId>springboot-nacos-config</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-nacos-config</name>
    <description>springboot-nacos-config</description>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.11.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>nacos-config-spring-boot-starter</artifactId>
            <version>0.2.5</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
nacos.config.server-addr=127.0.0.1:8848
  • NacosConfigController
@RestController
@NacosPropertySource(dataId = "test", autoRefreshed = true)
public class NacosConfigController {
    //其中info表示key,如果不存在使用默认值:Hello World。
    @NacosValue(value = "${info: Local Hello World}", autoRefreshed = true)
    private String info;

    @GetMapping("/config")
    public String get() {
        return info;
    }
}

1.2,SpringCloud Alibaba整合Nacos Config

【Nacos Config基本应用:spring-cloud-nacos】

  • pom.xml & bootstrap.properties
<?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>
    <groupId>com.nudt</groupId>
    <artifactId>spring-cloud-nacos</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-cloud-nacos</name>
    <description>spring-cloud-nacos</description>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>SpringCloudAlibaba</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

在SpringBoot中有两种上下文配置,一种是bootstrap,另外一种是application。bootstrap 是应用程序的父上下文,也就是说 bootstrap 加载优先于application。由于在加载远程配置之前,需要读取Nacos配置中心的服务地址信息,所以Nacos服务地址等属性配置需要放在bootstrap.properties文件中。

spring.application.name=spring-cloud-nacos-config
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
#Data ID,指定时会加载Data ID=test的配置,不指定加载spring.application.name的配置。
spring.cloud.nacos.config.prefix=test
  • SpringCloudNacosApplication
@SpringBootApplication
public class SpringCloudNacosApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringCloudNacosApplication.class, args);
        //从Enviroment读取配置
        String info = context.getEnvironment().getProperty("info");
        System.out.println(info);
    }
}
这是一个在Nacos上面创建的配置文件,不是本地文件。

1.3,动态更新配置 

【动态更新配置】配置中心必然需要支持配置的动态更新,应用程序需要感知配置的变化。

@SpringBootApplication
public class SpringCloudNacosApplication {
    public static void main(String[] args) throws InterruptedException {
        ConfigurableApplicationContext context = SpringApplication.run(SpringCloudNacosApplication.class, args);
        while (true) {
            String info = context.getEnvironment().getProperty("info");
            System.out.println(info);
            Thread.sleep(2000);
        }
    }
}
这是一个在Nacos上面创建的配置文件,不是本地文件。
这是一个在Nacos上面创建的配置文件,不是本地文件。
这是一个在Nacos上面创建的配置文件,不是本地文件。
这是一个在Nacos上面创建的配置文件,不是本地文件。
这是一个在Nacos上面创建的配置文件,不是本地文件。
这是一个在Nacos上面创建的配置文件,不是本地文件。
2023-07-10 11:43:46.009  INFO 12936 --- [35.188.205_8848] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration' of type [org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration$$EnhancerBySpringCGLIB$$3ec32282] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
这是一个在Nacos上面创建的配置文件,不是本地文件。
2023-07-10 11:43:47.005  INFO 12936 --- [35.188.205_8848] c.a.c.n.c.NacosPropertySourceBuilder     : Loading nacos data, dataId: 'test', group: 'DEFAULT_GROUP', data: info=这是一个在Nacos上面创建的配置文件,不是本地文件。123
2023-07-10 11:43:47.101  WARN 12936 --- [35.188.205_8848] c.a.c.n.c.NacosPropertySourceBuilder     : Ignore the empty nacos configuration and get it based on dataId[test.properties] & group[DEFAULT_GROUP]
2023-07-10 11:43:47.102  INFO 12936 --- [35.188.205_8848] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource {name='NACOS', propertySources=[NacosPropertySource {name='test.properties'}, NacosPropertySource {name='test'}]}
2023-07-10 11:43:47.104  INFO 12936 --- [35.188.205_8848] o.s.boot.SpringApplication               : No active profile set, falling back to default profiles: default
2023-07-10 11:43:47.123  INFO 12936 --- [35.188.205_8848] o.s.boot.SpringApplication               : Started application in 2.191 seconds (JVM running for 26.527)
2023-07-10 11:43:47.142  INFO 12936 --- [35.188.205_8848] o.s.c.e.event.RefreshEventListener       : Refresh keys changed: [info]
这是一个在Nacos上面创建的配置文件,不是本地文件。123
这是一个在Nacos上面创建的配置文件,不是本地文件。123
这是一个在Nacos上面创建的配置文件,不是本地文件。123

1.4,切换环境配置

【不同环境的配置切换】在Spring Boot中,可以基于spring.profiles.active实现不同环境下的配置切换这在实际工作中用得比较多。很多公司都会有开发环境、测试环境、预生产环境、生产环境等,服务部署在不同的环境下,有一些配置是不同的,所以希望非常方便地指定当前应用部署的环境,并根据不同的环境加载对应的配置。

  • application-dev.properties
  • application-test.properties
  • application-prod.properties
spring.application.name=spring-cloud-nacos-config
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
#spring.cloud.nacos.config.prefix=test

spring.profiles.active=test

不指定加载spring.application.name的配置,因此需要配置:

spring.application.name=spring-cloud-nacos-config-prod
spring.application.name=spring-cloud-nacos-config-dev
spring.application.name=spring-cloud-nacos-config-test

1.5,自定义Nampespace和Group

【自定义Namespace和Group】其中Namespace用于解决多环境及多租户数据的隔离问题,比如在多套不同的环境下,可以根据指定的环境创建不同的Namespace,实现多环境的隔离,或者在多用户的场景中,每个用户可以维护自己的Namespace,实现每个用户的配置数据和注册数据的隔离。需要注意的是,在不同的Namespace下,可以存在相同的Group或Datald。

官方的建议是:通过Namespace来区分不同的环境,而Group可以专注在业务层面的数据分组。

【Namespace】

  • 在Nacos控制台的“命名空间”下,创建一个命名空间。

  • 在bootstrap.properties中添加配置。
spring.cloud.nacos.config.namespace=9f0b19cb-b25f-4dd0-8da1-acc904ade994

【Group】

  • 在Nacos控制台的“新建配置”中指定所属的Group。

  • 在bootstrap.properties中添加配置。
spring.cloud.nacos.config.group=TEST_GROUP

【Data ID】

spring.cloud.nacos.config.ext-config[0].data-id=example.properties
spring.cloud.nacos.config.ext-config[0].group=TEST_GROUP
spring.cloud.nacos.config.ext-config[0].refresh=true
  • spring.cloud.nacos.config.ext-config[n] 支持多个Data ID的扩展配置,包含二个属性: data-id、group、refresh。
  • spring.cloud.nacos.config.ext-config[n].data-id 指定 Nacos Config 的 Data ID。
  • spring.cloud.nacos.config.ext-config[n].group 指定Data ID所在的组。
  • spring.cloud.nacos.config.ext-config[n].refresh 控制Data ID在配置发生变更时是否动态刷新,以感知最新的配置值。默认是false,也就是不会实现动态刷新。

通过自定义扩展的Data ld配置,既可以解决多个应用的配置共享问题,在支持人应用有多配置文件的情况。

  • spring.cloud.nacos.config.ext-config[n].data-id 的值必须要带文件的扩展名可以支持properties、yaml、json等。
  • spring.cloud.nacos.config.ext-config[n].data-id 配置多个Data ID时,n的值越大,优先级越高。

需要注意的是,在ext-config 和${spring.application.name].${file-extension: properties)都存在的情况下,优先级高的是后者。

2,基于Sentinel的微服务限流及熔断

2.1,服务限流和熔断

服务限流:通过限制并发访问数或者限制一个时间窗口内允许处理的请求数量来保护系统,一旦达到限制数量则对当前请求进行处理采取对应的拒绝策略,比如跳转到错误页面拒绝请求、进入排队系统、降级等。从本质上来说,限流的主要作用是损失一部分用户的可用性,为大部分用户提供稳定可靠的服务。

  • 计数器:在指定周期内累加访问次数,当访问次数达到设定的阀值时,触发限流策略,当进入下一个时间周期时进行访问次数的清零。
  • 滑动窗口:在固定窗口中分割出多个小时间窗口,分别在每个小时间窗口中记录访问次数,然后根据时间将窗口往前滑动并删除过期的小时间窗口,例如每小时只允许访问100次。(Sentinel限流实现方式)
  • 令牌桶限流(处理突发流量):系统会以一个恒定速度往固定容量的令牌桶中放入令牌,如果此时有客户端请求过来,则需要先从令牌桶中拿到令牌以获得访问资格。如果没有获得令牌,则需要触发限流策略。

  • 漏桶限流算法(匀速限流):在漏桶算法内部同样维护一个容器,这个容器会以恒定速度出水,不管上面的水流速度多快,漏桶水滴的流出速度始终保持不变。实际上消息中间件就使用了漏桶限流的思想,不管生产者的请求量有多大,消息的处理能力取决于消费者。

服务熔断与降级:服务熔断是指当某个服务提供者无法正常为服务调用者提供服务时,比如请求超时、服务异常等,为了防止整个系统出现雪崩效应,暂时将出现故障的接口隔离出来,断绝与外部接口的联系,当触发熔断之后,后续一段时间内该服务调用者的请求都会直接失败,直到目标服务恢复正常。

  • 平均响应时间:比如1s内持续进入5个请求,对应时刻的平均响应时间均超过阀值,那么接下来在一个固定的时间窗口内,对这个方法的访问都会自动熔断。
  • 异常比例:当某个方法每秒调用所获得的异常总数的比例超过设定的阀值时该资源会自动进入降级状态,也就是在接下来的一个固定时间窗口中,对这个方法的调用都会自动返回。
    异常数量:和异常比例类似,当某个方法在指定时间窗口内获得的异常数量超过阀值时会触发熔断。

Sentinel是面向分布式服务架构的轻量级流量控制组件,主要以流量为切入点从限流、流量整形、服务降级、系统负载保护等多个维度来帮助我们保障微服务的稳定性。

Sentinel组成

  • 核心库(Java客户端):不依赖任何框架/库,能够运行于所有Java运行时环境,同时对Dubbo、Spring Cloud等框架也有较好的支持。
  • 控制台(Dashboard):基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器。

Sentinel Dashboard 安装部署

  • 下载Sentinel仓库下载 sentinel-dashboard-1.8.5.jar(官网)。
  • 启动控制台:
java -Dserver.port=7777 -Dcsp.sentinel.dashboard.server=localhost:7777-Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.5.jar

-Dserver.port: 指定Sentinel控制台的访问端口,默认是8080。
-Dcsp.sentinel.dashboard.server: 指定Sentinel Dashboard控制台的IP地址和端口,这里进行设置的目的是把自己的限流数据暴露到监控平台。
-Dproject.name: 设置项目名称。
  • 访问目标,默认账号密码(sentinel)。

2.2,服务限流 

Sentinel实现限流

  • 引入Sentinel核心库
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.7.1</version>
</dependency>
  • 普通业务方法
public class Main{
    public static void main(String[] args) {
        initFlowRules();
        while (true) {
            say();
        }
    }

    public static void say() {
        try (Entry entry = SphU.entry("doSomething")) {
            System.out.println("hello, world  " + System.currentTimeMillis());
        } catch (BlockException ex) {
        }
    }

    public static void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        //设置需要保护的资源,必须与SphU中的相同
        rule.setResource("doSomething");
        //限流阀值类型,QPS模式|并发线程数模式
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //每秒最多允许通过5个请求,也就是QPS=5
        rule.setCount(5);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}
===============================================
hello, world  16899312 24 500
hello, world  16899312 24 500
hello, world  16899312 24 500
hello, world  16899312 24 500
hello, world  16899312 24 500
hello, world  16899312 25 500
hello, world  16899312 25 500
hello, world  16899312 25 500
hello, world  16899312 25 500
hello, world  16899312 25 500
hello, world  16899312 26 500
hello, world  16899312 26 500
hello, world  16899312 26 501
hello, world  16899312 26 501
hello, world  16899312 26 501
  • 资源定义方式:当资源被限流之后会抛出一个BlockException异常,这时需要捕获该异常进行限流后的逻辑
public static void say() {
    try (Entry entry = SphU.entry("doSomething")) {
        System.out.println("hello, world  " + System.currentTimeMillis());
    } catch (BlockException ex) {
        //被限流
        //资源使用完之后要调用Sph0.exit(),后者会导致调用链记录异常,抛出ErrorEntryFreeException异常。
        SphO.exit();
    }
}
  • 注解方式:SentinelResource注解虽然在core包里,但是SentinelResourceAspect却在 sentinel-annotation-aspectj 包里。
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>1.8.6</version>
</dependency>
  • SentinelResourceAspect 只是定义了一个切面类,但是还没有成为spring bean,需要注册一下。
@Configuration
public class SentinelConfig {
    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
}
@RestController
public class doSomething {
    @RequestMapping("/do")
    @SentinelResource(value = "doSomething", blockHandler = "blockHandler")
    public void say() {
        initFlowRules();
        System.out.println("hello, world" + System.currentTimeMillis());
    }

    public static void blockHandler(BlockException e) {
        System.out.println("限流后调用方法");
    }

    public void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        //设置需要保护的资源,必须与SphU中的相同
        rule.setResource("doSomething");
        //限流阀值类型,QPS模式|并发线程数模式
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //每秒最多允许通过1个请求
        rule.setCount(1);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}

Sentinel规则全部参数:

public class doSomething {
    public void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        //设置需要保护的资源,必须与SphU中的相同
        rule.setResource("doSomething");
        //限流阀值类型,QPS模式|并发线程数模式
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //每秒最多允许通过1个请求
        rule.setCount(1);
        //是否需要针对调用来源进行限流,默认default,不区分调用来源
        rule.setLimitApp("default");
        //调用关系限流策略:直接、链路、关联
        rule.setStrategy(RuleConstant.STRATEGY_CHAIN);
        //流控行为:直接拒绝(默认),排队等待,慢启动。
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        //是否是集群限流,默认否
        rule.setClusterMode(false);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}

【限流阀值】Sentinel流量控制统计有两种类型,grade属性。

  • 并发线程数(FLOW_GRADE_THREAD):统计当前请求的上下文线程数量,如果超出闻值新的请求就会被拒绝。
  • QPS(FLOW_GRADE_QPS):通过controlBehavior控制。

controlBehavior包括:

  • 直接拒绝:请求流量超过阀值时,直接抛出FlowException。
  • 冷启动(预热):当流量突然增大时,有可能会瞬间把系统压垮。冷启动处理请求的数量逐步递增,并在一个预期时间之后(并不是直接将QPS拉倒最大)达到允许处理请求的最大值。

  • 匀速排队:匀速排队的方式会严格控制请求通过的间隔时间,也就是让请求以均匀的速度通过,其实相当于前面讲的漏桶限流算法。

【调用方限流】根据请求来源进行流量控制,limitApp属性。

  • default:表示不区分调用者,也就是任何访问调用者的请求都会进行限流统计。
  • {some_origin_name}:设置特定调用者,只有来自这个调用者的请求才会进行流量统计和控制。
  • other:表示针对除{some_origin_name}外的其他调用者进行流量控制。

PS:如果定义了多个规则,生效顺序为:{some_origin_name}→other→default

【调用链路入口方限流】

  • 直接限流:直接限制某个具体的调用链路入口的流量。可以设置每秒钟或每分钟的最大请求数量,超过这个数量的请求将被拒绝或延迟处理。

  • 链路限流:根据请求链路的整体情况进行限流。例如,可以设置某个调用链路的整体访问频率,如果超过了预设的阈值,就对整个链路进行限制。

  • 关联限流:基于调用链路的关联关系进行限流。可以根据调用链路中不同元素之间的关联程度,设置相应的限流规则。当某个元素的限流规则触发时,与之关联的其他元素也会受到影响。

2.3,服务熔断

服务熔断采用的是DegradeRule

public class doSomething {
    public void initDegradeRule() {
        List<DegradeRule> rules = new ArrayList<>();
        DegradeRule degradeRule = new DegradeRule();
        degradeRule.setResource("KEY");
        degradeRule.setCount(10);
        //熔断策略:秒级RT(默认)、秒级异常比例、分钟级异常数。
        degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
        //熔断降级的时间窗口,单位s。触发熔断降级之后多长时间内自动熔断。
        degradeRule.setTimeWindow(10);
        //触发的异常熔断最小请求数,请求数小于该值时即时异常比例超出阀值也不会触发熔断,默认 5s。
        degradeRule.setMinRequestAmount(5);
        //在RT模式下,1s内持续输出多少个请求的平均RT超出阀值后触发熔断,默认 5s。
        degradeRule.setRtSlowRequestAmount(5);
        rules.add(degradeRule);
        DegradeRuleManager.loadRules(rules);
    }
}

【熔断策略】

  • 平均响应时间 (RuleConstant.DEGRADE_GRADE_RT) :如果1s内持续进入5个请求,对应的平均响应时间都超过了阀值 (count,单位为ms) ,那么在接下来的时间窗口 (timeWindow,单位为s) 内,对这个方法的调用都会自动熔断,抛出DegradeException。
  • 异常比例 (RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) :如果每秒资源数zminRequestAmount (默认值为5) ,并且每秒的异常总数占总通过量的比例超过阀值count (count的取值范围是[0.0,1.0],代表0%~100%) ,则资源将进入降级状态。同样,在接下来的timeWindow之内,对这个方法的调用都会自动触发熔断。
  • 异常数 (RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT):当资源最近一分钟的异常数目超过阀值之后,会触发熔断。需要注意的是,如果timeWindow小于60s,则结束熔断状态后仍然可能再进入熔新状态。

3,SpringCloud整合Sentinel

3.1,Sentinel接入SpringCloud

【Sentinel接入SpringCloud】

  • pom & SentinelConfig
<?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>com.example</groupId>
        <artifactId>SpringCloudAlibaba</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.nudt</groupId>
    <artifactId>spring-cloud-sentinel</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-cloud-sentinel</name>
    <description>spring-cloud-sentinel</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
@Configuration
public class SentinelConfig {
    @Bean
    public SentinelResourceAspect sentinelResourceAspect(){
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        //设置需要保护的资源,必须与SphU中的相同
        rule.setResource("hello");
        //限流阀值类型,QPS模式|并发线程数模式
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //每秒最多允许通过1个请求
        rule.setCount(1);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
        return new SentinelResourceAspect();
    }
}
  • HelloController
@RestController
public class HelloController {
    @SentinelResource(value = "hello", blockHandler = "blockHandlerHello")
    @GetMapping("/say")
    public String hello() {
        return "hello, word!";
    }

    public String blockHandlerHello(BlockException e) {
        return "当前访问人数过多!";
    }
}

3.2,基于Sentinel Dashboard实现流控配置

【基于Sentinel Dashboard实现流控配置】

  • 启动Sentinel Dashboard
  • 修改application.properties
spring.application.name=spring-cloud-sentinel
spring.cloud.sentinel.transport.dashboard=127.0.0.1:7777
  • 提供一个REST ful接口
@RestController
public class HelloController {
    @GetMapping("/bye")
    public String bye(){
        return "Bye~~~";
    }
}
  • 启动服务,访问bye。
  • 启动Sentinel,访问Sentinel Dashboard,进入spring.application.name对应的菜单。(需要Sentinel部署地址和微服务地址可以相互访问,要么都是本地,要么都是公网IP)

Blocked by Sentinel (flow limiting)

在实际应用中,不会采用上述默认返回值,大多采用JSON格式的数据,如果希望修改触发限流之后的返回形式结果,可以通过自定义限流异常来处理:实现UrlBlockHandler并且重写blocked方法

@Service
public class CustomUrlBlockHandler implements UrlBlockHandler {
    public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
        httpServletResponse.setHeader("Content-Type", "application/json;charset=UTF-8");
        String message = "{\"code\":999,\"msg\":\"访问人数过多\"}";
        httpServletResponse.getWriter().write(message);
    }
}
{"code":999,"msg":"访问人数过多"}

还有一种场景是,当触发限流之后,可以直接跳转到一个降级页面:

spring.cloud.sentinel.servlet.block-page=say

3.3,URL资源清洗

Sentinel中HTTP服务的限流默认由Sentinel-Web-Servlet包中的CommonFilter来实现,从代码中可以看到,这个Filter会把每个不同的URL都作为不同的资源来处理。

@RestController
public class HelloController {
    @GetMapping("/clean/{id}")
    public String clean(@PathVariable("id") int id) {
        return "Hello " + id;
    }
}

针对这人问题可以通过UrlCleaner接口来实现资源清洗,也就是对于/clean/id这个URL,可以统一归集到/clean/资源下:

@Service
public class CustomUrlClearner implements UrlCleaner {
    public String clean(String s) {
        if (StringUtil.isEmpty(s)){
            return s;
        }
        if (s.startsWith("/clean")){
            return "/clean/*";
        }
        return s;
    }
}

4,Nacos整合Sentinel

4.1,Sentinel集成Nacos实现动态流控规则

【Spring Cloud Sentinel集成Nacos】

  •  pom.xml & application.properties & DynamicController
<?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>com.example</groupId>
        <artifactId>SpringCloudAlibaba</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.nudt</groupId>
    <artifactId>spring-cloud-nacos-sentinel</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-cloud-nacos-sentinel</name>
    <description>spring-cloud-nacos-sentinel</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
            <version>1.7.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
spring.application.name=spring-cloud-nacos-sentinel

spring.cloud.sentinel.transport.dashboard=127.0.0.1:7777
#datasource目前支持:redis,apollo,zk,file,nacos
spring.cloud.sentinel.datasource.ds1.nacos.server-addr=101.35.188.205:8848
#设置成${spring.application.name},方便区分不同应用的配置
spring.cloud.sentinel.datasource.ds1.nacos.data-id=${spring.application.name}
spring.cloud.sentinel.datasource.ds1.nacos.group-id=DEFAULT_GROUP
#Sentinel提供了json和xml格式。如果需要自定义,可以配置为custom,配置converter-class指向converter类
spring.cloud.sentinel.datasource.ds1.nacos.data-type=json
#数据源中规则类型:flow,degrade,param-flow,gw-flow
spring.cloud.sentinel.datasource.ds1.nacos.rule-type=flow
@RestController
public class DynamicController {
    @GetMapping("/dynamic")
    public String dynamic(){
        return "Hello Dynamic Rule";
    }
}
  • 登录Nacos控制台,创建流控配置规则。

  • 登录Sentinel Dashboard,查看流控规则。在Nacos控制台上修改流控规则,虽然可以同步到SentinelDashboard,但是Nacos此时应该作为一个流控规则的持久化平台,所以正常的操作过程应该是开发者在Sentinel Dashboard上修改流控规则后同步到Nacos上,遗憾的是,目前Sentinel Dashboard不支持该功能。

Nacos在此处扮演的角色应该是一个“Datasource”,所以强烈建议大家不要在Nacos上修改流控规则,因为这种修改的危险系数很高,毕竟Nacos的UI并不是专门负责流控规则维护的。

这也就意味着流控规则的管理应该集中在Sentinel Dashboard上,接下来就需要实现Sentinel Dashboard来动态维护流控规则并同步到Nacos上,目前官方还没有提供支持,但是可以自己来实现。

4.2,Sentinel集成Nacos实现规则同步

【Sentinel Dashboard源码修改】Sentinel Dashboard的流控操作都会调用Sentinel-Dashboard源码中的FlowControllerV1类,这个类包含流控规则本地化的CRUD操作。另外,在com.alibaba.csp.sentinel.dashboard.controller.v2包下存在一个FlowControllerV2类,这个类与V1的不同是可以指定数据源的规则拉取(DynamicRuleProvider)和发布(DynamicRulePublisher)。

  • 下载Sentinel Dashboard 1.8.0 的源码,使用IDEA打开sentinel-dashboard模块。
  • 在pom中把sentinel-datasource-nacos依赖的<scope>注释。
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <!--<scope>test</scope>-->
</dependency>
  • 修改webapp/resources/app/scripts/directives/sidebar/sidebar.html,将dashboard.flowV1修改为dashboard.flow,去掉V1。修改之后会调用FlowControllerV2中的接口。

  • 在com.alibaba.csp.sentinel.dashboard.rule包下新建一个nacos包,并创建一个类用于加载外部化配置。
package com.alibaba.csp.sentinel.dashboard.rule.nacos;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix="sentinel.nacos",ignoreUnknownFields = true)
public class NacosPropertiesConfiguration {

    private String serverAddr;
    private String dataId;
    private String groupId = "DEFAULT_GROUP";
    private String namespace;

    public String getServerAddr() {
        return serverAddr;
    }

    public void setServerAddr(String serverAddr) {
        this.serverAddr = serverAddr;
    }

    public String getDataId() {
        return dataId;
    }

    public void setDataId(String dataId) {
        this.dataId = dataId;
    }

    public String getGroupId() {
        return groupId;
    }

    public void setGroupId(String groupId) {
        this.groupId = groupId;
    }

    public String getNamespace() {
        return namespace;
    }

    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }
}
  • 创建一个Nacos配置类NacosConfiguration:(1)注入Converter转换器,将FlowRuleEntity转换为FlowRule,以及反向转化。(2)注入Nacos配置服务ConfigService。
package com.alibaba.csp.sentinel.dashboard.rule.nacos;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.exception.NacosException;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.nacos.api.config.*;

import java.util.List;
import java.util.Properties;

@EnableConfigurationProperties(NacosPropertiesConfiguration.class)
@Configuration
public class NacosConfiguration {

    @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 ConfigService nacosConfigService(NacosPropertiesConfiguration nacosPropertiesConfiguration) throws NacosException {
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR, nacosPropertiesConfiguration.getServerAddr());
        properties.put(PropertyKeyConst.NAMESPACE, nacosPropertiesConfiguration.getNamespace());
        return ConfigFactory.createConfigService(properties);
    }
}
  • 创建一个常量类NacosConstants,分别表示默认的GROUP_ID和DATA_ID的后缀。
package com.alibaba.csp.sentinel.dashboard.rule.nacos;

public class NacosConstants {
    public static final String DATA_ID_POSTFIX = "-sentinel-flow";
    public static final String GROUP_ID = "DEFAULT_GROUP";
}
  • 实现动态从Nacos配置中心获取控流规则
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>>{
    private static Logger logger = LoggerFactory.getLogger(FlowRuleNacosProvider.class);

    @Autowired
    private NacosPropertiesConfiguration nacosConfigProperties;

    @Autowired
    private ConfigService configService;

    @Autowired
    private Converter<String, List<FlowRuleEntity>> converter;

    @Override
    public List<FlowRuleEntity> getRules(String appName) throws Exception {
        String dataID=new StringBuilder(appName).append(NacosConstants.DATA_ID_POSTFIX).toString();
        String rules = configService.getConfig(dataID, nacosConfigProperties.getGroupId(), 3000);
        logger.info("pull FlowRule from Nacos Config:{}",rules);
        if (StringUtil.isEmpty(rules)) {
            return new ArrayList<>();
        }
        return converter.convert(rules);
    }
}
  • 创建一个流控规则,在Sentinel Dashboard上修改配置之后,需要调用该发布方法将数据持久化到Nacos。
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 com.alibaba.nacos.api.config.annotation.NacosConfigurationProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {

    @Autowired
    private NacosPropertiesConfiguration nacosPropertiesConfiguration;

    @Autowired
    private ConfigService configService;
    @Autowired
    private Converter<List<FlowRuleEntity>, String> converter;

    @Override
    public void publish(String appName, List<FlowRuleEntity> rules) throws Exception {
        AssertUtil.notEmpty(appName, "appName cannot be empty");
        if (rules == null) {
            return;
        }
        String dataID=new StringBuilder(appName).append(NacosConstants.DATA_ID_POSTFIX).toString();
        configService.publishConfig(dataID, nacosPropertiesConfiguration.getGroupId(), converter.convert(rules));
    }
}
  • 修改FlowControllerV2类,注入上面两个类。
@RestController
@RequestMapping(value = "/v2/flow")
public class FlowControllerV2 {

    private final Logger logger = LoggerFactory.getLogger(FlowControllerV2.class);

    @Autowired
    private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;

    @Autowired
    @Qualifier("flowRuleNacosProvider")    //修改注入的实例
    private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
    @Autowired
    @Qualifier("flowRuleNacosPublisher")    //修改注入的实例
    private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
    ...
}
  • 在application.properties中添加Nacos服务端的配置信息。
sentinel.nacos.serverAddr=127.0.0.1:8848
sentinel.nacos.namespace=
sentinel.nacos.group-id=DEFAULT_GROUP
  • 修改identity.js,配置请求V2
//FlowService→FlowServiceV2

app.controller('IdentityCtl', ['$scope', '$stateParams', 'IdentityService',
  'ngDialog', 'FlowServiceV2', 'DegradeService', 'AuthorityRuleService', 'ParamFlowService', 'MachineService',
  '$interval', '$location', '$timeout',
----------------------------------------------
//'/dashboard/flow/'→'/dashboard/v2/flow/'

function saveFlowRule() {
      if (!FlowService.checkRuleValid(flowRuleDialogScope.currentRule)) {
        return;
      }
      FlowService.newRule(flowRuleDialogScope.currentRule).success(function (data) {
        if (data.code === 0) {
          flowRuleDialog.close();
          let url = '/dashboard/v2/flow/' + $scope.app;
  • 打包模块:sentinel-dashboard,然后启动该模块。
mvn clearn package

【Sentinel Dashboard规则数据同步】只需要主要配置文件中的data-id的命名以-sentinel-flow结尾即可,因为Sentinel Dashboard源码中固定了该格式。

  • 登录Sentinel Dashboard,添加流控规则。
  • 进入Nacos控制台,可以看到配置内容。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Nacos 可以与 Sentinel 集成,以实现动态的流量控制和熔断降级策略。这样,你可以使用 Nacos 配置中心来配置 Sentinel 规则,实现对应用程序的实时流量控制和熔断降级。 以下是集成 NacosSentinel 的基本步骤: 1. 首先,你需要在应用程序中引入 NacosSentinel 的客户端库。根据你使用的编程语言和框架,可能需要添加相应的依赖或库文件。 2. 在 Nacos 配置中心中创建配置项,用于存储 Sentinel 的规则配置。可以在 Nacos 控制台中手动创建或使用 API 自动创建。 3. 在应用程序中使用 Nacos配置读取 API,读取 Sentinel 规则配置。这些规则可以是流量控制规则、熔断降级规则等。 4. 将读取到的规则配置传递给 Sentinel 客户端,使其生效。这样,Sentinel 就会根据配置的规则对流量进行控制和熔断降级。 5. 在 Nacos 配置中心中更新规则配置时,应用程序会自动获取最新的配置,并且 Sentinel 会根据新的配置进行动态调整。 下面是一个示例伪代码,展示了如何集成 NacosSentinel: ```python import nacos import sentinel # 创建 Nacos 客户端实例 client = nacos.NacosClient(server_addresses='nacos-server:8848') # 从 Nacos 获取 Sentinel 规则配置 config = client.get_config(data_id='sentinel-rules', group='your-group') # 将规则配置传递给 Sentinel 客户端 sentinel.load_rules(config) # 应用程序主逻辑 def main(): # 执行应用程序逻辑 do_something() if __name__ == '__main__': main() ``` 在上面的示例中,`client.get_config()` 方法用于从 Nacos 中获取 Sentinel 规则配置。然后,通过 `sentinel.load_rules()` 方法将规则配置传递给 Sentinel 客户端。 通过上述步骤,你可以实现 NacosSentinel 的集成,实现动态的流量控制和熔断降级策略。在 Nacos 配置中心更新规则配置时,应用程序会自动获取最新的配置,并且 Sentinel 会根据新的配置进行动态调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值