Nacos源码—5.Nacos配置中心实现分析一
大纲
1.关于Nacos配置中心的几个问题
2.Nacos如何整合SpringBoot读取远程配置
3.Nacos加载读取远程配置数据的源码分析
4.客户端如何感知远程配置数据的变更
5.集群架构下节点间如何同步配置数据
1.关于Nacos配置中心的几个问题
问题一:SpringBoot项目启动时如何加载Nacos服务端存储的配置数据?
问题二:Nacos配置中心有很多类型的配置数据,它们之间的优先级是怎样的?
问题三:在Nacos后台修改配置数据后,客户端是如何实现感知的?
问题四:Nacos服务端的配置数据如何存储,集群间会如何同步数据?
2.Nacos如何整合SpringBoot读取远程配置
(1)通过PropertySourceLocator将Nacos配置中心整合到SpringBoot
(2)SpringBoot启动时如何执行到PropertySourceLocator扩展接口
(3)SpringBoot如何自动装配NacosPropertySourceLocator实现类
(4)NacosPropertySourceLocator如何加载Nacos服务端的配置数据
(1)通过PropertySourceLocator将Nacos配置中心整合到SpringBoot
在SpringBoot的启动过程中,会有一个准备上下文的动作,这个准备上下文动作会加载配置数据。
SpringBoot有一个用来收集配置数据的扩展接口PropertySourceLocator,nacos-config正是利用该接口将Nacos配置中心整合到SpringBoot中。
(2)SpringBoot启动时如何执行到PropertySourceLocator扩展接口
SpringBoot项目启动时都会使用main()方法。在执行SpringApplication的run()方法的过程中,会调用SpringApplication的prepareContext()方法来准备上下文,然后调用SpringApplication的applyInitializers()方法来初始化应用。
由于SpringBoot会有很多个初始化器,所以在SpringApplication的applyInitializers()方法中,会先通过SpringApplication的getInitializers()方法获取初始化器列表,然后循环遍历调用初始化器ApplicationContextInitializer的initialize()方法。
在这些初始化器列表initializers中,会有一个名为PropertySourceBootstrapConfiguration的初始化器,所以会调用到PropertySourceBootstrapConfiguration的initialize()方法。
在PropertySourceBootstrapConfiguration的initialize()方法中,SpringBoot会获取PropertySourceLocator扩展接口的所有实现类,然后遍历调用PropertySourceLocator实现类的locateCollection()方法。
在调用PropertySourceLocator实现类的locateCollection()方法时,会先调用PropertySourceLocator扩展接口的locateCollection()方法,从而才会触发调用PropertySourceLocator实现类实现的locate()方法,比如调用NacosPropertySourceLocator的locate()方法。
@SpringBootApplication
public class StockServiceApplication {public static void main(String[] args) {SpringApplication.run(StockServiceApplication.class, args);}
}public class SpringApplication {private List<ApplicationContextInitializer<?>> initializers;...public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class<?>[] { primarySource }, args);}public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return new SpringApplication(primarySources).run(args);}//Run the Spring application, creating and refreshing a newpublic ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);configureIgnoreBeanInfo(environment);Banner printedBanner = printBanner(environment);context = createApplicationContext();exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context);//准备上下文prepareContext(context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}listeners.started(context);callRunners(context, applicationArguments);} catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}try {listeners.running(context);} catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}return context;}private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {context.setEnvironment(environment);postProcessApplicationContext(context);//初始化应用applyInitializers(context);listeners.contextPrepared(context);if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}//Add boot specific singleton beansConfigurableListableBeanFactory beanFactory = context.getBeanFactory();beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);}if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}//Load the sourcesSet<Object> sources = getAllSources();Assert.notEmpty(sources, "Sources must not be empty");load(context, sources.toArray(new Object[0]));listeners.contextLoaded(context);}//Apply any {@link ApplicationContextInitializer}s to the context before it is refreshed.@SuppressWarnings({ "rawtypes", "unchecked" })protected void applyInitializers(ConfigurableApplicationContext context) {//getInitializers()方法会获取初始化器列表,然后循环调用初始化器的initialize()方法for (ApplicationContextInitializer initializer : getInitializers()) {Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");initializer.initialize(context);}}public Set<ApplicationContextInitializer<?>> getInitializers() {return asUnmodifiableOrderedSet(this.initializers);}...
}@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {@Autowired(required = false)private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();...@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {List<PropertySource<?>> composite = new ArrayList<>();AnnotationAwareOrderComparator.sort(this.propertySourceLocators);boolean empty = true;ConfigurableEnvironment environment = applicationContext.getEnvironment();//遍历PropertySourceLocator扩展接口的所有实现类this.propertySourceLocatorsfor (PropertySourceLocator locator : this.propertySourceLocators) {Collection<PropertySource<?>> source = locator.locateCollection(environment);if (source == null || source.size() == 0) {continue;}List<PropertySource<?>> sourceList = new ArrayList<>();for (PropertySource<?> p : source) {if (p instanceof EnumerablePropertySource) {EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) p;sourceList.add(new BootstrapPropertySource<>(enumerable));} else {sourceList.add(new SimpleBootstrapPropertySource(p));}}logger.info("Located property source: " + sourceList);composite.addAll(sourceList);empty = false;}if (!empty) {MutablePropertySources propertySources = environment.getPropertySources();String logConfig = environment.resolvePlaceholders("${logging.config:}");LogFile logFile = LogFile.get(environment);for (PropertySource<?> p : environment.getPropertySources()) {if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {propertySources.remove(p.getName());}}insertPropertySources(propertySources, composite);reinitializeLoggingSystem(environment, logConfig, logFile);setLogLevels(applicationContext, environment);handleIncludedProfiles(environment);}}...
}public interface PropertySourceLocator {PropertySource<?> locate(Environment environment);default Collection<PropertySource<?>> locateCollection(Environment environment) {return locateCollection(this, environment);}static Collection<PropertySource<?>> locateCollection(PropertySourceLocator locator, Environment environment) {//比如调用NacosPropertySourceLocator.locate()方法PropertySource<?> propertySource = locator.locate(environment);if (propertySource == null) {return Collections.emptyList();}if (CompositePropertySource.class.isInstance(propertySource)) {Collection<PropertySource<?>> sources = ((CompositePropertySource) propertySource).getPropertySources();List<PropertySource<?>> filteredSources = new ArrayList<>();for (PropertySource<?> p : sources) {if (p != null) {filteredSources.add(p);}}return filteredSources;} else {return Arrays.asList(propertySource);}}
}
(3)SpringBoot如何自动装配NacosPropertySourceLocator实现类
在nacos-config的spring.factories文件中,可以看到一个自动装配的配置类NacosConfigBootstrapConfiguration。
NacosConfigBootstrapConfiguration类会创建三个Bean对象。
第一个是NacosPropertySourceLocator。这样SpringBoot就能扫描到NacosPropertySourceLocator这个Bean,然后将NacosPropertySourceLocator整合到SpringBoot的启动流程中。在SpringBoot启动时,就会调用NacosPropertySourceLocator的locate()方法。
第二个是NacosConfigManager。由于NacosConfigManager的构造方法会创建ConfigService对象,所以在NacosPropertySourceLocator的locate()方法中,可以通过NacosConfigManager的getConfigService()方法获取ConfigService对象。
ConfigService是一个接口,定义了获取配置、发布配置、移除配置等方法。ConfigService只有一个实现类NacosConfigService,Nacos配置中心源码的核心其实就是这个NacosConfigService对象。
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {@Beanpublic NacosPropertySourceLocator nacosPropertySourceLocator(NacosConfigManager nacosConfigManager) {return new NacosPropertySourceLocator(nacosConfigManager);}@Bean@ConditionalOnMissingBeanpublic NacosConfigManager nacosConfigManager(NacosConfigProperties nacosConfigProperties) {return new NacosConfigManager(nacosConfigProperties);}@Bean@ConditionalOnMissingBeanpublic NacosConfigProperties nacosConfigProperties() {return new NacosConfigProperties();}
}@Order(0)
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();}...@Overridepublic PropertySource<?> locate(Environment env) {nacosConfigProperties.setEnvironment(env);ConfigService configService = nacosConfigManager.getConfigService();if (null == configService) {log.warn("no instance of config service found, can't load config from nacos");return null;}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);loadSharedConfiguration(composite);loadExtConfiguration(composite);loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);return composite;}...
}public class NacosConfigManager {private static ConfigService service = null;private NacosConfigProperties nacosConfigProperties;public NacosConfigManager(NacosConfigProperties nacosConfigProperties) {this.nacosConfigProperties = nacosConfigProperties;//创建ConfigService对象,其实就是创建NacosConfigService对象createConfigService(nacosConfigProperties);}//使用双重检查创建单例static ConfigService createConfigService(NacosConfigProperties nacosConfigProperties) {if (Objects.isNull(service)) {synchronized (NacosConfigManager.class) {try {if (Objects.isNull(service)) {//通过反射创建ConfigService对象,即NacosConfigService对象,然后给service属性赋值service = NacosFactory.createConfigService(nacosConfigProperties.assembleConfigServiceProperties());}} catch (NacosException e) {log.error(e.getMessage());throw new NacosConnectionFailureException(nacosConfigProperties.getServerAddr(), e.getMessage(), e);}}}return service;}public ConfigService getConfigService() {if (Objects.isNull(service)) {createConfigService(this.nacosConfigProperties);}return service;}public NacosConfigProperties getNacosConfigProperties() {return nacosConfigProperties;}
}public interface ConfigService {...String getConfig(String dataId, String group, long timeoutMs) throws NacosException;boolean publishConfig(String dataId, String group, String content) throws NacosException;boolean removeConfig(String dataId, String group) throws NacosException;...
}public class NacosConfigService implements ConfigService {......
}
(4)NacosPropertySourceLocator如何加载Nacos服务端的配置数据
在NacosPropertySourceLocator的locate()方法中,一共会加载三个不同类型的配置数据:共享的、额外的、自身应用的,加载这些配置数据时最终都会调用loadNacosDataIfPresent()方法。
执行NacosPropertySourceLocator的loadNacosDataIfPresent()方法时,会通过NacosPropertySourceBuilder创建NacosPropertySource对象。
在构建NacosPropertySource对象的过程中,会调用NacosPropertySourceBuilder的loadNacosData()方法加载配置。
而执行NacosPropertySourceBuilder的loadNacosData()方法时,最终会调用NacosConfigService的getConfig()方法来加载Nacos配置,即调用NacosConfigService的getConfigInner()方法来加载Nacos配置。
在执行NacosConfigService的getConfigInner()方法时,首先会先获取一下本地是否有对应的配置数据,如果有则优先使用本地的。本地数据是在从Nacos配置中心获取到数据后,持久化到本地的数据快照。如果本地没有,才会去发起HTTP请求获取远程Nacos服务端的配置数据。也就是调用ClientWorker的getServerConfig()方法来获取远程配置数据。获取到Nacos配置中心的数据后,会马上将数据持久化到本地。
@Order(0)
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();}...@Overridepublic PropertySource<?> locate(Environment env) {nacosConfigProperties.setEnvironment(env);//获取NacosConfigService对象ConfigService configService = nacosConfigManager.getConfigService();if (null == configService) {log.warn("no instance of config service found, can't load config from nacos");return null;}//获取yml配置信息long timeout = nacosConfigProperties.getTimeout();//传入NacosConfigService对象创建NacosPropertySourceBuilder构造器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);//加载共享的配置数据,对应的配置是:spring.cloud.nacos.shared-configsloadSharedConfiguration(composite);//加载额外的配置数据,对应的配置是:spring.cloud.nacos.extension-configsloadExtConfiguration(composite);//加载自身应用的配置数据loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);return composite;}private void loadSharedConfiguration(CompositePropertySource compositePropertySource) {List<NacosConfigProperties.Config> sharedConfigs = nacosConfigProperties.getSharedConfigs();if (!CollectionUtils.isEmpty(sharedConfigs)) {checkConfiguration(sharedConfigs, "shared-configs");loadNacosConfiguration(compositePropertySource, sharedConfigs);}}private void loadExtConfiguration(CompositePropertySource compositePropertySource) {List<NacosConfigProperties.Config> extConfigs = nacosConfigProperties.getExtensionConfigs();if (!CollectionUtils.isEmpty(extConfigs)) {checkConfiguration(extConfigs, "extension-configs");loadNacosConfiguration(compositePropertySource, extConfigs);}}private void loadNacosConfiguration(final CompositePropertySource composite, List<NacosConfigProperties.Config> configs) {for (NacosConfigProperties.Config config : configs) {loadNacosDataIfPresent(composite, config.getDataId(), config.getGroup(), NacosDataParserHandler.getInstance().getFileExtension(config.getDataId()), config.isRefresh());}}private void loadApplicationConfiguration(CompositePropertySource compositePropertySource, String dataIdPrefix, NacosConfigProperties properties, Environment environment) {String fileExtension = properties.getFileExtension();String nacosGroup = properties.getGroup();//load directly once by defaultloadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup, fileExtension, true);//load with suffix, which have a higher priority than the defaultloadNacosDataIfPresent(compositePropertySource, dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);//Loaded with profile, which have a higher priority than the suffixfor (String profile : environment.getActiveProfiles()) {String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup, fileExtension, true);}}//加载三个不同类型的配置数据最终都会调用到loadNacosDataIfPresent()方法private void loadNacosDataIfPresent(final CompositePropertySource composite, final String dataId, final String group, String fileExtension, boolean isRefreshable) {...//加载Nacos中的配置数据NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group, fileExtension, isRefreshable);//把从Nacos中读取到的配置添加到Spring容器中this.addFirstPropertySource(composite, propertySource, false);}private NacosPropertySource loadNacosPropertySource(final String dataId, final String group, String fileExtension, boolean isRefreshable) {...//创建NacosPropertySourceBuilder构造器时已传入NacosConfigService对象return nacosPropertySourceBuilder.build(dataId, group, fileExtension, isRefreshable);}...
}public class NacosPropertySourceBuilder {private ConfigService configService;private long timeout;public NacosPropertySourceBuilder(ConfigService configService, long timeout) {//创建NacosPropertySourceBuilder构造器时已传入NacosConfigService对象this.configService = configService;this.timeout = timeout;}NacosPropertySource build(String dataId, String group, String fileExtension, boolean isRefreshable) {//加载Nacos中的配置数据List<PropertySource<?>> propertySources = loadNacosData(dataId, group, fileExtension);NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources, group, dataId, new Date(), isRefreshable);NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);return nacosPropertySource;}private List<PropertySource<?>> loadNacosData(String dataId, String group, String fileExtension) {String data = null;try {//调用NacosConfigService.getConfig()方法data = configService.getConfig(dataId, group, timeout);...return NacosDataParserHandler.getInstance().parseNacosData(dataId, data, fileExtension);} catch (NacosException e) {...}return Collections.emptyList();}
}public class NacosConfigService implements ConfigService {private final ClientWorker worker;...@Overridepublic 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);//优先使用本地配置String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);if (content != null) {LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", agent.getName(), dataId, group, tenant, ContentUtils.truncateContent(content));cr.setContent(content);configFilterChainManager.doFilter(null, cr);content = cr.getContent();return content;}//如果本地配置没有,才会调用远程Nacos服务端的配置try {//通过ClientWorker.getServerConfig()方法来读取远程配置数据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;}LOGGER.warn("[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}", agent.getName(), dataId, group, tenant, ioe.toString());}LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", agent.getName(), dataId, group, tenant, ContentUtils.truncateContent(content));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);}//发起服务调用HTTP请求,请求地址是:/v1/cs/configsresult = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);} catch (Exception ex) {String message = String.format("[%s] [sub-server] get server config exception, dataId=%s, group=%s, tenant=%s", agent.getName(), dataId, group, tenant);LOGGER.error(message, 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;...}}...
}
(5)总结
Nacos是按如下方式整合SpringBoot去读取远程配置数据的:在SpringBoot项目启动的过程中,有一个步骤是准备上下文,该步骤中就会去加载配置文件,加载配置文件时就会调用Nacos提供的获取配置数据的HTTP接口,最终完成从Nacos服务端拉获取置数据的整个流程,并且在获取到配置数据后会将数据持久化到本地。
3.Nacos加载读取远程配置数据的源码分析
(1)配置文件的类型与使用介绍
(2)远程配置文件的加载顺序源码
(3)远程配置文件的读取源码分析
(4)总结
(1)配置文件的类型与使用介绍
SpringBoot在启动过程中,会调用Nacos实现的加载配置文件扩展接口PropertySourceLocator,从而实现加载Nacos配置中心的远程配置文件。
在Nacos实现的扩展接口PropertySourceLocator中,便会加载好几个不同类型的配置文件,这些配置文件会存在优先级关系:自身应用的配置文件 > 额外的配置文件 > 共享的配置文件。
一.读取自身应用的配置文件
第一种情况:如下的项目yaml配置是最简单的配置,只需指定Nacos配置中心的地址。在读取Nacos配置中心文件时,是通过微服务名称去加载的,所以只需要在Nacos后台创建一个stock-service配置文件就可以读取到。
spring:application:name: stock-servicecloud:nacos:# 配置中心config:server-addr: http://124.223.102.236:8848
第二种情况:但项目中一般会指定配置文件的类型,所以可以在如下项目yaml配置中把配置文件类型加上。在项目yaml配置中加上配置文件类型后,会使用使用带后缀的配置文件。并且会覆盖之前的配置,说明带文件后缀的配置文件的优先级更高。
spring:application:name: stock-servicecloud:nacos:# 配置中心config:server-addr: http://124.223.102.236:8848# 配置文件类型file-extension: yaml
第三种情况:当然公司配置文件一般也会区分环境的。测试环境有测试环境的配置文件,生产环境有生产环境的配置文件。在如下的项目yaml配置中指定使用区分了环境的配置文件,这时带有环境变量的配置文件,比前面两个配置文件优先级更高。
spring:application:name: stock-serviceprofiles:# 测试环境active: testcloud:nacos:# 配置中心config:server-addr: http://124.223.102.236:8848# 配置文件类型file-extension: yaml
总结:读取自身应用的配置文件,如上三种情况,会存在优先级关系。通过微服务名称简单去获取stock-service配置文件的优先级最低,指定配置文件类型去获取stock-service配置文件的优先级比前者高,指定项目环境去获取stock-service配置文件的优先级是最高。
二.读取共享的配置文件
实际中会存在多个业务系统都共用同一数据库、Redis等中间件,这时不宜把每个中间件信息都配置到每个业务系统中,而是应该统一集中管理。比如在一个共享配置文件中配置,各业务系统使用共享配置文件即可。
项目yaml配置指定读取Nacos的共享配置文件如下:在spring.cloud.nacos.config配置下可以指定shared-configs配置。shared-configs配置是一个数组类型,表示可以配置多个共享配置文件,所以可以通过shared-configs配置将一些中间件配置管理起来。但要注意共享配置文件里的配置不要和自身应用配置文件里的配置重复,因为自身应用配置文件比共享配置文件的优先级高。
当然除了自身应用配置文件、共享配置文件外,还有一种额外的配置文件。如果一些配置不适合放在前两种配置文件,可以放到额外的配置文件中。
spring:application:name: stock-serviceprofiles:# 测试环境active: testcloud:nacos:# 配置中心config:server-addr: http://124.223.102.236:8848# 配置文件类型file-extension: yaml# 共享配置文件shared-configs:dataId: common-mysql.yamlgroup: DEFAULT_GROUP# 中间件配置一般不需要刷新refresh: false
(2)远程配置文件的加载顺序源码
在NacosPropertySourceLocator的locate()方法中,最先加载的配置文件,相同配置项会被后面加载的配置文件给覆盖掉。因为这些配置文件本身就是kv形式存储,所以共享配置文件优先级最低。自身应用配置文件 > 额外配置文件 > 共享配置文件。
在NacosPropertySourceLocator的loadApplicationConfiguration()方法中,加载自身应用的配置文件的优先级为:"微服务名"的配置文件 < "微服务名.后缀名"的配置文件 < "微服务-环境变量名.后缀名"的配置文件。同样对于相同配置项,先加载的会被后加载的替换掉。
但不管获取的是哪一种类型的配置文件,最终都调用NacosPropertySourceLocator的loadNacosDataIfPresent()方法。在这个方法里最终会通过HTTP方式去获取Nacos服务端的配置文件数据,请求的HTTP地址是"/v1/cs/configs",获得数据后会马上持久化到本地。
@Order(0)
public class NacosPropertySourceLocator implements PropertySourceLocator {...@Overridepublic PropertySource<?> locate(Environment env) {nacosConfigProperties.setEnvironment(env);//获取NacosConfigService对象ConfigService configService = nacosConfigManager.getConfigService();if (null == configService) {log.warn("no instance of config service found, can't load config from nacos");return null;}//获取yml配置信息long timeout = nacosConfigProperties.getTimeout();//传入NacosConfigService对象创建NacosPropertySourceBuilder构造器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);//1.加载共享的配置数据,对应的配置是:spring.cloud.nacos.shared-configsloadSharedConfiguration(composite);//2.加载额外的配置数据,对应的配置是:spring.cloud.nacos.extension-configsloadExtConfiguration(composite);//3.加载自身应用的配置数据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();//1.加载"微服务名"的配置文件loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup, fileExtension, true);//2.加载"微服务名.后缀名"的配置文件loadNacosDataIfPresent(compositePropertySource, dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);//3.加载"微服务-环境变量名.后缀名"的配置文件,因为环境变量可以配置多个,所以这里是循环for (String profile : environment.getActiveProfiles()) {String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup, fileExtension, true);}}private void loadExtConfiguration(CompositePropertySource compositePropertySource) {List<NacosConfigProperties.Config> extConfigs = nacosConfigProperties.getExtensionConfigs();if (!CollectionUtils.isEmpty(extConfigs)) {checkConfiguration(extConfigs, "extension-configs");loadNacosConfiguration(compositePropertySource, extConfigs);}}private void loadNacosConfiguration(final CompositePropertySource composite, List<NacosConfigProperties.Config> configs) {for (NacosConfigProperties.Config config : configs) {loadNacosDataIfPresent(composite, config.getDataId(), config.getGroup(), NacosDataParserHandler.getInstance().getFileExtension(config.getDataId()), config.isRefresh());}}...
}
(3)远程配置文件的读取源码
Nacos服务端处理HTTP请求"/v1/cs/configs"的入口是:ConfigController的getConfig()方法。
执行ConfigController的getConfig()方法时,会调用ConfigServletInner的doGetConfig()方法,而该方法的核心代码就是通过DiskUtil的targetBetaFile()方法获取磁盘上的文件数据。
所以Nacos客户端发送HTTP请求来获取配置文件数据时,Nacos服务端并不是去数据库中获取对应的配置文件数据,而是直接读取本地磁盘文件的配置文件数据然后返回给客户端。那么Nacos服务端是什么时候将配置文件数据持久化到本地磁盘文件的?
其实在执行ExternalDumpService的init()方法进行初始化Bean实例时,会调用DumpService的dumpOperate()方法,然后会调用DumpService的dumpConfigInfo()方法,接着会调用DumpAllProcessor的process()方法查询数据库。
DumpAllProcessor的process()方法会做两件事:一是通过分页查询数据库中的config_info表数据,二是将查询到的数据持久化到本地磁盘文件中。
@RestController
@RequestMapping(Constants.CONFIG_CONTROLLER_PATH)
public class ConfigController {private final ConfigServletInner inner;...//Get configure board infomation fail.@GetMapping@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)public void getConfig(HttpServletRequest request, HttpServletResponse response,@RequestParam("dataId") String dataId, @RequestParam("group") String group,@RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant,@RequestParam(value = "tag", required = false) String tag)throws IOException, ServletException, NacosException {//check tenantParamUtils.checkTenant(tenant);tenant = NamespaceUtil.processNamespaceParameter(tenant);//check paramsParamUtils.checkParam(dataId, group, "datumId", "content");ParamUtils.checkParam(tag);final String clientIp = RequestUtil.getRemoteIp(request);inner.doGetConfig(request, response, dataId, group, tenant, tag, clientIp);}...
}@Service
public class ConfigServletInner {...public String doGetConfig(HttpServletRequest request, HttpServletResponse response, String dataId, String group,String tenant, String tag, String clientIp) throws IOException, ServletException {...File file = null;//核心代码:获取磁盘上的文件数据file = DiskUtil.targetBetaFile(dataId, group, tenant);...}...
}@Conditional(ConditionOnExternalStorage.class)
@Component
public class ExternalDumpService extends DumpService {...@PostConstruct@Overrideprotected void init() throws Throwable {dumpOperate(processor, dumpAllProcessor, dumpAllBetaProcessor, dumpAllTagProcessor);}...
}//Dump data service.
public abstract class DumpService {protected DumpProcessor processor;protected DumpAllProcessor dumpAllProcessor;protected DumpAllBetaProcessor dumpAllBetaProcessor;protected DumpAllTagProcessor dumpAllTagProcessor;protected final PersistService persistService;protected final ServerMemberManager memberManager;...protected void dumpOperate(DumpProcessor processor, DumpAllProcessor dumpAllProcessor, DumpAllBetaProcessor dumpAllBetaProcessor, DumpAllTagProcessor dumpAllTagProcessor) throws NacosException {...//持久化配置文件到磁盘dumpConfigInfo(dumpAllProcessor);...}private void dumpConfigInfo(DumpAllProcessor dumpAllProcessor) throws IOException {...//查询数据库配置dumpAllProcessor.process(new DumpAllTask());...}...
}public class DumpAllProcessor implements NacosTaskProcessor {static final int PAGE_SIZE = 1000;final DumpService dumpService;final PersistService persistService;public DumpAllProcessor(DumpService dumpService) {this.dumpService = dumpService;this.persistService = dumpService.getPersistService();}@Overridepublic boolean process(NacosTask task) {//查询最大IDlong currentMaxId = persistService.findConfigMaxId();long lastMaxId = 0;while (lastMaxId < currentMaxId) {//分页查询配置信息Page<ConfigInfoWrapper> page = persistService.findAllConfigInfoFragment(lastMaxId, PAGE_SIZE);if (page != null && page.getPageItems() != null && !page.getPageItems().isEmpty()) {for (ConfigInfoWrapper cf : page.getPageItems()) {long id = cf.getId();lastMaxId = id > lastMaxId ? id : lastMaxId;if (cf.getDataId().equals(AggrWhitelist.AGGRIDS_METADATA)) {AggrWhitelist.load(cf.getContent());}if (cf.getDataId().equals(ClientIpWhiteList.CLIENT_IP_WHITELIST_METADATA)) {ClientIpWhiteList.load(cf.getContent());}if (cf.getDataId().equals(SwitchService.SWITCH_META_DATAID)) {SwitchService.load(cf.getContent());}//把查询到的配置信息写入到磁盘boolean result = ConfigCacheService.dump(cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getContent(), cf.getLastModified(), cf.getType()); final String content = cf.getContent();final String md5 = MD5Utils.md5Hex(content, Constants.ENCODE);LogUtil.DUMP_LOG.info("[dump-all-ok] {}, {}, length={}, md5={}", GroupKey2.getKey(cf.getDataId(), cf.getGroup()), cf.getLastModified(), content.length(), md5);}DEFAULT_LOG.info("[all-dump] {} / {}", lastMaxId, currentMaxId);} else {lastMaxId += PAGE_SIZE;}}return true;}
}public class ConfigCacheService {...//Save config file and update md5 value in cache.public static boolean dump(String dataId, String group, String tenant, String content, long lastModifiedTs, String type) {String groupKey = GroupKey2.getKey(dataId, group, tenant);CacheItem ci = makeSure(groupKey);ci.setType(type);final int lockResult = tryWriteLock(groupKey);assert (lockResult != 0);if (lockResult < 0) {DUMP_LOG.warn("[dump-error] write lock failed. {}", groupKey);return false;}try {final String md5 = MD5Utils.md5Hex(content, Constants.ENCODE);if (md5.equals(ConfigCacheService.getContentMd5(groupKey))) {DUMP_LOG.warn("[dump-ignore] ignore to save cache file. groupKey={}, md5={}, lastModifiedOld={}, " + "lastModifiedNew={}", groupKey, md5, ConfigCacheService.getLastModifiedTs(groupKey), lastModifiedTs);} else if (!PropertyUtil.isDirectRead()) {//调用持久化到本地磁盘的方法DiskUtil.saveToDisk(dataId, group, tenant, content);}updateMd5(groupKey, md5, lastModifiedTs);return true;} catch (IOException ioe) {DUMP_LOG.error("[dump-exception] save disk error. " + groupKey + ", " + ioe.toString(), ioe);if (ioe.getMessage() != null) {String errMsg = ioe.getMessage();if (NO_SPACE_CN.equals(errMsg) || NO_SPACE_EN.equals(errMsg) || errMsg.contains(DISK_QUATA_CN) || errMsg.contains(DISK_QUATA_EN)) {//Protect from disk full.FATAL_LOG.error("磁盘满自杀退出", ioe);System.exit(0);}}return false;} finally {releaseWriteLock(groupKey);}}...
}
(4)总结
一.不同类型配置文件的优先级:自身应用配置文件 > 额外配置文件 > 共享配置文件。
二.自身应用配置文件的优先级:"微服务名"的配置文件 < "微服务名.后缀名"的配置文件 < "微服务-环境变量名.后缀名"的配置文件。
三.Nacos客户端向服务端获取配置数据的流程
客户端向服务端查询配置数据时,服务端会直接获取其本地磁盘文件中的配置进行返回。
服务端本地磁盘文件上的配置数据,是在服务端启动时查询数据库数据,然后持久化到本地磁盘上的。
所以如果直接手动修改数据库中的配置信息,客户端是不生效的,因为客户端向服务端获取配置信息时并不是读取数据库的。
相关文章:
Nacos源码—5.Nacos配置中心实现分析一
大纲 1.关于Nacos配置中心的几个问题 2.Nacos如何整合SpringBoot读取远程配置 3.Nacos加载读取远程配置数据的源码分析 4.客户端如何感知远程配置数据的变更 5.集群架构下节点间如何同步配置数据 1.关于Nacos配置中心的几个问题 问题一:SpringBoot项目启动时如…...
【spring】Spring、Spring MVC、Spring Boot、Spring Cloud?
这些都是 Spring 家族的重要组成部分,但它们各自定位不同、功能层级不同,可以用一张表格和简要说明来帮你快速理解: 一、四者概念和区别表格 名称功能定位主要用途/核心功能是否依赖其他部分Spring基础框架(核心)IOC、…...
RDD的处理过程
1. 创建RDD 通过SparkContext的parallelize方法从本地集合创建RDD。 从外部存储(如HDFS、本地文件系统)加载数据创建RDD。 通过对已有RDD进行转换操作生成新的RDD。 2. 转换操作(Transformation) 对RDD进行操作(如…...
Vue3 中当组件嵌套层级较深导致 ref 无法直接获取子组件实例时,可以通过 provide/inject + 回调函数的方式实现子组件方法传递到父组件
需求:vue3中使用defineExposeref调用子组件方法报错不是一个function 思路:由于组件嵌套层级太深导致ref失效,通过provide/inject 回调函数来实现多层穿透 1. 父组件提供「方法注册函数」 父组件通过 provide 提供一个用于接收子组件方法…...
如何在Ubuntu上安装NVIDIA显卡驱动?
作者:算力魔方创始人/英特尔创新大使刘力 一,前言 对于使用NVIDIA显卡的Ubuntu用户来说,正确安装显卡驱动是获得最佳图形性能的关键。与Windows系统不同,Linux系统通常不会自动安装专有显卡驱动。本文将详细介绍在Ubuntu系统上安…...
Linux 修改bond后网关不生效的问题
1.前言 bond原本是OK的,但是某个同事变更后,发现网关路由存在问题 #查看路由,默认网关信息,发现没有配置的网关信息 ip route show #排查/etc/sysconfig/network-script/下面的ifcfg-* 文件没有问题 1.重启network 服务 systemct…...
chili调试笔记13 工程图模块 mesh渲染 mesh共享边显示实现
把模型投影到工程图要用什么模块当工程图的画板,最后要导出dxf的 three是怎么读取他的3d数据的 mesh不是三角形吗怎么渲染出四边形面的 我想看到三角形的边怎么设置 ai让我干嘛我就干嘛 static getAllEdges(face: { positions: Float32Array; indices: Uint16Array …...
Eclipse 插件开发 5 编辑器
Eclipse 插件开发 5 编辑器 1 编辑器描述2 自定义编辑器2.1 注册插件(plugin.xml)2.2 继承 EditorPart2.3 实现 IEditorInput2.4 打开编辑器 1 编辑器描述 Eclipse 的 UI 基于 Workbench(工作台)模型,Editor Area 是 Workbench 的核心区域之…...
Java消息队列性能优化实践:从理论到实战
Java消息队列性能优化实践:从理论到实战 1. 引言 在现代分布式系统架构中,消息队列(Message Queue,MQ)已经成为不可或缺的中间件组件。它不仅能够实现系统间的解耦,还能提供异步通信、流量削峰等重要功能…...
Android学习总结之Binder篇
一、Binder 跨进程通信底层实现 Q1:Binder 如何实现一次完整的跨进程方法调用?请描述内核态与用户态交互流程 高频错误:仅回答 “通过 AIDL 生成代码”,未涉及 Binder 驱动三层协作模型 满分答案(附内核交互流程图&a…...
very_easy_sql(SSRF+SQL注入)
题目有一行提示: you are not an inner user, so we can not let you have identify~(你不是内部用户,所以我们不能让你进行身份验证)联想到可能存在SSRF漏洞,一般情况下,SSRF攻击的目标是外网无法访问的内…...
MCP认证全解析:从零到微软认证专家
MCP认证全解析:从零到微软认证专家 什么是MCP认证? Microsoft Certified Professional(MCP)是由微软官方颁发的技术认证,旨在验证IT从业者在微软技术栈(如Azure、Windows Server、SQL Server等࿰…...
leetcode刷题日记——反转链表
[ 题目描述 ]: [ 思路 ]: 题目要求将链表中指定位置的部分进行翻转反转的部分,其实可以看做使用头插法插入链表运行如下: struct ListNode* reverseBetween(struct ListNode* head, int left, int right) {struct ListNode te…...
【day04】Fibonacci数列 | 单词搜索 | 杨辉三角
1.Fibonacci数列 题目链接: Fibonacci数列_牛客题霸_牛客网 解题思路: 求斐波那契数列的过程中,判断⼀下:何时n会在两个fib数之间。 #include <bits/stdc.h>using namespace std;#define int long longsigned main() {i…...
win11指定Microsoft Print To PDF的输出路径(电子书djvu转pdf输出路径)
首先,看一张效果图 前面都是废话,解决方法看最后,看最后 Microsoft Print To PDF功能作为Windows操作系统内置的一项便捷工具,为用户提供了将任何可打印内容高效转换为PDF格式的能力。本文深入探讨了Microsoft Print To PDF的工作…...
第3章 数据和C
目录 3.1 示例程序3.2 变量与常量数据3.3 数据:数据类型关键字3.4 C数据类型3.4.1 int类型3.4.2 其它整数类型3.4.3 使用字符:char类型3.4.4 _Bool类型3.4.5 可移植的类型:inttypes.h3.4.6 float、double和long double类型3.4.7 复数和虚数类…...
迁移学习:如何加速模型训练和提高性能
📌 友情提示: 本文内容由银河易创AI(https://ai.eaigx.com)创作平台的gpt-4-turbo模型生成,旨在提供技术参考与灵感启发。文中观点或代码示例需结合实际情况验证,建议读者通过官方文档或实践进一步确认其准…...
Kotlin zip 函数的作用和使用场景
1. zip 函数的作用 zip 是 Kotlin 集合操作中的一个函数,用于将两个集合按照索引一一配对,生成一个新的 List<Pair<T, R>> 集合。 作用:将两个集合的元素按位置组合成键值对(Pair)。返回值:一…...
通用分布式锁组件
Redisson的分布式锁使用并不复杂,基本步骤包括: 1)创建锁对象 2)尝试获取锁 3)处理业务 4)释放锁 但是,除了第3步以外,其它都是非业务代码,对业务的侵入较多&#x…...
FastDFS,分布式文件存储系统,介绍+配置+工具类
FastDFS 什么是分布式文件存储系统 随着文件逐渐增多,单台计算机已经存储不下这么多数据,需要用多台计算机存储不同的数据或进行备份,这样就需要有一个管理系统管理不同的计算机节点,这就是分布式管理系统。 使用分布式管理系统的…...
查询nvidia边缘设备的软硬件版本jetson_release
通过jetson_release命令可查询nvidia边缘设备的软硬件版本 nvidianvidia-desktop:~/leo/test_onnx$ jetson_release Software part of jetson-stats 4.2.12 - (c) 2024, Raffaello Bonghi Model: NVIDIA Orin Nano Developer Kit - Jetpack 5.1.1 [L4T 35.3.1] NV Power Mode[…...
[学习]RTKLib详解:ppp.c与ppp_ar.c
文章目录 RTKLib详解:ppp.c与ppp_ar.cPart A: ppp.c一、整体作用与工作流程二、核心函数说明1. pppos2. res_ppp3. tide_solid4. prectrop5. corrmeas6. udbias_ppp 三、数学原理补充四、代码特点 Part B: ppp_ar.c一、整体作用与工作流程分析二、函数功…...
ntdll!LdrpSnapThunk函数分析之LdrpNameToOrdinal函数返回之后得到函数地址
第一部分: OrdinalNumber LdrpNameToOrdinal( ImportString, ExportDirectory->NumberOfNames, DllBase, NameTableBase, …...
LeetCode 267:回文排列 II —— Swift 解法全解析
文章目录 摘要描述题解答案题解代码分析统计字符频率判断是否可能构成回文构建半边字符数组回溯生成半边排列 示例测试及结果时间复杂度空间复杂度实际使用场景:回文排列在真实项目里能干啥?文本处理、数据清洗类系统游戏开发:名字合法性验证…...
【渗透测试】命令执行漏洞的原理、利用方式、防范措施
文章目录 命令执行漏洞的原理、利用方式、防范措施一、原理**1. 定义与触发条件****2. 攻击链流程图** 二、利用方式**1. 利用手法与分类**(1) 系统命令注入(2) 代码执行漏洞(3) 框架漏洞利用 **2. 案例** 三、防范措施**1. 输入过滤与验证****2. 禁用危险函数****3. 安全开发*…...
旧版谷歌浏览器Chrome v116.0.5845.141下载
63位和32位均有: https://bbs.pcbeta.com/forum.php?modviewthread&tid1978299 https://www.31du.cn/open/google-chrome-v116-0-5845-141.html v116.0.5845.141的win32位版本: https://www.cr173.com/soft/435106.html v116.0.5845.97版本&…...
行业洞察| 当大模型开始协同工作:多智能体系统的崛起与挑战
你有没有想过,如果一群AI智能体拉了个工作群,它们会聊些什么? 程序员AI:“这段代码我来写!” 产品经理AI:“需求还没说完呢!” 辩论家AI:“我觉得这个方案不行!” 吃瓜…...
The Action Replay Process
Preface A commonly used inequality − x > ln ( 1 − x ) , 0 < x < 1 -x > \ln(1 - x), \quad 0 < x < 1 −x>ln(1−x),0<x<1 Proof: Let f ( x ) ln ( 1 − x ) x f(x) \ln(1 - x) x f(x)ln(1−x)x, for 0 < x < 1 0 < …...
Python基于Django的病人信息管理系统及安全策略分析(附源码,文档说明)
博主介绍:✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇dz…...
YOLOv1:开创实时目标检测新纪元
一、引言 在计算机视觉领域,目标检测一直是重要的研究方向,广泛应用于自动驾驶、安防监控、智能机器人等场景。2016年,Joseph Redmon等人提出的YOLO(You Only Look Once)v1模型,以其端到端、单阶段、实时性…...
【今日三题】跳台阶扩展问题(找规律) / 包含不超过两种字符的最长子串 / 字符串的排列(回溯—全排列)
⭐️个人主页:小羊 ⭐️所属专栏:Linux 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 跳台阶扩展问题(找规律)包含不超过两种字符的最长子串(字符串哈希)字符串的排列(回溯—全排列) 跳台阶扩展问题(找规律) 跳台阶扩…...
MySQL 中常见的日志
1. MySQL 中常见的日志有哪些? MySQL 主要包含以下几种日志: 错误日志(Error Log):记录 MySQL 服务器的启动和停止过程中的信息,以及运行过程中出现的错误或警告信息。默认情况下,错误日志文件…...
ubuntu nobel + qt5.15.2 设置qss语法识别正确
问题展示 解决步骤 首选项里面的高亮怎么编辑选择都没用。如果已经有generic-highlighter和css.xml,直接修改css.xml文件最直接! 在generic-highlighter目录下找到css.xml文件,位置是:/opt/Qt/Tools/QtCreator/share/qtcreator/…...
线程池技术
线程池基本概念 线程池就是在任务还没有到来前,预先创建一定数量的线程放入空闲列表。这些线程都是处于阻塞状态,不消耗CPU,但占用较小的内存空间。 当新任务到来时,缓冲池选择一个空线程,把任务传入此线程中运行&…...
matlab App自动化安装和卸载
这个是文件mlappinstall安装和卸载的使用函数,并且包括运行函数。File must be a toolbox file (*.mlappinstall) Package and Share Apps — Functions matlab.apputil.createCreate or modify app project file interactively using the Package App dialog box…...
MATLAB技巧——命令行输入的绘图,中文是正常的,到了脚本(m文件)里面就变成乱码的解决方法
文章目录 文件编码(根本性措施)字体设置使用 sprintf 或 text 函数系统语言设置示例代码 使用mlx方法 总结 在 M A T L A B MATLAB MATLAB中,中文字符在命令行和脚本中的显示问题通常与字符编码设置有关。以下是一些可能导致中文乱码的原因及…...
使用OpenCV 和 Dlib 实现人脸融合技术
文章目录 引言一、技术概述二、环境准备三、关键代码解析1. 人脸关键点定义2. 获取人脸掩模3. 计算仿射变换矩阵4. 检测并提取人脸关键点5. 颜色校正 四、完整流程五、效果展示六、总结 引言 本文将介绍如何使用Python、OpenCV和dlib库实现人脸融合技术,将一张人脸…...
Codeforces Round 1022 (Div. 2)
Problem - A - Codeforces 看这数据量,算出每个排列来,是不现实的,需要找找规律 来看找规律代码 #include <bits/stdc.h> using namespace std;int main() {int t;cin >> t;while (t--){int n;cin >> n;vector<int&g…...
uniapp 震动功能实现
项目场景: 提示:这里简述项目相关背景: 在项目中有时候需要一些功能,比如震动 描述 提示:这里描述项目中遇到的问题: 在移动应用中,震动反馈是提升用户体验的重要方式。uniapp 提供了两种震…...
uniapp 搭配 uCharts
在插件市场导入插件到项目中 <view class"charts-box-main"> <qiun-data-charts type"area" :opts"opts" :chartData"chartData" /> </view> data(&#…...
Kubernetes(k8s)学习笔记(八)--KubeSphere定制化安装
1执行下面的命令修改上一篇中yaml文件来实现定制化安装devops kubectl edit cm -n kubesphere-system ks-installer 主要是将devops几个配置由False改为True 然后使用下面的命令查看安装日志 kubectl logs -n kubesphere-system $(kubectl get pod -n kubesphere-system -l …...
阿里云codeup以及本地gitclone+http
cmd命令行乱码问题、解决 chcp 65001 git代码提交 git add . git commit -m init git push origin master...
Spring Boot 多数据源事务管理
在 Spring Boot 中,当需要操作多个数据源时,事务管理会变得更加复杂。因为默认的 DataSourceTransactionManager 只支持单数据源事务,跨数据源的事务需要使用 分布式事务(Distributed Transaction) 或 柔性事务&#x…...
4.系统定时器基本定时器
目录 系统定时器 系统定时器(systick)--内核 系统定时器结构 系统滴答定时器寄存器--内核 定时周期的确定公式 配置滴答定时器 系统定时器应用 应用1.定时器构造时间点任务,解决while循环阻塞问题 应用2.定时器构造精准的ms延时 应…...
lvgl多语言设置
搭建开发环境 安装node.js 安装node.js,点击进入官网地址 安装lv_i18n lv_i18n项目地址:Github:https://github.com/lvgl/lv_i18ngit运行命令安装lv_i18n:npm i lv_i18n -g。测试命令:lv_i18n -h 搭建过程报错 …...
ICode国际青少年编程竞赛—Python—4级训练场—复杂嵌套循环
ICode国际青少年编程竞赛—Python—4级训练场—复杂嵌套循环 icode练习时遇到卡顿没有思路时怎么办,题目也很难找到不会的那道题~针对这个问题,我们开发了通过“步数”、“积木行数”来快速定位到你不会的题目~ 题目会持续更新…...
【Windows】怎么解决Win 10家庭版WMI Provider Host占用CPU过高的问题?-篇一【2025.05.07】
本文面向两种用户,普通小白和windows开发程序员。 对于小白,目标就是阻止wmi对资源的高占用。解决方法有以下几步: 查出谁在调用这个wmiprvse.exe winR 组合键打开运行,输入命令services.msc,回车或确认。 找到如下蓝色…...
Matlab 多策略改进蜣螂优化算法及其在CEC2017性能
1、内容简介 Matlab214-多策略改进蜣螂优化算法及其在CEC2017性能 可以交流、咨询、答疑 2、内容说明 对蜣螂优化算法(Dung Beetle Algorithm,DBA)进行多种策略改进,以提高其在CEC2017基准测试中的表现。 蜣螂优化算法是一种仿…...
深度学习中常见的矩阵变换函数汇总(持续更新...)
1. 转置操作 (Transpose) 概念:将矩阵的行和列互换应用场景: 在卷积神经网络中转换特征图维度矩阵乘法运算前的维度调整数据预处理过程中的特征重排 原始矩阵 A [[1, 2, 3], 转置后 A^T [[1, 4],[4, 5, 6]] [2, 5],[3, 6]]代码…...
react+ts中函数组件父子通信方式
1. 父组件通过 Props 向子组件传递数据 这是最常见也是最基本的父子组件通信方式。父组件通过 props 将数据或回调函数传递给子组件。 示例代码: // 子组件接收来自父组件的数据 interface ChildProps {message: string; }const ChildComponent: React.FC<Chi…...