nacos配置刷新失败导致的cpu上升和频繁重启,nacos配置中心源码解析

大家好,我是烤鸭:
nacos 版本 1.3.2,先说下结论,频繁重启的原因确实没有找到,跟nacos有关,日志没有保留多少,只能从源码找下头绪(出问题的版本 server用的是 nacos 1.1,nacos-client 1.0)

nacos 拉取配置原理

有两个核心的类 ClientWorker 和 ServerHttpAgent,先从头捋一下。

NacosConfigBootstrapConfiguration 初始化 NacosConfigManager

@Bean
@ConditionalOnMissingBean
public NacosConfigManager nacosConfigManager(NacosConfigProperties nacosConfigProperties) {return new NacosConfigManager(nacosConfigProperties);
}

NacosConfigManager

static ConfigService createConfigService(NacosConfigProperties nacosConfigProperties) {if (Objects.isNull(service)) {synchronized (NacosConfigManager.class) {try {if (Objects.isNull(service)) {service = NacosFactory.createConfigService(nacosConfigProperties.assembleConfigServiceProperties());}}catch (NacosException e) {log.error(e.getMessage());throw new NacosConnectionFailureException(nacosConfigProperties.getServerAddr(), e.getMessage(), e);}}}return service;
}

NacosFactory -> ConfigFactory 初始化 NacosConfigService

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);return vendorImpl;} catch (Throwable e) {throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);}
}

executor 每10ms 检查配置信息, executorService 执行 LongPollingRunnable 的线程池(单线程)

public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager, final Properties properties) {this.agent = agent;this.configFilterChainManager = configFilterChainManager;// 设置超时时间(默认30s)init(properties);//...// 每10ms 检查配置信息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);
}

ClientWorker.checkConfigInfo
拉取任务的执行条件,cacheMap 是维护在内存里的任务数据(Map的key是groupid+dataid 分别 urlencode、value是CacheData)

