【微服务专题】深入理解与实践微服务架构(十八)之Sentinel配置规则持久化

1,352 阅读30分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情

Sentinel配置规则持久化

上面测试了Sentinel服务限流、降级和熔断规则的使用;但是有个很明显的问题就是,一旦服务重启,当前配置的针对某个接口的规则就丢失了,然后就需要重新进行配置。这在生产环境简直就是灾难,并且要是接口过多,配置也是噩梦。

因此,这个功能其实就是Sentinel动态规则,也可以称为Sentinel持久化配置

而Sentinel动态规则必然依赖于数据源实现,不同的数据源有不能的数据推送模式,而在微服务中有推(Push)/拉(Pull)模式的概念:

Sentinel官方文档参考

DataSource 扩展常见的实现方式有:

  • 拉模式:客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件,甚至是 VCS 等。这样做的方式是简单,缺点是无法及时获取变更;
  • 推模式:规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。

Sentinel 目前支持以下数据源扩展:

从官方文档,可以了解到:Push模式下Nacos将规则主动推给Sentinel客户端,可以做到几秒内将规则成功分发到服务器上,并且配置中心修改规则后实时生效。

这样的设计,在需要保证性能的同时尽可能减小客户端压力的情况下,Push模式无疑是优于轮询Pull服务端的方式的。当然,在消息队列日志采集的场景下,需要减小服务端压力保证服务端数据连续且可靠的场景下,Pull模式无疑是优于Push模式的。

其他Sentinel官方文档参考说明

上面的规则配置,都是存在内存中的。即如果应用重启,这个规则就会失效。因此我们提供了开放的接口,您可以通过实现 DataSource 接口的方式,来自定义规则的存储数据源。通常我们的建议有:

  • 整合动态配置系统,如 ZooKeeper、Nacos、Apollo 等,动态地实时刷新配置规则
  • 结合 RDBMS、NoSQL、VCS 等来实现该规则
  • 配合 Sentinel Dashboard 使用

更多详情请参考 动态规则配置

因此需要一个地方来保存dashboard中配置的规则,Sentinel提供了多种持久化的方案,可以集成redis,mysql等。这里我们采用nacos作为规则持久化数据源,下面来看具体的使用步骤吧:

1. 添加依赖

因为从Nacos同步配置到Sentinel中,首先就需要用到Nacos的配置中心功能,因此需要添加 spring-cloud-starter-alibaba-nacos-configspring-cloud-starter-bootstrap 依赖来使 nacos-config 客户端生效;其次,Sentinel需要支持Nacos作为持久化数据源的支持,因此需要集成Sentinel的插件依赖 sentinel-datasource-nacos 以支持配置持久化功能

        <!-- nacos config 配置中心 -->
        <!-- 启动从注册中心下载yaml等配置文件覆盖本地application.yml -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!-- 2021以上版本需要引入该jar才能使bootstrap配置文件生效 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <!-- sentinel-nacos配置持久化 -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>

这里Sentinel能够实现本身不具备的功能,底层是利用NettySPI机制通过引入 sentinel-datasource-nacos 依赖来扩展Sentinel的功能。不得不说这是一种优秀的设计,能够使架构变得轻量、高效和具备可扩展性。

2. 创建注册数据源配置

这是代码的方式实现本地配置文件持久化的方式,通过读取本地yml配置的内容然后写入到Sentinel规则管理器。

这里有两种官方的实现方式:

  • ① 手动将数据源注册至指定的规则管理器(FlowRuleManager)中;
  • ② 实现Sentinel 的 InitFunc SPI 扩展接口.

我们采用第一种方式即可:

这里Sentinel流控规则管理器同样有两种实现:一种是来自 Sentinel-Gateway的桥接依赖 spring-cloud-alibaba-sentinel-gateway 的GatewayRuleManager规则管理器,一种是官方推荐的Sentinel核心包下的FlowRuleManager规则管理器。理论上两种实现都是可以的,这里我们采用第一种基于第三方依赖实现的GatewayRuleManager规则管理器

因为第一种采用ConcurrentHashMap存储的flowRules,而第二种采用的HashMap存储的flowRules,第一种相比于第二种在高并发场景下更加安全和高效。

package com.deepinsea.common.config;
​
import com.alibaba.cloud.sentinel.SentinelProperties;
import com.alibaba.cloud.sentinel.datasource.config.NacosDataSourceProperties;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
​
import java.util.Set;
​
/**
 * Created by deepinsea on 2022/7/9.
 * 限流规则持久化
 */
