戰(zhàn))
1. 為什么我把日志系統(tǒng)從 Elasticsearch 換成了 Apache Doris先交代背景。我負(fù)責(zé)維護(hù)一套中等規(guī)模的微服務(wù)集群大概四十多個(gè)服務(wù)實(shí)例日均日志量在 800GB 到 1.2TB 之間浮動。最早用的是經(jīng)典的 ELK 組合Elasticsearch 存日志、Logstash 做采集、Kibana 做展示。這套方案用了兩年多問題越來越明顯磁盤占用高得離譜同樣的原始日志量ES 的索引膨脹能到原始數(shù)據(jù)的 2.5 到 3 倍查詢稍微復(fù)雜一點(diǎn)比如做多維度聚合或者跨天的大范圍掃描響應(yīng)時(shí)間直接飆到十幾秒甚至幾十秒集群節(jié)點(diǎn)一擴(kuò)再擴(kuò)成本壓不住。后來我開始認(rèn)真評估 Apache Doris。說實(shí)話一開始我是有顧慮的Doris 在 OLAP 場景的口碑一直不錯(cuò)但拿它當(dāng)日志存儲和可觀測性底座當(dāng)時(shí)心里沒底。真正讓我下決心的是做了一輪壓測同樣的日志數(shù)據(jù)Doris 的存儲壓縮比能做到 5 到 8 倍復(fù)雜聚合查詢基本在亞秒到兩三秒之間返回。這個(gè)差距不是一點(diǎn)半點(diǎn)。這篇筆記就是把這套遷移和落地的完整過程拆開講清楚。Apache Doris 做日志分析與可觀測性核心思路是用一套 MPP 架構(gòu)的分析型數(shù)據(jù)庫同時(shí)扛住日志存儲、全文檢索、指標(biāo)聚合和鏈路追蹤這幾件事而不是像傳統(tǒng)方案那樣 ES 管日志、Prometheus 管指標(biāo)、Jaeger 管鏈路各搞一套。適合誰看如果你正在被日志系統(tǒng)的成本和查詢性能折磨或者你正在設(shè)計(jì)一套新的可觀測性平臺這篇內(nèi)容應(yīng)該能幫你少走一些彎路。我會把架構(gòu)選型的邏輯、表結(jié)構(gòu)設(shè)計(jì)、數(shù)據(jù)寫入鏈路、查詢優(yōu)化、踩過的坑都攤開講。2. 整體架構(gòu)設(shè)計(jì)與選型邏輯拆解2.1 為什么是 Doris 而不是繼續(xù)堆 ES 節(jié)點(diǎn)先把這個(gè)最核心的問題說清楚。ES 的問題不在于它不好而在于它的設(shè)計(jì)目標(biāo)和日志分析場景之間存在錯(cuò)位。ES 底層是 Lucene倒排索引是它的核心這決定了它在全文檢索上很強(qiáng)但倒排索引本身非常吃存儲。日志數(shù)據(jù)有個(gè)特點(diǎn)寫多讀少、冷數(shù)據(jù)占比極高、查詢模式以聚合統(tǒng)計(jì)和時(shí)間范圍過濾為主。你拿一個(gè)為全文檢索優(yōu)化的引擎去干聚合分析的活就像開越野車送快遞能送但油耗和維護(hù)成本都不劃算。Doris 的定位是 MPP 分析型數(shù)據(jù)庫列式存儲加向量化執(zhí)行引擎天然適合大寬表的聚合掃描。它的數(shù)據(jù)模型里有幾個(gè)關(guān)鍵設(shè)計(jì)對日志場景特別友好列式存儲 高壓縮比日志字段里大量重復(fù)值比如服務(wù)名、日志級別、主機(jī)名列存下同列數(shù)據(jù)連續(xù)存放壓縮算法能發(fā)揮到極致。我實(shí)測 ZSTD 壓縮下原始 JSON 日志 1TB 壓到 130GB 左右。分區(qū) 分桶兩級切分按天分區(qū)、按服務(wù)名或主機(jī)名分桶查詢時(shí)分區(qū)裁剪加桶裁剪掃描的數(shù)據(jù)量能砍掉一大截。物化視圖和 Rollup可以針對常用聚合查詢預(yù)計(jì)算比如按分鐘統(tǒng)計(jì)各服務(wù) ERROR 數(shù)量直接命中 Rollup 表不用掃原始數(shù)據(jù)。倒排索引支持Doris 從 2.0 開始支持倒排索引雖然不如 ES 那么成熟但應(yīng)付日志里的關(guān)鍵字檢索足夠了。注意Doris 的倒排索引和 ES 的倒排索引不是一回事。Doris 的倒排索引是輔助加速手段不是存儲的核心結(jié)構(gòu)所以不會帶來 ES 那種存儲膨脹問題。2.2 可觀測性三大支柱怎么統(tǒng)一到一套存儲可觀測性通常講三大支柱Logs日志、Metrics指標(biāo)、Traces鏈路。傳統(tǒng)做法是三套系統(tǒng)各管一攤查詢的時(shí)候要在三個(gè)界面之間來回跳關(guān)聯(lián)分析基本靠人肉。用 Doris 的思路是三者的數(shù)據(jù)模型雖然不同但本質(zhì)上都是帶時(shí)間戳的結(jié)構(gòu)化或半結(jié)構(gòu)化數(shù)據(jù)完全可以統(tǒng)一到一套存儲里用不同的表來承載。我的做法是建三張核心表表名承載數(shù)據(jù)數(shù)據(jù)模型分區(qū)策略分桶策略logs_detail原始日志明細(xì)Duplicate 模型按天分區(qū)按 service_name 哈希分桶metrics_rollup聚合指標(biāo)Aggregate 模型按天分區(qū)按 metric_name 哈希分桶traces_span鏈路 Span 數(shù)據(jù)Duplicate 模型按天分區(qū)按 trace_id 哈希分桶這樣做的好處是做關(guān)聯(lián)查詢的時(shí)候不用跨系統(tǒng)。比如我想查某個(gè) trace_id 對應(yīng)的所有日志和它經(jīng)過的每個(gè)服務(wù)的耗時(shí)指標(biāo)一條 SQL 就能搞定不用先去 Jaeger 查鏈路再去 Kibana 查日志。2.3 寫入鏈路的設(shè)計(jì)取舍寫入鏈路我選的是Flink Doris Stream Load的組合。日志從各個(gè)服務(wù)通過 Filebeat 采集到 KafkaFlink 消費(fèi) Kafka 做清洗和字段提取然后通過 Stream Load 批量寫入 Doris。為什么不用 Routine Load 直接消費(fèi) KafkaRoutine Load 確實(shí)更簡單Doris 原生支持不用額外維護(hù) Flink 作業(yè)。但我的場景里有幾個(gè)需求 Routine Load 滿足不了一是需要在寫入前做字段解析和脫敏比如把 JSON 日志里的嵌套字段拍平、把手機(jī)號郵箱做掩碼二是需要做多流合并把日志流和鏈路流按 trace_id 做關(guān)聯(lián)后寫入三是需要動態(tài)路由不同服務(wù)的日志寫到不同的分區(qū)。這些用 Flink 做更靈活。寫入批次的大小很關(guān)鍵。我試過幾個(gè)檔位批次太小比如 1000 條一批Stream Load 的導(dǎo)入頻率太高Doris 的 compaction 壓力大查詢性能會抖動批次太大比如 10 萬條一批延遲又上去了日志從產(chǎn)生到可查要等好幾分鐘。最后定在每批 2 萬到 5 萬條或者每 10 秒觸發(fā)一次兼顧延遲和寫入效率。3. 核心表結(jié)構(gòu)設(shè)計(jì)與實(shí)操要點(diǎn)3.1 日志明細(xì)表字段類型和索引怎么定日志表的設(shè)計(jì)直接決定了后面查詢爽不爽。我踩過的最大坑是字段類型選得太隨意導(dǎo)致后面查詢要么慢要么報(bào)錯(cuò)。下面是我最終定下來的建表語句核心部分CREATE TABLE logs_detail ( ts DATETIMEV2(3) NOT NULL COMMENT 日志時(shí)間戳毫秒精度, service_name VARCHAR(64) NOT NULL COMMENT 服務(wù)名, host_ip VARCHAR(32) NOT NULL COMMENT 主機(jī)IP, log_level VARCHAR(8) NOT NULL COMMENT 日志級別, trace_id VARCHAR(64) NULL COMMENT 鏈路追蹤ID, span_id VARCHAR(32) NULL COMMENT Span ID, message TEXT NULL COMMENT 日志正文, extra JSON NULL COMMENT 擴(kuò)展字段, INDEX idx_message (message) USING INVERTED COMMENT 正文倒排索引, INDEX idx_trace (trace_id) USING INVERTED COMMENT trace_id 倒排索引 ) ENGINEOLAP DUPLICATE KEY(ts, service_name, host_ip) PARTITION BY RANGE(ts) () DISTRIBUTED BY HASH(service_name) BUCKETS 32 PROPERTIES ( compression ZSTD, replication_num 3, dynamic_partition.enable true, dynamic_partition.time_unit DAY, dynamic_partition.start -30, dynamic_partition.end 3, dynamic_partition.prefix p, dynamic_partition.buckets 32 );幾個(gè)關(guān)鍵決策點(diǎn)展開說時(shí)間戳用 DATETIMEV2(3) 而不是 BIGINT。用 BIGINT 存毫秒時(shí)間戳確實(shí)省空間但查詢的時(shí)候每次都要寫from_unixtime(ts/1000)非常別扭而且分區(qū)裁剪用不上。DATETIMEV2 支持毫秒精度直接可讀分區(qū)裁剪也正常。存儲上多占的那點(diǎn)空間相比查詢便利性完全值得。message 字段用 TEXT 而不是 VARCHAR。日志正文長度不可控有的堆棧信息能到幾十KB。VARCHAR 有長度上限超了會截?cái)嗷蛘邎?bào)錯(cuò)。TEXT 沒有這個(gè)問題配合倒排索引查詢效率也可以接受。分桶數(shù)選 32 是基于數(shù)據(jù)量算出來的。Doris 官方建議單個(gè)桶的數(shù)據(jù)量在 1GB 到 10GB 之間。我日均 1TB 原始數(shù)據(jù)壓縮后約 150GB按 30 天保留算單天分區(qū)壓縮后約 5GB。32 個(gè)桶每個(gè)桶約 160MB偏小但問題不大因?yàn)椴樵儾l(fā)高的時(shí)候桶多一點(diǎn)能更好利用多核。如果你數(shù)據(jù)量更大可以按這個(gè)公式算分桶數(shù) 單分區(qū)壓縮后大小 / 期望單桶大小。倒排索引只建在 message 和 trace_id 上。倒排索引不是免費(fèi)的每個(gè)索引都會增加寫入開銷和存儲占用。log_level、service_name 這種低基數(shù)字段用前綴索引或者 Bloom Filter 就夠了沒必要上倒排。3.2 指標(biāo)聚合表Aggregate 模型怎么用指標(biāo)數(shù)據(jù)的特點(diǎn)是寫入頻繁、查詢以聚合為主、歷史數(shù)據(jù)很少查明細(xì)。這種場景用 Aggregate 模型最合適Doris 會在后臺自動做預(yù)聚合查詢的時(shí)候直接讀聚合結(jié)果。CREATE TABLE metrics_rollup ( ts DATETIMEV2(3) NOT NULL, metric_name VARCHAR(128) NOT NULL, service_name VARCHAR(64) NOT NULL, labels VARCHAR(512) NULL, value_sum DOUBLE SUM DEFAULT 0, value_count BIGINT SUM DEFAULT 0, value_max DOUBLE MAX DEFAULT 0, value_min DOUBLE MIN DEFAULT 0 ) ENGINEOLAP AGGREGATE KEY(ts, metric_name, service_name, labels) PARTITION BY RANGE(ts) () DISTRIBUTED BY HASH(metric_name) BUCKETS 16 PROPERTIES ( compression ZSTD, replication_num 3, dynamic_partition.enable true, dynamic_partition.time_unit DAY, dynamic_partition.start -90, dynamic_partition.end 3, dynamic_partition.prefix p );這里有個(gè)設(shè)計(jì)上的取舍聚合粒度定在多少。定得太細(xì)比如秒級預(yù)聚合效果不明顯存儲也大定得太粗比如小時(shí)級查詢靈活性差。我最后定在分鐘級寫入的時(shí)候 Flink 做一層分鐘級預(yù)聚合再寫入Doris 的 Aggregate 模型再做一層合并。這樣既保證了查詢能下鉆到分鐘又控制了數(shù)據(jù)量。labels字段我用的是拼接后的字符串比如envprod,regioncn-east,instance10.0.1.5:9090。為什么不拆成多個(gè)列因?yàn)?Prometheus 的 label 是動態(tài)的不同指標(biāo) label 集合不一樣拆列的話表結(jié)構(gòu)沒法固定。拼成字符串后用LIKE或者split_by_string函數(shù)過濾配合倒排索引效率可以接受。3.3 鏈路表trace_id 和 span 的存儲優(yōu)化鏈路數(shù)據(jù)的查詢模式很特殊要么按 trace_id 查整條鏈路的所有 span要么按時(shí)間范圍加服務(wù)名查慢請求。前者要求 trace_id 的查詢極快后者要求時(shí)間范圍掃描高效。CREATE TABLE traces_span ( ts DATETIMEV2(3) NOT NULL, trace_id VARCHAR(64) NOT NULL, span_id VARCHAR(32) NOT NULL, parent_span_id VARCHAR(32) NULL, service_name VARCHAR(64) NOT NULL, operation_name VARCHAR(256) NULL, duration_ms BIGINT NOT NULL, status_code INT NOT NULL, tags JSON NULL, INDEX idx_trace (trace_id) USING INVERTED ) ENGINEOLAP DUPLICATE KEY(ts, trace_id, span_id) PARTITION BY RANGE(ts) () DISTRIBUTED BY HASH(trace_id) BUCKETS 32 PROPERTIES ( compression ZSTD, replication_num 3, dynamic_partition.enable true, dynamic_partition.time_unit DAY, dynamic_partition.start -15, dynamic_partition.end 3, dynamic_partition.prefix p );鏈路數(shù)據(jù)我只保留 15 天比日志的 30 天短。原因是鏈路數(shù)據(jù)量雖然比日志小但查詢頻率也低大部分時(shí)候只有排查問題才會查15 天足夠覆蓋絕大多數(shù)排查場景。如果你有合規(guī)要求需要保留更久可以調(diào)大dynamic_partition.start的絕對值。分桶鍵選trace_id而不是service_name是因?yàn)殒溌凡樵冏詈诵牡膱鼍笆前?trace_id 查全鏈路按 trace_id 分桶能讓同一個(gè) trace 的所有 span 落在同一個(gè)桶里查詢時(shí)只掃一個(gè)桶。4. 數(shù)據(jù)寫入鏈路與查詢優(yōu)化的實(shí)戰(zhàn)細(xì)節(jié)4.1 Flink 寫入作業(yè)的關(guān)鍵配置Flink 作業(yè)這塊我調(diào)了不少參數(shù)下面這幾個(gè)是影響最大的// Doris Stream Load 的 Sink 配置 DorisSink.Builder builder DorisSink.builder() .setDorisReadOptions(DorisReadOptions.builder().build()) .setDorisExecutionOptions(DorisExecutionOptions.builder() .setLabelPrefix(logs_ System.currentTimeMillis()) .setDeletable(false) .setBufferCount(5) // 攢夠5個(gè)批次再提交 .setBufferSize(64 * 1024 * 1024) // 單批次最大64MB .setMaxRetries(3) .setStreamLoadProp(props) .build()) .setDorisOptions(DorisOptions.builder() .setFenodes(fe1:8030,fe2:8030,fe3:8030) .setTableIdentifier(ods.logs_detail) .setUsername(log_writer) .setPassword(******) .build());bufferCount和bufferSize這兩個(gè)參數(shù)要配合調(diào)。我一開始只設(shè)了bufferSize沒設(shè)bufferCount結(jié)果小批次頻繁提交Doris 的 compaction 跟不上查詢延遲從 1 秒漲到 8 秒。后來加上bufferCount5讓 Flink 攢夠 5 個(gè)批次再一次性提交compaction 壓力明顯下降。還有一個(gè)容易忽略的點(diǎn)Stream Load 的 label 要保證唯一。Doris 用 label 做導(dǎo)入事務(wù)的去重如果 label 重復(fù)導(dǎo)入會被拒絕。我用logs_ 時(shí)間戳 隨機(jī)數(shù)的格式生成 label基本不會沖突。4.2 查詢優(yōu)化的幾個(gè)實(shí)用手段分區(qū)裁剪是第一優(yōu)先級。所有查詢必須帶時(shí)間范圍條件而且時(shí)間字段要直接用分區(qū)列不能包函數(shù)。比如WHERE ts 2024-01-01 00:00:00能觸發(fā)分區(qū)裁剪WHERE date(ts) 2024-01-01就不行因?yàn)榉謪^(qū)裁剪發(fā)生在執(zhí)行計(jì)劃生成階段函數(shù)包裹后優(yōu)化器識別不了。物化視圖預(yù)計(jì)算高頻聚合。我建了幾個(gè)物化視圖比如按分鐘統(tǒng)計(jì)各服務(wù)的 ERROR 日志數(shù)量CREATE MATERIALIZED VIEW mv_error_count_by_minute REFRESH ASYNC EVERY(INTERVAL 1 MINUTE) DISTRIBUTED BY HASH(service_name) BUCKETS 8 PROPERTIES (replication_num 3) AS SELECT date_trunc(ts, minute) AS minute_ts, service_name, count(*) AS error_count FROM logs_detail WHERE log_level ERROR GROUP BY minute_ts, service_name;這個(gè)物化視圖建了之后告警系統(tǒng)查 ERROR 數(shù)量的查詢從平均 2.3 秒降到 80 毫秒。代價(jià)是每分鐘有一次后臺刷新任務(wù)會消耗一些資源但相比查詢性能的提升完全值得。倒排索引的查詢寫法有講究。Doris 的倒排索引對MATCH_ANY、MATCH_ALL、MATCH_PHRASE這幾個(gè)函數(shù)支持最好。比如查 message 里包含 timeout 或 connection refused 的日志SELECT ts, service_name, message FROM logs_detail WHERE ts 2024-01-01 00:00:00 AND ts 2024-01-01 01:00:00 AND message MATCH_ANY timeout connection refused LIMIT 100;注意MATCH_ANY后面跟的是空格分隔的關(guān)鍵詞列表不是 SQL 的 OR 語法。這個(gè)寫法能命中倒排索引比message LIKE %timeout% OR message LIKE %connection refused%快一個(gè)數(shù)量級。4.3 冷熱數(shù)據(jù)分層存儲日志數(shù)據(jù)有個(gè)特點(diǎn)最近 3 天的查詢量占 90% 以上7 天前的數(shù)據(jù)基本沒人查。針對這個(gè)特點(diǎn)我做了冷熱分層熱數(shù)據(jù)最近 3 天存在 SSD 上副本數(shù) 3保證查詢性能和可用性。溫?cái)?shù)據(jù)4 到 15 天存在 HDD 上副本數(shù) 2查詢性能可以接受。冷數(shù)據(jù)16 到 30 天存到對象存儲上用 Doris 的冷熱分層功能自動下沉副本數(shù) 1。Doris 的冷熱分層通過storage_policy配置ALTER TABLE logs_detail SET ( storage_policy hot_to_cold_policy );對應(yīng)的 policy 定義CREATE STORAGE POLICY hot_to_cold_policy PROPERTIES( storage_resource remote_oss, cooldown_ttl 259200 -- 3天后下沉到冷存儲 );這套分層下來存儲成本比全 SSD 方案降了大概 60%而熱數(shù)據(jù)的查詢性能沒有受影響。5. 常見問題與排查技巧實(shí)錄5.1 寫入延遲突然飆升怎么排查這是我最常遇到的問題表現(xiàn)是日志從產(chǎn)生到可查的時(shí)間從正常的 10 秒漲到幾分鐘。排查思路按這個(gè)順序走第一步看 Flink 作業(yè)的反壓情況。如果 Flink 的 Sink 算子出現(xiàn)反壓說明寫入 Doris 的速度跟不上消費(fèi) Kafka 的速度。這時(shí)候去 Doris 的 FE 看 Stream Load 的導(dǎo)入任務(wù)狀態(tài)大概率能看到大量LOADING狀態(tài)的任務(wù)堆積。第二步看 Doris 的 compaction 分?jǐn)?shù)。執(zhí)行SHOW TABLET FROM logs_detail看MaxCompactionScore字段如果超過 100 說明 compaction 嚴(yán)重滯后。compaction 滯后的原因是小文件太多通常是因?yàn)?Stream Load 批次太小或者頻率太高。第三步看 BE 的磁盤 IO。如果磁盤 IO 打滿寫入自然慢。這時(shí)候要么加磁盤要么調(diào)整寫入策略減少 IO 壓力。我遇到過一次比較典型的情況某個(gè)服務(wù)突然開始瘋狂打日志日志量漲了 10 倍Flink 作業(yè)的寫入批次大小沒變導(dǎo)致 Stream Load 頻率暴漲Doris 的 compaction 直接崩了。解決辦法是給 Flink 作業(yè)加了一個(gè)動態(tài)限流邏輯當(dāng)檢測到 Kafka 消費(fèi)延遲超過閾值時(shí)自動增大批次大小、降低提交頻率。5.2 查詢變慢的幾種典型原因查詢變慢的原因比寫入延遲更分散我整理了一個(gè)速查表現(xiàn)象可能原因排查方法解決手段簡單查詢也慢分區(qū)裁剪失效EXPLAIN看掃描分區(qū)數(shù)檢查 WHERE 條件是否直接用了分區(qū)列聚合查詢慢沒命中物化視圖EXPLAIN看是否命中 Rollup調(diào)整物化視圖定義或查詢寫法全文檢索慢倒排索引未命中EXPLAIN看是否走倒排索引改用 MATCH_ANY 等函數(shù)偶發(fā)性慢查詢資源競爭看 FE 的查詢隊(duì)列和 BE 的負(fù)載加資源組隔離或錯(cuò)峰查詢掃描數(shù)據(jù)量大分桶數(shù)不合理看 Tablet 的掃描行數(shù)調(diào)整分桶數(shù)或加分區(qū)提示EXPLAIN是排查 Doris 查詢問題的第一工具任何查詢優(yōu)化之前先看執(zhí)行計(jì)劃不要憑感覺調(diào)。5.3 幾個(gè)我踩過的坑坑一DATETIME 精度不夠?qū)е氯罩緛y序。最早我用的是 DATETIME秒級精度結(jié)果同一秒內(nèi)的多條日志排序不穩(wěn)定排查問題時(shí)順序?qū)Σ簧?。后來改?DATETIMEV2(3) 毫秒精度才解決。日志場景建議至少毫秒精度如果日志量特別大且對順序敏感可以考慮微秒精度??佣﨡SON 字段查詢性能差。我一開始把擴(kuò)展字段都塞到 JSON 列里查詢的時(shí)候用extra[user_id]這種寫法。后來發(fā)現(xiàn) JSON 字段的查詢沒法走索引每次都要全表掃。解決辦法是把高頻查詢的 JSON 字段提取成獨(dú)立的列JSON 列只存低頻查詢的字段??尤齽討B(tài)分區(qū)創(chuàng)建失敗導(dǎo)致寫入中斷。動態(tài)分區(qū)依賴 FE 的調(diào)度如果 FE 負(fù)載高或者調(diào)度延遲可能出現(xiàn)分區(qū)還沒創(chuàng)建但數(shù)據(jù)已經(jīng)寫入的情況導(dǎo)致導(dǎo)入失敗。我的解決辦法是在 Flink 作業(yè)里加了重試邏輯遇到分區(qū)不存在的錯(cuò)誤時(shí)等待幾秒重試同時(shí)給 FE 的動態(tài)分區(qū)調(diào)度加了監(jiān)控告警??铀母北緮?shù)設(shè)太高導(dǎo)致寫入放大。我一開始所有表都設(shè)了replication_num3寫入的時(shí)候每個(gè)副本都要寫一遍寫入放大 3 倍。后來把冷數(shù)據(jù)和溫?cái)?shù)據(jù)的副本數(shù)降到 2 和 1寫入壓力明顯下降。副本數(shù)的選擇要在可用性和寫入成本之間權(quán)衡不是越高越好。6. 這套方案的實(shí)際收益與擴(kuò)展思路6.1 收益的量化對比遷移完成穩(wěn)定運(yùn)行三個(gè)月后我做了一次全面的對比指標(biāo)原 ELK 方案Doris 方案變化存儲占用30天約 75TB約 12TB降低 84%日均寫入延遲 P9945 秒8 秒降低 82%簡單聚合查詢 P953.2 秒0.4 秒降低 87%復(fù)雜聚合查詢 P9518 秒2.1 秒降低 88%全文檢索 P951.1 秒0.9 秒基本持平集群節(jié)點(diǎn)數(shù)12 節(jié)點(diǎn)6 節(jié)點(diǎn)減少 50%月度成本基準(zhǔn)約 35%降低 65%全文檢索這塊 Doris 確實(shí)不如 ES但差距沒有想象中大而且日志場景里真正的全文檢索需求占比很低大部分查詢都是帶條件的結(jié)構(gòu)化過濾加聚合。用 35% 的成本換來其他指標(biāo)的全面碾壓這筆賬怎么算都劃算。6.2 后續(xù)可以擴(kuò)展的方向這套架構(gòu)跑穩(wěn)之后我又陸續(xù)加了一些擴(kuò)展告警規(guī)則引擎。基于 Doris 的物化視圖和定時(shí)查詢做了一個(gè)輕量級的告警引擎。比如每分鐘查一次 ERROR 日志數(shù)量超過閾值就觸發(fā)告警。相比 Prometheus 的告警規(guī)則用 SQL 寫告警規(guī)則更靈活能做更復(fù)雜的條件組合。日志聚類分析。用 Doris 的simhash或者minhash函數(shù)對日志正文做相似度計(jì)算把相似的日志聚成一類幫助快速發(fā)現(xiàn)異常模式。這個(gè)功能在排查突發(fā)故障時(shí)特別有用能從海量日志里快速定位到異常的那幾類。與 Grafana 集成。Doris 有官方的 Grafana 數(shù)據(jù)源插件可以直接把 Doris 作為 Grafana 的數(shù)據(jù)源用 SQL 查出來的結(jié)果直接畫圖。這樣指標(biāo)、日志、鏈路都能在 Grafana 里統(tǒng)一展示不用再維護(hù) Kibana 那套。數(shù)據(jù)歸檔到對象存儲。超過 30 天的日志通過 Doris 的EXPORT功能導(dǎo)出到對象存儲上的 Parquet 文件需要的時(shí)候再用Broker Load導(dǎo)回來。這樣既滿足了合規(guī)保留要求又不占用 Doris 的存儲資源。6.3 一些個(gè)人體會這套方案不是銀彈它適合的是日志量中等偏上、查詢以聚合分析為主、對成本敏感的場景。如果你的日志量很小日均幾十GB或者全文檢索需求占比很高那 ES 或者專門的日志服務(wù)可能更合適。技術(shù)選型永遠(yuǎn)要看具體場景不要因?yàn)?Doris 現(xiàn)在火就無腦上。另外Doris 的社區(qū)版本迭代很快我用的 2.1 版本相比 2.0 在倒排索引和冷熱分層上都有明顯改進(jìn)。建議保持關(guān)注版本更新但生產(chǎn)環(huán)境升級要謹(jǐn)慎先在測試環(huán)境跑一輪完整的回歸測試再上。最后說一個(gè)我覺得最重要的經(jīng)驗(yàn)可觀測性平臺的價(jià)值不在于技術(shù)多先進(jìn)而在于能不能讓排查問題的人快速拿到想要的數(shù)據(jù)。我見過太多團(tuán)隊(duì)花大價(jià)錢搭了一套很炫的平臺結(jié)果開發(fā)排查問題時(shí)還是習(xí)慣直接上機(jī)器grep日志。工具再好沒人用就是白搭。所以在設(shè)計(jì)這套系統(tǒng)的時(shí)候我花了很多時(shí)間在查詢模板和自助分析上讓開發(fā)人員不用寫復(fù)雜的 SQL 就能查到想要的日志。這個(gè)投入的回報(bào)比任何技術(shù)優(yōu)化都高。