您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

Spring Cloud Nacos配置中心实现原理

时间:2022-06-13 16:03:18  来源:  作者:Spring全家桶实战案例

环境:Springboot2.3.12.RELEASE + Spring Cloud Alibaba2.2.5.RELEASE + Spring Cloud Hoxton.SR12

应用的核心技术是:自定义PropertySourceLocator,然后配置spring.factories

在如下包中配置:


spring-cloud-context-xxx.jar中

org.springframework.cloud.bootstrap.BootstrapConfiguration=
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration

Nacos加载配置的时候默认会通过一下三个DatAId加载数据:

核心类:
NacosPropertySourceLocator

  1. dataId=nacos-config, tenant(namespace)=7205e694-ac51-4ac1-bbe9-87c28689b88a, group=HisGroup
  2. loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
    fileExtension, true);
  3. dataId=nacos-config.properties, tenant=7205e694-ac51-4ac1-bbe9-87c28689b88a, group=HisGroup
  4. loadNacosDataIfPresent(compositePropertySource,
    dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
  5. 通过定义的profile
  6. private static final String NACOS_PROPERTY_SOURCE_NAME = "NACOS";
    private static final String SEP1 = "-";
    private static final String DOT = ".";
    for (String profile : environment.getActiveProfiles()) {
    String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
    loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
    fileExtension, true);
    }

自动配置核心

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {
    // Nacos Config相关的配置属性
    @Bean
    @ConditionalOnMissingBean
    public NacosConfigProperties nacosConfigProperties() {
        return new NacosConfigProperties();
    }
    
    // 管理Nacos Config相关的服务
    @Bean
    @ConditionalOnMissingBean
    public NacosConfigManager nacosConfigManager(
            NacosConfigProperties nacosConfigProperties) {
        return new NacosConfigManager(nacosConfigProperties);
    }
    
    // 核心类自定义启动加载配置文件(Bootstraps)
    @Bean
    public NacosPropertySourceLocator nacosPropertySourceLocator(
            NacosConfigManager nacosConfigManager) {
        return new NacosPropertySourceLocator(nacosConfigManager);
    }
}
org.springframework.cloud.bootstrap.BootstrapConfiguration=
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration

自定义属性源

public class NacosPropertySourceLocator implements PropertySourceLocator {
    private NacosPropertySourceBuilder nacosPropertySourceBuilder;
    private NacosConfigProperties nacosConfigProperties;
    private NacosConfigManager nacosConfigManager;
    public NacosPropertySourceLocator(NacosConfigManager nacosConfigManager) {
        this.nacosConfigManager = nacosConfigManager;
        this.nacosConfigProperties = nacosConfigManager.getNacosConfigProperties();
    }
    public PropertySource<?> locate(Environment env) {
        nacosConfigProperties.setEnvironment(env);
        // 关键通过NacosConfigManager获取ConfigService服务
        ConfigService configService = nacosConfigManager.getConfigService();
        // ...
        long timeout = nacosConfigProperties.getTimeout();
        nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
                timeout);
        // 下面这些设置就是对应从配置文件中获取
        String name = nacosConfigProperties.getName();
        String dataIdPrefix = nacosConfigProperties.getPrefix();
        if (StringUtils.isEmpty(dataIdPrefix)) {
            dataIdPrefix = name;
        }
        if (StringUtils.isEmpty(dataIdPrefix)) {
            dataIdPrefix = env.getProperty("spring.Application.name");
        }
        CompositePropertySource composite = new CompositePropertySource(
                NACOS_PROPERTY_SOURCE_NAME);// NACOS_PROPERTY_SOURCE_NAME = NACOS
        // 加载共享配置
        loadSharedConfiguration(composite);
        // 加载扩展配置
        loadExtConfiguration(composite);
        // 加载应用程序配置(这里就以应用程序配置,深入查看)
        loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);

        return composite;
    }
}

获取配置服务

ConfigService配置服务是获取配置信息的核心方法:

public interface ConfigService {
    