public void checkConfigInfo() {// 获取cache的数据数量int listenerSize = cacheMap.get().size();// 向上取整int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());if (longingTaskCount > currentLongingTaskCount) {for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {// 要判断任务是否在执行 这块需要好好想想。 任务列表现在是无序的。变化过程可能有问题executorService.execute(new LongPollingRunnable(i));}currentLongingTaskCount = longingTaskCount;}}

ClientWorker$LongPollingRunnable

 class LongPollingRunnable implements Runnable {private int taskId;public LongPollingRunnable(int taskId) {this.taskId = taskId;}@Overridepublic void run() {List<CacheData> cacheDatas = new ArrayList<CacheData>();List<String> inInitializingCacheList = new ArrayList<String>();try {// 校验本地数据for (CacheData cacheData : cacheMap.get().values()) {if (cacheData.getTaskId() == taskId) {cacheDatas.add(cacheData);try {// 看下面具体方法checkLocalConfig(cacheData);if (cacheData.isUseLocalConfigInfo()) {// 是否有属性变更(变更会通过notify改变监听器的md5值),变更的话更新cacheDatacacheData.checkListenerMd5();}} catch (Exception e) {LOGGER.error("get local config info error", e);}}}// 获取变更属性,更多看下面List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);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 {String[] ct = getServerConfig(dataId, group, tenant, 3000L);CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));cache.setContent(ct[0]);if (null != ct[1]) {cache.setType(ct[1]);}LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}, type={}", agent.getName(), dataId, group, tenant, cache.getMd5(), ContentUtils.truncateContent(ct[0]), ct[1]);} 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);}}// 更新缓存cacheDatafor (CacheData cacheData : cacheDatas) {if (!cacheData.isInitializing() || inInitializingCacheList.contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {cacheData.checkListenerMd5();cacheData.setInitializing(false);}}inInitializingCacheList.clear();// 由于从server端拉取配置是 30s一次(同步操作), 这里可以理解为每30s执行的定时线程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);}}}

ClientWorker.checkLocalConfig

private void checkLocalConfig(CacheData cacheData) {final String dataId = cacheData.dataId;final String group = cacheData.group;final String tenant = cacheData.tenant;File path = LocalConfigInfoProcessor.getFailoverFile(agent.getName(), dataId, group, tenant);// 开启本地配置并且本地文件存在,以本地文件为准,更新缓存if (!cacheData.isUseLocalConfigInfo() && path.exists()) {String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);final String md5 = MD5Utils.md5Hex(content, Constants.ENCODE);cacheData.setUseLocalConfigInfo(true);cacheData.setLocalConfigInfoVersion(path.lastModified());cacheData.setContent(content);LOGGER.warn("[{}] [failover-change] failover file created. dataId={}, group={}, tenant={}, md5={}, content={}", agent.getName(), dataId, group, tenant, md5, ContentUtils.truncateContent(content));return;}// 开启本地配置并且本地文件不存在,以服务器拉取为准,同时关闭本地配置if (cacheData.isUseLocalConfigInfo() && !path.exists()) {cacheData.setUseLocalConfigInfo(false);LOGGER.warn("[{}] [failover-change] failover file deleted. dataId={}, group={}, tenant={}", agent.getName(), dataId, group, tenant);return;}// 开启本地配置并且本地文件存在并且缓存中版本和本地文件版本不一样,以本地文件为准,更新缓存if (cacheData.isUseLocalConfigInfo() && path.exists() && cacheData.getLocalConfigInfoVersion() != path.lastModified()) {String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);final String md5 = MD5Utils.md5Hex(content, Constants.ENCODE);cacheData.setUseLocalConfigInfo(true);cacheData.setLocalConfigInfoVersion(path.lastModified());cacheData.setContent(content);LOGGER.warn("[{}] [failover-change] failover file changed. dataId={}, group={}, tenant={}, md5={}, content={}", agent.getName(), dataId, group, tenant, md5, ContentUtils.truncateContent(content));}}

ClientWorker.checkUpdateDataIds —> ClientWorker.checkUpdateConfigStr —> (ServerHttpAgent)HttpAgent.httpPost
组装请求头和参数,重试,异常处理的代码都删掉了,这里只保留了这块,这就是为什么每次请求server端会本地阻塞30s。

public HttpRestResult<String> httpPost(String path, Map<String, String> headers, Map<String, String> paramValues,String encode, long readTimeoutMs) throws Exception {// ...do {// ...HttpRestResult<String> result = NACOS_RESTTEMPLATE.postForm(getUrl(currentServerAddr, path), httpConfig, newHeaders,new HashMap<String, String>(0), paramValues, String.class);// ...} while (System.currentTimeMillis() <= endTime);}

CacheData.checkListenerMd5 ,判断内存中的和server端的md5 值

void checkListenerMd5() {for (ManagerListenerWrap wrap : listeners) {if (!md5.equals(wrap.lastCallMd5)) {safeNotifyListener(dataId, group, content, type, md5, wrap);}}}

CacheData.safeNotifyListener ,可以看到 listener.receiveConfigChange 是执行具体的刷新操作(热加载),最后会更新内存的 lastCallMd5 值

Runnable job = new Runnable() {@Overridepublic void run() {//...// 执行回调之前先将线程classloader设置为具体webapp的classloader,以免回调方法中调用spi接口是出现异常或错用(多应用部署才会有该问题)。Thread.currentThread().setContextClassLoader(appClassLoader);// ...// 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);// ...}};

问题排查

先看下当时的日志:
能看出来服务在不停的刷新应用(热重启,间隔时间30s),不停热重启引起cpu升高(又是另一个问题了)
在这里插入图片描述

再看一下机器的cpu,3台不同的机器都几乎是同时出现了这个问题,cpu在24h之内缓慢上升
在这里插入图片描述

日志内容很少,结合刚才看的源码,获取服务端配置确实是30s一次,也就是找到触发点就知道了。

假设猜想

1.0 的代码和 1.3.2 还是有点区别,这是 1.0 的 CacheData.safeNotifyListener

 private void safeNotifyListener(final String dataId, final String group, final String content,final String md5, final ManagerListenerWrap listenerWrap) {final Listener listener = listenerWrap.listener;Runnable job = new Runnable() {public void run() {// ...Thread.currentThread().setContextClassLoader(appClassLoader);ConfigResponse cr = new ConfigResponse();cr.setDataId(dataId);cr.setGroup(group);cr.setContent(content);configFilterChainManager.doFilter(null, cr);String contentTmp = cr.getContent();listener.receiveConfigInfo(contentTmp);listenerWrap.lastCallMd5 = md5;LOGGER.info("[{}] [notify-ok] dataId={}, group={}, md5={}, listener={} ", name, dataId, group, md5,listener);}};

结合日志猜测是 listenerWrap.lastCallMd5 = md5; 没有执行,也就是执行 listener.receiveConfigInfo(contentTmp); 报错了。

listener.receiveConfigInfo 都干啥了
在这里插入图片描述

简单来说就是调用了 applicationContext.publishEvent,传入的是刷新事件 RefreshEvent

RefreshEventListener 会接收刷新事件执行刷新操作,继续发布事件 RefreshScopeRefreshedEvent
在这里插入图片描述

对 @RefreshScope 的 Bean 完成热加载。

看一段nacos正常刷新属性(相同的项目)的日志。

后续是对注册中心再次注册和发现服务的过程 (DiscoveryClient),目前来看是这个地方报错了,但是具体原因还是不知道。

在这里插入图片描述

总结

虽然是nacos 引起的,但是问题原因并没有找到。

注册中心使用的是eurka。

服务是新服务,暂时没流量,重启之后也没再发现类似的问题,等下次出现了一定要保留日志…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/412571.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

nova— 计算服务

一、nova介绍&#xff1a; Nova 是 OpenStack 最核心的服务&#xff0c;负责维护和管理云环境的计算资源。OpenStack 作为 IaaS 的云操作系统&#xff0c;虚拟机生命周期管理也就是通过 Nova 来实现的。用途与功能 :1) 实例生命周期管理2) 管理计算资源3) 网络和认证管理4)REST…

[css] 写出你知道的CSS水平和垂直居中的方法

[css] 写出你知道的CSS水平和垂直居中的方法 flex布局水平垂直居中:<!-- html --> <div class"outer"><div class"inner"></div> </div>/*css*/ .outer{display:flex;width:200px;height:200px;border:1px solid red; } .…

springcloud gateway 自定义 accesslog elk

大家好&#xff0c;我是烤鸭&#xff1a; ​ 最近用 springcloud gateway 时&#xff0c;想使用类似 logback-access的功能&#xff0c;用来做数据统计和图表绘制等等&#xff0c;发现没有类似的功能&#xff0c;只能自己开发了。 环境&#xff1a; <dependency><gr…

第二阶段团队绩效考核报告

团队绩效考核 基于各种客观问题本次绩效考核采用和第一次冲刺不一样的标准&#xff0c;根据团队贡献事实打分如下 组员打分&#xff1a; 郭良 &#xff08;9.0&#xff09; 赵承龙 &#xff08;5.5&#xff09; &#xff08;根据组内之前定下的打分细则和本期冲刺过程的事实…

[css] 实现单行文本居中和多行文本左对齐并超出显示“...“

[css] 实现单行文本居中和多行文本左对齐并超出显示"…" .one {text-align: center }.multi {overflow: hiddentext-overflow: ellipsisdisplay: -webkit-box-webkit-line-clamp: 3-webkit-box-orient: vertical }可惜多行文本省略, 有严重的兼容性问题个人简介 我…

jenkins发布docker项目 harbor

大家好&#xff0c;我是烤鸭&#xff1a; ​ jenkins 部署k8s 项目还是比较流畅的&#xff0c;本身建立多流水线项目&#xff0c;在项目中添加jenkinsfile就好了&#xff0c;镜像需要额外的参数&#xff0c;还可以添加dokcerfile文件。由于我现在的问题是不能够修改原有的项…

[css] 不使用border画出1px高的线,在不同浏览器的标准和怪异模式下都能保持效果一样

[css] 不使用border画出1px高的线&#xff0c;在不同浏览器的标准和怪异模式下都能保持效果一样 <div style"width: 100%;height: 1px;"></div><hr size"1">个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&am…

bzoj 3173 最长上升子序列

Written with StackEdit. Description 给定一个序列&#xff0c;初始为空。现在我们将\(1\)到\(N\)的数字插入到序列中&#xff0c;每次将一个数字插入到一个特定的位置。每插入一个数字&#xff0c;我们都想知道此时最长上升子序列长度是多少&#xff1f; Input 第一行一个整数…

java 调用linux 脚本并获取返回值

大家好&#xff0c;我是烤鸭&#xff1a; 今天分享下java 调用 shell脚本 并获取返回值。 代码实践 String cmd "df -h"; StringBuffer sb new StringBuffer(); Process process Runtime.getRuntime().exec(cmd); BufferedReader br new BufferedReader(new In…

[css] 写出主流浏览器内核私有属性的css前缀

[css] 写出主流浏览器内核私有属性的css前缀 完善一下&#xff1a; Chrome&#xff1a;Blink内核 -webkit-Safari&#xff1a;WebKit内核 -webkit-Firefox &#xff1a;Gecko内核 -moz-IE&#xff1a;Trident内核 -ms-Opera&#xff1a;Presto内核 …

补充小知识:文件句柄与文件标识符

#文件句柄 这是操作系统里的一个概念&#xff0c;句柄是WINDOWS用来标识被应用程序所建立或使用的对象的唯一整数&#xff0c;WINDOWS使用各种各样的句柄标识诸如应用程序实例&#xff0c;窗口&#xff0c;控制&#xff0c;位图&#xff0c;GDI对象等等。WINDOWS句柄有点象C语言…

[css] 使用flex实现三栏布局,两边固定,中间自适应

[css] 使用flex实现三栏布局&#xff0c;两边固定&#xff0c;中间自适应 同意里面的一个回答&#xff0c;现在有很多简单的实现方式&#xff0c;传统的这个也是一种hack的方式&#xff0c;真的没必要去追究了&#xff0c;但是核心知识点可以掌握。1.把 center 放在最前面&…

saltstack部署java应用失败无日志——CICD 部署

大家好&#xff0c;我是烤鸭&#xff1a; ​   最近在搞公司的CICD&#xff0c;遇到各种问题。复盘总结一下。 CICD 架构 这篇文章写得很详细&#xff0c;可以看一下 https://linux.cn/article-9926-1.html 而这里只是结合现在的情况分析下&#xff1a; CI 持续集成&…

day15 webUI自动化

一、webdriver的原理 driver webdriver.Chrome()创建浏览器&#xff0c;当做我们的服务端&#xff0c;代码就是客户端&#xff0c;和客户端进行ip绑定&#xff0c;基于http协议发送post请求 WebDriver webdriver是按照server – client的经典设计模式设计的。 webdriver的作用…

[css] 浏览器是怎样判断元素是否和某个CSS选择器匹配?

[css] 浏览器是怎样判断元素是否和某个CSS选择器匹配&#xff1f; 有选择器&#xff1a; div.ready #wrapper > .bg-red 先把所有元素 class 中有 bg-red 的元素拿出来组成一个集合&#xff0c;然后上一层&#xff0c;对每一个集合中的元素&#xff0c;如果元素的 parent i…

idea 插件开发 扫描sqlserver

大家好&#xff0c;我是烤鸭&#xff1a; 最近在搞sqlserver 升级 mysql/tidb&#xff0c;发现代码里的sql有很多地方需要改&#xff0c;想着能不能开发一个省点力。 官方的迁移指南&#xff1a; https://www.mysql.com/why-mysql/white-papers/sql-server-to-mysql-zh/ 方案…

VUE之文字跑马灯效果

1.效果演示 2.相关代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><script src"js/vue-2.4.0.js"></script> </head> <body> <div id&…

[css] 用CSS绘制一个三角形

[css] 用CSS绘制一个三角形 .triangle{width: 0;border-bottom: 35px solid lightgreen;border-left: 35px solid transparent;}wrong.triangle{width: 0;border: 35px solid transparent;border-bottom: 35px solid lightgreen; }个人简介 我是歌谣&#xff0c;欢迎和大家一起…

计算机从加电到系统(Linux)启动完成

0x0 背景 在我参加的面试和我面试别人、或者参加别人对别人的面试的事后经常遇到的一个问题就是&#xff1a;请从计算机加电开始描述一下计算机启动到操作系统正式启动起来的全过程。这是一个考验对计算机体系结构和基本知识了解程度的问题。今天也就特别针对这个问题做一个回答…

《少有人走的路——心智成熟的旅程》读书笔记

大家好&#xff0c;我是烤鸭&#xff1a; 《少有人走的路——心智成熟的旅程》&#xff0c;读书笔记。 第一部分 自律 规避问题和逃避痛苦的趋向&#xff0c;是人类心理疾病的根源。(正视自己是最重要的) 自律可以消除人的痛苦&#xff1a;延迟满足、承担责任、尊重事实…