【Nacos】Nacos配置中心客户端配置更新源码分析
上文我们说了服务启动的时候从远程Nacos服务端拉取配置,这节我们来说下Nacos服务端配置的变动怎么实时通知到客户端,首先需要注册监听器。
注册监听器
NacosContextRefresher类会监听应用启动发布的ApplicationReadyEvent事件,然后进行配置监听器的注册。
com.alibaba.cloud.nacos.refresh.NacosContextRefresher#onApplicationEvent
public void onApplicationEvent(ApplicationReadyEvent event) {// many Spring contextif (this.ready.compareAndSet(false, true)) {this.registerNacosListenersForApplications();}
}
registerNacosListenersForApplications()方法里会进行判断,如果自动刷新机制是开启的,则进行监听器注册。上文我们说到了会将拉到的配置缓存到NacosPropertySourceRepository中, 这儿就从缓存中获取所有的配置,然后循环进行监听器注册(如果配置文件中配置refresh字段为 false,则不注册监听器)。
com.alibaba.cloud.nacos.refresh.NacosContextRefresher#registerNacosListenersForApplications
private void registerNacosListenersForApplications() {if (isRefreshEnabled()) {for (NacosPropertySource propertySource : NacosPropertySourceRepository.getAll()) {if (!propertySource.isRefreshable()) {continue;}String dataId = propertySource.getDataId();registerNacosListener(propertySource.getGroup(), dataId);}}
}
我们可以看到,监听器是以dataId+groupId+namespace为维度进行注册的,后续配置更新时会回调此监听器。
监听器的逻辑主要就三步:
- REFRESH_COUNT++,在之前的loadNacosPropertySource()方法有用到
- 往NacosRefreshHistory#records中添加一条刷新记录
- 发布一个RefreshEvent事件,该事件是SpringCloud提供的,主要就是用来做环境变更刷新用的
com.alibaba.cloud.nacos.refresh.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() {@Overridepublic void innerReceive(String dataId, String group,String configInfo) {refreshCountIncrement();nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);// 发布RefreshEvent事件// todo feature: support single refresh for listeningapplicationContext.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);}catch (NacosException e) {log.warn(String.format("register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,groupKey), e);}
}
监听器的注册操作又委托到了ConfigService。
com.alibaba.nacos.client.config.NacosConfigService#addListener
public void addListener(String dataId, String group, Listener listener) throws NacosException {worker.addTenantListeners(dataId, group, Arrays.asList(listener));
}
监听器的注册在ClientWorker中处理,这块会创建一个CacheData对象,该对象主要就是用来管理监听器的,也是非常重要的一个类。
com.alibaba.nacos.client.config.impl.ClientWorker#addTenantListeners
public void addTenantListeners(String dataId, String group, List<? extends Listener> listeners)throws NacosException {group = blank2defaultGroup(group);String tenant = agent.getTenant();CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);for (Listener listener : listeners) {cache.addListener(listener);}
}
CacheData中中药字段如下:
// 可对配置进行拦截处理,可用于配置加密解密
private final ConfigFilterChainManager configFilterChainManager;public final String dataId;public final String group;public final String tenant;// 关注此配置的监听器
private final CopyOnWriteArrayList<ManagerListenerWrap> listeners;// 用于比较此配置是否变更
private volatile String md5;/*** whether use local config.*/
private volatile boolean isUseLocalConfig = false;/*** last modify time.*/
private volatile long localConfigLastModified;// 配置的内容
private volatile String content;
addCacheDataIfAbsent()方法中会将刚才创建的CacheData缓存到ClientWorker中的一个Map中,后续会用到。
com.alibaba.nacos.client.config.impl.ClientWorker#addCacheDataIfAbsent
public CacheData addCacheDataIfAbsent(String dataId, String group, String tenant) throws NacosException {String key = GroupKey.getKeyTenant(dataId, group, tenant);CacheData cacheData = cacheMap.get(key);if (cacheData != null) {return cacheData;}cacheData = new CacheData(configFilterChainManager, agent.getName(), dataId, group, tenant);// multiple listeners on the same dataid+group and race conditionCacheData lastCacheData = cacheMap.putIfAbsent(key, cacheData);if (lastCacheData == null) {//fix issue # 1317if (enableRemoteSyncConfig) {ConfigResponse response = getServerConfig(dataId, group, tenant, 3000L);cacheData.setContent(response.getContent());}int taskId = cacheMap.size() / (int) ParamUtil.getPerTaskConfigSize();cacheData.setTaskId(taskId);lastCacheData = cacheData;}// reset so that server not hang this checklastCacheData.setInitializing(true);LOGGER.info("[{}] [subscribe] {}", agent.getName(), key);MetricsMonitor.getListenConfigCountMonitor().set(cacheMap.size());return lastCacheData;
}
至此,在服务启动后向每一个需要支持热更新的配置都注册了一个监听器,用来监听远程配置的变动,以及做相应的处理。
获取更新的配置
ClientWorker是在ConfigService的构造方法中创建的。
ClientWorker的构造函数里会去创建两个线程池,executor会每隔10ms进行一次配置变更的检查,executorService主要是用来处理长轮询请求的。
com.alibaba.nacos.client.config.impl.ClientWorker#ClientWorker
public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager,final Properties properties) {this.agent = agent;this.configFilterChainManager = configFilterChainManager;// Initialize the timeout parameterinit(properties);this.executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(r);t.setName("com.alibaba.nacos.client.Worker." + agent.getName());t.setDaemon(true);return t;}});this.executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(r);t.setName("com.alibaba.nacos.client.Worker.longPolling." + agent.getName());t.setDaemon(true);return t;}});this.executor.scheduleWithFixedDelay(new Runnable() {@Overridepublic void run() {try {// 检查配置信息checkConfigInfo();} catch (Throwable e) {LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);}}}, 1L, 10L, TimeUnit.MILLISECONDS);
}
checkConfigInfo()负责提交长轮询任务。
com.alibaba.nacos.client.config.impl.ClientWorker#checkConfigInfo
public void checkConfigInfo() {// Dispatch tasks.int listenerSize = cacheMap.size();// Round up the longingTaskCount.int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());if (longingTaskCount > currentLongingTaskCount) {for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {// The task list is no order.So it maybe has issues when changing.executorService.execute(new LongPollingRunnable(i));}currentLongingTaskCount = longingTaskCount;}
}
长轮询任务的执行过程。
com.alibaba.nacos.client.config.impl.ClientWorker.LongPollingRunnable#run
public void run() {List<CacheData> cacheDatas = new ArrayList<CacheData>();List<String> inInitializingCacheList = new ArrayList<String>();try {// check failover configfor (CacheData cacheData : cacheMap.values()) {if (cacheData.getTaskId() == taskId) {cacheDatas.add(cacheData);try {checkLocalConfig(cacheData);if (cacheData.isUseLocalConfigInfo()) {cacheData.checkListenerMd5();}} catch (Exception e) {LOGGER.error("get local config info error", e);}}}// check server config// 校验配置List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);if (!CollectionUtils.isEmpty(changedGroupKeys)) {LOGGER.info("get changedGroupKeys:" + changedGroupKeys);}for (String groupKey : changedGroupKeys) {String[] key = GroupKey.parseKey(groupKey);String dataId = key[0];String group = key[1];String tenant = null;if (key.length == 3) {tenant = key[2];}try {// 根据dataId从服务端查询最新的配置ConfigResponse response = getServerConfig(dataId, group, tenant, 3000L);CacheData cache = cacheMap.get(GroupKey.getKeyTenant(dataId, group, tenant));cache.setContent(response.getContent());cache.setEncryptedDataKey(response.getEncryptedDataKey());if (null != response.getConfigType()) {cache.setType(response.getConfigType());}LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}, type={}",agent.getName(), dataId, group, tenant, cache.getMd5(),ContentUtils.truncateContent(response.getContent()), response.getConfigType());} catch (NacosException ioe) {String message = String.format("[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s",agent.getName(), dataId, group, tenant);LOGGER.error(message, ioe);}}for (CacheData cacheData : cacheDatas) {if (!cacheData.isInitializing() || inInitializingCacheList.contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {// 校验md5是否变化,有变化就发通知cacheData.checkListenerMd5();cacheData.setInitializing(false);}}inInitializingCacheList.clear();executorService.execute(this);} catch (Throwable e) {// If the rotation training task is abnormal, the next execution time of the task will be punishedLOGGER.error("longPolling error : ", e);executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);}
}
checkUpdateDataIds()该方法中,会将所有的dataId按定义格式拼接出一个字符串,构造一个长轮询请求,发给服务端,Long-Pulling-Timeout 超时时间默认30s,如果服务端没有配置变更,则会保持该请求直到超时,有配置变更则直接返回有变更的dataId列表。
com.alibaba.nacos.client.config.impl.ClientWorker#checkUpdateDataIds
List<String> checkUpdateDataIds(List<CacheData> cacheDatas, List<String> inInitializingCacheList) throws Exception {StringBuilder sb = new StringBuilder();for (CacheData cacheData : cacheDatas) {if (!cacheData.isUseLocalConfigInfo()) {sb.append(cacheData.dataId).append(WORD_SEPARATOR);sb.append(cacheData.group).append(WORD_SEPARATOR);if (StringUtils.isBlank(cacheData.tenant)) {sb.append(cacheData.getMd5()).append(LINE_SEPARATOR);} else {sb.append(cacheData.getMd5()).append(WORD_SEPARATOR);sb.append(cacheData.getTenant()).append(LINE_SEPARATOR);}if (cacheData.isInitializing()) {// It updates when cacheData occurs in cacheMap by first time.// 添加要初始化的cacheDatainInitializingCacheList.add(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant));}}}boolean isInitializingCacheList = !inInitializingCacheList.isEmpty();// 检验服务器端的配置return checkUpdateConfigStr(sb.toString(), isInitializingCacheList);
}
checkUpdateConfigStr()会发起HTTP接口/v1/cs/configs/listener
的调用。
com.alibaba.nacos.client.config.impl.ClientWorker#checkUpdateConfigStr
List<String> checkUpdateConfigStr(String probeUpdateString, boolean isInitializingCacheList) throws Exception {Map<String, String> params = new HashMap<String, String>(2);params.put(Constants.PROBE_MODIFY_REQUEST, probeUpdateString);Map<String, String> headers = new HashMap<String, String>(2);// 使用长轮询headers.put("Long-Pulling-Timeout", "" + timeout);// told server do not hang me up if new initializing cacheData added inif (isInitializingCacheList) {headers.put("Long-Pulling-Timeout-No-Hangup", "true");}if (StringUtils.isBlank(probeUpdateString)) {return Collections.emptyList();}try {// In order to prevent the server from handling the delay of the client's long task,// increase the client's read timeout to avoid this problem.long readTimeoutMs = timeout + (long) Math.round(timeout >> 1);// 调用远程的监听接口HttpRestResult<String> result = agent.httpPost(Constants.CONFIG_CONTROLLER_PATH + "/listener", headers, params, agent.getEncode(),readTimeoutMs);if (result.ok()) {setHealthServer(true);return parseUpdateDataIdResponse(result.getData());} else {setHealthServer(false);LOGGER.error("[{}] [check-update] get changed dataId error, code: {}", agent.getName(),result.getCode());}} catch (Exception e) {setHealthServer(false);LOGGER.error("[" + agent.getName() + "] [check-update] get changed dataId exception", e);throw e;}return Collections.emptyList();
}
checkListenerMd5()主要就是判断两个md5是不是相同,不同则调用safeNotifyListener()处理。
com.alibaba.nacos.client.config.impl.CacheData#checkListenerMd5
void checkListenerMd5() {for (ManagerListenerWrap wrap : listeners) {if (!md5.equals(wrap.lastCallMd5)) {// 配置有变化通知监听器safeNotifyListener(dataId, group, content, type, md5, encryptedDataKey, wrap);}}
}
safeNotifyListener()方法主要就是调用监听器的receiveConfigInfo()方法,然后更新监听器包装器中的lastContent、lastCallMd5字段。
com.alibaba.nacos.client.config.impl.CacheData#safeNotifyListener
private void safeNotifyListener(final String dataId, final String group, final String content, final String type,final String md5, final String encryptedDataKey, final ManagerListenerWrap listenerWrap) {final Listener listener = listenerWrap.listener;Runnable job = new Runnable() {@Overridepublic void run() {ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();ClassLoader appClassLoader = listener.getClass().getClassLoader();try {if (listener instanceof AbstractSharedListener) {AbstractSharedListener adapter = (AbstractSharedListener) listener;adapter.fillContext(dataId, group);LOGGER.info("[{}] [notify-context] dataId={}, group={}, md5={}", name, dataId, group, md5);}// 执行回调之前先将线程classloader设置为具体webapp的classloader,以免回调方法中调用spi接口是出现异常或错用(多应用部署才会有该问题)。Thread.currentThread().setContextClassLoader(appClassLoader);ConfigResponse cr = new ConfigResponse();cr.setDataId(dataId);cr.setGroup(group);cr.setContent(content);cr.setEncryptedDataKey(encryptedDataKey);configFilterChainManager.doFilter(null, cr);String contentTmp = cr.getContent();/*** @see com.alibaba.cloud.nacos.refresh.NacosContextRefresher#registerNacosListener(java.lang.String, java.lang.String)*/listener.receiveConfigInfo(contentTmp);// compare lastContent and contentif (listener instanceof AbstractConfigChangeListener) {Map data = ConfigChangeHandler.getInstance().parseChangeData(listenerWrap.lastContent, content, type);ConfigChangeEvent event = new ConfigChangeEvent(data);((AbstractConfigChangeListener) listener).receiveConfigChange(event);listenerWrap.lastContent = content;}listenerWrap.lastCallMd5 = md5;LOGGER.info("[{}] [notify-ok] dataId={}, group={}, md5={}, listener={} ", name, dataId, group, md5,listener);} catch (NacosException ex) {LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} errCode={} errMsg={}",name, dataId, group, md5, listener, ex.getErrCode(), ex.getErrMsg());} catch (Throwable t) {LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} tx={}", name, dataId,group, md5, listener, t.getCause());} finally {Thread.currentThread().setContextClassLoader(myClassLoader);}}};final long startNotify = System.currentTimeMillis();try {if (null != listener.getExecutor()) {listener.getExecutor().execute(job);} else {job.run();}} catch (Throwable t) {LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} throwable={}", name, dataId,group, md5, listener, t.getCause());}final long finishNotify = System.currentTimeMillis();LOGGER.info("[{}] [notify-listener] time cost={}ms in ClientWorker, dataId={}, group={}, md5={}, listener={} ",name, (finishNotify - startNotify), dataId, group, md5, listener);
}
监听器要执行的方法我们上面也已经讲过了,主要就是发布RefreshEvent事件。至此,Nacos的处理流程已经结束了,RefreshEvent事件主要由 SpringCloud相关类来处理。
RefreshEvent事件处理
RefreshEvent事件会由RefreshEventListener来处理。
org.springframework.cloud.endpoint.event.RefreshEventListener#onApplicationEvent
public void onApplicationEvent(ApplicationEvent event) {if (event instanceof ApplicationReadyEvent) {handle((ApplicationReadyEvent) event);}else if (event instanceof RefreshEvent) {handle((RefreshEvent) event);}
}
委托给ContextRefresher来刷新容器中的配置。
org.springframework.cloud.endpoint.event.RefreshEventListener#handle(org.springframework.cloud.endpoint.event.RefreshEvent)
public void handle(RefreshEvent event) {if (this.ready.get()) { // don't handle events before app is readylog.debug("Event received " + event.getEventDesc());Set<String> keys = this.refresh.refresh();log.info("Refresh keys changed: " + keys);}
}
org.springframework.cloud.context.refresh.ContextRefresher#refresh
public synchronized Set<String> refresh() {Set<String> keys = refreshEnvironment();this.scope.refreshAll();return keys;
}
refreshEnvironment()会去刷新Spring环境变量,实际上是交给addConfigFilesToEnvironment()方法去做的刷新,具体刷新思想就是重新创建一个新的Spring容器,然后将这个新容器中的环境信息设置到原有的Spring环境中。拿到所有变化的配置项后,发布一个环境变化的 EnvironmentChangeEvent事件。
org.springframework.cloud.context.refresh.ContextRefresher#refreshEnvironment
public synchronized Set<String> refreshEnvironment() {Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());addConfigFilesToEnvironment();Set<String> keys = changes(before,extract(this.context.getEnvironment().getPropertySources())).keySet();this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));return keys;
}/* For testing. */ ConfigurableApplicationContext addConfigFilesToEnvironment() {ConfigurableApplicationContext capture = null;try {StandardEnvironment environment = copyEnvironment(this.context.getEnvironment());SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class).bannerMode(Mode.OFF).web(WebApplicationType.NONE).environment(environment);// Just the listeners that affect the environment (e.g. excluding logging// listener because it has side effects)builder.application().setListeners(Arrays.asList(new BootstrapApplicationListener(),new ConfigFileApplicationListener()));capture = builder.run();if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) {environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);}MutablePropertySources target = this.context.getEnvironment().getPropertySources();String targetName = null;for (PropertySource<?> source : environment.getPropertySources()) {String name = source.getName();if (target.contains(name)) {targetName = name;}if (!this.standardSources.contains(name)) {if (target.contains(name)) {target.replace(name, source);}else {if (targetName != null) {target.addAfter(targetName, source);// update targetName to preserve orderingtargetName = name;}else {// targetName was null so we are at the start of the listtarget.addFirst(source);targetName = name;}}}}}finally {ConfigurableApplicationContext closeable = capture;while (closeable != null) {try {closeable.close();}catch (Exception e) {// Ignore;}if (closeable.getParent() instanceof ConfigurableApplicationContext) {closeable = (ConfigurableApplicationContext) closeable.getParent();}else {break;}}}return capture;
}
org.springframework.cloud.context.scope.refresh.RefreshScope#refreshAll
public void refreshAll() {super.destroy();this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
@Value注解的属性要实现热更新就需要配合@RefreshScope注解,被@RefreshScope注解的对象的作用域为RefreshScope,这种对象不是存在Spring容器的一级缓存中,而是存在GenericScope对象的cache属性中,当配置变更时会清空缓存在cache属性的对象,这样Bean下次使用时就会被重新创建,从而从Environment中获取最新的配置。
org.springframework.cloud.context.scope.GenericScope#destroy()
public void destroy() {List<Throwable> errors = new ArrayList<Throwable>();// 清空缓存Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();for (BeanLifecycleWrapper wrapper : wrappers) {try {Lock lock = this.locks.get(wrapper.getName()).writeLock();lock.lock();try {wrapper.destroy();}finally {lock.unlock();}}catch (RuntimeException e) {errors.add(e);}}if (!errors.isEmpty()) {throw wrapIfNecessary(errors.get(0));}this.errors.clear();
}
相关文章:

【Nacos】Nacos配置中心客户端配置更新源码分析
上文我们说了服务启动的时候从远程Nacos服务端拉取配置,这节我们来说下Nacos服务端配置的变动怎么实时通知到客户端,首先需要注册监听器。 注册监听器 NacosContextRefresher类会监听应用启动发布的ApplicationReadyEvent事件,然后进行配置…...
按钮防抖与节流-vue2
防抖与节流,应用场景有很多,例如:禁止重复提交数据的场景、搜索框输入搜索条件,待输入停止后再开始搜索。 防抖 点击button按钮,设置定时器,在规定的时间内再次点击会重置定时器重新计时,在规定…...

PyTorch学习笔记:nn.SmoothL1Loss——平滑L1损失
PyTorch学习笔记:nn.SmoothL1Loss——平滑L1损失 torch.nn.SmoothL1Loss(size_averageNone, reduceNone, reductionmean, beta1.0)功能:创建一个平滑后的L1L_1L1损失函数,即Smooth L1: l(x,y)L{l1,…,lN}Tl(x,y)L\{l_1,\dots,l…...

2年时间,涨薪20k,想拿高薪还真不能老老实实的工作...
2016年开始了我的测试生活。 2016年刚到公司的时候,我做的是测试工程师。做测试工程师是我对自己的职业规划。说实话,我能得到这份工作真的很高兴。 来公司的第一个星期,因为有一个项目缺人,所以部门经理提前结束了我的考核期&a…...
Spark - Spark SQL中RBO, CBO与AQE简单介绍
Spark SQL核心是Catalyst, Catalyst执行流程主要分4个阶段, 语句解析, 逻辑计划与优化, 物理计划与优化, 代码生成 前三个阶段都由Catalyst负责, 其中, 逻辑计划的优化采用RBO思路, 物理计划的优化采用CBO思路 RBO (Rule Based Optimization) 基于规则优化, 通过一系列预定好…...

NeurIPS/ICLR/ICML AI三大会国内高校和企业近年中稿量完整统计
点击文末公众号卡片,找对地方,轻松参会。 近日,有群友转发了一张网图,统计了近年来中国所有单位在NeurIPS、ICLR、ICML论文情况。原图如下: 中稿数100: 清华(1) 北大(2) 占比:22.6%。 累计数…...

Android IO 框架 Okio 的实现原理,到底哪里 OK?
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问。 前言 大家好,我是小彭。 今天,我们来讨论一个 Square 开源的 I/O 框架 Okio,我们最开始接触到 Okio 框架还是源于 Square 家的 OkHttp 网络…...

一文讲解Linux 设备模型 kobject,kset
设备驱动模型 面试的时候,有面试官会问,什么是Linux 设备驱动模型?你要怎么回答? 这个问题,突然这么一问,可能你会愣住不知道怎么回答,因为Linux 设备驱动模型是一个比较整体的概念࿰…...

linux配置密码过期的安全策略(/etc/login.defs的解读)
长期不更换密码很容易导致密码被破解,而linux的密码过期安全策略主要在/etc/login.defs中配置。一、/etc/login.defs文件的参数解读1、/etc/login.defs文件的内容示例[rootlocalhost ~]# cat /etc/login.defs # # Please note that the parameters in this configur…...
c_character_string 字符串----我认真的弄明白了,也希望你们也是。
字符串 1. 字符串长度strlen 1.1strlen 函数介绍 size_t strlen ( const char * str );strlen ——string length strlen 的头文件是 #include <string.h> 参数指向的字符串必须要以 ‘\0’ 结束。 strlen 是求字符串长度的函数,统计的是字符串中\0之前出现…...

spring面试题 一
一、为了降低Java开发的复杂性,Spring采取了那4种关键策略 基于POJO的轻量级和最小侵入性编程; 通过依赖注入和面向接口实现松耦合; 基于切面和惯例进行声明式编程; 通过切面和模板减少样板式代码。 二、Spring框架的核心&am…...
C++中char *,char a[ ]的特殊应用
1.数组的本质 数组是多个元素的集合,在内存中分布在地址相连的单元中,所以可以通过其下标访问不同单元的元素。 2.指针 指针也是一种变量,只不过它的内存单元中保存的是一个标识其他位置的地址。 3.字符串常量的本质是它的第一个字符的地…...

【Windows10】电脑副屏无法调节屏幕亮度?解决方法
先说下情况,本人对显示器不太懂,属于小白 这个副屏无法调节的问题出现也已经很久了,但是之前亮度适合就无所谓,今天突然按了之后很亮,于是就找问题。 第一步,我直接百度,遇事不决,百…...
Paper简读 - ProGen2: Exploring the Boundaries of Protein Language Models
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://blog.csdn.net/caroline_wendy/article/details/128976102 ProGen2: Exploring the Boundaries of Protein Language Models ProGen2:探索蛋白质语言模型的边界Cumulative density:累积密度 Ligand:在生…...

leaflet 加载WKT数据(示例代码050)
第050个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中加载WKT文件,将图形显示在地图上。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果; 注意如果OpenStreetMap无法加载,请加载其他来练习 文章目录 示例效果配置方式示例源代码(共67行…...

设计模式-组合模式和建筑者模式详解
一. 组合模式 1. 背景 在现实生活中,存在很多“部分-整体”的关系,例如,大学中的部门与学院、总公司中的部门与分公司、学习用品中的书与书包、生活用品中的衣月艮与衣柜以及厨房中的锅碗瓢盆等。在软件开发中也是这样,例如&…...
Pcap文件的magic_number
1. 简述 pcap文件头中的magicNum是来标识pcap文件文件头和包头字节序和应用是否一致的。 在标准情况下为0xa1b2c3d4。如果相反则说明需要调换字节序。 一般格式 Global Header Packet Header Packet Data Packet Header Packet Data ....pcap文件头格式 typedef struct pca…...

MDS75-16-ASEMI三相整流模块MDS75-16
编辑-Z MDS75-16在MDS封装里采用的6个芯片,是一款工业焊机专用大功率整流模块。MDS75-16的浪涌电流Ifsm为920A,漏电流(Ir)为5mA,其工作时耐温度范围为-40~150摄氏度。MDS75-16采用GPP硅芯片材质,里面有6颗芯片组成。MDS75-16的电…...

基本TCP编程
1. 基本概念 TCP (即传输控制协议) 是一种面向连接的传输层协议,它能提供高可靠性通信 (即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)。 2. 通信流程解析 TCP 通信的流程与打电话的过程相似,以下以一对情侣打电话的过程来展示TCP的通信流程: 其中服务端 …...

【沁恒WCH CH32V307V-R1开发板读取板载温度实验】
【沁恒WCH CH32V307V-R1开发板读取板载温度实验】1. 前言2. 软件配置2.1 安装MounRiver Studio3. ADC项目测试3.1 打开ADC工程3.2 编译项目4. 下载验证4.1 接线4.2 演示效果5. 小结1. 前言 ADC 模块包含 2 个 12 位的逐次逼近型的模拟数字转换器,最高 14MHz 的输入时…...

React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...

JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...