    // 获取配置
    String getConfig(String dataId, String group, long timeoutMs) throws NacosException;
    
    // 获取配置并设置监听
    String getConfigAndSignListener(String dataId, String group, long timeoutMs, Listener listener)
            throws NacosException;
    
    // 给配置添加监听
    void addListener(String dataId, String group, Listener listener) throws NacosException;
    
    // 发布配置
    boolean publishConfig(String dataId, String group, String content) throws NacosException;
    
    // 删除配置
    boolean removeConfig(String dataId, String group) throws NacosException;
    
    // 删除监听
    void removeListener(String dataId, String group, Listener listener);
    
    // 获取服务状态
    String getServerStatus();
    
    // 关闭资源服务
    void shutDown() throws NacosException;
}

NacosConfigManager

public class NacosConfigManager {
    private static ConfigService service = null;
    private NacosConfigProperties nacosConfigProperties;
    public NacosConfigManager(NacosConfigProperties nacosConfigProperties) {
        this.nacosConfigProperties = nacosConfigProperties;
        // 创建配置服务
        createConfigService(nacosConfigProperties);
    }
    static ConfigService createConfigService(
            NacosConfigProperties nacosConfigProperties) {
        if (Objects.isNull(service)) {
            synchronized (NacosConfigManager.class) {
                try {
                    if (Objects.isNull(service)) {
                        // 通过工厂创建服务
                        // assembleConfigServiceProperties方法
                        // 就是收集关于Nacos所有配置信息,如:地址,端口,用户名,密码等
                        // 详细查看NacosConfigProperties#assembleConfigServiceProperties
                        service = NacosFactory.createConfigService(
                                nacosConfigProperties.assembleConfigServiceProperties());
                    }
                }
                // ...
            }
        }
        return service;
    }
}
// NacosFactory
public class NacosFactory {
    public static ConfigService createConfigService(Properties properties) throws NacosException {
        return ConfigFactory.createConfigService(properties);
    }
}
public class ConfigFactory {
    public static ConfigService createConfigService(Properties properties) throws NacosException {
        try {
            Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.config.NacosConfigService");
            Constructor constructor = driverImplClass.getConstructor(Properties.class);
            ConfigService vendorImpl = (ConfigService) constructor.newInstance(properties);
            // 通过反射构造了NacosConfigservice,同时设置属性信息
            // 这些属性信息就是一些Naocs服务的地址,端口,用户名,密码等信息
            return vendorImpl;
        } catch (Throwable e) {
            throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
        }
    }
}

到此就得到了一个ConfigService服务类NacosConfigService。

获取应用程序配置

接着95.2获取到了ConfigService以后继续执行