@Configuration
@Order(2)
public class SentinelPersistenceConfig {
​
    @Autowired
    private SentinelProperties sentinelProperties;
​
    @Bean
    public SentinelPersistenceConfig init() throws Exception {
        loadGWFlowRule();
        return new SentinelPersistenceConfig();
    }
​
    private void loadGWFlowRule() {
        sentinelProperties.getDatasource().entrySet().stream().filter(map -> {
            return map.getValue().getNacos() != null;
        }).forEach(map -> {
            NacosDataSourceProperties nacos = map.getValue().getNacos(); //读取nacos配置文件配置
            ReadableDataSource<String, Set<GatewayFlowRule>> gwFlowRuleDataSource = new NacosDataSource<>(
                    nacos.getServerAddr(), nacos.getGroupId(), nacos.getDataId(),
                    source -> JSON.parseObject(source, new TypeReference<Set<GatewayFlowRule>>() {
                    }));
            GatewayRuleManager.register2Property(gwFlowRuleDataSource.getProperty());
        });
    }
}

注意:这一步如果不配置nacos配置读取并通过SPI接口设置到sentinel的话,sentinel是无法获取nacos配置的持久化规则的!

这里同样是多数持久化配置失败的原因所在,这里我们要了解sentinel搭配nacos实现配置持久化功能的话,就必须了解它的底层原理了。

网关限流原理

  • 通过GatewayRuleManager加载网关限流规则GatewayFlowRule时,无论是否针对请求进行限流,Sentinel底层都会将网关流控规则GateWayFlowRule转化为热点参数规则ParamFlowRule 存储在GatewayFlowManager中,与正常的热点参数规则进行隔离。在转化时候,Sentinel会根据请求属性配置,为网关流控规则设置参数索引idx,并添加到热点参数规则中
  • 在外部请求进入API网关时候,会先经过SentinelGatewayFilter,在该过滤器一次进行Router ID/API分组匹配,请求属性解析和参数组装
  • Sentinel根据配置的网管限流规则来解析请求属性,并依次参照索引顺序组装参数数组。最终传入SphU.entry中
  • 在Sentinel Api Gateway AdapterCommon模块中在Slot Chain中添加了一个GatewayFlowSlot,专门处理房管限流规则的检查
  • 如果当前限流规则并没有指定限流参数,则Sentinel会在参数最后一个位置置入一个预设的常量,最终实现普通限流效果。

参考

3. 添加本地Sentinel数据源限流规则配置

首先,前面讲过的:在最新版本的 Spring Cloud Alibaba 2021.0.1.0 中,要使Nacos Config配置中心生效,需要将application.yml改为bootstrap.yml,并且添加spring-cloud-starter-bootstrap依赖。原因是,application.yml加载的优先级低于远端配置文件,需要将名称改为bootstrap.yml以获得spring默认第一的配置文件加载优先级。但是使用bootstrap.yml名称作为加载配置文件存在bug,需要添加spring-cloud-starter-bootstrap依赖以修复(支持)。

官方的文档中说明了:可以通过 GatewayRuleManager.loadRules(rules)手动加载网关规则,或通过 GatewayRuleManager.register2Property(property)注册动态规则源动态推送(推荐方式)

这里我们需要在yml配置中添加sentinel的datasource数据源配置,选用nacos作为数据源,生效配置的话需要添加 server-addrdataIdgroupIddata-type 远端nacos配置中心的参数以及 rule-type 本地sentinel规则管理器的参数。

先将application.yml改名为bootstrap.yml,再进行配置

    sentinel:
      # sentinel开关
      enabled: true
      # 是否饥开启饿加载(默认为false)
      eager: true # 默认情况下Sentinel会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包
      transport:
        dashboard: localhost:8849
        # sentinel客户端-数据端口(默认为8719端口,但因为控制台默认是8719端口,为了防止冲突会使用8720端口)
        # 这个端口配置会在应用对应的机器上启动一个Http Server,该Server会与Sentinel控制台做通信
        port: 8721
        # 指定心跳周期,默认null
        #        heartbeat-interval-ms: 10000
        # sentinel客户端-外部端口
        # 如果有多套网络,又无法正确获取本机IP,则需要使用下面的参数设置当前机器可被外部访问的IP地址,供admin控制台使用
      #        client-ip: 0.0.0.0:8849
      datasource:
        # 限流规则配置(flow名称可自定义)
        flow:
          nacos:
            server-addr: localhost:8848 #nacos的访问地址,,根据上面准备工作中启动的实例配置
            dataId: service-sentinel-flow-rules #nacos中存储规则的dataId
            groupId: DEFAULT_GROUP #nacos中存储规则的groupId
#           namespace: 5a5f3595-c5b3-4165-8fc5-a5dc44aaf80f #Nacos命名空间的ID(public下不用添加)
            data-type: json #配置文件类型
            # 规则类型,取值见: com.alibaba.cloud.sentinel.datasource.RuleType
            # (flow,degrade,authority,system, param-flow, gw-flow, gw-api-group)
            # 注意:网关流控规则数据源类型是gw-flow,若将网关流控规则数据源指定为flow则不生效。
            rule-type: gw-flow #类型来自RuleType类 - 流控规则

