(四)springCloud2021整合nacos配置中心

1. 前言

1.1 本文将迭代4与5点合并

1.2 为什么使用nacos作为配置中心

        动态配置服务:可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置,因此可用来解决上一篇文章末尾的疑问,sentinel无法动态配置。

动态DNS服务:支持自定义配置权重路由,更容易地实现中间层负载均衡

2. 项目迭代历程

  1. 以springboot搭建spring-authorization-server(即认证与资源服务器)

  2. 升级为springcloud2021.0.x框架,引入nacos作为注册中心

  3. 引入sentinel,进行限流降级熔断配置

  4. 引入nacos配置中心

  5. 升级sentinel,配合nacos,使项目能动态配置落地所有Feign的降级配置

  6. 引入gateway网关,swagger文档工具

  7. 待续

3.项目接入

        pom.xml中引入相关依赖

        因为新版cloud对nacos配置做了些许改变,因此引入依赖spring-cloud-starter-bootstrap

        参考地址:SpringCloud项目无法读取bootstrap.yml配置文件的解决办法_jackob-94的博客-CSDN博客_读取不到bootstrap.yml



    com.alibaba.cloud
    spring-cloud-starter-alibaba-nacos-config
    2021.0.1.0




    org.springframework.cloud
    spring-cloud-starter-bootstrap
    3.1.2

        修改项目application配置文件

        名字改为bootstrap,增加以下内容

spring:
  cloud:
    nacos:
      config:
        server-addr: 192.168.1.69:8848
        namespace: 0f0fe57f-d908-48c3-b5a5-78f12a8411ce
        file-extension: yaml
        prefix: user-server
        shared-dataids: base.yaml