public class NacosPropertySourceLocator implements PropertySourceLocator {
    private NacosPropertySourceBuilder nacosPropertySourceBuilder;
    public PropertySource<?> locate(Environment env) {
        nacosConfigProperties.setEnvironment(env);
        // 关键通过NacosConfigManager获取ConfigService服务
        ConfigService configService = nacosConfigManager.getConfigService();
        // ...
        nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
                timeout);
        CompositePropertySource composite = new CompositePropertySource(
                NACOS_PROPERTY_SOURCE_NAME);
        loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
        return composite;
    }
    private void loadApplicationConfiguration(
            CompositePropertySource compositePropertySource, String dataIdPrefix,
            NacosConfigProperties properties, Environment environment) {
        // 获取文件扩展
        String fileExtension = properties.getFileExtension();
        // 获取分组
        String nacosGroup = properties.getGroup();
        // ...
        // load with suffix, which have a higher priority than the default
        // 这里就以这里为例
        loadNacosDataIfPresent(compositePropertySource,
                dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
        // Loaded with profile, which have a higher priority than the suffix
        // 这里会根据不同配置的profiles再次加载不同环境的配置
        for (String profile : environment.getActiveProfiles()) {
            String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
            loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
                    fileExtension, true);
        }

    }
    private void loadNacosDataIfPresent(final CompositePropertySource composite,
            final String dataId, final String group, String fileExtension,
            boolean isRefreshable) {
        if (null == dataId || dataId.trim().length() < 1) {
            return;
        }
        if (null == group || group.trim().length() < 1) {
            return;
        }
        // 加载Nacos属性源
        NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
                fileExtension, isRefreshable);
        this.addFirstPropertySource(composite, propertySource, false);
    }
    private NacosPropertySource loadNacosPropertySource(final String dataId,
            final String group, String fileExtension, boolean isRefreshable) {
        // ...
        return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
                isRefreshable);
    }
    private void addFirstPropertySource(final CompositePropertySource composite,
            NacosPropertySource nacosPropertySource, boolean ignoreEmpty) {
        if (null == nacosPropertySource || null == composite) {
            return;
        }
        if (ignoreEmpty && nacosPropertySource.getSource().isEmpty()) {
            return;
        }
        composite.addFirstPropertySource(nacosPropertySource);
    }
}
// 这里面开始加载数据
public class NacosPropertySourceBuilder {
    NacosPropertySource build(String dataId, String group, String fileExtension,
            boolean isRefreshable) {
        Map<String, Object> p = loadNacosData(dataId, group, fileExtension);
        NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId,
                p, new Date(), isRefreshable);
        NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
        return nacosPropertySource;
    }
    // 加载数据
    private Map<String, Object> loadNacosData(String dataId, String group,
            String fileExtension) {
        String data = null;
        try {
            // 通过ConfigService加载配置内容(从远程服务获取)
            data = configService.getConfig(dataId, group, timeout);
            if (StringUtils.isEmpty(data)) {
                return EMPTY_MAP;
            }
            Map<String, Object> dataMap = NacosDataParserHandler.getInstance()
                    .parseNacosData(data, fileExtension);
            return dataMap == null ? EMPTY_MAP : dataMap;
        }
        // ...
        return EMPTY_MAP;
    }
}
public class NacosConfigService implements ConfigService {
    public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
        return getConfigInner(namespace, dataId, group, timeoutMs);
    }
    private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
        group = null2defaultGroup(group);
        ParamUtils.checkKeyParam(dataId, group);
        ConfigResponse cr = new ConfigResponse();
        
        cr.setDataId(dataId);
        cr.setTenant(tenant);
        cr.setGroup(group);
        
        // 这里会从缓存文件中获取,如果能获取就不会再从远程加载了
        // 会从如下缓存目录下加载配置:
        // System.getProperty("JM.SNAPSHOT.PATH", 
        // System.getProperty("user.home")) + File.separator + "nacos" 
        // + File.separator + "config"
        String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
        if (content != null) {
            cr.setContent(content);
            configFilterChainManager.doFilter(null, cr);
            content = cr.getContent();
            return content;
        }
        
        try {
            // 缓存总无法获取则从远程服务上拉取数据
            String[] ct = worker.getServerConfig(dataId, group, tenant, timeoutMs);
            cr.setContent(ct[0]);
            
            configFilterChainManager.doFilter(null, cr);
            content = cr.getContent();
            
            return content;
        } catch (NacosException ioe) {
            if (NacosException.NO_RIGHT == ioe.getErrCode()) {
                throw ioe;
            }
        }
        content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);
        cr.setContent(content);
        configFilterChainManager.doFilter(null, cr);
        content = cr.getContent();
        return content;
    }
}
public class ClientWorker implements Closeable {
     // 这里就是从远程服务拉取配置
     public String[] getServerConfig(String dataId, String group, String tenant, long readTimeout)
            throws NacosException {
        String[] ct = new String[2];
        if (StringUtils.isBlank(group)) {
            group = Constants.DEFAULT_GROUP;
        }
        
        HttpRestResult<String> result = null;
        try {
            Map<String, String> params = new HashMap<String, String>(3);
            if (StringUtils.isBlank(tenant)) {
                params.put("dataId", dataId);
                params.put("group", group);
            } else {
                params.put("dataId", dataId);
                params.put("group", group);
                params.put("tenant", tenant);
            }
            result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);
        } catch (Exception ex) {
            throw new NacosException(NacosException.SERVER_ERROR, ex);
        }
        
