Nacos Config源码剖析

2023-03-06  本文已影响0人  王侦

1.Nacos启动从数据库加载配置文件并存储到本地磁盘上

ExternalDumpService#init

客户端的处理

1.1 推拉模型

ConfigChangeNotifyRequest客户端处理的handler:

listenExecutebell阻塞队列的处理:

流程是:
NacosConfigService构造方法
-> ClientWorker构造方法
-> agent.start()
-> ClientWorker.ConfigRpcTransportClient#startInternal

1.2 客户端定时拉取配置信息

ClientWorker.ConfigRpcTransportClient#executeConfigListen

服务端的处理,注意服务端不是去查mysql,而是去查nacos本地磁盘文件,所以直接修改mysql配置是不会触发客户端配置变更的。

1.3 服务启动添加对配置的监听器

SpringBootApplication#run

NacosContextRefresher#registerNacosListener

    private void registerNacosListener(final String groupKey, final String dataKey) {
        String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
        Listener listener = listenerMap.computeIfAbsent(key,
                lst -> new AbstractSharedListener() {
                    @Override
                    public void innerReceive(String dataId, String group,
                            String configInfo) {
                        refreshCountIncrement();
                        nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
                        applicationContext.publishEvent(
                                new RefreshEvent(this, null, "Refresh Nacos config"));
                        if (log.isDebugEnabled()) {
                            log.debug(String.format(
                                    "Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
                                    group, dataId, configInfo));
                        }
                    }
                });
        try {
            configService.addListener(dataKey, groupKey, listener);
        }

1.4 事件驱动模型NotifyCenter

NotifyCenter#publishEvent

事件处理:

事件驱动模型和事件监听(ApplicationListener遵循JDK规范)有啥区别?

2.用户通过nacos-console控制台修改配置,并发布

ConfigController#publishConfig

问题:
1)变更配置,只会通知nacos集群一个服务器?
2)拉取配置,也只会找nacos集群任意一台吗?

2.1 任务执行引擎

NacosTask
NacosTaskProcessor
NacosTaskExecuteEngine(管理processor和task)

3.应用启动期间从Nacos Config拉取配置

在创建子容器SpringBoot容器prepareContext()阶段,调用PropertySourceBootstrapConfiguration#Initialize()会加载配置并合并到CompositePropertySource。

NacosPropertySourceLocator#locate会从Nacos Config配置中心拉取配置。

具体的流程:

4.动态配置@RefreshScope

4.1 @RefreshScope相关Bean创建

启动流程:

AbstractBeanFactory#doGetBean

                if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, () -> {
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            // Explicitly remove instance from singleton cache: It might have been put there
                            // eagerly by the creation process, to allow for circular reference resolution.
                            // Also remove any beans that received a temporary reference to the bean.
                            destroySingleton(beanName);
                            throw ex;
                        }
                    });
                    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }

                else if (mbd.isPrototype()) {
                    // It's a prototype -> create a new instance.
                    Object prototypeInstance = null;
                    try {
                        beforePrototypeCreation(beanName);
                        prototypeInstance = createBean(beanName, mbd, args);
                    }
                    finally {
                        afterPrototypeCreation(beanName);
                    }
                    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
                }

                else {
                    String scopeName = mbd.getScope();
                    if (!StringUtils.hasLength(scopeName)) {
                        throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
                    }
                    Scope scope = this.scopes.get(scopeName);
                    if (scope == null) {
                        throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                    }
                    try {
                        Object scopedInstance = scope.get(beanName, () -> {
                            beforePrototypeCreation(beanName);
                            try {
                                return createBean(beanName, mbd, args);
                            }
                            finally {
                                afterPrototypeCreation(beanName);
                            }
                        });
                        bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                    }

@RefreshScope注解的bean存在哪里?是个什么形式?

存在RefreshScope的cache中。

@RestController
@RefreshScope  //动态感知修改后的值
public class TestController implements ApplicationListener<RefreshScopeRefreshedEvent>{

调用@RefreshScope注解的bean,是从哪里获取?

@RefreshScope的bean销毁后,重新获取,是在哪里触发的?

在哪里创建RefreshScope动态代理对象?

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RefreshScope.class)
@ConditionalOnProperty(name = RefreshAutoConfiguration.REFRESH_SCOPE_ENABLED,
        matchIfMissing = true)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
public class RefreshAutoConfiguration {

ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {

    /**
     * @see Scope#proxyMode()
     * @return proxy mode
     */
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}

ScopedProxyUtils#createScopedProxy

    public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
            BeanDefinitionRegistry registry, boolean proxyTargetClass) {

        String originalBeanName = definition.getBeanName();
        BeanDefinition targetDefinition = definition.getBeanDefinition();
        String targetBeanName = getTargetBeanName(originalBeanName);

        // Create a scoped proxy definition for the original bean name,
        // "hiding" the target bean in an internal target definition.
        RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
        proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
        proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
        proxyDefinition.setSource(definition.getSource());
        proxyDefinition.setRole(targetDefinition.getRole());

        proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
        if (proxyTargetClass) {
            targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
            // ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here.
        }
        else {
            proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
        }

        // Copy autowire settings from original bean definition.
        proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
        proxyDefinition.setPrimary(targetDefinition.isPrimary());
        if (targetDefinition instanceof AbstractBeanDefinition) {
            proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
        }

        // The target bean should be ignored in favor of the scoped proxy.
        targetDefinition.setAutowireCandidate(false);
        targetDefinition.setPrimary(false);

        // Register the target bean as separate bean in the factory.
        registry.registerBeanDefinition(targetBeanName, targetDefinition);

        // Return the scoped proxy definition as primary bean definition
        // (potentially an inner bean).
        return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
    }

4.2 @RefreshScope刷新配置和销毁动态配置Bean

在1.2中ContextRefresher#refresh 刷新配置

在1.2中ContextRefresher#refresh 销毁@RefreshScope注解的bean

4.3 定时器失效的问题

详细分析参考:https://www.jianshu.com/p/9760ff3bb311?v=1678273650499

ScheduledAnnotationBeanPostProcessor

@RefreshScope,如果配置更新后,销毁@RefreshScope注解的bean,此时定时器会失效。必须要重新请求一下定时器所在类的路径,定时器才能重新生效。

怎样解决?
加一个监听器,监听容器启动完成事件,然后在监听器里面调用监听器。

@RestController
@RefreshScope  //动态感知修改后的值
public class TestController implements ApplicationListener<RefreshScopeRefreshedEvent>{

    @Value("${common.age}")
     String age;
    @Value("${common.name}")
     String name;

    @GetMapping("/common")
    public String hello() {
        return name+","+age;
    }

    //触发@RefreshScope执行逻辑会导致@Scheduled定时任务失效
    @Scheduled(cron = "*/3 * * * * ?")  //定时任务每隔3s执行一次
    public void execute() {
        System.out.println("定时任务正常执行。。。。。。");
    }

    @Override
    public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
        this.execute();
    }
上一篇 下一篇

猜你喜欢

热点阅读