这里如果 rule-type 配置正确(为gw-flow而不是flow)的话,可以不需要上面配置类的方式手动构建限流规则到sentinel持久化。

注意

  • Sentinel 网关流控默认的粒度是 route 维度以及自定义 API 分组维度,默认不支持 URL 粒度。若通过 Spring Cloud Alibaba 接入,请将 spring.cloud.sentinel.filter.enabled 配置项置为 false(若在网关流控控制台上看到了 URL 资源,就是此配置项没有置为 false)。
  • 若使用 Spring Cloud Alibaba Sentinel 数据源模块,需要注意网关流控规则数据源类型是 gw-flow,若将网关流控规则数据源指定为 flow 则不生效。

rule-type的参数类型来自RuleType类,主要有以下几种规则类型

规则名说明
flow流控规则
degrade降级规则
param-flow热点规则
system系统规则
authority授权规则

完整参数配置

参数名描述属性类型枚举项说明
resourceModeAPI类型数字0ROUTE ID区分,不设置此为默认值
1API分组区分
resource资源名称字符——API名称,在网关应用中指scg的routes:id
grade阈值类型数字0按线程数
1按QPS,不设置此为默认值
count阈值数字——QPS类型就是限制时间间隔的请求数 线程数类型就是指并行的线程数
QPS阈值类型专属配置项目
intervalSec间隔数字——单位是秒,dashboard上面有单位下拉框,实际也是换算秒后存储的
controlBehavior流控方式数字0快速失败,不设置此为默认值
2匀速排队
burst额外流量数字——快速失败流控方式下,允许突出的流量数
maxQueueingTimeoutMs排队时间数字——单位是毫秒,匀速排队流控方式下,允许排队的时间
以下是设置了针对请求属性的配置,笔者没有此需求所以上面的实例图中也没有配置,此处列出是顺手之事,请有需要的人参考
paramItem请求属性keyjson对象——为配置的json key,value就是余下的配置项
parseStrategy参数属性数字0ClientIP,请求方的IP
1RemoteHost,请求的host
2Header,请求的Header参数值
3URL参数
4Cookie,请求的Cookie参数
fieldName参数名称字符——若参数属性选择 Header 模式或 URL 参数模式,则需要指定对应的 header 名称或 URL 参数名称
matchStrategy参数值匹配策略数字0精确匹配,不设置此为默认值
1子串查询匹配
2正则表达式匹配
pattern参数值匹配内容字符——按匹配策略来配置的值,只有匹配该模式的请求属性值会纳入统计和流控;若为空则统计该请求属性的所有值。

4. 创建Nacos远端限流规则配置

上面配置好本地Sentinel对应Nacos的本地配置后,下面需要配置nacos远端规则配置。

在Nacos控制台的 配置列表 中的 public 命名空间下创建一个配置文件,然后添加sentinel规则的详细配置:

路由ID的方式限流

image-20220707044540659

[
    {
        "resource":"/sentinel/limit", //资源名-- 对应Route ID或API分组名
        "limitApp":"default", // sentinel客户端名称
        "grade":1, //线程数或QPS(0 or 1)
        "count":1, //限流阈值
        "strategy":0, //流控策略(0表示直接,1表示关联,2表示链路)
        "controlBehavior":0, //流量控制行为(0表示直接拒绝,1表示Warm Up,2表示匀速排队)
        "clusterMode":false //是否为sentinel集群模式
    }
]

注意:创建Nacos配置文件需要指定DATA ID,即为yml配置文件中的dataId参数的值。

API分组的方式限流(推荐)

另外,我们还可以通过在sentinel控制台添加配置后以抓包的形式获取参数:

image-20220711095440136

下面是抓包获取的详细限流规则配置:

[
    {
        app: "service-sentinel"
        burst: 0
        controlBehavior: 0
        count: 1
        grade: 1
        interval: 1
        intervalUnit: 0
        ip: "192.168.174.1"
        maxQueueingTimeoutMs: 0
        port: "8721"
        resource: "/sentinel/limit"
        resourceMode: "1" // 资源类型(路由ID或API分组)
    }
]

具体项目的配置:

