本文正在参与「金石计划 . 瓜分6万现金大奖」

今天下午突然 呈现 测验环境 cpu飙高,干到了 60%,其他项目 呼应时刻明显变长。。。有点吓人,不想背锅

项目布景

出问题的项目是 需要衔接各个不同nacos 和不同的 namespace 进行对应操作的 一个项目,对nacos的操作都是httpClient 调用的api接口,httpClient办法 没有问题,不必质疑这个

定位问题

首要 这 cpu高了,直接top -Hp 看看

定位到 进程id,然后 履行 jstack 进程id -> 1.txt

看到仓库信息 ,下面提示信息有许多


"com.alibaba.nacos.client.config.security.updater" #2269 daemon prio=5 os_prio=0 tid=0x00007fa3ec401800 nid=0x8d85 waiting on condition [0x00007fa314396000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000f7f3eae0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

可是上面这个提示信息 显现 是 线程内部的,而且是nacos client 内部的

【雨夜】一次nacos 导致的 CPU 飙高问题

你这么搞,让我很难过啊,我都是http 调用的,其时就是为了 防止敞开无用的线程,这。。。。。怎样

那我去 依据你的关键字找找 是哪里打印的,关键字 com.alibaba.nacos.client.config.security.updater

ServerHttpAgent 类的办法

// init executorService
this.executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setName("com.alibaba.nacos.client.config.security.updater");
        t.setDaemon(true);
        return t;
    }
});

这是结构办法啊,应该只初始化一次的啊,往上debug,我靠,NacosConfigService 类中调用了,debug 看什么时分调用了 不就行了嘛

项目初始化的时分 调用了一次,事务系统依靠nacos嘛,ok 能够理解

再就是绵长的等待,30s后 发现又是一次调用,我去,怎样或许。。。

往回debug,代码如下

scheduler.schedule("定时校正灰度nacos 配置", () -> loadGrayConfig(grayFileName),
    1800, 1800, TimeUnit.SECONDS);
/**
 * 灰度配置更新 处理 网络阻隔的问题
 *
 * @param grayFileName 灰度文件的称号
 */
private void loadGrayConfig(String grayFileName) {
    synchronized (this) {
        System.err.println("loadGrayConfig datetime: " + DateUtils.formatDate(new Date()));
        //刷一次 缓存 从头获取nacos 内容 赋值
        grayConfigManager.loadNoCache(grayFileName);
    }
}

【雨夜】一次nacos 导致的 CPU 飙高问题

等会,难道 小丑是我。。。。

【雨夜】一次nacos 导致的 CPU 飙高问题

这其时是为了灰度功用,定时数据校验用的 用了一个线程池,其时以为用了线程池 妥妥的。。。还特意调用的 Nocache 办法,让他创建新的nacos Config对象,做数据校正

可是每调用一次 NacosFactory.createConfigService(properties) ,nacos config 结构器就会开一个线程,就导致了这个问题

这里或许你要问了 你说 为了防止网络阻隔 才加的这个调度任务,什么是网络阻隔啊?

【雨夜】一次nacos 导致的 CPU 飙高问题

我刚开始听说这个概念是 其时学习 Raft

假定一个Raft集群拥有三个节点,其间节点3的网络被阻隔,那么依照BasicRaft的实现,集群会有以下动作:

  • 节点3由于网络被阻隔,收不到来自Leader的Heartbeat和AppendEntries,所以节点3会进入选举过程,当然选举过程也是收不到投票的,所以节点3会重复超时选举;节点3的Term就会一直增大
  • 节点1与节点2会正常作业,并停留在其时的Term

网络康复之后,Leader给节点3发送RPC的时分,节点3会拒绝这些RPC理由是发送方任期太小。

Leader收到节点3发送的拒绝后,会增大自己的Term,然后变成Follower。

随后,集群开始新的选举,大概率本来的Leader会成为新一轮的Leader。

那么网络阻隔 Raft是怎样处理的呢?

多轮投票的安全问题是棘手的,有必要防止同一高度不同轮数分别提交两个不同区块的情形。在Tendermint中,这个问题能够通过锁机制(locking mechanism)得到处理。

确认规矩: 预投票锁(Prevote-the-Lock)

验证者只能预投票(pre-vote) 他们被确认的区块。这样就阻挠验证者在上一轮中预提交(pre-commit)一个区块,之后又预投票了下一轮的另一个区块。

波尔卡解锁(Unlock-on-Polka ):验证者只有在看到更高一轮(相对于其当时被确认区块的轮数)的波尔卡之后才干释放该锁。这样就允许验证者解锁,如果他们预提交了某个区块,可是这个区块网络的剩下节点不想提交,这样就保护了整个网络的运转,而且这样做并没有危害网络安全性。

处理方案是把term替换成(term, nodeid),而且依照字典序比较大小(a > b === a.term > b.term || a.term == b.term && a.nodeid > b. node_id). 这是paxos里的做法, 保证不会呈现raft里的冲突.

原理是, raft对voting的阶段有2个值来描述: term和当时投了哪个node_id, 即[term, nodeid], 由于raft不允许一个term vote2个不同的不同的node, 也就是说, vote_req.term > local.term && vote_req.nodeid == local.nodeid 才会grant这个vote请求.

把term替换成(term,nodeid)后, vote阶段的大小比较变成了: vote_req.term > local.term || vote_req.term == local.term && vote_req.nodeid >= local.nodeid, 条件边宽松了. 同一个term内, 较大nodeid的能够抢走较小nodeid 已经树立的leader.

而日志中本来记录的term也需要将其替换成(term, node_id), 由于这两项加起来才干唯一确认一个leader. 之前raft里只需一个term就能够唯一确认一个leader.

vote中比较最大log id相应的,从比较tuple (term, index) 改成比较tuple (term, node_id, index).

就这么点修正.

总结下来就是 依照字典排序 和 预投票锁 保证 当多个 term 相同的 candidate 相遇后,肯定会有一个 获得多数派投票

主意

咱们如果呈现 异常的网络阻隔情况再回来,或许导致 数据的不一致,可是上面的 处理办法 由于 比较重,不适合咱们,咱们就单纯 引入 定时校正的调度任务 进行比较(和 对账相同)

修正

我对nacos config 衔接进行 遍历查找 是否存活,不存活 我就shutdown,然后生成一个新的,而不是这种全部生成一边,毕竟人家 结构器开了线程。。。。

说回来还是由于 我其时自傲了,没往这个调用下面看,在子类中 写的开线程 哈哈,行吧,改改 ,跑到测验环境 看看效果(CPU)

【雨夜】一次nacos 导致的 CPU 飙高问题

嗯嗯 安稳了,明日再看看,应该没问题了

彩蛋

如同 测验环境呼应时刻 变长,和我不要紧。。。。是别人压测呢,把带宽吃了。。。。。看透不说透

【雨夜】一次nacos 导致的 CPU 飙高问题