到運(yùn)行系統(tǒng):自研輕量資源監(jiān)控工具rea的完整實(shí)踐)
很多朋友看到rea這個(gè)標(biāo)題時(shí)大概率會(huì)以為打錯(cuò)了字或者覺(jué)得這是某個(gè)庫(kù)的縮寫。我要先坦白一件事——它最初真沒(méi)有什么標(biāo)準(zhǔn)全稱只是某天我在終端里隨手敲的三個(gè)字母用來(lái)命名一個(gè)空目錄。后來(lái)這個(gè)目錄慢慢長(zhǎng)成了一個(gè)完整的資源效率分析工具Resource Efficiency Analyzer內(nèi)部代號(hào)縮寫就是rea專門負(fù)責(zé)采集服務(wù)器的CPU、內(nèi)存、磁盤與網(wǎng)絡(luò)指標(biāo)再把這些數(shù)據(jù)整理成實(shí)時(shí)看板幫我從被動(dòng)救火式運(yùn)維變成主動(dòng)觀察式管理。這篇文章就把一個(gè)模糊標(biāo)題如何變成一個(gè)可運(yùn)行系統(tǒng)的完整過(guò)程寫出來(lái)適合手里有個(gè)想法、但還沒(méi)想清楚架構(gòu)的開發(fā)者參考也適合那些想自建一套輕量監(jiān)控體系、又不想直接套用重型全家桶的朋友。1. 先給 rea 一個(gè)定義從模糊代號(hào)到明確需求1.1 代號(hào)背后的真實(shí)需求別再讓服務(wù)器走在下線邊緣見過(guò)凌晨?jī)牲c(diǎn)的告警郵件嗎說(shuō)的不是那種CPU 忽高忽低再觀察一下的假警報(bào)而是磁盤使用率沖到 98%數(shù)據(jù)庫(kù)進(jìn)程直接崩掉然后你在睡夢(mèng)中被電話叫起來(lái)瞇著眼睛查哪張表把空間吃光了。我經(jīng)歷過(guò)幾次這種場(chǎng)景之后決定不能再靠感覺(jué)運(yùn)維了。我需要一個(gè)工具能持續(xù)地告訴我每一臺(tái)機(jī)器的資源消耗長(zhǎng)什么樣不是事后翻日志而是事前能看到趨勢(shì)。rea 的第一個(gè)需求就這么定下來(lái)了采集多臺(tái)服務(wù)器的系統(tǒng)資源指標(biāo)存入數(shù)據(jù)庫(kù)用圖表展示歷史趨勢(shì)。聽起來(lái)很簡(jiǎn)單但里面藏了幾個(gè)硬約束。一是采集不能太影響業(yè)務(wù)agent 本身要輕二是數(shù)據(jù)量會(huì)持續(xù)增長(zhǎng)存儲(chǔ)和查詢得接得住三是展示要讓人能快速定位問(wèn)題不能打開看板還要等十幾秒。給項(xiàng)目起代號(hào)的時(shí)候我壓根沒(méi)花心思。rea 就是 resource efficiency analyzer 的縮寫順手打的也沒(méi)打算對(duì)外發(fā)布。但正式動(dòng)手前我給自己寫了一段項(xiàng)目說(shuō)明用最低的運(yùn)維成本記錄基礎(chǔ)設(shè)施的資源水位讓趨勢(shì)可見、異??刹椤_@段話后來(lái)幫我擋掉了不少需求膨脹的沖動(dòng)誰(shuí)再說(shuō)順便加個(gè) XX 功能我就把這段話翻出來(lái)給他看。1.2 誰(shuí)是它的目標(biāo)用戶我為什么不做成通用監(jiān)控平臺(tái)市面上的監(jiān)控平臺(tái)很多從老牌開源方案到商業(yè)全家桶隨便挑一個(gè)都能用。但我做 rea目標(biāo)用戶從一開始就只有兩類一個(gè)是我自己另一個(gè)是和我類似的小團(tuán)隊(duì)——機(jī)器數(shù)量在 5 到 30 臺(tái)之間沒(méi)有專職運(yùn)維預(yù)算有限又不想為了看幾個(gè)指標(biāo)把整套重型框架搬回家。自研的邊界一定要?jiǎng)澢宄?。我不追求覆蓋所有監(jiān)控場(chǎng)景也不打算做高可用集群。rea 要解決的是 80% 的常見問(wèn)題機(jī)器負(fù)載有沒(méi)有異常、內(nèi)存是不是長(zhǎng)期吃緊、磁盤大概還有多少天會(huì)被寫滿、網(wǎng)絡(luò)流量是不是有突發(fā)。至于分布式鏈路追蹤、日志檢索、APM 這類能力那是另外的戰(zhàn)場(chǎng)硬塞進(jìn)來(lái)只會(huì)讓項(xiàng)目失焦。而且自建這條路有個(gè)隱形好處整個(gè)系統(tǒng)從采集到展示都是自己寫的每個(gè)環(huán)節(jié)都清楚出了問(wèn)題不會(huì)像黑盒一樣無(wú)從下手。公司里如果買了商業(yè)產(chǎn)品排障時(shí)只能提工單用開源方案遇到不滿足的場(chǎng)景得讀源碼改插件。而 rea 是我自己的我想讓它支持什么業(yè)務(wù)指標(biāo)直接加一個(gè)字段就行這種掌控感用錢買不到。2. 技術(shù)方案怎么定三層結(jié)構(gòu)是小項(xiàng)目的保命符2.1 為什么第一反應(yīng)不是全家桶監(jiān)控系統(tǒng)有現(xiàn)成的開源監(jiān)控組合可用為什么要自己寫這個(gè)問(wèn)題我在動(dòng)手前認(rèn)真回答過(guò)自己。最直接的原因是學(xué)習(xí)成本。全家桶方案確實(shí)強(qiáng)大但它的學(xué)習(xí)曲線很陡配置項(xiàng)非常多默認(rèn)參數(shù)不一定適合你的場(chǎng)景踩坑時(shí)要在告警規(guī)則、服務(wù)發(fā)現(xiàn)、模板變量之間來(lái)回排查。我見過(guò)不少團(tuán)隊(duì)把監(jiān)控系統(tǒng)裝起來(lái)了結(jié)果沒(méi)人會(huì)配告警閾值最后變成一個(gè)昂貴的圖表庫(kù)。另一個(gè)原因是業(yè)務(wù)指標(biāo)接入的靈活性。系統(tǒng)監(jiān)控指標(biāo)可以用現(xiàn)成方案采集但業(yè)務(wù)指標(biāo)比如某個(gè)核心接口的響應(yīng)時(shí)間、某條消息隊(duì)列的積壓數(shù)量、今天新增的訂單量這些需要業(yè)務(wù)代碼主動(dòng)上報(bào)。與其在自研系統(tǒng)之外再接一套上報(bào)通道不如把 rea 做成一個(gè)通用的指標(biāo)接收器無(wú)論是系統(tǒng)數(shù)據(jù)還是業(yè)務(wù)數(shù)據(jù)只要按約定格式推過(guò)來(lái)就能落庫(kù)、查詢、展示。當(dāng)然現(xiàn)成方案也有它的價(jià)值。我并沒(méi)有完全否定它只是在 rea 這個(gè)特定項(xiàng)目里我希望它足夠輕、足夠可控。如果哪天機(jī)器規(guī)模漲到上百臺(tái)需要服務(wù)發(fā)現(xiàn)、自動(dòng)告警、多租戶隔離這些能力到時(shí)候再評(píng)估遷移也不遲。先解決眼前的問(wèn)題不為了未來(lái)可能的規(guī)模提前背上復(fù)雜度的包袱。2.2 前后端與存儲(chǔ)選型的一個(gè)參考組合rea 的技術(shù)棧沒(méi)有用花哨的新框架全是經(jīng)得起時(shí)間考驗(yàn)的組合。前端選了 Vue3 加 TypeScript用 Vite 做構(gòu)建工具圖表庫(kù)用 ECharts。后端用 Python 的 FastAPI接口寫起來(lái)快性能也夠用。存儲(chǔ)這塊是重頭戲選的是 TimescaleDB跑在 PostgreSQL 之上既保留關(guān)系數(shù)據(jù)庫(kù)的熟悉感又針對(duì)時(shí)序數(shù)據(jù)做了專門優(yōu)化。選擇依據(jù)其實(shí)很務(wù)實(shí)環(huán)節(jié)選型核心理由前端框架Vue3 TypeScript Vite組件化清晰類型安全開發(fā)效率高圖表庫(kù)ECharts折線圖、面積圖開箱即用大數(shù)據(jù)量渲染有內(nèi)置優(yōu)化后端框架Python FastAPI異步支持好自動(dòng)生成接口文檔開發(fā)速度快數(shù)據(jù)存儲(chǔ)TimescaleDB原生支持時(shí)序表分區(qū)壓縮率高SQL 查詢降低成本部署方式Docker Compose Nginx一套命令拉起全部依賴適合小規(guī)模場(chǎng)景為什么不用 MySQL 或者 MongoDBMySQL 存時(shí)序數(shù)據(jù)不是不行但數(shù)據(jù)量上來(lái)后按時(shí)間范圍刪除舊數(shù)據(jù)、按時(shí)間窗口聚合這些操作都會(huì)變得別扭。MongoDB 文檔模型靈活但聚合查詢寫起來(lái)不如 SQL 直觀而且對(duì)時(shí)間序列的壓縮能力幾乎沒(méi)有。TimescaleDB 本質(zhì)上還是 SQL我可以用熟悉的 JOIN 和 GROUP BY同時(shí)獲得自動(dòng)分區(qū)和壓縮帶來(lái)的性能紅利這是它最打動(dòng)我的地方。2.3 一次架構(gòu)的簡(jiǎn)化-膨脹-再簡(jiǎn)化很多個(gè)人項(xiàng)目死在想得太多上rea 也不例外。最初畫架構(gòu)圖的時(shí)候我列了一堆組件消息隊(duì)列緩沖采集流量流處理引擎做實(shí)時(shí)聚合NoSQL 存原始數(shù)據(jù)寬表存聚合數(shù)據(jù)再配一套 Redis 做緩存前端還要考慮 WebSocket 推送。畫完之后自己看著都害怕這哪是個(gè)人工具這是照著大型互聯(lián)網(wǎng)公司的基礎(chǔ)設(shè)施在搭。后來(lái)我做了個(gè)減法把架構(gòu)壓縮成三條鏈路采集器定時(shí)把指標(biāo) POST 到后端接口后端寫入數(shù)據(jù)庫(kù)前端通過(guò)查詢接口取數(shù)繪圖。中間的消息隊(duì)列和流處理全部去掉理由是數(shù)據(jù)量沒(méi)有大到需要異步削峰的程度一臺(tái)采集器 10 秒上報(bào)一次20 臺(tái)機(jī)器也就每秒兩條請(qǐng)求FastAPI 處理這點(diǎn)請(qǐng)求綽綽有余。這個(gè)砍需求的過(guò)程讓我學(xué)到一點(diǎn)小項(xiàng)目最值錢的優(yōu)勢(shì)就是簡(jiǎn)單任何你覺(jué)得以后可能用得上的組件都先記在文檔里而不是裝進(jìn)代碼里。真到了需要消息隊(duì)列的那天再把它加進(jìn)來(lái)而不是一開始就為想象中的人流量修一條十車道的高速公路。3. 核心鏈路落地采集、寫入、查詢、展示一個(gè)都不能少3.1 數(shù)據(jù)表設(shè)計(jì)時(shí)序數(shù)據(jù)的第一課rea 落地的第一個(gè)關(guān)鍵環(huán)節(jié)是數(shù)據(jù)庫(kù)表結(jié)構(gòu)。時(shí)序數(shù)據(jù)的特點(diǎn)是寫多讀少、數(shù)據(jù)只追加、舊數(shù)據(jù)需要定期清理所以表結(jié)構(gòu)不能照搬普通業(yè)務(wù)表。我建的第一張表是主機(jī)資源指標(biāo)表核心字段包括采集時(shí)間、主機(jī)名、CPU 使用率、內(nèi)存使用率、磁盤使用率、網(wǎng)絡(luò)流入量、網(wǎng)絡(luò)流出量以及一個(gè) JSONB 類型的擴(kuò)展字段。建表語(yǔ)句如下CREATE TABLE host_metrics ( time TIMESTAMPTZ NOT NULL, host TEXT NOT NULL, cpu_usage DOUBLE PRECISION, mem_usage DOUBLE PRECISION, disk_usage DOUBLE PRECISION, net_in BIGINT, net_out BIGINT, tags JSONB DEFAULT {} ); SELECT create_hypertable(host_metrics, by_range, time);這里有幾個(gè)設(shè)計(jì)細(xì)節(jié)值得展開說(shuō)一下。第一個(gè)是為什么用TIMESTAMPTZ而不是TIMESTAMP。時(shí)序數(shù)據(jù)最怕時(shí)區(qū)混亂如果用不帶時(shí)區(qū)的TIMESTAMP采集器從不同時(shí)區(qū)的機(jī)器上報(bào)數(shù)據(jù)時(shí)時(shí)間軸會(huì)錯(cuò)位。統(tǒng)一用帶時(shí)區(qū)的類型所有時(shí)間都按 UTC 存儲(chǔ)展示層再按用戶時(shí)區(qū)轉(zhuǎn)換這是最不容易出錯(cuò)的方案。第二個(gè)是為什么保留一個(gè)tags字段。單臺(tái)機(jī)器的指標(biāo)字段是固定的但業(yè)務(wù)上可能想給不同機(jī)器打標(biāo)簽比如區(qū)分線上環(huán)境、測(cè)試環(huán)境或者標(biāo)注機(jī)器所屬的可用區(qū)。把這些維度塞進(jìn)單獨(dú)的列會(huì)很死板放在 JSONB 里就靈活多了查詢時(shí)用tags-env就能做過(guò)濾。第三個(gè)是為什么time和host不加聯(lián)合主鍵。剛開始我也習(xí)慣性地想加后來(lái)實(shí)測(cè)發(fā)現(xiàn)唯一約束會(huì)嚴(yán)重拖慢寫入速度時(shí)序數(shù)據(jù)本身就帶有時(shí)間戳這個(gè)天然標(biāo)識(shí)重復(fù)數(shù)據(jù)靠應(yīng)用層去重就夠了數(shù)據(jù)庫(kù)層的唯一性約束在這里性價(jià)比很低。3.2 寫一個(gè)不添亂的采集器采集器是 rea 的神經(jīng)末梢它要足夠輕不能給被監(jiān)控的機(jī)器添亂。我用 Python 的 psutil 庫(kù)實(shí)現(xiàn)資源采集這個(gè)庫(kù)的優(yōu)勢(shì)是跨平臺(tái)、接口穩(wěn)定、文檔全Linux 和 Windows 都能跑對(duì)個(gè)人項(xiàng)目來(lái)說(shuō)覆蓋面足夠了。核心采集邏輯比較直接import socket import time import requests import psutil INTERVAL 10 ENDPOINT http://rea-server:8080/api/metrics def collect_once(): return { host: socket.gethostname(), cpu_usage: psutil.cpu_percent(interval1), mem_usage: psutil.virtual_memory().percent, disk_usage: psutil.disk_usage(/).percent, net_in: psutil.net_io_counters().bytes_recv, net_out: psutil.net_io_counters().bytes_sent, ts: int(time.time()) } while True: try: data collect_once() requests.post(ENDPOINT, jsondata, timeout3) except Exception as exc: log_to_file(freport failed: {exc}) time.sleep(INTERVAL)代碼看著簡(jiǎn)單但有幾個(gè)細(xì)節(jié)如果處理不好后續(xù)會(huì)相當(dāng)難受。第一個(gè)是cpu_percent的interval參數(shù)。psutil.cpu_percent(interval0)是絕大多數(shù)初學(xué)者的寫法結(jié)果往往返回 0因?yàn)榈谝淮握{(diào)用沒(méi)有歷史采樣數(shù)據(jù)。代碼里傳interval1含義是阻塞 1 秒、計(jì)算這 1 秒內(nèi)的平均使用率這樣第一次拿到的值就是可信的。當(dāng)然這 1 秒阻塞是可以接受的畢竟采集頻率本來(lái)就不高。第二個(gè)是網(wǎng)絡(luò)流量讀的是累積值而不是速率。bytes_recv和bytes_sent是從開機(jī)以來(lái)的累計(jì)字節(jié)數(shù)存到數(shù)據(jù)庫(kù)里之后繪圖時(shí)要用當(dāng)前值減去上一時(shí)刻值再除以時(shí)間間隔才能得到平均速率。我可以選擇在采集器里先把速率算好也可以把累積值存下來(lái)交給查詢端計(jì)算。我選了后者理由是想保留最原始的計(jì)數(shù)這樣即使后來(lái)想調(diào)整時(shí)間窗口也能靈活重算。第三個(gè)是采集器不需要花哨的并發(fā)能力單線程循環(huán)加異常隔離就足夠了。上傳失敗不能影響采集日志要寫到文件而不是控制臺(tái)不然服務(wù)跑起來(lái)之后根本看不到報(bào)錯(cuò)。啟動(dòng)之后先用一次cpu_percent(None)做預(yù)熱避免第一次上報(bào)的數(shù)據(jù)點(diǎn)失真這也是容易忽略的小坑。3.3 服務(wù)端接口別讓單條寫入拖垮寫入性能后端接口是整個(gè)鏈路的中轉(zhuǎn)站它要同時(shí)面對(duì)兩個(gè)問(wèn)題來(lái)自多臺(tái)采集器的持續(xù)寫入請(qǐng)求以及來(lái)自前端的時(shí)間范圍查詢請(qǐng)求。寫入接口的寫法一定要考慮批量處理。最直觀的做法是每收到一條數(shù)據(jù)就 INSERT 一條但這樣做數(shù)據(jù)庫(kù)壓力會(huì)大連接頻繁開關(guān)TPS 一口就被壓死了。我采用的方案是內(nèi)存隊(duì)列加定時(shí) flush。接口收到指標(biāo)后先把數(shù)據(jù)放進(jìn)一個(gè)線程安全的隊(duì)列后臺(tái)任務(wù)每隔 5 秒或者隊(duì)列累積到 500 條時(shí)統(tǒng)一執(zhí)行批量插入。這樣數(shù)據(jù)庫(kù)每次操作面對(duì)的是一批數(shù)據(jù)寫入吞吐量能差出一個(gè)數(shù)量級(jí)。下面是簡(jiǎn)化后的核心結(jié)構(gòu)import asyncio import asyncpg from fastapi import FastAPI app FastAPI() _batch [] async def flush_batch(): if not _batch: return records _batch[:] _batch.clear() async with pool.acquire() as conn: await conn.executemany( INSERT INTO host_metrics (time, host, cpu_usage, mem_usage, disk_usage, net_in, net_out, tags) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) , records ) app.post(/api/metrics) async def receive_metrics(item: MetricItem): _batch.append(( datetime.fromtimestamp(item.ts, tztimezone.utc), item.host, item.cpu_usage, item.mem_usage, item.disk_usage, item.net_in, item.net_out, json.dumps(item.tags) )) if len(_batch) 500: await flush_batch() return {status: ok}這個(gè)設(shè)計(jì)的關(guān)鍵不只是批量還包括失敗重試和容量控制。如果后臺(tái) flush 任務(wù)拋異常不能把隊(duì)列里已有的數(shù)據(jù)丟掉要記錄日志并保留備份否則會(huì)出現(xiàn)看板缺數(shù)據(jù)的詭異現(xiàn)象。隊(duì)列本身也需要一個(gè)上限比如最大 5000 條超過(guò)上限的請(qǐng)求先返回錯(cuò)誤而不是無(wú)限堆積內(nèi)存否則采集器一旦異常大量上報(bào)后端的進(jìn)程內(nèi)存會(huì)被撐爆這時(shí)候反而需要采集端主動(dòng)降頻。3.4 查詢與前端看板如何讓頁(yè)面不卡數(shù)據(jù)寫進(jìn)去之后查詢接口要能支持時(shí)間范圍、主機(jī)過(guò)濾、指標(biāo)選擇這些常見條件。最關(guān)鍵的一個(gè)優(yōu)化是降采樣。一張時(shí)序表里如果存了數(shù)十萬(wàn)條數(shù)據(jù)前端小屏幕根本畫不下強(qiáng)行渲染只會(huì)讓頁(yè)面卡死。SQL 層用time_bucket函數(shù)把秒級(jí)數(shù)據(jù)聚合成分鐘級(jí)或小時(shí)級(jí)數(shù)據(jù)曲線既平滑又輕量這才是正確姿勢(shì)。一個(gè)典型的趨勢(shì)查詢是這樣的SELECT time_bucket(1 minute, time) AS bucket, host, avg(cpu_usage) AS cpu_avg, max(cpu_usage) AS cpu_max FROM host_metrics WHERE host $1 AND time $2 AND time $3 GROUP BY bucket, host ORDER BY bucket;前端看圖只關(guān)心大體趨勢(shì)分鐘級(jí)均值足夠用了。如果用戶想看某個(gè)瞬間的精確值再加一個(gè)下鉆接口返回原始明細(xì)數(shù)據(jù)這樣的兩級(jí)策略兼顧體驗(yàn)和性能。前端看板用的是 Vue3 加 ECharts。整體布局分三塊頂部是時(shí)間范圍選擇器左側(cè)是主機(jī)列表中間是多個(gè)指標(biāo)折線圖。主機(jī)列表支持多選選中的每臺(tái)機(jī)器用不同顏色區(qū)分時(shí)間范圍改變后所有圖表聯(lián)動(dòng)刷新。實(shí)現(xiàn)聯(lián)動(dòng)時(shí)有一個(gè)并發(fā)問(wèn)題要注意多個(gè)請(qǐng)求同時(shí)發(fā)出返回順序可能不一致后返回的舊數(shù)據(jù)不能覆蓋新數(shù)據(jù)。我用請(qǐng)求序號(hào)做了一層校驗(yàn)只渲染最后一次發(fā)起的請(qǐng)求結(jié)果這個(gè)細(xì)節(jié)花了我一晚上才定位到。4. 部署細(xì)節(jié)與踩坑實(shí)錄rea 從本機(jī)跑到服務(wù)器的過(guò)程4.1 Docker Compose 一鍵拉起整套環(huán)境rea 的部署目標(biāo)是一臺(tái) 2C4G 的云服務(wù)器所有組件用 Docker Compose 管理。這個(gè)選擇意味著環(huán)境依賴被全部封進(jìn)容器換機(jī)器、重新部署都很從容不用在一臺(tái)新服務(wù)器上手動(dòng)裝 Python、Node、PostgreSQL也避免版本不一致的問(wèn)題。docker-compose.yml 的骨架如下version: 3.8 services: timescaledb: image: timescale/timescaledb:latest-pg16 environment: POSTGRES_PASSWORD: rea_db_pass POSTGRES_DB: rea volumes: - rea_db_data:/var/lib/postgresql/data ports: - 5432:5432 rea-server: build: ./server environment: DATABASE_URL: postgresql://postgres:rea_db_passtimescaledb:5432/rea depends_on: - timescaledb ports: - 8080:8080 rea-web: build: ./web ports: - 80:80 depends_on: - rea-server volumes: rea_db_data:容器化部署里有幾個(gè)容易踩的點(diǎn)。第一個(gè)是數(shù)據(jù)庫(kù)數(shù)據(jù)卷必須單獨(dú)掛載否則容器一刪數(shù)據(jù)全沒(méi)。第二個(gè)是容器之間通信用服務(wù)名而不是 IP因?yàn)槊看沃亟ㄈ萜?IP 都可能變而timescaledb這個(gè)服務(wù)名在 Compose 網(wǎng)絡(luò)內(nèi)是穩(wěn)定的。第三個(gè)是整個(gè)鏈路的前端與后端分離部署前端服務(wù)不僅負(fù)責(zé)提供靜態(tài)頁(yè)面還要用 Nginx 反向代理后端接口這樣頁(yè)面里的請(qǐng)求地址可以統(tǒng)一寫相對(duì)路徑避免瀏覽器跨域問(wèn)題。4.2 我踩過(guò)的四個(gè)坑每一個(gè)都是性能殺手rea 從能跑變成好跑的過(guò)程中我踩了不少坑撿四個(gè)印象最深的分享出來(lái)。第一個(gè)是唯一約束拖垮寫入性能。開始建表時(shí)我習(xí)慣性地加了PRIMARY KEY (host, time)想著防止重復(fù)數(shù)據(jù)結(jié)果寫入一壓測(cè)TPS 只有幾百完全不是 TimescaleDB 該有的水平。去掉唯一約束之后寫入吞吐直接翻了幾倍。時(shí)序場(chǎng)景下重復(fù)數(shù)據(jù)很少偶爾重復(fù)也可以容忍數(shù)據(jù)庫(kù)不應(yīng)為不可能發(fā)生的情況買單。第二個(gè)是時(shí)區(qū)混亂導(dǎo)致曲線偏移八小時(shí)。采集器上報(bào)的是 UTC 時(shí)間前端 ECharts 默認(rèn)按瀏覽器本地時(shí)間格式化看起來(lái)所有曲線都平移了一段。折騰了半天才發(fā)現(xiàn)是時(shí)區(qū)問(wèn)題?,F(xiàn)在我的原則是通信全程用時(shí)間戳展示層轉(zhuǎn)成用戶本地時(shí)間數(shù)據(jù)庫(kù)里統(tǒng)一存 UTC三層各管一段絕不混用。第三個(gè)是cpu_percent首次調(diào)用返回 0。這個(gè)問(wèn)題說(shuō)起來(lái)有點(diǎn)可笑但新手真的會(huì)碰到。psutil 的 CPU 使用率計(jì)算依賴前后兩次調(diào)用之間的差值第一次調(diào)用沒(méi)有基準(zhǔn)直接返回 0。在采集器啟動(dòng)后先做一次預(yù)熱調(diào)用問(wèn)題就消失了。第四個(gè)是前端圖表一次性拉全量數(shù)據(jù)導(dǎo)致瀏覽器崩潰。最初我沒(méi)做降采樣一次查 7 天秒級(jí)數(shù)據(jù)幾萬(wàn)甚至十幾萬(wàn)個(gè)點(diǎn)ECharts 直接卡成幻燈片。后來(lái)改成time_bucket降采樣加 ECharts 自帶的sampling: lttb圖表不卡了加載速度也快了。在數(shù)據(jù)可視化里聰明地丟棄細(xì)節(jié)比死磕完整性重要得多。4.3 常見問(wèn)題速查表把日常運(yùn)行中出現(xiàn)過(guò)的問(wèn)題整理成一張速查表方便自己排查也方便后來(lái)者現(xiàn)象可能原因處理方式看板長(zhǎng)時(shí)間沒(méi)有新數(shù)據(jù)采集器進(jìn)程掛了或網(wǎng)絡(luò)不通檢查采集器日志telnet 測(cè)試接口連通性數(shù)據(jù)庫(kù)磁盤快速增長(zhǎng)保留策略未配置舊數(shù)據(jù)無(wú)限堆積添加定時(shí)任務(wù)刪除超期數(shù)據(jù)圖表曲線出現(xiàn)缺口批量寫入失敗且無(wú)重試檢查后端日志確認(rèn) flush 隊(duì)列是否丟失數(shù)據(jù)跨天數(shù)據(jù)時(shí)間軸錯(cuò)亂采集端時(shí)區(qū)與數(shù)據(jù)庫(kù)不一致統(tǒng)一用 UTC 時(shí)間戳展示層再做時(shí)區(qū)轉(zhuǎn)換多臺(tái)機(jī)器曲線顏色重復(fù)前端只按主機(jī)名取色主機(jī)名過(guò)短改用主機(jī)列表的索引作為顏色區(qū)分依據(jù)查詢接口響應(yīng)越來(lái)越慢沒(méi)有按時(shí)間范圍建立索引確保time列有索引查詢盡量帶時(shí)間下推條件5. 給 rea 留出的后路從監(jiān)控工具變成分析平臺(tái)5.1 告警、預(yù)測(cè)、權(quán)限優(yōu)先級(jí)怎么排rea 目前已經(jīng)能穩(wěn)定采集和展示了接下來(lái)要擴(kuò)展的方向按優(yōu)先級(jí)排告警第一預(yù)測(cè)第二權(quán)限第三。告警最好做因?yàn)閿?shù)據(jù)已經(jīng)有了只需要在后端加一個(gè)定時(shí)檢查任務(wù)對(duì)最近一分鐘的指標(biāo)做閾值判斷觸發(fā)條件時(shí)把消息推到通知渠道。這個(gè)功能能極大降低人肉盯看板的頻率算是對(duì)自由時(shí)間的解放。預(yù)測(cè)功能更有意思也更能體現(xiàn)數(shù)據(jù)分析的價(jià)值。比如磁盤使用率可以根據(jù)過(guò)去三十天的增長(zhǎng)趨勢(shì)做線性擬合估算出按當(dāng)前速度還能用幾天。這種預(yù)測(cè)不需要復(fù)雜的機(jī)器學(xué)習(xí)模型最小二乘法就夠了但它的提醒效果比生硬的閾值告警好得多讓人有充分的緩沖時(shí)間去清理或擴(kuò)容。權(quán)限這塊暫時(shí)不急因?yàn)槟壳爸挥形易约涸诳?。但如果未?lái)某個(gè)團(tuán)隊(duì)要用多用戶體系就必須考慮了。我的思路是在 API 層面加一層 Token 鑒權(quán)前端每個(gè)請(qǐng)求帶上身份標(biāo)識(shí)后端按用戶維度限制可見的主機(jī)范圍。暫時(shí)不需要引入完整的關(guān)系權(quán)限模型保持簡(jiǎn)單等需求真的來(lái)了再細(xì)化。5.2 業(yè)務(wù)指標(biāo)接入后看板才真正有生命力系統(tǒng)資源指標(biāo)只能告訴你基礎(chǔ)設(shè)施健不健康但業(yè)務(wù)指標(biāo)才是決定要不要立刻起床的關(guān)鍵。rea 的上報(bào)接口是通用的這意味著業(yè)務(wù)代碼可以把關(guān)鍵指標(biāo)直接 POST 進(jìn)來(lái)和系統(tǒng)指標(biāo)存在同一張鏈路里看板就能把業(yè)務(wù)異常和資源異常對(duì)照著看。比如某個(gè)核心服務(wù)響應(yīng)時(shí)間突然上漲很可能是部署機(jī)器 CPU 被打滿導(dǎo)致的。只看業(yè)務(wù)指標(biāo)你只知道慢了只看 CPU 指標(biāo)你不知道它影響到什么。兩個(gè)維度疊加在一個(gè)時(shí)間軸上看因果關(guān)系就清晰多了。我的做法是給業(yè)務(wù)指標(biāo)加一個(gè)metric_type標(biāo)識(shí)字段系統(tǒng)指標(biāo)使用system業(yè)務(wù)指標(biāo)使用business查詢時(shí)既可以在同一張圖上疊加展示也可以按類型分開過(guò)濾。我在實(shí)際使用中還有一個(gè)體會(huì)是自研監(jiān)控最有價(jià)值的點(diǎn)不是省掉了采購(gòu)成本而是在這個(gè)過(guò)程中建立的數(shù)據(jù)第一的觀念。當(dāng)你連續(xù)觀察了一段時(shí)間的曲線之后你會(huì)開始相信數(shù)據(jù)而不是直覺(jué)。以前我總覺(jué)得機(jī)器快不行了打開看板一看流量平穩(wěn)心里就踏實(shí)了有時(shí)候我感覺(jué)一切正常但曲線顯示內(nèi)存緩緩爬升反而能提前發(fā)現(xiàn)隱患。這種感覺(jué)用任何人都給你裝不上的監(jiān)控配方也換不來(lái)。最后再分享一個(gè)小技巧如果你也想做類似的項(xiàng)目名字叫什么并不重要哪怕像我一樣隨手敲三個(gè)字母也行。重要的是從第一天開始就把數(shù)據(jù)模型當(dāng)成地基來(lái)打采集頻率、存儲(chǔ)格式、時(shí)間精度這些基礎(chǔ)決策一旦定了后面很難改改一次就牽一發(fā)動(dòng)全身。先把地基打穩(wěn)再慢慢加樓層。