[
    {
        "resource": "/sentinel/limit",
        "resourceMode": "1",
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

创建完成后,配置列表public命名空间中出现了对应本地dataId名称的配置文件:

image-20220709045220121

sentinel规则参数说明

规则参数说明
resource资源名,对应Route ID(Gateway)或API分组名(Sentinel)
resourceMode资源类型(0表示Route ID,1表示API分组)
limitApp流控针对的调用来源,若为 default 则不区分调用来源
grade限流阈值类型(0表示线程数,1表示QPS)
count单机限流阈值
strategy流控模式(0表示直接,1表示关联,2表示链路)
controlBehavior流量控制效果(0表示直接拒绝,1表示Warm Up,2表示匀速排队)
clusterMode是否为集群模式

参考官方文档:Sentinel 限流规则

到此,远端nacos配置sentinel限流规则成功!下面进行持久化限流规则测试:

5. 启动Sentinel项目

启动Sentinel项目,控制台日志输出如下所示:

  .   ____          _            __ _ _
 /\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )___ | '_ | '_| | '_ / _` | \ \ \ \
 \/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |___, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.3)
​
2022-07-09 04:43:57.845  WARN 43456 --- [           main] c.a.c.n.c.NacosPropertySourceBuilder     : Ignore the empty nacos configuration and get it based on dataId[service-sentinel] & group[DEFAULT_GROUP]
2022-07-09 04:43:57.848  WARN 43456 --- [           main] c.a.c.n.c.NacosPropertySourceBuilder     : Ignore the empty nacos configuration and get it based on dataId[service-sentinel.yaml] & group[DEFAULT_GROUP]
2022-07-09 04:43:57.849  INFO 43456 --- [           main] b.c.PropertySourceBootstrapConfiguration : Located property source: [BootstrapPropertySource {name='bootstrapProperties-service-sentinel.yaml,DEFAULT_GROUP'}, BootstrapPropertySource {name='bootstrapProperties-service-sentinel,DEFAULT_GROUP'}]
2022-07-09 04:43:57.852  INFO 43456 --- [           main] c.deepinsea.ServiceSentinelApplication   : No active profile set, falling back to default profiles: default
2022-07-09 04:43:58.221  INFO 43456 --- [           main] o.s.cloud.context.scope.GenericScope     : BeanFactory id=3e7f94e0-5772-381b-bf98-f9628769dad6
2022-07-09 04:43:58.338  INFO 43456 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.commons.config.CommonsConfigAutoConfiguration' of type [org.springframework.cloud.commons.config.CommonsConfigAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2022-07-09 04:43:58.338  INFO 43456 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.client.loadbalancer.LoadBalancerDefaultMappingsProviderAutoConfiguration' of type [org.springframework.cloud.client.loadbalancer.LoadBalancerDefaultMappingsProviderAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2022-07-09 04:43:58.338  INFO 43456 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'loadBalancerClientsDefaultsMappingsProvider' of type [org.springframework.cloud.client.loadbalancer.LoadBalancerDefaultMappingsProviderAutoConfiguration$$Lambda$375/1294605731] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2022-07-09 04:43:58.338  INFO 43456 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'defaultsBindHandlerAdvisor' of type [org.springframework.cloud.commons.config.DefaultsBindHandlerAdvisor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2022-07-09 04:43:58.402  INFO 43456 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.hibernate.validator.internal.constraintvalidators.bv.notempty.NotEmptyValidatorForCharSequence' of type [org.hibernate.validator.internal.constraintvalidators.bv.notempty.NotEmptyValidatorForCharSequence] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2022-07-09 04:43:58.407  INFO 43456 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.hibernate.validator.internal.constraintvalidators.bv.NotNullValidator' of type [org.hibernate.validator.internal.constraintvalidators.bv.NotNullValidator] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2022-07-09 04:43:58.427  INFO 43456 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'spring.cloud.sentinel-com.alibaba.cloud.sentinel.SentinelProperties' of type [com.alibaba.cloud.sentinel.SentinelProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
INFO: Sentinel log output type is: file
INFO: Sentinel log charset is: utf-8
INFO: Sentinel log base directory is: C:\Users\deepinsea\logs\csp\
INFO: Sentinel log name use pid is: false
2022-07-09 04:43:58.503  INFO 43456 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration' of type [com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2022-07-09 04:43:58.506  INFO 43456 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration' of type [org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2022-07-09 04:43:58.507  INFO 43456 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration$ReactorDeferringLoadBalancerFilterConfig' of type [org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration$ReactorDeferringLoadBalancerFilterConfig] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2022-07-09 04:43:58.508  INFO 43456 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'reactorDeferringLoadBalancerExchangeFilterFunction' of type [org.springframework.cloud.client.loadbalancer.reactive.DeferringLoadBalancerExchangeFilterFunction] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2022-07-09 04:44:00.438  INFO 43456 --- [           main] o.s.cloud.commons.util.InetUtils         : Cannot determine local hostname
2022-07-09 04:44:02.071  INFO 43456 --- [           main] o.s.cloud.commons.util.InetUtils         : Cannot determine local hostname
2022-07-09 04:44:02.227  INFO 43456 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [After]
2022-07-09 04:44:02.227  INFO 43456 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Before]
2022-07-09 04:44:02.227  INFO 43456 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Between]
2022-07-09 04:44:02.227  INFO 43456 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Cookie]
2022-07-09 04:44:02.227  INFO 43456 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Header]
2022-07-09 04:44:02.227  INFO 43456 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Host]
2022-07-09 04:44:02.227  INFO 43456 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Method]
2022-07-09 04:44:02.227  INFO 43456 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Path]
2022-07-09 04:44:02.227  INFO 43456 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Query]
2022-07-09 04:44:02.227  INFO 43456 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [ReadBody]
2022-07-09 04:44:02.227  INFO 43456 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [RemoteAddr]
2022-07-09 04:44:02.227  INFO 43456 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [XForwardedRemoteAddr]
2022-07-09 04:44:02.227  INFO 43456 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [Weight]
2022-07-09 04:44:02.227  INFO 43456 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [CloudFoundryRouteService]
2022-07-09 04:44:02.240  INFO 43456 --- [           main] c.a.c.s.g.s.SentinelSCGAutoConfiguration : [Sentinel SpringCloudGateway] register SentinelGatewayFilter with order: -2147483648
2022-07-09 04:44:02.323  INFO 43456 --- [           main] c.a.c.s.g.s.SentinelSCGAutoConfiguration : [Sentinel SpringCloudGateway] register SentinelGatewayBlockExceptionHandler
2022-07-09 04:44:03.992  INFO 43456 --- [           main] o.s.cloud.commons.util.InetUtils         : Cannot determine local hostname
2022-07-09 04:44:04.049  WARN 43456 --- [           main] iguration$LoadBalancerCaffeineWarnLogger : Spring Cloud LoadBalancer is currently working with the default cache. While this cache implementation is useful for development and tests, it's recommended to use Caffeine cache in production.You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath.
2022-07-09 04:44:05.228  INFO 43456 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port 9080
2022-07-09 04:44:06.829  INFO 43456 --- [           main] o.s.cloud.commons.util.InetUtils         : Cannot determine local hostname
2022-07-09 04:44:08.460  INFO 43456 --- [           main] o.s.cloud.commons.util.InetUtils         : Cannot determine local hostname
2022-07-09 04:44:08.471  INFO 43456 --- [           main] c.a.c.n.registry.NacosServiceRegistry    : nacos registry, DEFAULT_GROUP service-sentinel 192.168.174.1:9080 register finished
2022-07-09 04:44:08.482  INFO 43456 --- [           main] c.deepinsea.ServiceSentinelApplication   : Started ServiceSentinelApplication in 14.755 seconds (JVM running for 16.039)
2022-07-09 04:44:08.482  INFO 43456 --- [           main] c.a.c.n.refresh.NacosContextRefresher    : listening config: dataId=service-sentinel.yaml, group=DEFAULT_GROUP
2022-07-09 04:44:08.482  INFO 43456 --- [           main] c.a.c.n.refresh.NacosContextRefresher    : listening config: dataId=service-sentinel, group=DEFAULT_GROUP

