网关整合sentinel无法读取nacos配置问题分析

sentinel无法读取nacos配置问题分析

最近公司需要上线一个集约项目,虽然为内网项目,但曾经有过内网被攻破,导致内部系统被攻击的案例,且集约系统同时在线人数较多,所以需要对系统整体进行流控。市面上的流控方案有很多,不过新系统已经集成了sprin-cloud-alibaba-nacos,所以技术选型就选择了阿里的流控系统sentinel。

1.spring-cloud-gateway整合sentinel

  1. springcloud项目整合sentinel需要引入sentinel相关组件

    xml 复制代码
     <!-- SpringCloud Alibaba Sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
    
        <!-- SpringCloud Alibaba Sentinel Gateway -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
        </dependency>
  2. 因为我们选择将流控规则持久化到nacos中,所以还需要引入sentinel的nacos数据库插件依赖

    xml 复制代码
         <!-- Sentinel Datasource Nacos -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
  3. 配置sentinel控制台地址及nacos相关信息

    yml 复制代码
    spring:
      cloud:
        nacos:
          username: your-name
          password: your-pass
          discovery:
            # 服务注册地址
            server-addr: your-addr
            namespace: your-namespace
          config:
            # 配置中心地址
            server-addr: your-addr
            namespace: your-namespace
            # 配置文件格式
            file-extension: yml
       sentinel:
          # 取消控制台懒加载
          eager: true
          transport:
            # 控制台地址
            dashboard: your-dashboard-addr
         # nacos配置持久化
          datasource:
            ds1:
              nacos:
                server-addr: ${spring.cloud.nacos.discovery.server-addr}
                namespace: ${spring.cloud.nacos.discovery.namespace}
                username: ${spring.cloud.nacos.username}
                password: ${spring.cloud.nacos.password}
                # 限流规则的nacos配置名称
                dataId: sentinel-gateway
                groupId: DEFAULT_GROUP
                data-type: json
                # 规则类型:网关限流
                # 这是枚举类,其他类型可以在com.alibaba.cloud.sentinel.datasource.RuleType这个枚举类中查看
                rule-type: gw-flow
  4. 下载sentinel-dashboard的jar包【传送门】;执行启动命令:

    shell 复制代码
    java -Dserver.port=8088 -Dcsp.sentinel.dashboard.server=localhost:8088 -Dcsp.sentinel.app.type=1 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.8.jar

    还可以指定用户名及密码,通过-Dsentinel.dashboard.auth.username-Dsentinel.dashboard.auth.password配置,不配置默认用户名密码都是sentinel。

  5. 启动网关及sentinel控制台,查看网关是否已经注册,查看控制台是否已经有服务注册

2.问题现象

  • 上面一通操作之后,本地启动了个nacos,gateway和sentinel控制台看了一下,嗯,没问题,提交,发布测试
  • 测试环境使用k8s部署,部署完网关及sentinel控制台服务后,访问控制台。网关成功注册到nacos及sentinel控制台,再查看流控规则,居然没有读取到nacos中持久化的流控策略