4. 升级sentinel,配合nacos,动态配置落地所有Feign的降级配置

        增加bootstrap配置信息(我这里指定了命名空间,可使用默认的

spring:
  cloud:
    sentinel:
      datasource:
        ds:
          nacos:
            server-addr: 192.168.1.69:8848
            namespace: 0f0fe57f-d908-48c3-b5a5-78f12a8411ce
            dataId: ${spring.application.name}_FEIGN_RULES
            groupId: DEFAULT_GROUP
            rule-type: degrade

        动态配置代码

        代码较长,总结了大致思路。新建AutoLoadFeignFuseConfig 配置类,此类功能为,动态获取当前项目下所有带@FeignClient的接口,读取其中各接口,然后根据自定义的命名规则,以及自定义的熔断配置模板进行生成配置文件,存到nacos上。且保存到nacos前,先拉去nacos上的原有配置,手动进行合并。

/**
 * @author zxg
 *
 * 自动加载全局Feign接口,进行全局降级熔断配置
 *
 * 参考地址1:https://juejin.cn/post/6933072764395847693
 * 仓靠地址2:https://blog.csdn.net/dawnsun2013/article/details/124328651
 */

@Component
@Slf4j
public class AutoLoadFeignFuseConfig implements ApplicationRunner , EnvironmentAware {
    @Autowired
    private NacosConfigManager nacosConfigManager;

    private Environment environment;

    @Value("${spring.application.name}")
    private String applicationName;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    private static final String GROUP_ID = "DEFAULT_GROUP";
    private static final String DATA_ID = "FEIGN_RULES";
    private final static String HTTP_PROTOCOL_PREFIX = "http://";
    private static final String DYNAMIC_VALUE_PREFIX = "${";
    private static final String DYNAMIC_VALUE_SUFFIX = "}";

    private String getDataId(){
        return applicationName + "_" + DATA_ID;
    }

    /**
     * 加载项目中feign,初始化降级规则
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 获取启动类对象
        Class mainClass = deduceMainApplication();

        log.info("==============开始加载Feign默认规则");
        if (mainClass == null){
            throw new RuntimeException("没有发现main主程序类");
        }

        EnableFeignClients enableFeignClients = mainClass.getAnnotation(EnableFeignClients.class);

        // 定义feign所在文件夹目录
        String[] feignClientPackages;

        if (enableFeignClients != null){
            String[] feignClientDeclaredPackages = enableFeignClients.basePackages();
            // 声明feign的包名
            if (feignClientDeclaredPackages.length == 0){
                feignClientPackages = new String[]{mainClass.getPackage().getName()};
            }else {
                feignClientPackages = feignClientDeclaredPackages;
            }
            // 初始化降级规则
            initDegradeRule(feignClientPackages);
        }

        log.info("==============Feign降级处理完成");
    }

    /**
     * 获取启动类对象
     */
    private Class deduceMainApplication(){
        try {
            StackTraceElement[] stackTraceElements = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTraceElements) {
                if ("main".equals(stackTraceElement.getMethodName())){
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException e) {
            log.warn("获取启动类对象失败:" + e.getMessage());
        }

        return null;
    }

    /**
     * 初始化降级规则
     * @param feignClientPackages feign所在包数组
     */
    private void initDegradeRule(String[] feignClientPackages) {
        List localDegradeRuleList = new ArrayList<>();
        Set feignClientClazz = getFeignClientClazz(feignClientPackages);

        // 生成本地配置
        for (Class clientClazz : feignClientClazz) {
            List rules = initRules(clientClazz);
            localDegradeRuleList.addAll(rules);
        }

        // 获取nacos上远程规则
        List remoteDegradeRuleList = fetchRemoteRules();

        // 若远程配置不存在,直接上传本地配置
        if (remoteDegradeRuleList == null || remoteDegradeRuleList.isEmpty()){
            // 上传配置到nacos
            pushRules(localDegradeRuleList);
            return;
        }

        // 本地规则 合并 远程规则策略
        proess(localDegradeRuleList, remoteDegradeRuleList);
        // 上传配置到nacos
        pushRules(localDegradeRuleList);
    }

    /**
     * 获取nacos上远程规则
     */
    private List fetchRemoteRules() {
        try {
            return JSONObject.parseArray(nacosConfigManager.getConfigService().getConfig(getDataId(), GROUP_ID, 10000L), DegradeRule.class);
        } catch (NacosException e) {
            log.error("获取远程Feign配置失败:" + e.getErrMsg());
        }
        return new ArrayList<>();
    }

    /**
     * 上传配置到nacos
     */
    private void pushRules(List rules){
        try {
            String contentStr = JSONUtil.toJsonStr(rules);
            nacosConfigManager.getConfigService().publishConfig(getDataId(), GROUP_ID, contentStr);
        } catch (NacosException e) {
            log.error("上传Feign降级熔断配置信息到nacos失败");
        }
    }

    /**
     * 本地规则 合并 远程规则策略
     * @param localDegradeRuleList 本地配置
     * @param remoteDegradeRuleList 远程配置
     */
    private void proess(List localDegradeRuleList, List remoteDegradeRuleList) {
        for (DegradeRule rule : remoteDegradeRuleList) {
            if (localDegradeRuleList.contains(rule)) {
                DegradeRule ldr = localDegradeRuleList.get(localDegradeRuleList.indexOf(rule));
                if (ldr.equals(rule)) {
                    continue;
                }
                localDegradeRuleList.remove(ldr);
                localDegradeRuleList.add(rule);
            } else {
                localDegradeRuleList.add(rule);
            }
        }
    }

    /**
     * 扫描Feign的包路径,获取类对象
     * @param packageNames 包路径
     */
    private Set getFeignClientClazz(String[] packageNames){
        Set feignClientClazz = new HashSet<>();
        for (String packageName : packageNames) {
            packageName = packageName.replace(".**","");
            Set> classes = ClassScanner.scanAllPackageByAnnotation(packageName, FeignClient.class);
            feignClientClazz.addAll(classes);
        }

        return feignClientClazz;
    }

    /**
     * 初始化规则
     */
    public List initRules(Class clazz){
        List degradeRuleList = new ArrayList<>();
        FeignClient feignClient = (FeignClient) clazz.getAnnotation(FeignClient.class);

//        String url = feignClient.url();
//        String serverName = "";
//        if (StringUtils.isNotBlank(url)){
//            if (url.startsWith(DYNAMIC_VALUE_PREFIX) && url.endsWith(DYNAMIC_VALUE_SUFFIX)){
//                url = this.environment.resolvePlaceholders(url);
//            }
//        }else {
//            serverName = feignClient.name();
//        }

        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
//            degradeRuleList.add(buildDegradeRule(getResourceName(url, serverName, method)));
            degradeRuleList.add(buildDegradeRule(getResourceName(method)));
        }

        DegradeRuleManager.loadRules(degradeRuleList);
        return degradeRuleList;
    }

    /**
     * 获取资源服务器名
     */
    private String getResourceName(Method method) {
        String resourceName = "";
        RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
        if (null != methodRequestMapping) {
            String mrm = methodRequestMapping.value()[0];
            resourceName = (mrm.startsWith("/") ? mrm : "/" + mrm);
        }

        GetMapping methodGetMapping = method.getAnnotation(GetMapping.class);
        if (null != methodGetMapping) {
            String mpm = methodGetMapping.value()[0];
            resourceName = (mpm.startsWith("/") ? mpm : "/" + mpm);
        }

        PostMapping methodPostMapping = method.getAnnotation(PostMapping.class);
        if (null != methodPostMapping) {
            String mpm = methodPostMapping.value()[0];
            resourceName = (mpm.startsWith("/") ? mpm : "/" + mpm);
        }
        return resourceName;
    }

    /**
     * 构建熔断规则
     */
    private DegradeRule buildDegradeRule(String resourceName) {
        DegradeRule rule = new DegradeRule();
        //设置资源名
        rule.setResource(resourceName);
        //设置降级规则 TR 10 ms
        rule.setCount(200);
        // 规则类型 RT
        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
        // 窗口时间
        rule.setTimeWindow(10);
        rule.setMinRequestAmount(3);
        rule.setStatIntervalMs(30000);
        rule.setSlowRatioThreshold(0.6);
        return rule;
    }


}

        启动项目,自动生成配置到nacos,查看nacos

(四)springCloud2021整合nacos配置中心_第1张图片

        因为项目配置文件中配置了相关代码,因此sentinel会自动拉去配置文件,生成对应降级策略,如下图

(四)springCloud2021整合nacos配置中心_第2张图片

5. 总结

        要点主要是如何获取当前项目的所有feign接口,包括所依赖的jar包下,可以通过反射,也可以通过其他方法。找到后组装成sentinel需要的类存入nacos,然后通过配置文件生成对应的降级熔断策略

        后续整合gateway+swagger接口文档部分,请关注后续部分

你可能感兴趣的:(cloud,nacos,eureka,java,云原生)