可以看到,本地nacos配置中心已经监听到了yml配置文件中的相关配置:

2022-07-09 04:44:08.482  INFO 43456 --- [           main] c.a.c.n.refresh.NacosContextRefresher    : listening config: dataId=service-sentinel.yaml, group=DEFAULT_GROUP
2022-07-09 04:44:08.482  INFO 43456 --- [           main] c.a.c.n.refresh.NacosContextRefresher    : listening config: dataId=service-sentinel, group=DEFAULT_GROUP

同样的远端的nacos服务器也有监听本地nacos客户端的配置,我们可以在nacos服务端控制台界面进行查看:

image-20220709045930506

可以看到,nacos以及检索到Data ID和Group对应的监听记录了:

image-20220709050025497

并且,可以看到,nacos服务端是通过MD5的形式进行配置文件唯一性校验的。

6. 测试Sentinel限流规则

我们这里分三个部分进行测试:

  • 首先是测试配置文件是否首次启动项目就加载了限流规则;
  • 其次是测试接口查看限流规则是否生效;
  • 最后是测试重启sentinel服务后限流规则是否保存。

简单来说,就是:是否同步、生效以及重启还存在

下面开始进行分步测试:

① 首先进行首次启动规则加载测试(使用Nacos的Push模式推送规则实现)

上面启动完成项目后,我们打开Sentinel控制台查看流控规则:

控制台地址:http://localhost:8849/#/dashboard/gateway/flow/service-sentinel

image-20220709051700107

可以看到,控制台出现了我们提前设置好的规则,通过API名称(对应resource参数)可以分辨出这个是我们设置限流规则。

测试成功!下面我们进行接口请求测试:

② 限流规则是否生效测试

不生效原因排查:①rule-type是否为gw-flow或gw-api-group; ②nacos远端配置的resource参数是否为网关的路由ID或API分组名。

我们使用curl命令,测试前面创建过的 http://localhost:9080/service-sentinel/sentinel/limit 限流接口:

C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
hi, this is service-sentinel-limit test!
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
hi, this is service-sentinel-limit test!
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
hi, this is service-sentinel-limit test!
``````

但是,好像没有触发限流规则,也就是说限流规则没有生效

查看 流控规则 选项中的限流规则配置,怀疑是yml配置中Gateway没有/sentinel/limit的route id。

image-20220709052744459