        switch (result.getCode()) {
            case HttpURLConnection.HTTP_OK:
                // 获取成功后会将数据保存到缓存目录下
                LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.getData());
                ct[0] = result.getData();
                if (result.getHeader().getValue(CONFIG_TYPE) != null) {
                    ct[1] = result.getHeader().getValue(CONFIG_TYPE);
                } else {
                    ct[1] = ConfigType.TEXT.getType();
                }
                return ct;
            case HttpURLConnection.HTTP_NOT_FOUND:
                LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, null);
                return ct;
            case HttpURLConnection.HTTP_CONFLICT: {
                throw new NacosException(NacosException.CONFLICT,
                        "data being modified, dataId=" + dataId + ",group=" + group + ",tenant=" + tenant);
            }
                throw new NacosException(result.getCode(), result.getMessage());
            }
            default: {
                throw new NacosException(result.getCode(),
                        "http error, code=" + result.getCode() + ",dataId=" + dataId + ",group=" + group + ",tenant="
                                + tenant);
            }
        }
    }
}

到此就完成了远程配置数据的加载

总结:Nacos Config先从本地缓存中获取数据,如果不能获取才从远程拉取(如果远程服务无法连接,那么会不断重试,前提是本地能够加载到数据,否则服务将无法正常启动)。

完毕!!!