3.原因猜测

  • 问题一出肯定要排查呀,百度了一下有没有人遇到相似的问题,百度的结果很多,但是都不符合我的情况。

  • 猜测问题应该出现在环境问题上。测试环境jdk和本地版本相同,nacos版本相同,sentinel-dashboard版本相同。可能问题出现在配置上。

  • 还记得我们上面怎么配置sentinel datasource的吗

    yml 复制代码
    	  datasource:
            ds1:
              nacos:
                server-addr: ${spring.cloud.nacos.discovery.server-addr}
                namespace: ${spring.cloud.nacos.discovery.namespace}
                username: ${spring.cloud.nacos.username}
                password: ${spring.cloud.nacos.password}
                # 限流规则的nacos配置名称
                dataId: sentinel-gateway
                groupId: DEFAULT_GROUP
                data-type: json
                # 规则类型:网关限流
                # 这是枚举类,其他类型可以在com.alibaba.cloud.sentinel.datasource.RuleType这个枚举类中查看
                rule-type: gw-flow

    因为我们是部署在k8s中,nacos的访问地址密码等都配置在环境变量中,相当于bootstrap.yml的配置的配置其实一直是本地的配置,而 ${spring.cloud.nacos.discovery.server-addr}这种配置方式是直接读取bootstrap.yml中的nacos地址,其他的属性也是如此,相当于读取的全部都是本地环境的nacos配置,导致sentinel读取配置文件失败。

  • 解决方法:springboot支持直接读取系统环境变量,配置方法也是${系统变量名},并开启了sentinel日志配置,配置如下:

    yml 复制代码
        sentinel:
      # 取消控制台懒加载
      eager: true
      transport:
        # 控制台地址
        dashboard: 127.0.0.1:8088
      # nacos配置持久化
      ds1:
        datasource:
          nacos:
            server-addr: ${NacosServerAddr}
            namespace: ${ConfigNamespace}
            username: ${username}
            password: ${password}
            dataId: sentinel-iwos-gateway
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: gw-flow
      log:
        dir: /usr/local/logs/iwos-gateway

    再次发布版本启动网关,查看sentinel控制台发现已经能够从nacos中读取持久化流控策略。问题到这里已经解决了,如果你和我一样好奇sentinel是如何从nacos配置文件中读取配置文件的话,那就继续往下看吧