仔细想想,直接使用sentinel上的接口路径名命名路由ID也确实不好,起码换了URL(指的是对应真实接口URL的路由ID)就会导致整个限流功能失效!

另外,Sentinel的限流规则也有支持的要求,首先就是限流粒度必须为Route:

参考官方文档说明:Sentinel 网关限流

注意

  • Sentinel 网关流控默认的粒度是 route 维度以及自定义 API 分组维度,默认不支持 URL 粒度。若通过 Spring Cloud Alibaba 接入,请将 spring.cloud.sentinel.filter.enabled 配置项置为 false(若在网关流控控制台上看到了 URL 资源,就是此配置项没有置为 false)。
  • 若使用 Spring Cloud Alibaba Sentinel 数据源模块,需要注意网关流控规则数据源类型是 gw-flow,若将网关流控规则数据源指定为 flow 则不生效。

因为目前只能从Nacos配置中心向Sentinel中单向同步配置(Sentinel控制台不能修改规则),因此还需要配置好Nacos中的限流规则来推送到Sentinel。

因为我们前面关闭了Gateway的动态路由功能,并且以及配置了Sentinel子模块服务的静态路由,Route ID为service-sentinel。因此,下面只需要修改远端nacos配置列表中Sentinel规则配置中source参数的值为service-sentinel添加resourceMode参数指定资源类型为API分组即可(我们这里为了测试默认路由限流的方式,选择第一种方式,实际项目推荐粒度更细、支持路径匹配的API分组方式)。

[
    {
        "resource":"service-sentinel", //资源名-- 对应Route ID或API分组名
        "limitApp":"default", // sentinel客户端名称
        "grade":1, //线程数或QPS(0 or 1)
        "count":1, //限流阈值
        "strategy":0, //流控策略(0表示直接,1表示关联,2表示链路)
        "controlBehavior":0, //流量控制行为(0表示直接拒绝,1表示Warm Up,2表示匀速排队)
        "clusterMode":false //是否为sentinel集群模式
    }
]

注意:这里的"//"注释在json文件中是不支持的,只是作为参数说明,json文件注释只能以k-v键值对注释字段的方式添加。

点击发布,然后打开Sentinel控制台并刷新页面,查看规则是否实时修改:

image-20220709054952645

可以看到,在修改完成的几秒内,规则就成功从Nacos推送到了Sentinel中!

然后再次使用curl命令,请求上一次限流未生效的limit接口:

C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
hi, this is service-sentinel-limit test!
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
hi, this is service-sentinel-limit test!

可以看到接口限流成功,并且返回的也是我们之前配置过的自定义sentinel全局异常结果。

③ 最后需要测试重启Sentinel项目后规则是否存在

我们点击重启项目,然后查看sentinel控制台的 流控规则 选项中是否存在之前由nacos推送过来的限流规则:

image-20220709055707607

同样,重启项目后规则也是存在的。这说明,sentinel本身是不存储规则的(基于内存),规则完全依赖于Nacos推送过来(可以通过重启项目时查看sentinel控制台来进行验证 -- 规则消失)。

那么,我们如果将nacos服务停止后,规则还存在吗? 很明显,nacos没有推送规则的话,sentinel重启后限流规则是不存在的。

我们分两种情况测试:

  • 第一种:先停止Nacos服务,然后启动Sentinel项目
  • 第二种:先启动Sentinel项目,然后停止Nacos服务

先停止Nacos服务,然后启动Sentinel项目

第一种情况下测试,Sentinel控制台输出结果如下所示:

2022-07-09 18:32:05.585  WARN 69672 --- [oundedElastic-1] o.s.c.l.core.RoundRobinLoadBalancer      : No servers available for service: service-sentinel
32:05.905 ERROR 69672 --- [           main] c.a.c.n.registry.NacosServiceRegistry    : nacos registry, service-sentinel register failed...NacosRegistration{nacosDiscoveryProperties=NacosDiscoveryProperties{serverAddr='localhost:8848', username='nacos', password='nacos', endpoint='', namespace='public', watchDelay=30000, logName='', service='service-sentinel', weight=1.0, clusterName='DEFAULT', group='DEFAULT_GROUP', namingLoadCacheAtStart='false', metadata={preserved.register.source=SPRING_CLOUD}, registerEnabled=true, ip='192.168.174.1', networkInterface='', port=9080, secure=false, accessKey='', secretKey='', heartBeatInterval=null, heartBeatTimeout=null, ipDeleteTimeout=null, instanceEnabled=true, ephemeral=true, failureToleranceEnabled=false}, ipDeleteTimeout=null, failFast=true}},
​
com.alibaba.nacos.api.exception.NacosException: failed to req API:/nacos/v1/ns/instance after all servers([localhost:8848]) tried: java.net.ConnectException: Connection refused: connect
    at com.alibaba.nacos.client.naming.net.NamingProxy.reqApi(NamingProxy.java:556) ~[nacos-client-1.4.2.jar:na]

