今天下午突然 出現(xiàn) 測試環(huán)境 cpu飆高,干到了 60%,其他項目 響應(yīng)時間明顯變長。。。有點嚇人,不想背鍋
項目背景
出問題的項目是 需要連接各個不同nacos 和不同的 namespace 進行對應(yīng)操作的 一個項目,對nacos的操作都是httpClient 調(diào)用的api接口,httpClient方法 沒有問題,不用質(zhì)疑這個
定位問題
首先 這 cpu高了,直接top -Hp 看看
定位到 進程id,然后 執(zhí)行 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)
但是上面這個提示信息 顯示 是 線程內(nèi)部的,而且是nacos client 內(nèi)部的
你這么搞,讓我很難受啊,我都是http 調(diào)用的,當(dāng)時就是為了 防止開啟無用的線程,這。。。。。怎么
那我去 根據(jù)你的關(guān)鍵字找找 是哪里打印的,關(guān)鍵字 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; ????} });
這是構(gòu)造方法啊,應(yīng)該只初始化一次的啊,往上debug,我靠,NacosConfigService 類中調(diào)用了,debug 看什么時候調(diào)用了 不就行了嘛
項目初始化的時候 調(diào)用了一次,業(yè)務(wù)系統(tǒng)依賴nacos嘛,ok 可以理解
再就是漫長的等待,30s后 發(fā)現(xiàn)又是一次調(diào)用,我去,怎么可能。。。
往回debug,代碼如下
scheduler.schedule("定時校對灰度nacos?配置",?()?->?loadGrayConfig(grayFileName), ????1800,?1800,?TimeUnit.SECONDS);
/** ?*?灰度配置更新?解決?網(wǎng)絡(luò)隔離的問題 ?* ?*?@param?grayFileName?灰度文件的名稱 ?*/ private?void?loadGrayConfig(String?grayFileName)?{ ????synchronized?(this)?{ ????????System.err.println("loadGrayConfig?datetime:?"?+?DateUtils.formatDate(new?Date())); ????????//刷一次?緩存?重新獲取nacos?內(nèi)容?賦值 ????????grayConfigManager.loadNoCache(grayFileName); ????} }
?
?
等會,難道 小丑是我。。。。
這當(dāng)時是為了灰度功能,定時數(shù)據(jù)校驗用的 用了一個線程池,當(dāng)時以為用了線程池 妥妥的。。。還特意調(diào)用的 Nocache 方法,讓他創(chuàng)建新的nacos Config對象,做數(shù)據(jù)校對
但是每調(diào)用一次 NacosFactory.createConfigService(properties) ,nacos config 構(gòu)造器就會開一個線程,就導(dǎo)致了這個問題
這里可能你要問了你說為了防止網(wǎng)絡(luò)隔離才加的這個調(diào)度任務(wù),什么是網(wǎng)絡(luò)隔離?。?/p>
我剛開始聽說這個概念是 當(dāng)時學(xué)習(xí) Raft
假設(shè)一個Raft集群擁有三個節(jié)點,其中節(jié)點3的網(wǎng)絡(luò)被隔離,那么按照BasicRaft的實現(xiàn),集群會有以下動作:
節(jié)點3由于網(wǎng)絡(luò)被隔離,收不到來自Leader的Heartbeat和AppendEntries,所以節(jié)點3會進入選舉過程,當(dāng)然選舉過程也是收不到投票的,所以節(jié)點3會反復(fù)超時選舉;節(jié)點3的Term就會一直增大
節(jié)點1與節(jié)點2會正常工作,并停留在當(dāng)時的Term
網(wǎng)絡(luò)恢復(fù)之后,Leader給節(jié)點3發(fā)送RPC的時候,節(jié)點3會拒絕這些RPC理由是發(fā)送方任期太小。
Leader收到節(jié)點3發(fā)送的拒絕后,會增大自己的Term,然后變成Follower。
隨后,集群開始新的選舉,大概率原本的Leader會成為新一輪的Leader。
那么網(wǎng)絡(luò)隔離 Raft是怎么解決的呢?
多輪投票的安全問題是棘手的,必須避免同一高度不同輪數(shù)分別提交兩個不同區(qū)塊的情形。在Tendermint中,這個問題可以通過鎖機制(locking mechanism)得到解決。
鎖定規(guī)則:預(yù)投票鎖(Prevote-the-Lock):
驗證者只能預(yù)投票(pre-vote) 他們被鎖定的區(qū)塊。這樣就阻止驗證者在上一輪中預(yù)提交(pre-commit)一個區(qū)塊,之后又預(yù)投票了下一輪的另一個區(qū)塊。
波爾卡解鎖(Unlock-on-Polka ):驗證者只有在看到更高一輪(相對于其當(dāng)前被鎖定區(qū)塊的輪數(shù))的波爾卡之后才能釋放該鎖。這樣就允許驗證者解鎖,如果他們預(yù)提交了某個區(qū)塊,但是這個區(qū)塊網(wǎng)絡(luò)的剩余節(jié)點不想提交,這樣就保護了整個網(wǎng)絡(luò)的運轉(zhuǎn),并且這樣做并沒有損害網(wǎng)絡(luò)安全性。
解決方案是把term替換成(term, nodeid),并且按照字典序比較大小(a > b === a.term > b.term || a.term == b.term && a.nodeid > b. node_id). 這是paxos里的做法, 保證不會出現(xiàn)raft里的沖突.
原理是, raft對voting的階段有2個值來描述: term和當(dāng)前投了哪個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內(nèi), 較大nodeid的可以搶走較小nodeid 已經(jīng)建立的leader.
而日志中原本記錄的term也需要將其替換成(term, node_id), 因為這兩項加起來才能唯一確定一個leader. 之前raft里只需一個term就可以唯一確定一個leader.
vote中比較最大log id相應(yīng)的,從比較tuple (term, index) 改成比較tuple (term, node_id, index).
就這么點修改.
總結(jié)下來就是 按照字典排序 和 預(yù)投票鎖 保證 當(dāng)多個 term 相同的 candidate 相遇后,肯定會有一個 獲得多數(shù)派投票
想法
我們?nèi)绻霈F(xiàn) 異常的網(wǎng)絡(luò)隔離情況再回來,可能導(dǎo)致 數(shù)據(jù)的不一致,但是上面的 解決辦法 因為 比較重,不適合我們,我們就單純 引入 定時校對的調(diào)度任務(wù) 進行比較(和 對賬一樣)
修復(fù)
我對nacos config 連接進行 遍歷查找 是否存活,不存活 我就shutdown,然后生成一個新的,而不是這種全部生成一邊,畢竟人家 構(gòu)造器開了線程。。。。
說回來還是因為 我當(dāng)時自信了,沒往這個調(diào)用下面看,在子類中 寫的開線程 哈哈,行吧,改改 ,跑到測試環(huán)境 看看效果(CPU)
嗯嗯 穩(wěn)定了,明天再看看,應(yīng)該沒問題了
編輯:黃飛
?
評論
查看更多