4.源码分析

  1. sentinel控制台是从哪里获取的配置文件

    java 复制代码
    @GetMapping("/list.json")
    @AuthAction(AuthService.PrivilegeType.READ_RULE)
    public Result<List<GatewayFlowRuleEntity>> queryFlowRules(String app, String ip, Integer port) {
    
        // 上面是一些参数校验,不重要,可以不看
    
        try {
        	// 这里是获取配置的核心代码
        	// 简单就是网关将自己注册到sentinel时,携带自身的ip和端口(这里可以在启动项配置固定值,如果不配置就是默认本地的ip和网关sentinel端口)
        	// sentinel控制台访问网关集成的sentinel开放的端口,查询持久化在配置中心的配置文件
            List<GatewayFlowRuleEntity> rules = sentinelApiClient.fetchGatewayFlowRules(app, ip, port).get();
            repository.saveAll(rules);
            return Result.ofSuccess(rules);
        } catch (Throwable throwable) {
            logger.error("query gateway flow rules error:", throwable);
            return Result.ofThrowable(-1, throwable);
        }
    }

    所以其实配置文件不是dashboard控制台去读取的,而是从网关的sentinel开放的restapi中拉取。

    java 复制代码
        public CompletableFuture<List<GatewayFlowRuleEntity>> fetchGatewayFlowRules(String app, String ip, int port) {
        if (StringUtil.isBlank(ip) || port <= 0) {
            return AsyncUtils.newFailedFuture(new IllegalArgumentException("Invalid parameter"));
        }
    
        try {
        // FETCH_GATEWAY_FLOW_RULE_PATH: gateway/getRules
        // 这里就是通过rest请求获取配置文件
            return executeCommand(ip, port, FETCH_GATEWAY_FLOW_RULE_PATH, false)
                    .thenApply(r -> {
                        List<GatewayFlowRule> gatewayFlowRules = JSON.parseArray(r, GatewayFlowRule.class);
                        List<GatewayFlowRuleEntity> entities = gatewayFlowRules.stream().map(rule -> GatewayFlowRuleEntity.fromGatewayFlowRule(app, ip, port, rule)).collect(Collectors.toList());
                        return entities;
                    });
            } catch (Exception ex) {
                logger.warn("Error when fetching gateway flow rules", ex);
                return AsyncUtils.newFailedFuture(ex);
        	}
    	}

    控制台是请求网关gateway/getRules这个路径来获取配置文件的,完整的url为: http://ip:port/gateway/getRules

    来看看网关sentinel这个api是怎么工作的。

    java 复制代码
    @CommandMapping(
    name = "gateway/getRules",
    desc = "Fetch all gateway rules"
    )
    public class GetGatewayRuleCommandHandler implements CommandHandler<String> {
        public GetGatewayRuleCommandHandler() {
        }
    	// 获取配置文件
        public CommandResponse<String> handle(CommandRequest request) {
        	// 有效代码就是GatewayRuleManager.getRules()
            return CommandResponse.ofSuccess(JSON.toJSONString(GatewayRuleManager.getRules()));
        }
    }

    看一下GatewayRuleManager.getRules()

    java 复制代码
    public static Set<GatewayFlowRule> getRules() {
        Set<GatewayFlowRule> rules = new HashSet();
        // 这里是读的内存中的一个map,往下看这个map是GatewayRulePropertyListener在监听器中进行加载和更新的
        Iterator var1 = GATEWAY_RULE_MAP.values().iterator();
        while(var1.hasNext()) {
            Set<GatewayFlowRule> ruleSet = (Set)var1.next();
            rules.addAll(ruleSet);
        }
    
        return rules;
    }

    GatewayRulePropertyListener中的逻辑简单看下

    java 复制代码
    private static final class GatewayRulePropertyListener implements PropertyListener<Set<GatewayFlowRule>> {
        private GatewayRulePropertyListener() {
        }
    
        public void configUpdate(Set<GatewayFlowRule> conf) {
            this.applyGatewayRuleInternal(conf);
            RecordLog.info("[GatewayRuleManager] Gateway flow rules received: {}", new Object[]{GatewayRuleManager.GATEWAY_RULE_MAP});
        }
    
        public void configLoad(Set<GatewayFlowRule> conf) {
            this.applyGatewayRuleInternal(conf);
            RecordLog.info("[GatewayRuleManager] Gateway flow rules loaded: {}", new Object[]{GatewayRuleManager.GATEWAY_RULE_MAP});
        }
    }

    这里是通过监听器调用configLoad方法来初始化加载配置规则,但是哪里创建监听器,创建的什么监听器,从这里已经无法再看出来了。线索中断了。。。。。要放弃吗?不!小小监听器,拿下。既然这个方向已经查不到什么信息了,那么我们换一个思路,来看看网关这边是在哪里读取nacos配置信息,然后顺藤摸瓜应该就能找出到底是哪里读取的文件。

  2. sentinel是如何读取你的配置文件

    想要知道哪个类读取的你的配置文件,这里,使用IDEA的朋友们可以按住control,同时单击配置项,就能快速定位到引用配置文件。

    下面是我们定位到的地方:

    java 复制代码
    @ConfigurationProperties(
    	// 读取spring.cloud.sentinel下的所有属性配置
        prefix = "spring.cloud.sentinel"
    )
    public class SentinelProperties {
    	// 省略其他代码...
    	private Map<String, DataSourcePropertiesConfiguration> datasource;
    	// 省略其他代码
    	// 我们的配置文件就是映射到Map<String, DataSourcePropertiesConfiguration>中
    	// map整体结构就是{"ds1:":{"nacos":{....[配置的nacos属性]}}}
    	public void setDatasource(Map<String, DataSourcePropertiesConfiguration> datasource) {
        	this.datasource = datasource;
    	}
    }

    如果不是用IDEA开发的要怎么看呢,其实也有个通用的方法,

    去看resources中META-INFO目录下spring自动装配的类。sentinel入口jar包是spring-cloud-starter-alibaba-sentinel-2021.0.6.0.jar。META-INFO下有一个spring.factories文件,spring会在启动的时候加载这个文件中的工厂类。看看这个文件里有哪些:

    shell 复制代码
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.alibaba.cloud.sentinel.SentinelWebAutoConfiguration,\
    com.alibaba.cloud.sentinel.SentinelWebFluxAutoConfiguration,\
    com.alibaba.cloud.sentinel.endpoint.SentinelEndpointAutoConfiguration,\
    com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration,\
    com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration

    加载用户配置的是SentinelAutoConfiguration类。

    java 复制代码
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true)
    @EnableConfigurationProperties(SentinelProperties.class)
    public class SentinelAutoConfiguration {
    
    	@Value("${project.name:${spring.application.name:}}")
    	private String projectName;
    
    	@Autowired
    	private SentinelProperties properties;
    	// 其他代码省略
    	....
    }

    可以看到这里在创建bean的时候注入了SentinelProperties,也就是刚才我们通过IDEA定位到的配置类。

    两种方法都了解之后我们继续看SentinelProperties中的DataSourcePropertiesConfiguration

    java 复制代码
    public class DataSourcePropertiesConfiguration {
    	// 省略其他数据源的代码配置...
    	// 刚才的setDatasource就是拿到map中的value找到对应的属性类,也就是{"nacos":{...[配置的nacos属性]}}
    	// 从这里可以看出datasource的key是无所谓写什么值得,也就是配置类中的ds1.nacos可以替换成任意key.nacos
    	// 测试了一下改成datasource.nacos也完全没有问题
    	private NacosDataSourceProperties nacos;
    }

    nacos配置类

    java 复制代码
    public class NacosDataSourceProperties extends AbstractDataSourceProperties {
    	// 这些都是sentinel可以配置的nacos配置项
    	private String serverAddr;
    
    	private String contextPath;
    
    	private String username;
    
    	private String password;
    
    	@NotEmpty
    	private String groupId = "DEFAULT_GROUP";
    
    	@NotEmpty
    	private String dataId;
    
    	private String endpoint;
    
    	private String namespace;
    
    	private String accessKey;
    
    	private String secretKey;
    
    	public NacosDataSourceProperties() {
    		super(NacosDataSourceFactoryBean.class.getName());
    	}
    	// 预先检查,如果地址为空就设置一个默认的nacos地址
    	@Override
    	public void preCheck(String dataSourceName) {
    		if (StringUtils.isEmpty(serverAddr)) {
    			serverAddr = this.getEnv().getProperty(
    					"spring.cloud.sentinel.datasource.nacos.server-addr",
    					"127.0.0.1:8848");
    		}
    	}
    }

    nacos配置类就是我们常规的配置,还记得sentinel是在哪个key下找到nacos的配置吗,可以回去看看sentinelProperties。它从spring.cloud.sentinel下读取一个map作为数据源。这个map的结构是Map<String, DataSourcePropertiesConfiguration> datasource。也就是spring会把spring.cloud.sentinel.任意key下的配置全部映射到DataSourcePropertiesConfiguration中。如果你配置的是nacos数据源,那么就需要将nacos配置在spring.cloud.sentinel.任意key.nacos下,spring会将属性值自动映射到名为nacos的成员变量上,这个成员变量的类就是NacosDataSourceProperties

