容翻車(chē)到數(shù)據(jù)驅(qū)動(dòng):自研資源估算工具rea的實(shí)踐與踩坑)
1. rea 是什么從一次擴(kuò)容翻車(chē)聊起如果你刷到標(biāo)題里孤零零的 rea 一頭霧水太正常了。它不是某個(gè)熱門(mén)開(kāi)源框架的名字也不是大廠(chǎng)產(chǎn)品線(xiàn)的代號(hào)而是我給自己折騰的一套內(nèi)部小工具起的代號(hào)。要講清楚 rea 到底解決了什么問(wèn)題得從一次讓我印象深刻的擴(kuò)容翻車(chē)說(shuō)起。當(dāng)時(shí)團(tuán)隊(duì)有個(gè)核心服務(wù)大促前按老規(guī)矩做容量評(píng)估拉一下監(jiān)控面板的峰值 CPU再乘以一個(gè)業(yè)務(wù)側(cè)的預(yù)估流量增長(zhǎng)倍數(shù)就這么把擴(kuò)容申請(qǐng)交上去了。結(jié)果大促當(dāng)天服務(wù) CPU 一路沖到 95%接口超時(shí)一片用戶(hù)側(cè)體驗(yàn)直接崩了。事后復(fù)盤(pán)發(fā)現(xiàn)估算值和真實(shí)需求差了快一倍。問(wèn)題不是哪個(gè)人不細(xì)心而是整個(gè)評(píng)估鏈條全憑手工拍腦袋資源數(shù)據(jù)散落在不同的監(jiān)控面板里業(yè)務(wù)預(yù)估、歷史趨勢(shì)、安全余量各算各的根本沒(méi)有一套統(tǒng)一口徑。那次復(fù)盤(pán)之后我沒(méi)有急著去申請(qǐng)更多機(jī)器而是決定做個(gè)工具把資源到底夠不夠、什么時(shí)候會(huì)不夠、要提前多久擴(kuò)容這三件事用數(shù)據(jù)而不是感覺(jué)來(lái)回答。這就是 rea 的起點(diǎn)。rea 的全稱(chēng)是 Resource Estimation Assistant資源估算助手定位非常聚焦輸入服務(wù)的歷史監(jiān)控?cái)?shù)據(jù)輸出未來(lái)一段時(shí)間的內(nèi)存、CPU、磁盤(pán)水位預(yù)測(cè)并給出擴(kuò)容建議。誰(shuí)適合參考這套東西后端、運(yùn)維、全棧以及任何被容量評(píng)估折磨過(guò)的開(kāi)發(fā)。我后面會(huì)把我踩過(guò)的坑、手寫(xiě)的估算邏輯、還有上線(xiàn)半年才悟出來(lái)的那些細(xì)節(jié)全部攤開(kāi)講你可以直接把這套思路搬回自己的團(tuán)隊(duì)。1.1 那次復(fù)盤(pán)里暴露的四個(gè)真問(wèn)題先說(shuō)痛點(diǎn)否則你看后面的方案會(huì)不明白我為什么要這么設(shè)計(jì)。那次復(fù)盤(pán)整理下來(lái)團(tuán)隊(duì)容量評(píng)估的毛病集中在四個(gè)方面數(shù)據(jù)割裂。CPU、內(nèi)存、磁盤(pán)分別在三個(gè)不同的監(jiān)控系統(tǒng)里導(dǎo)出格式還不一樣合并全靠人工復(fù)制粘貼到 Excel。口徑不統(tǒng)一。業(yè)務(wù)側(cè)說(shuō)的流量翻倍和運(yùn)維側(cè)理解的流量翻倍根本不是一回事前者是 QPS 翻倍后者以為帶寬翻倍算出來(lái)的資源需求能差出兩三倍。經(jīng)驗(yàn)留不住。團(tuán)隊(duì)里能估算得比較準(zhǔn)的只有一兩個(gè)老同學(xué)他們的判斷依據(jù)是長(zhǎng)期感覺(jué)沒(méi)有文檔、沒(méi)有公式、沒(méi)有沉淀人一休假評(píng)估就癱瘓。沒(méi)有回看機(jī)制。每次擴(kuò)容完了就算完了沒(méi)有人把預(yù)測(cè)值和實(shí)際值做對(duì)比錯(cuò)了也不知道錯(cuò)在哪下次接著錯(cuò)。這四條任何一條單拎出來(lái)都不致命但湊在一起容量評(píng)估就成了一個(gè)靠運(yùn)氣撐著的環(huán)節(jié)。rea 的設(shè)計(jì)目標(biāo)很簡(jiǎn)單把估算這件事從經(jīng)驗(yàn)驅(qū)動(dòng)變成數(shù)據(jù)驅(qū)動(dòng)哪怕一個(gè)剛?cè)肼毜男峦瑢W(xué)只要會(huì)看圖表也能在五分鐘內(nèi)判斷某個(gè)服務(wù)是否需要擴(kuò)容。1.2 rea 的定位與能力邊界做工具最容易犯的毛病是貪大。我一開(kāi)始也想過(guò)要不要把監(jiān)控采集、告警、自動(dòng)擴(kuò)縮容全做進(jìn)去做一個(gè)容量管理全家桶。后來(lái)我強(qiáng)行給自己畫(huà)了條邊界rea 只做預(yù)測(cè)和輔助決策不做自動(dòng)化執(zhí)行。為什么不碰自動(dòng)擴(kuò)縮容因?yàn)橘Y源伸縮牽涉到審批、預(yù)算、服務(wù)規(guī)格、流量切換一堆事情自動(dòng)化擴(kuò)錯(cuò)了容比不擴(kuò)還可怕。rea 的價(jià)值是當(dāng)那個(gè)提前預(yù)警的參謀告訴你哪臺(tái)服務(wù)在什么時(shí)間點(diǎn)會(huì)碰到水位線(xiàn)需要提前準(zhǔn)備什么規(guī)格的資源。真正的擴(kuò)容動(dòng)作依然保留人工審批環(huán)節(jié)。這個(gè)邊界劃清楚之后整個(gè)項(xiàng)目的復(fù)雜度降了一半都不止。rea 的輸入輸出也因此在第一天就定死了輸入是服務(wù)的歷史指標(biāo)序列輸出是未來(lái) 7/30/90 天的水位預(yù)測(cè)、風(fēng)險(xiǎn)標(biāo)記和擴(kuò)容建議。不做實(shí)時(shí)告警因?yàn)閷?shí)時(shí)告警是監(jiān)控系統(tǒng)的事不做自動(dòng)擴(kuò)縮容因?yàn)槟鞘菆?zhí)行系統(tǒng)的事只負(fù)責(zé)把什么時(shí)候會(huì)不夠這件事算準(zhǔn)這就夠了。1.3 技術(shù)選型為什么是 React Node 輕量存儲(chǔ)技術(shù)棧選擇上我?guī)缀鯖](méi)有猶豫。前端選了 React 加 ECharts因?yàn)閳F(tuán)隊(duì)現(xiàn)有后臺(tái)基本都是這個(gè)組合新成員上手成本最低后端用 Node.js 加 Express原因是采集腳本要復(fù)用公司現(xiàn)有的 Node 工具鏈存儲(chǔ)用的是 SQLite加上定時(shí)聚合邏輯。這里可能有同學(xué)會(huì)問(wèn)資源預(yù)測(cè)按理說(shuō)數(shù)據(jù)量不小用 SQLite 夠嗎我的答案是分階段看。rea 初期只管理幾十個(gè)服務(wù)每個(gè)服務(wù)每 5 分鐘一個(gè)采樣點(diǎn)一天也就幾千條記錄SQLite 完全扛得住。等真到了每天幾百萬(wàn)條數(shù)據(jù)的規(guī)模再遷移到時(shí)序數(shù)據(jù)庫(kù)也不遲。為了一個(gè)還沒(méi)影的規(guī)模提前引入一套重量級(jí)時(shí)序存儲(chǔ)只會(huì)讓項(xiàng)目在第一周就死在部署復(fù)雜度上。存儲(chǔ)方案優(yōu)勢(shì)代價(jià)完整時(shí)序數(shù)據(jù)庫(kù)查詢(xún)能力強(qiáng)生態(tài)完善多維護(hù)一套組件部署復(fù)雜SQLite 定時(shí)聚合零新增依賴(lài)隨項(xiàng)目走數(shù)據(jù)量大后需要定期清理純內(nèi)存數(shù)組實(shí)現(xiàn)最簡(jiǎn)單重啟丟數(shù)據(jù)不適合長(zhǎng)期積累最終選了 SQLite。后面的事實(shí)證明這個(gè)決定很務(wù)實(shí)項(xiàng)目三天下地一周接入第一批服務(wù)沒(méi)有一次因?yàn)榇鎯?chǔ)出過(guò)問(wèn)題。2. 資源估算的核心指標(biāo)、公式與數(shù)據(jù)準(zhǔn)備很多類(lèi)似的工具做到最后效果不好問(wèn)題往往不出在模型上而出在最基礎(chǔ)的兩件事指標(biāo)沒(méi)選對(duì)數(shù)據(jù)是臟的。所以我把這一章放在模型前面先把這些基礎(chǔ)講透。2.1 先分清楚直接指標(biāo)和派生指標(biāo)資源估算要先明確一個(gè)問(wèn)題你預(yù)測(cè)的到底是什么。我習(xí)慣把指標(biāo)分成兩類(lèi)。直接指標(biāo)就是資源本身的使用量比如 CPU 使用率、內(nèi)存使用率、磁盤(pán)已用空間、網(wǎng)絡(luò)流量。這類(lèi)指標(biāo)直接從系統(tǒng)層拿到是所有預(yù)測(cè)的基礎(chǔ)。派生指標(biāo)則是業(yè)務(wù)層面的信號(hào)比如 QPS每秒請(qǐng)求數(shù)、平均響應(yīng)時(shí)間、GC 頻率、連接數(shù)。這類(lèi)指標(biāo)不直接對(duì)應(yīng)資源消耗但它能幫你解釋資源曲線(xiàn)為什么長(zhǎng)這樣。比如某服務(wù)的 CPU 在每天下午三點(diǎn)準(zhǔn)時(shí)爬升只看資源曲線(xiàn)可能以為是異常結(jié)合 QPS 一看才發(fā)現(xiàn)是那個(gè)時(shí)段業(yè)務(wù)集中請(qǐng)求導(dǎo)致一切正常。rea 在采集層兩類(lèi)指標(biāo)都收但估算模型只用直接指標(biāo)派生指標(biāo)只用來(lái)做變化歸因。這樣做的好處是模型簡(jiǎn)單可靠不會(huì)被業(yè)務(wù)邏輯的復(fù)雜性拖垮。采集頻率統(tǒng)一設(shè)成 5 分鐘一個(gè)點(diǎn)這個(gè)粒度對(duì)容量預(yù)測(cè)來(lái)說(shuō)足夠了再密除了浪費(fèi)存儲(chǔ)沒(méi)有太多意義。2.2 估算公式趨勢(shì) 季節(jié) 安全系數(shù)容量預(yù)測(cè)的模型沒(méi)有你想的那么玄。rea 用的核心公式是預(yù)測(cè)值 趨勢(shì)基準(zhǔn)值 × 季節(jié)性系數(shù) × 安全系數(shù)為什么不能只做一條簡(jiǎn)單的線(xiàn)性回歸因?yàn)榉?wù)資源使用有非常明顯的周期性。同一個(gè)服務(wù)工作日白天和凌晨的 CPU 能差出一倍周一往往比周三高月底還有例行任務(wù)疊加。如果不把這些季節(jié)性因素拆出來(lái)預(yù)測(cè)值要么在高峰期不夠用要么在低峰期過(guò)度分配。趨勢(shì)基準(zhǔn)值我用的是最小二乘法擬合出的線(xiàn)性趨勢(shì)。這里有個(gè)很直觀(guān)的處理方式把過(guò)去 90 天的指標(biāo)按時(shí)間順序排好算出每天的日均值然后擬合一條直線(xiàn)延長(zhǎng)到未來(lái)。舉個(gè)例子某服務(wù)過(guò)去 30 天的 CPU 日均值是 45%通過(guò)擬合發(fā)現(xiàn)每 7 天大約上漲 3%那 30 天后的趨勢(shì)基準(zhǔn)值就是 45% 乘以 1.03 的 4 次方約等于 50.6%。季節(jié)性系數(shù)怎么算把歷史數(shù)據(jù)按星期幾 小時(shí)分桶比如周一 15:00這個(gè)桶把過(guò)去所有這個(gè)時(shí)間點(diǎn)的 CPU 值取中位數(shù)再除以全局中位數(shù)得到一個(gè)系數(shù)。高峰期這個(gè)系數(shù)可能是 1.3凌晨低谷可能是 0.6。預(yù)測(cè)時(shí)先算趨勢(shì)值再乘上對(duì)應(yīng)時(shí)間桶的季節(jié)性系數(shù)就能得到跟隨業(yè)務(wù)節(jié)奏起伏的預(yù)測(cè)曲線(xiàn)。最后乘安全系數(shù)。我默認(rèn)設(shè) 1.2也就是在預(yù)測(cè)值上再留 20% 的余量。這樣算下來(lái)剛才那個(gè)例子30 天后 CPU 預(yù)測(cè)值大約 60.7%仍然低于 80% 的警戒水位線(xiàn)結(jié)論是暫不需要擴(kuò)容但需要持續(xù)觀(guān)察。磁盤(pán)的估算方法和 CPU 不太一樣。磁盤(pán)關(guān)注的是什么時(shí)候會(huì)滿(mǎn)我直接算兩個(gè)數(shù)當(dāng)前可用量除以日均增長(zhǎng)量得到預(yù)計(jì)耗盡天數(shù)再算未來(lái) N 天后的剩余空間。舉個(gè)例子某服務(wù)磁盤(pán)可用量 150GB過(guò)去 30 天的日均增長(zhǎng)是 2GB那預(yù)計(jì)耗盡天數(shù)就是 75 天。如果這個(gè)數(shù)字小于 45 天rea 就會(huì)標(biāo)記風(fēng)險(xiǎn)提醒準(zhǔn)備加盤(pán)。45 天這個(gè)閾值是基于擴(kuò)容準(zhǔn)備周期反推的從申請(qǐng)資源到審批、采購(gòu)、切換上線(xiàn)一般需要 7 到 10 天我留出至少五倍的時(shí)間余量應(yīng)對(duì)突發(fā)增長(zhǎng)。2.3 數(shù)據(jù)清洗決定模型上限的關(guān)鍵模型再?gòu)?qiáng)喂進(jìn)來(lái)的數(shù)據(jù)是臟的輸出就是垃圾。這是 rea 開(kāi)發(fā)過(guò)程中我感受最深的一句話(huà)。容量預(yù)測(cè)的數(shù)據(jù)清洗我總結(jié)了三條經(jīng)驗(yàn)。第一條發(fā)布和重啟造成的空洞不能隨便填值。服務(wù)發(fā)布期間會(huì)有一段時(shí)間沒(méi)有采集數(shù)據(jù)或者 CPU 短暫飆高然后回落到新水平。這種數(shù)據(jù)如果直接參與擬合會(huì)把趨勢(shì)線(xiàn)帶偏。rea 的做法是標(biāo)記空窗期訓(xùn)練時(shí)直接跳過(guò)不插值。第二條聚合統(tǒng)計(jì)用 P95 而不是平均值更不是最大值。平均值會(huì)被每天凌晨的低谷拖低算出來(lái)的容量永遠(yuǎn)不夠用最大值又會(huì)被偶發(fā)的瞬時(shí)抖動(dòng)拉高導(dǎo)致過(guò)度分配。P95 的意思是95% 的時(shí)間都不超過(guò)這個(gè)值這對(duì)容量規(guī)劃來(lái)說(shuō)是最貼合實(shí)際的度量。用生活化的話(huà)說(shuō)你不是關(guān)心全班平均分而是關(guān)心大多數(shù)同學(xué)是不是都能及格P95 就是那個(gè)大多數(shù)同學(xué)的真實(shí)水平。第三條固定的批處理窗口要單獨(dú)處理。很多服務(wù)有每天零點(diǎn)跑批的習(xí)慣那一個(gè)小時(shí)的 CPU 會(huì)異常地高但它不代表常態(tài)。rea 在數(shù)據(jù)采集階段會(huì)打業(yè)務(wù)標(biāo)簽凡是落在固定批處理窗口內(nèi)的數(shù)據(jù)要么排除要么單獨(dú)建一個(gè)批處理畫(huà)像模型。剛開(kāi)始我沒(méi)做這一步結(jié)果 0 點(diǎn)整的 CPU 尖峰每次都把預(yù)測(cè)值抬高一大截后來(lái)才通過(guò)這個(gè)辦法解決。2.4 訓(xùn)練窗口怎么選訓(xùn)練窗口長(zhǎng)短直接影響預(yù)測(cè)的穩(wěn)定性和及時(shí)性。窗口太短模型對(duì)近期波動(dòng)過(guò)于敏感預(yù)測(cè)值忽高忽低窗口太長(zhǎng)模型又跟不上業(yè)務(wù)變化上次發(fā)版優(yōu)化了性能舊數(shù)據(jù)還在拖后腿。rea 的默認(rèn)配置是 90 天訓(xùn)練窗口。90 天能覆蓋完整的星期周期性也能提供足夠的數(shù)據(jù)量做季節(jié)系數(shù)。如果某個(gè)服務(wù)版本迭代特別快一周發(fā)兩三次版我會(huì)手動(dòng)把它降到 30 天如果有年度周期性的業(yè)務(wù)比如電商大促就用 365 天窗口做專(zhuān)項(xiàng)分析。這個(gè)配置寫(xiě)在一個(gè)配置文件里不用改代碼。3. 從零實(shí)現(xiàn) rea架構(gòu)、代碼與接入這里我把 rea 在實(shí)現(xiàn)層面的核心細(xì)節(jié)展開(kāi)講。項(xiàng)目規(guī)模不大但每一塊都有值得說(shuō)一說(shuō)的地方。3.1 項(xiàng)目結(jié)構(gòu)設(shè)計(jì)整套代碼分服務(wù)端、前端、部署三塊。目錄結(jié)構(gòu)是這樣的rea/ ├── server/ │ ├── index.js # Express 入口 │ ├── collector.js # 數(shù)據(jù)采集調(diào)度 │ ├── estimators/ │ │ ├── trend.js # 趨勢(shì)擬合 │ │ ├── seasonal.js # 季節(jié)性系數(shù) │ │ └── disk.js # 磁盤(pán)耗盡預(yù)測(cè) │ └── db/ │ ├── schema.sql # 表結(jié)構(gòu) │ └── index.js # SQLite 封裝 ├── web/ │ ├── src/ │ │ ├── pages/Dashboard.jsx │ │ └── components/CapacityChart.jsx │ └── package.json ├── collector.conf.json # 采集任務(wù)配置 └── docker-compose.yml模塊拆分的邏輯很簡(jiǎn)單collector 負(fù)責(zé)把指標(biāo)寫(xiě)進(jìn) SQLiteestimators 負(fù)責(zé)算web 負(fù)責(zé)展示。三個(gè)模塊之間只通過(guò)數(shù)據(jù)庫(kù)和 HTTP 接口通信互不依賴(lài)后面哪塊要重寫(xiě)都不影響其他兩塊。3.2 后端估算核心實(shí)現(xiàn)趨勢(shì)擬合我選擇了手寫(xiě)最小二乘法。為什么不用統(tǒng)計(jì)庫(kù)因?yàn)檫@套計(jì)算只有一個(gè)公式為此引入一個(gè)幾 MB 的依賴(lài)庫(kù)完全不劃算手寫(xiě) 20 行代碼人人都看得懂、改得動(dòng)。核心實(shí)現(xiàn)是這樣的// estimators/trend.js function leastSquares(points) { const n points.length; if (n 2) return null; let sumX 0, sumY 0, sumXY 0, sumXX 0; for (let i 0; i n; i) { sumX points[i].x; sumY points[i].y; sumXY points[i].x * points[i].y; sumXX points[i].x * points[i].x; } const slope (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX); const intercept (sumY - slope * sumX) / n; return { slope, intercept }; }這個(gè)函數(shù)輸入是[{ x: 天數(shù)序號(hào), y: 日均值 }]輸出是斜率和截距。有了這兩個(gè)值任意一天的趨勢(shì)基準(zhǔn)值就是y slope * x intercept。季節(jié)性系數(shù)的實(shí)現(xiàn)也不復(fù)雜。按星期幾 小時(shí)做分桶每個(gè)桶取中位數(shù)// estimators/seasonal.js function computeSeasonalFactors(history) { const buckets new Map(); for (const point of history) { const key ${point.weekday}:${point.hour}; if (!buckets.has(key)) buckets.set(key, []); buckets.get(key).push(point.value); } const globalMedian median(history.map(p p.value)); const factors {}; for (const [key, values] of buckets) { factors[key] median(values) / globalMedian; } return factors; }預(yù)測(cè)主流程就是把這兩塊串起來(lái)再乘上安全系數(shù)。需要注意的細(xì)節(jié)是歷史序列和未來(lái)預(yù)測(cè)之間隔著訓(xùn)練窗口的長(zhǎng)度趨勢(shì)線(xiàn)要延展這段距離不能直接從最后一天起跳。這個(gè)小坑我在第一版就踩過(guò)預(yù)測(cè)曲線(xiàn)整體比實(shí)際低了一截。3.3 前端儀表盤(pán)一張圖說(shuō)清楚現(xiàn)狀與未來(lái)前端我只做了一個(gè)頁(yè)面核心是一個(gè)趨勢(shì)圖。用 ECharts 渲染三條信息歷史水位線(xiàn)、預(yù)測(cè)水位線(xiàn)、80% 閾值線(xiàn)。用戶(hù)通過(guò)下拉框選服務(wù)通過(guò)按鈕切換 7/30/90 天預(yù)測(cè)周期。// web/src/components/CapacityChart.jsx import React, { useEffect, useState } from react; import ReactECharts from echarts-for-react; export default function CapacityChart({ service, range }) { const [data, setData] useState(null); useEffect(() { fetch(/api/forecast/${service}?days${range}) .then(res res.json()) .then(setData); }, [service, range]); const option { xAxis: { type: time }, yAxis: { type: value, min: 0, max: 120 }, series: [ { name: 歷史水位, type: line, data: data?.history, smooth: true }, { name: 預(yù)測(cè)水位, type: line, data: data?.forecast, lineStyle: { type: dashed } }, { name: 閾值, type: line, markLine: { data: [{ yAxis: 80 }] } } ] }; return ReactECharts option{option} style{{ height: 360 }} /; }這個(gè)組件有一個(gè)容易忽略的優(yōu)化點(diǎn)切換服務(wù)和周期時(shí)如果每次都重新拉數(shù)據(jù)并重繪整個(gè)圖表會(huì)有明顯的閃爍感。我在實(shí)際項(xiàng)目里加了一個(gè)很簡(jiǎn)單的緩存預(yù)測(cè)結(jié)果按服務(wù) 周期存進(jìn)內(nèi)存 Map切換回來(lái)時(shí)直接讀緩存體驗(yàn)立刻順滑了很多。圖表上還要把風(fēng)險(xiǎn)狀態(tài)用顏色區(qū)分出來(lái)。我的做法是預(yù)測(cè)曲線(xiàn)在某個(gè)時(shí)間點(diǎn)超過(guò) 80% 閾值的部分用紅色顯示低于閾值的部分保持綠色。這樣一張圖掃過(guò)去哪些服務(wù)在未來(lái)一個(gè)月會(huì)亮紅燈一目了然。3.4 接入一個(gè)新服務(wù)的四步走工具做得再好接入流程太麻煩就沒(méi)人愿意用。rea 針對(duì)新服務(wù)接入設(shè)計(jì)了一套標(biāo)準(zhǔn)化流程全程不需要寫(xiě)代碼只改配置。第一步創(chuàng)建服務(wù)檔案。在配置表里登記服務(wù)名、所屬團(tuán)隊(duì)、負(fù)責(zé)人、當(dāng)前資源規(guī)格。服務(wù)名必須和監(jiān)控系統(tǒng)里的名字完全一致這一條看似簡(jiǎn)單實(shí)際是個(gè)大坑后面我會(huì)專(zhuān)門(mén)講。第二步配置指標(biāo)來(lái)源。rea 的采集器支持兩種模式拉取模式是從公司已有的監(jiān)控平臺(tái)按 API 拉數(shù)據(jù)推送模式是服務(wù)側(cè)腳本主動(dòng)把指標(biāo) POST 到 rea 的采集接口。對(duì)大多數(shù)服務(wù)直接用拉取模式就行。第三步校準(zhǔn)基線(xiàn)。新服務(wù)接入后先跑 7 天這期間 rea 只記錄數(shù)據(jù)、不輸出預(yù)測(cè)結(jié)論。7 天后人工把 rea 的預(yù)測(cè)和實(shí)際情況做一次對(duì)比確認(rèn)沒(méi)有明顯偏差再放行。第四步回測(cè)驗(yàn)證。我用 MAPE也就是平均絕對(duì)百分比誤差來(lái)衡量預(yù)測(cè)準(zhǔn)不準(zhǔn)。公式是MAPE (1 / n) × Σ |實(shí)際值 - 預(yù)測(cè)值| / 實(shí)際值舉個(gè)例子某服務(wù)過(guò)去 7 天的實(shí)際 CPU 是 50、52、51、55、60、58、62預(yù)測(cè)值是 47、50、54、53、58、61、60。逐點(diǎn)算絕對(duì)誤差再除以實(shí)際值最后平均得到 MAPE 約 4.4%。rea 的驗(yàn)收標(biāo)準(zhǔn)是 MAPE 小于 15%超過(guò)這個(gè)值就說(shuō)明數(shù)據(jù)或模型哪里有問(wèn)題需要排查。3.5 部署與日常運(yùn)行部署我用了 Docker Compose一條命令起來(lái)整個(gè)服務(wù)。采集器通過(guò) node-cron 每 5 分鐘觸發(fā)一次每次拉取所有服務(wù)最近 5 分鐘的指標(biāo)寫(xiě)入 SQLite。version: 3 services: rea-server: build: ./server ports: - 8080:8080 volumes: - ./data:/app/data restart: unless-stopped rea-web: build: ./web ports: - 3000:3000 environment: - API_BASEhttp://rea-server:8080 depends_on: - rea-server運(yùn)行期間自動(dòng)維護(hù)就這么幾件事每天凌晨清理超過(guò) 400 天的歷史明細(xì)數(shù)據(jù)只保留聚合結(jié)果每周一自動(dòng)生成一份下周水位周報(bào)推送到工作群每次預(yù)測(cè)任務(wù)結(jié)束后把結(jié)果寫(xiě)入預(yù)測(cè)記錄表方便以后回看。整套系統(tǒng)跑起來(lái)之后日常需要人工介入的頻率基本降到了每周一次看周報(bào)。4. 上線(xiàn)半年踩過(guò)的坑與排查實(shí)錄工具上線(xiàn)只是開(kāi)始真正讓人成長(zhǎng)的是上線(xiàn)之后的那些故障和誤報(bào)。這一章我把實(shí)際運(yùn)維中踩過(guò)的典型坑整理出來(lái)每一個(gè)都配有現(xiàn)象、原因和解決辦法。4.1 幽靈峰值模型被假數(shù)據(jù)帶偏rea 上線(xiàn)第二周就有個(gè)服務(wù)連續(xù)三天被標(biāo)記為高風(fēng)險(xiǎn)但運(yùn)維同學(xué)去查發(fā)現(xiàn)服務(wù)實(shí)際毫無(wú)壓力。排查后發(fā)現(xiàn)那三天每天零點(diǎn)到一點(diǎn)CPU 都會(huì)有一個(gè)很高的尖峰拉高了一整天的均值。原因是一個(gè)定時(shí)清理腳本每天零點(diǎn)準(zhǔn)時(shí)啟動(dòng)雖然是批處理卻短時(shí)間吃掉了大量 CPU。這類(lèi)數(shù)據(jù)我稱(chēng)之為幽靈峰值真實(shí)存在、但不是常態(tài)、卻會(huì)污染趨勢(shì)計(jì)算。處理辦法有兩種要么在采集時(shí)打批處理標(biāo)簽然后排除要么把這些周期性任務(wù)單獨(dú)建檔用它們自己的歷史數(shù)據(jù)做預(yù)測(cè)不混進(jìn)業(yè)務(wù)主指標(biāo)。我之前說(shuō)的批處理畫(huà)像模型就是給這類(lèi)場(chǎng)景留的口子。同樣的道理也適用于探針重啟和發(fā)布造成的空洞。這些數(shù)據(jù)點(diǎn)有一個(gè)共同特點(diǎn)它們代表的不是服務(wù)的正常運(yùn)行狀態(tài)。rea 在數(shù)據(jù)入庫(kù)前加了一道校驗(yàn)凡是被標(biāo)記為空窗、批處理、發(fā)布窗口內(nèi)的記錄都默認(rèn)不參與模型訓(xùn)練只有業(yè)務(wù)常態(tài)數(shù)據(jù)才進(jìn)訓(xùn)練集。4.2 發(fā)版導(dǎo)致趨勢(shì)突變舊數(shù)據(jù)一夜之間過(guò)時(shí)這是容量預(yù)測(cè)最大的敵人沒(méi)有之一。某服務(wù)做了一次代碼優(yōu)化平均 CPU 從 70% 直接降到 35%。按 rea 原來(lái)的邏輯訓(xùn)練窗口是 90 天那 90 天里有大半的數(shù)據(jù)是優(yōu)化前的預(yù)測(cè)值會(huì)明顯偏高導(dǎo)致系統(tǒng)持續(xù)誤報(bào)需要擴(kuò)容。解決這個(gè)問(wèn)題不能靠手工刪數(shù)據(jù)因?yàn)榘l(fā)版太頻繁每次都手動(dòng)處理根本維護(hù)不過(guò)來(lái)。我在 rea 里加了一個(gè)突變檢測(cè)計(jì)算最近 7 天日均值和前 30 天日均值如果變化幅度超過(guò) 30%就判定發(fā)生了趨勢(shì)突變。一旦判定突變訓(xùn)練窗口自動(dòng)縮短到 7 天并在服務(wù)檔案里打上數(shù)據(jù)已重置的標(biāo)記提醒使用者在下次大促評(píng)估時(shí)手動(dòng)確認(rèn)一次業(yè)務(wù)基線(xiàn)。這個(gè)邏輯我用的是一個(gè)簡(jiǎn)化版的閾值法沒(méi)有用復(fù)雜的 CUSUM 算法。對(duì)內(nèi)部工具來(lái)說(shuō)可解釋性比算法復(fù)雜度重要得多。運(yùn)營(yíng)的同學(xué)能看懂因?yàn)樽罱?7 天變化超過(guò) 30%所以窗口重置了這樣的邏輯但對(duì)CUSUM 檢測(cè)到均值漂移這種說(shuō)法大概率只能點(diǎn)頭表示聽(tīng)不懂。4.3 誤報(bào)與告警疲勞水位超過(guò) 80% 就告警這個(gè)規(guī)則看上去沒(méi)問(wèn)題上線(xiàn)后卻發(fā)現(xiàn)告警頻率高到大家直接無(wú)視。為什么因?yàn)檎鎸?shí)世界里短時(shí)間沖高 80% 這種情況太多了大促流量波峰、定時(shí)任務(wù)并發(fā)、其他服務(wù)搶占資源都會(huì)造成瞬時(shí)超線(xiàn)。我現(xiàn)在把告警邏輯改成了兩條更嚴(yán)格的規(guī)則連續(xù)三個(gè)采樣點(diǎn)超過(guò) 80% 才告警或者預(yù)測(cè)未來(lái) 7 天內(nèi)任意時(shí)段超過(guò) 85% 才告警。這兩條規(guī)則一改誤報(bào)率降了八成大家對(duì)告警的信任度也回來(lái)了。這里有個(gè)經(jīng)驗(yàn)想提醒你做容量工具寧可漏報(bào)也不要錯(cuò)報(bào)。錯(cuò)報(bào)一次用戶(hù)就不再相信工具漏報(bào)一次最多被吐槽一句后面用更長(zhǎng)窗口的數(shù)據(jù)補(bǔ)回來(lái)就行。工具的可信度建立起來(lái)很難毀掉只需要一次。4.4 常見(jiàn)問(wèn)題速查表現(xiàn)象可能原因處理辦法預(yù)測(cè)值忽高忽低訓(xùn)練窗口太短、異常數(shù)據(jù)多窗口調(diào)到 90 天聚合改用 P95磁盤(pán)預(yù)測(cè)總是偏大日志清理任務(wù)被計(jì)入增長(zhǎng)量排除清理類(lèi)任務(wù)的數(shù)據(jù)按周平滑前端圖表空白服務(wù)名不一致或數(shù)據(jù)未入庫(kù)檢查采集日志、核對(duì)服務(wù)檔案字典告警天天刷屏閾值設(shè)置過(guò)低規(guī)則太敏感改為連續(xù) 3 點(diǎn)超限才觸發(fā)或只看預(yù)測(cè)超線(xiàn)回測(cè) MAPE 過(guò)高歷史窗口內(nèi)發(fā)生過(guò)發(fā)版或配置變更啟用突變檢測(cè)重置訓(xùn)練窗口預(yù)測(cè)曲線(xiàn)比實(shí)際整體偏低趨勢(shì)線(xiàn)沒(méi)有延展訓(xùn)練窗口跨度把訓(xùn)練窗口長(zhǎng)度計(jì)入預(yù)測(cè)起點(diǎn)的 x 坐標(biāo)這張表是我每次給別人介紹 rea 時(shí)一定會(huì)展示的內(nèi)容因?yàn)檫@些問(wèn)題幾乎每個(gè)做類(lèi)似工具的人都會(huì)遇到提前避坑比事后排查省心得多。5. 從算出來(lái)到用起來(lái)rea 的下一步預(yù)測(cè)模型做好之后我花在怎么讓建議更容易被執(zhí)行上的精力比繼續(xù)調(diào)模型的精力還多。因?yàn)橐粋€(gè)工具如果輸出只是一堆曲線(xiàn)決策者還是不知道怎么動(dòng)手價(jià)值就折半了。5.1 把數(shù)字翻譯成擴(kuò)容建議rea 的預(yù)測(cè)結(jié)果最終要落到一句話(huà)上某個(gè)服務(wù)建議從 4C8G 升級(jí)到 8C16G。這個(gè)翻譯規(guī)則我做了非常簡(jiǎn)單直接的映射CPU 預(yù)測(cè)超過(guò) 70% 且持續(xù)一周以上建議增加 CPU 核數(shù)。內(nèi)存水位預(yù)測(cè)超過(guò) 75%建議增加內(nèi)存。磁盤(pán)預(yù)計(jì)耗盡天數(shù)小于 45建議增加磁盤(pán)容量或清理歷史數(shù)據(jù)。為什么不建議給出精確的數(shù)字因?yàn)樵茝S(chǎng)商的規(guī)格是離散的4C8G 下一步是 8C16G不存在 5.3C12.7G 這種規(guī)格。既然最終都要調(diào)到規(guī)格檔位工具直接把建議映射到規(guī)格省去決策者二次換算。每條建議后面都附了依據(jù)摘要預(yù)測(cè)曲線(xiàn)、超線(xiàn)時(shí)間點(diǎn)、近 30 天增長(zhǎng)率。這樣負(fù)責(zé)審批的同事能快速判斷這個(gè)建議是否合理。我記得有個(gè)服務(wù)的擴(kuò)容申請(qǐng)以前要走兩輪解釋現(xiàn)在把 rea 生成的報(bào)告附上一輪就過(guò)了。5.2 小團(tuán)隊(duì)也能維護(hù)的工作流rea 的運(yùn)維成本被壓縮到了最低。每周一早上定時(shí)任務(wù)會(huì)跑一遍全部服務(wù)的 7 天預(yù)測(cè)生成一張匯總表推送到工作群。匯總表里只有三類(lèi)狀態(tài)綠燈未來(lái) 7 天安全、黃燈未來(lái) 30 天內(nèi)可能超線(xiàn)、紅燈未來(lái) 7 天內(nèi)預(yù)計(jì)超線(xiàn)。大促前兩周團(tuán)隊(duì)會(huì)額外執(zhí)行一次全量評(píng)估。所謂全量評(píng)估就是把所有服務(wù)的 90 天預(yù)測(cè)都跑一遍篩選出紅燈和黃燈項(xiàng)逐個(gè)確認(rèn)擴(kuò)容計(jì)劃。整個(gè)過(guò)程從以前的兩三個(gè)人忙一天變成了現(xiàn)在一個(gè)人花一小時(shí)看報(bào)表、兩小時(shí)確認(rèn)清單。5.3 后續(xù)擴(kuò)展方向按優(yōu)先級(jí)排第一個(gè)方向是多指標(biāo)關(guān)聯(lián)分析。現(xiàn)在 rea 對(duì) CPU 和內(nèi)存是分開(kāi)預(yù)測(cè)的但實(shí)際情況是 QPS 先漲CPU 跟著漲內(nèi)存可能滯后。如果把 QPS 作為前置指標(biāo)做回歸預(yù)測(cè)的提前量能更大。第二個(gè)方向是接入業(yè)務(wù)預(yù)估流量。有些業(yè)務(wù)會(huì)提前給出下一階段的流量預(yù)判這些信息如果能作為輸入?yún)⑴c估算模型的準(zhǔn)確度會(huì)明顯提升。前提是業(yè)務(wù)給的數(shù)字要靠譜否則模型會(huì)被帶偏。第三個(gè)方向是安全系數(shù)的動(dòng)態(tài)調(diào)整。目前固定乘 1.2但不同服務(wù)的預(yù)測(cè)穩(wěn)定度差很多。有些服務(wù) MAPE 常年 5%乘 1.2 就顯得過(guò)于保守有些服務(wù) MAPE 徘徊在 14%乘 1.2 可能還不夠。后續(xù)可以按每個(gè)服務(wù)的歷史預(yù)測(cè)準(zhǔn)確度自動(dòng)在 1.1 到 1.4 之間調(diào)整系數(shù)。第四個(gè)方向才是長(zhǎng)期來(lái)看最誘人的聯(lián)動(dòng)自動(dòng)擴(kuò)縮容。rea 的預(yù)測(cè)可以作為擴(kuò)縮容的輸入信號(hào)但要加上嚴(yán)格的熔斷機(jī)制——比如實(shí)際水位與預(yù)測(cè)偏差超過(guò) 30% 時(shí)自動(dòng)關(guān)閉自動(dòng)擴(kuò)縮。這個(gè)方向我不會(huì)急著做先把預(yù)測(cè)做準(zhǔn)再談執(zhí)行。6. 使用半年后的個(gè)人體會(huì)rea 從立項(xiàng)到現(xiàn)在跑了小半年我最有感觸的一點(diǎn)是這個(gè)項(xiàng)目最值錢(qián)的地方從來(lái)不是那套估算公式而是讓容量評(píng)估從一種個(gè)人經(jīng)驗(yàn)變成了一項(xiàng)團(tuán)隊(duì)能力。以前評(píng)估只存在于幾個(gè)老同學(xué)腦子里現(xiàn)在它沉淀成了數(shù)據(jù)、模型、報(bào)表和一套可以隨時(shí)被任何人復(fù)核的流程。最后分享一個(gè)我自己堅(jiān)持的小做法每次擴(kuò)容請(qǐng)求通過(guò)后我會(huì)把 rea 的預(yù)測(cè)報(bào)告存檔等下一次同類(lèi)評(píng)估時(shí)翻出來(lái)對(duì)比。對(duì)比得多了你會(huì)發(fā)現(xiàn)模型的偏差模式是有規(guī)律的——某個(gè)服務(wù)長(zhǎng)期偏保守、某個(gè)服務(wù)總在版本發(fā)布后被高估。掌握這些規(guī)律之后你在查看 rea 輸出時(shí)就能自動(dòng)在心里留出修正空間。這種工具加人的協(xié)作方式比任何單一模型都可靠。如果你也想在自己團(tuán)隊(duì)搞一套類(lèi)似的工具我的建議是別一上來(lái)就追求算法多高級(jí)、模型多復(fù)雜。先把數(shù)據(jù)采集和清洗做好把最簡(jiǎn)單的趨勢(shì)預(yù)測(cè)跑通再逐步疊加季節(jié)性和突變檢測(cè)。你會(huì)發(fā)現(xiàn)把那些臟數(shù)據(jù)和口徑問(wèn)題處理完之后最簡(jiǎn)單的模型已經(jīng)能解決大部分問(wèn)題了。