Tags:Spring Cloud   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
监控 Spring Cloud 微服务的实践方案
一、简介Spring Cloud是一个基于Spring Boot实现的微服务框架,它提供了丰富的微服务功能,如分布式配置、服务注册与发现、服务熔断、负载均衡等。为了更好地管理和监控这样复...【详细内容】
2023-12-19  Search: Spring Cloud  点击:(152)  评论:(0)  加入收藏
彻底解决Spring Cloud Gateway中Body读取问题
在构建微服务架构时,Spring Cloud Gateway作为一个重要的微服务网关,经常需要在过滤器(Filter)中对POST请求的Body内容进行操作,如日志记录、签名验证和权限验证等。然而,由于Requ...【详细内容】
2023-11-28  Search: Spring Cloud  点击:(169)  评论:(0)  加入收藏
Spring Cloud 实现分布式实时日志分析采集的三种方案
ELK 已经成为目前最流行的集中式日志解决方案,它主要是由Beats、Logstash、Elasticsearch、Kibana等组件组成,来共同完成实时日志的收集,存储,展示等一站式的解决方案。本文将会...【详细内容】
2023-11-27  Search: Spring Cloud  点击:(194)  评论:(0)  加入收藏
十个使用Spring Cloud和Java创建微服务的实践案例
在使用Java构建微服务时,许多人认为只要学习一些微服务设计模式就足够了,比如CQRS、SAGA或每个微服务一个数据库。虽然这是正确的,但同时学习一些通用的最佳实践也是很有意义的...【详细内容】
2023-11-10  Search: Spring Cloud  点击:(211)  评论:(0)  加入收藏
四个步骤,掌握Spring Cloud Stream
随着微服务和云原生应用程序的不断发展,事件驱动架构成为了一种实现微服务之间高效通信的主流方法。在Java Spring生态系统中,Spring Cloud Stream是一个专门为事件驱动、云原...【详细内容】
2023-10-13  Search: Spring Cloud  点击:(220)  评论:(0)  加入收藏
Spring Cloud 远程调用 OpenFeign 这些知识点,能颠覆你的认知!
环境:SpringBoot2.7.12 + Spring Cloud2021.0.71. 概述Spring Cloud Openfeign是一种声明式、模板化的HTTP客户端,主要用于在Spring Cloud微服务架构中进行服务调用。相比于传...【详细内容】
2023-10-13  Search: Spring Cloud  点击:(318)  评论:(0)  加入收藏
Spring Cloud微服务架构:构建弹性和可伸缩的云原生应用
Spring Cloud是一个开源的微服务架构框架,它基于Spring Boot构建,通过一系列的组件和工具,为开发人员提供了快速构建弹性和可伸缩的云原生应用的能力。Spring Cloud的核心概念1...【详细内容】
2023-10-12  Search: Spring Cloud  点击:(328)  评论:(0)  加入收藏
你了解Spring Cloud的这些组件吗?
今天,我要和大家一起探讨一个热门的话题:Spring Cloud。如果你在Java领域有一定的开发经验,那么你一定听说过Spring Cloud,它是一套用于构建分布式系统的开发工具,今天,我们将深入...【详细内容】
2023-09-25  Search: Spring Cloud  点击:(295)  评论:(0)  加入收藏
Spring Cloud 微服务系列之 ShardingSphere-Proxy 数据库代理
ShardingSphere-Proxy是在数据库和应用程序之间起到了一个桥梁的作用,对于应用程序来说,它不需要感知ShardingSphere-Proxy的存在,依然可以使用原来的方式操作数据库。1. 简介S...【详细内容】
2023-09-19  Search: Spring Cloud  点击:(213)  评论:(0)  加入收藏
Spring Cloud Gateway提供的简易网关实现方式,你使用过吗?
环境:SpringBoot2.5.13Spring Cloud Gateway提供了一个名为ProxyExchange的实用程序对象。你可以在常规Spring web处理程序中使用它作为方法参数。它通过镜像HTTP动词的方法...【详细内容】
2023-09-15  Search: Spring Cloud  点击:(210)  评论:(0)  加入收藏
▌简易百科推荐
Qt与Flutter:在跨平台UI框架中哪个更受欢迎?
在跨平台UI框架领域,Qt和Flutter是两个备受瞩目的选择。它们各自具有独特的优势,也各自有着广泛的应用场景。本文将对Qt和Flutter进行详细的比较,以探讨在跨平台UI框架中哪个更...【详细内容】
2024-04-12  刘长伟    Tags:UI框架   点击:(7)  评论:(0)  加入收藏
Web Components实践:如何搭建一个框架无关的AI组件库
一、让人又爱又恨的Web ComponentsWeb Components是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的Web应用程序中重复使用,并且...【详细内容】
2024-04-03  京东云开发者    Tags:Web Components   点击:(11)  评论:(0)  加入收藏
Kubernetes 集群 CPU 使用率只有 13% :这下大家该知道如何省钱了
作者 | THE STACK译者 | 刘雅梦策划 | Tina根据 CAST AI 对 4000 个 Kubernetes 集群的分析,Kubernetes 集群通常只使用 13% 的 CPU 和平均 20% 的内存,这表明存在严重的过度...【详细内容】
2024-03-08  InfoQ    Tags:Kubernetes   点击:(23)  评论:(0)  加入收藏
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  风舞凋零叶    Tags:Spring Security   点击:(61)  评论:(0)  加入收藏
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  贝格前端工场    Tags:框架   点击:(51)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  程序员wayn  微信公众号  Tags:Spring   点击:(41)  评论:(0)  加入收藏
开发者的Kubernetes懒人指南
你可以将本文作为开发者快速了解 Kubernetes 的指南。从基础知识到更高级的主题,如 Helm Chart,以及所有这些如何影响你作为开发者。译自Kubernetes for Lazy Developers。作...【详细内容】
2024-02-01  云云众生s  微信公众号  Tags:Kubernetes   点击:(58)  评论:(0)  加入收藏
链世界:一种简单而有效的人类行为Agent模型强化学习框架
强化学习是一种机器学习的方法,它通过让智能体(Agent)与环境交互,从而学习如何选择最优的行动来最大化累积的奖励。强化学习在许多领域都有广泛的应用,例如游戏、机器人、自动驾...【详细内容】
2024-01-30  大噬元兽  微信公众号  Tags:框架   点击:(71)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  HELLO程序员  微信公众号  Tags:Spring   点击:(94)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19   Java中文社群  微信公众号  Tags:SpringBoot   点击:(91)  评论:(0)  加入收藏
站内最新
站内热门
站内头条