4. 结语

好了,到这里sentinel已经全部拿到nacos的配置信息了,后面就是调用nacos rpc接口进行认证及获取配置文件。如果觉得有用就点个赞吧

相关推荐
2401_8337880519 分钟前
Scala的模式匹配(2)
java·开发语言
宋冠巡1 小时前
Spring Boot Validation 封装自定义校验注解和校验器(validation-spring-boot-starter)
spring boot·参数校验·validation
悠悠龙龙2 小时前
框架模块说明 #05 权限管理_03
java·开发语言·spring
开心羊咩咩3 小时前
Idea 2024.3 突然出现点击run 运行没有反应,且没有任何提示。
java·ide·intellij-idea
waterme1onY3 小时前
IDEA中MAVEN的一些设置问题
java·maven·intellij-idea
阿华的代码王国3 小时前
【算法】——前缀和(矩阵区域和详解,文末附)
java·开发语言·算法·前缀和
梦.清..3 小时前
面向对象(二)——类和对象(上)
java
Mercury_@224 小时前
JAVA设计模式,责任链模式
java·设计模式
happycao1234 小时前
常见限流算法介绍 和 Spring Cloud Sentinel使用方式
spring cloud·sentinel·限流算法
不修×蝙蝠4 小时前
数据结构--二叉树的创建和遍历
java·数据结构·二叉树·深度遍历·广度遍历·迭代法·递归法