可以看到,控制台已经报无service-sentinel服务可用服务器的警告拉取Nacos服务列表信息的错误了,但这时我们的服务还没停止。

2022-07-09 18:32:05.905  WARN 69672 --- [           main] onfigReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'; nested exception is java.lang.reflect.UndeclaredThrowableException
2022-07-09 18:32:09.328  WARN 69672 --- [oundedElastic-1] o.s.c.l.core.RoundRobinLoadBalancer      : No servers available for service: service-sentinel
2022-07-09 18:32:14.939  WARN 69672 --- [oundedElastic-1] o.s.c.l.core.RoundRobinLoadBalancer      : No servers available for service: service-sentinel
2022-07-09 18:32:19.135  INFO 69672 --- [           main] ConditionEvaluationReportLoggingListener : 
​
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-07-09 18:32:19.152 ERROR 69672 --- [           main] o.s.boot.SpringApplication               : Application run failed

下面我们使用curl命令,来请求service-sentinel服务的接口:

curl http://localhost:9080/service-sentinel/sentinel/limit

控制台出现了No servers available for service: service-sentinel 无服务可用的异常日志,并且一段时间后彻底停止了正在启动的应用(过了一会停止的原因大概是Nacos有心跳检活超时时间吧,在第一次检测失败后应该在重试请求,直到超时范围内多次检测失败后才会关闭端口连接)。

很明显,第一种情况下限流规则是不存在的。没有了注册中心,那么本地服务都无法注册上去,更别提同步nacos配置到sentinel上了。

注意:如果Nacos服务停止了但控制台还是运行的话,可以通过尝试停止JDK服务来停止Nacos服务。

先启动Sentinel项目,再停止Nacos服务

首先我们启动Sentinel项目:

image-20220709190021410

然后停止Nacos服务,并进入控制台查看服务是否真实停止了(前面就出现nacos服务停止后还在运行的情况):

image-20220709190157164

image-20220709185936317

可以看到nacos服务的确是停止了,我们再来查看sentinel控制台上限流规则是否还在:

image-20220709190413246

可以看到,限流规则是还存在的,下面我们来测试下规则是否生效:

使用curl命令来请求limit接口:

curl http://localhost:9080/service-sentinel/sentinel/limit
{"timestamp":"2022-07-09T11:32:24.491+00:00","path":"/service-sentinel/sentinel/limit","status":503,"error":"Service Unavailable","requestId":"fa96b6df-4"}

控制台信息如下所示:

2022-07-09 19:31:53.211  WARN 69596 --- [oundedElastic-1] o.s.c.l.core.RoundRobinLoadBalancer      : No servers available for service: service-sentinel

可以看到,nacos服务停止后,依赖于Nacos的Gateway无法获取到服务信息,而依赖于Gateway的过滤器实现的限流规则也自然不生效。

我们恢复nacos服务为启动,然后再次请求:

C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
hi, this is service-sentinel-limit test!
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}

可以看到,限流规则又重新生效了,并且没有重启sentinel服务。

总结:"先停止Nacos服务,然后启动Sentinel项目"和"先启动Sentinel项目,然后停止Nacos服务"这两种情况,sentinel的限流规则都不会生效,解决方法都是恢复nacos服务即可。

至于nacos注册中心宕机了,只需重启nacos无需重启sentinel服务就可以使sentinel限流规则继续生效的原理是:Nacos推送配置是Push模式

下面,接着上面的第③点(-- 重启sentinel服务)来继续进行:我们除了查看重启服务后sentinel规则是否存在,还需要测试重启后限流规则是否继续生效。

我们可以使用curl命令,进行限流规则测试:

C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
hi, this is service-sentinel-limit test!
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}

可以看到,同步的限流规则生效成功!

另外,如果在nacos和sentinel都正常启动,但启动后sentinel删除了nacos推送过来的持久化规则结果会怎样?

答案是,规则会消失,但重启sentinel后规则又会重新推送到sentinel中。

因此,由此可知:sentinel启动后会向nacos发送下发配置请求,但是在配置不变的情况下配置下发只有一次,nacos推送或更新配置会实时通知sentinel,参考阿里HSF架构中的configServer的配置下发机制也可以略知一二。

关于Sentinel限流规则配置持久化的所有测试,到此都已经全部完成了。

7. 降级熔断规则持久化

上面只有配置限流规则持久化,下面我们将Nacos作为数据源来持久化降级熔断规则:

首先我们需要添加本地yml配置

      datasource:
        # 限流(基于路由id和api分组)规则配置(名称可自定义)
        flow:
          nacos:
            server-addr: localhost:8848 #nacos的访问地址,,根据上面准备工作中启动的实例配置
            dataId: service-sentinel-flow-rules #nacos中存储规则的dataId
            groupId: DEFAULT_GROUP #nacos中存储规则的groupId
#           namespace: 5a5f3595-c5b3-4165-8fc5-a5dc44aaf80f #Nacos命名空间的ID(public下不用添加)
            data-type: json #配置文件类型
            # 流控规则类型来自RuleType,取值见: com.alibaba.cloud.sentinel.datasource.RuleType
            # (flow,degrade,authority,system, param-flow, gw-flow, gw-api-group)
            # 注意:网关流控规则数据源类型是gw-flow,若将网关流控规则数据源指定为flow则不生效(当然配置文件手动读取也可以强制生效)。
#            rule-type: flow #网关限流规则(1.8.3以前的版本有效)
            rule-type: gw-flow #网关限流规则(1.8.3及以后版本的flow参数)
#            rule-type: gw-api-group #网关API分组
        # 降级熔断规则配置
        degrade:
          nacos:
            server-addr: 127.0.0.1:8848
            dataId: service-sentinel-degrade-rules
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: degrade

然后在控制台添加降级熔断规则,通过抓包获得参数配置:

image-20220713214505665

抓包并去除设备特征参数并格式化JSON配置后,得到通用降级熔断参数配置如下所示:

[
    {
        "app": "service-sentinel",
        "count": 1,
        "enable": false,
        "grade": 0,
        "limitApp": "default",
        "minRequestAmount": 1,
        "resource": "service-sentinel",
        "slowRatioThreshold": 0.5,
        "statIntervalMs": 1000,
        "strategy": 0,
        "timeWindow": 10
    }
]

在nacos控制台上,创建sentinel降级熔断配置文件

我们需要创建一个对应本地yml配置文件中的Data ID的远程降级熔断配置文件 service-sentinel-degrade-rules

image-20220713220016738

创建完成后,可以看到Nacos配置列表中成功出现了新增的降级熔断规则配置文件:

image-20220713220114591

8. 测试降级熔断规则

这里测试降级熔断规则是否同步和生效

我们在Sentinel控制台删除原来创建的降级熔断配置,然后重启项目,查看是否成功同步降级熔断规则:

image-20220713220532439

image-20220713220646206

可以看到,我们成功同步了降级熔断规则,并且请求链路为空。原来配置降级规则的API接口,必须要在sentinel客户端请求一次接口之后才可以出现;这里没有请求并且没有添加熔断规则,然后在重启项目后出现了降级熔断规则,说明降级熔断规则同步成功!

下面我们只要测试降级熔断规则是否生效即可:

同样的,为了防止限流规则阻止降级熔断效果触发从而干扰降级熔断功能测试,我们先删除基于Route ID的限流规则。

使用curl命令访问fusing接口,测试降级熔断效果:

C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/fusing
hi, this is service-sentinel-fusing test!
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/fusing
{"code":429,"message":"Blocked by Sentinel: DegradeException"}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/fusing
{"code":429,"message":"Blocked by Sentinel: DegradeException"}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/fusing
{"code":429,"message":"Blocked by Sentinel: DegradeException"}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/fusing
{"code":429,"message":"Blocked by Sentinel: DegradeException"}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/fusing
{"code":429,"message":"Blocked by Sentinel: DegradeException"}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/fusing
{"code":429,"message":"Blocked by Sentinel: DegradeException"}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/fusing
{"code":429,"message":"Blocked by Sentinel: DegradeException"}
hi, this is service-sentinel-fusing test!

测试成功,降级熔断规则持久化到Nacos成功!

深入思考

对于接口的限流和降级规则可以通过两个地方修改:Sentinel控制台和Nacos控制台

  • Sentinel控制台中修改规则:仅存在于服务的内存中,不会修改Nacos中的配置值,重启后恢复原来的值。
  • Nacos控制台中修改规则:服务的内存中规则会更新,Nacos中持久化规则也会更新,重启后依然保持。

我们是不是还可以通过sentinel修改nacos中的规则配置呢?答案是可以的。但是需要下载sentinel源码,然后进行修改源码并重新编译来支持双向同步规则配置的功能,需要改造Sentinel Dashboard,在后面的章节进行实践。

9. Sentinel配置持久化总结

使用Nacos持久化Sentinel配置后,重启Sentinel并刷新Sentinel 控制台,发现流控配置会自动同步过来;有改动时,修改Nacos配置文件无需重新启动gateway,刷新Sentinel即可实时生效。

使用Nacos持久化Sentinel规则相比于MySQL而言,本身就是Spring Cloud Alibaba生态的组件无需维护额外的组件即可生效;此外,依赖于Nacos的心跳检测机制,可以做到数据库等持久化源所没有的实时动态更新配置效果。并且,因为Nacos可以配置MySQL持久化配置,因此即使Nacos挂了后Sentinel的规则配置还是不会丢失。

因此,基于以上实时性动态配置性以及稳定性的优点,因此我们可以选用Nacos作为Sentinel配置的持久化源

欢迎点赞,谢谢大佬了ヾ(◍°∇°◍)ノ゙