:從埋點到秒級看板的實踐與踩坑)
去年做活動復盤的時候我對著后臺的SQL報表愣了很久明明活動還在進行中業(yè)務方問當前實時新增有多少我答不上來。不是沒有數(shù)據(jù)而是數(shù)據(jù)散落在日志、數(shù)據(jù)庫和一堆臨時腳本里等我把它們攢齊、清洗、匯總出來十分鐘已經過去了。就是在那一天我決定自己動手做一套輕量級的實時事件分析系統(tǒng)代號就叫rea全稱是Real-time Event Analytics。這套系統(tǒng)從設計到上線前前后后花了不到三周卻徹底改變了我們處理現(xiàn)在到底發(fā)生了什么這類問題的方式。它沒有做成多么龐大的平臺只是一個能支撐內部運營、產品和開發(fā)同學實時看數(shù)的系統(tǒng)。這篇內容適合那些不想一上來就引入重型流計算框架的中小團隊也適合想搞清楚埋點、采集、聚合、查詢全鏈路到底是怎么回事的前后端工程師。1. 逼我動手的三個需求痛點1.1 活動大屏背后的實時其實是延遲統(tǒng)計我所在的團隊負責一個中型產品日活不算夸張但運營活動非常頻繁。每當首頁改版或者營銷活動上線業(yè)務方最關心的問題永遠是現(xiàn)在到底有多少用戶進來了點擊率怎么樣聽起來很簡單實際查詢卻要繞一大圈。事件日志被統(tǒng)一收在文件里每天的定時任務負責把它們載入數(shù)據(jù)庫再生成一份離線報表。這意味著當天白天的數(shù)據(jù)要等到凌晨才能看到白天只能靠寫臨時SQL去查每次執(zhí)行時間還取決于數(shù)據(jù)量?;顒悠陂g流量是平時的幾倍臨時查詢一個不小心就是幾十萬行記錄去聚合數(shù)據(jù)庫CPU直接被頂滿其他業(yè)務跟著遭殃。這種偽實時帶來的痛點不只是慢。更關鍵的是它嚴重壓縮了運營決策的反應時間?;顒由暇€前半小時運營想根據(jù)實時數(shù)據(jù)調整入口位置或廣告語等我們算出數(shù)字用戶早就流失了。要解決問題我得讓數(shù)據(jù)鏈路從小時級至少走到秒級。1.2 找現(xiàn)成工具時遇到的三個不合身著手之前我花了一周時間調研現(xiàn)有的開源和商業(yè)方案。結論是沒有哪一款能直接塞進我們團隊而不產生新的問題。第一類是商業(yè)數(shù)據(jù)分析SaaS。它們體驗確實好接入也快但費用不低而且核心數(shù)據(jù)要傳到第三方平臺。當時我們產品里有一些核心業(yè)務行為的數(shù)據(jù)業(yè)務方明確不希望在外部系統(tǒng)留存這一條就把大部分SaaS方案排除了。第二類是開源重型組件。比如基于流計算生態(tài)的方案功能強大但需要配套的消息隊列、計算集群、監(jiān)控體系。我們團隊一共就四五個人還要兼顧日常業(yè)務開發(fā)根本沒有余力去維護一套分布式系統(tǒng)。為了每天幾萬個事件上流計算框架明顯是殺雞用牛刀。第三類是傳統(tǒng)BI報表工具。它們擅長把已有表結構做可視化但對事件流這件事沒什么概念。我想要的是從埋點采集到實時聚合到查詢API一整條鏈路都能自己掌控的東西而不是在數(shù)據(jù)已經入庫之后再手工建模。三種路數(shù)擺在一起差距很明顯方案類型優(yōu)點不合適的地方商業(yè)SaaS接入快、圖表全費用高、數(shù)據(jù)出域、定制受限開源流計算全家桶擴展性強、生態(tài)完善運維復雜、對團隊要求高傳統(tǒng)BI可視化強、報表豐富不關心事件鏈路、實時性弱自研輕量系統(tǒng)完全可控、成本低需要自己維護、迭代1.3 用一句話給rea劃清邊界當時很多人勸我要不先用定時SQL湊合等數(shù)據(jù)量大了再說。我堅持要做是因為等數(shù)據(jù)量大往往永遠等不到。關鍵不是數(shù)據(jù)量而是能不能在被問到時快速給出答案。所以rea的邊界在一開始就定死了用一句話說就是一個輕量級的、秒級延遲的、只關注關鍵事件的內部實時事件分析系統(tǒng)。具體來說rea不做以下幾件事不采集全量用戶行為只收錄我們關心的核心事件不做用戶畫像和廣告投放決策不做毫秒級實時競價那樣的低延遲場景不追求分布式和高可用。這幾條邊界非常重要因為它們決定了技術上可以走簡單粗暴但可靠的路線。邊界劃清之后整個系統(tǒng)的技術選型就變得非常輕松因為我只需要回答一個問題在每秒幾千事件、查詢頻率不高的規(guī)模下怎么用最少的人力把鏈路打通下面是我當時的完整設計。2. 技術架構怎么在夠用和未來擴展之間找平衡2.1 埋點SDK自己寫還是用現(xiàn)成的埋點是一切分析的地基。調研了一圈現(xiàn)成的開源埋點SDK功能確實不少自動采集頁面瀏覽、點擊熱圖、用戶屬性全都有。但我掂量了一下決定自己寫一個不到10KB的輕量SDK原因有三。首先是包體積。我們的前端頁面本身掛了圖表庫、請求庫隨便一個商業(yè)埋點SDK動輒幾十KB對移動端用戶體驗影響不是小事。自己寫的SDK可以只保留事件發(fā)送、重試、節(jié)流三個能力。其次是數(shù)據(jù)協(xié)議的可控性?,F(xiàn)成SDK的事件字段格式往往是固定的想加一個團隊自定義的環(huán)境標識要么做二次開發(fā)要么在數(shù)據(jù)落庫之后再清洗。自研SDK可以直接采用我們內部定義的事件協(xié)議采集端、傳輸端、存儲端使用同一套結構省掉很多中間轉換。最后是隱私合規(guī)。自研SDK可以明確地控制哪些數(shù)據(jù)允許采集例如默認不采集系統(tǒng)字體、屏幕亮度這類無意義但容易引起誤會的字段。由于rea從一開始就面向內部場景我們不希望采集用戶敏感信息自研可以讓這個承諾寫進代碼而不是寫進文檔。這里給一個最簡單的瀏覽器端發(fā)送邏輯function sendReaEvent(eventName, props {}) { if (!window._rea || !window._rea.userId) { return; // 未初始化或匿名場景 } const event { v: 1, // 事件協(xié)議版本 id: generateUUID(), // 全局唯一事件ID name: eventName, userId: window._rea.userId, anonymousId: window._rea.anonymousId, time: Date.now(), // 設備本地時間戳 props: JSON.stringify(props), }; if (navigator.sendBeacon window.location.protocol https:) { navigator.sendBeacon(/rea/track, new Blob([JSON.stringify(event)], { type: application/json })); } else { fetch(/rea/track, { method: POST, body: JSON.stringify(event), headers: { Content-Type: application/json }, keepalive: true, }).catch(() {}); } }為什么用sendBeacon因為它適合頁面卸載場景下上報事件不阻塞頁面跳轉瀏覽器會在網絡空閑時把數(shù)據(jù)發(fā)出去。如果沒有這條很多點擊后立刻跳走的事件就會白白丟失。2.2 消息管道別一上來就上Kafka消息隊列是整個鏈路的緩沖層很多人第一反應是Kafka。Kafka確實好高吞吐、持久化、消費者組一應俱全。但對我們這個每天幾十萬事件、峰值每秒兩三千請求的場景它的問題也很明顯依賴獨立集群運維心智高磁盤占用、副本配置、分區(qū)調優(yōu)哪一項都要花時間。我的選擇是先用NATS。這是一個極簡的云原生消息系統(tǒng)安裝一個二進制就能跑支持主題訂閱吞吐量對rea的場景完全夠用還自帶持久化選項。團隊沒有專職運維NATS單節(jié)點崩潰了也能快速重啟成本非常低。如果你不想引入新組件用Redis Stream也可以它一樣能做到消息的持久化和消費組。我后來把兩種都跑過壓測結論在下面這張表里對比項NATSRedis StreamKafka安裝復雜度低低依賴Redis高單機吞吐數(shù)萬級/s數(shù)萬級/s百萬級/s持久化支持JetStream支持強運維成本低低高適合rea適合適合大材小用選擇NATS之后我們用它的主題把事件從采集服務分發(fā)給聚合服務和歸檔服務。這樣即使聚合服務重啟事件也會留在JetStream中不會丟失。哪天流量真的漲上來了從NATS遷移到Kafka只需要改消費端的幾行代碼因為rea的業(yè)務邏輯全部在消費者內部與消息系統(tǒng)解耦得比較干凈。2.3 存儲選型PostgreSQL起步ClickHouse留后路事件明細最終要落到數(shù)據(jù)庫里。最開始我的候選名單里有四個選手PostgreSQL、MySQL、ClickHouse、DuckDB。先排除DuckDB它是嵌入式分析型數(shù)據(jù)庫做離線分析很爽但并發(fā)查詢能力和多用戶訪問的支持弱一些不適合作為線上服務的主存儲。MySQL是順手就能用但數(shù)據(jù)分析場景下它的聚合能力確實不如PostgreSQL更何況后面我打算用預處理索引優(yōu)化。ClickHouse是我心里的未來方向列式存儲、壓縮比高、聚合極快特別適合事件分析。之所以沒有一開始就用是因為我們團隊對它的運維經驗不足同時ClickHouse更適合在數(shù)據(jù)量已經很大的情況下體現(xiàn)優(yōu)勢。幾十萬行數(shù)據(jù)在PostgreSQL里用一條帶索引的SQL也能在幾百毫秒內返回完全夠用。所以在第一版里我選了PostgreSQL但留下了一個伏筆定義事件明細表時把event_time、userId、event_name這些分析字段單獨作為事件維度寬表來建方便以后原樣遷移到ClickHouse。表格長這樣CREATE TABLE rea_events ( id VARCHAR(40) PRIMARY KEY, -- 事件ID冪等鍵 name VARCHAR(64) NOT NULL, -- 事件名 user_id VARCHAR(64), -- 用戶ID哈希后 anonymous_id VARCHAR(64), occurred_at TIMESTAMPTZ NOT NULL, -- 事件發(fā)生時間 received_at TIMESTAMPTZ NOT NULL, -- 服務端接收時間 props JSONB NOT NULL DEFAULT {}, -- 擴展屬性 created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_rea_events_name_time ON rea_events (name, occurred_at DESC);2.4 查詢與展示用最少代碼做內部看板存儲定了之后展示層我猶豫了一下要不要接一個開源BI工具它們功能確實豐富可以做下鉆、聯(lián)動、權限管理。但我們的需求其實很單一幾個關鍵指標的趨勢、實時粗略計數(shù)、按事件名分組匯總。為一個單一需求引入一套完整的BI同樣不劃算。最后我用一個輕量API服務加一個不到200行的前端頁面解決了。API服務負責查庫、聚合、緩存前端頁面就放幾塊圖表實時趨勢折線、事件排行榜、基礎漏斗。數(shù)據(jù)格式統(tǒng)一用JSON返回前端用一套現(xiàn)成的圖表庫渲染。這個方案的好處是后續(xù)想換BI、想開放數(shù)據(jù)給其他系統(tǒng)只要API不變底層隨便換。3. 核心實現(xiàn)rea從0到1的五個關鍵環(huán)節(jié)3.1 先定事件協(xié)議再寫代碼這是我認為整個項目最重要的決定。如果沒有統(tǒng)一的事件協(xié)議后面每一個環(huán)節(jié)都會因為字段不匹配而加班。rea的事件協(xié)議在JSON層面就定義死了字段意義如下v協(xié)議版本整數(shù)從1開始。后續(xù)加字段就升版本消費端按版本做兼容。id事件唯一ID由前端生成UUID。這個字段為冪等去重而生后面踩坑部分會專門講到。name事件名命名規(guī)則是對象_動作比如button_click、page_view、order_submit。userId和anonymousId登錄用戶ID經過哈希后的值和匿名用戶ID用來做漏斗和留存。time設備本地時間毫秒時間戳。props擴展屬性JSON對象允許不同事件帶不同的業(yè)務屬性。這里有個細節(jié)為什么不直接用Protobuf或者Avro因為rea是內部系統(tǒng)解析鏈路上的消費者只有采集服務和聚合服務JSON的解析開銷完全不是瓶頸。Protobuf雖然節(jié)省帶寬、類型約束強但多一層編譯、多一層schema管理對三周內要上線的項目來說收益不抵成本。技術選型要放在具體約束下看脫離場景談性能沒有意義。3.2 采集服務批量寫入和背壓處理采集服務是前端埋點請求的第一站它的職責很簡單接收事件、校驗字段、寫入消息管道。但簡單不代表可以隨意寫。我見過不少同類系統(tǒng)采集接口一個一個往數(shù)據(jù)庫插結果流量稍大就卡死。rea的做法是在服務內做兩級緩沖。第一級緩沖是一個內存隊列接收到的每一條事件先丟進隊列由后臺批量任務每500毫秒或者攢夠1000條后一次性打包發(fā)給消息管道。第二級緩沖就是NATS本身它保證即使采集服務進程崩潰已提交到JetStream的事件也不會丟。緩沖帶來的直接問題是怎么處理隊列滿了。如果生產速度遠超消費能力繼續(xù)往隊列里塞只會導致內存溢出。我當時定了一個降級策略核心事件比如訂單、支付結果不允許丟棄即使延遲也要保非核心事件比如普通的按鈕點擊在隊列超過80%水位時直接采樣丟棄一部分并打一條日志。寫代碼時大概是這個樣子# 偽代碼示意reactive處理的思路 def handle_event(event, is_criticalFalse): if not queue.offer(event, timeout_ms100): if is_critical: # 核心事件阻塞等待 queue.put(event) else: dropped_count.inc() logger.warning(queue full, drop non-critical event) def batch_flush(): while True: batch queue.take_n(max_count1000, timeout_ms500) if batch: nats.publish(rea.event, batch)背壓處理是最容易被忽略的。很多自建采集系統(tǒng)都是從單條寫入改成批量寫入就完事了完全不考慮生產者太快會怎樣。結果就是突發(fā)流量一來服務直接內存溢出。3.3 實時聚合計數(shù)器放Redis明細留給數(shù)據(jù)庫實時看數(shù)和精確統(tǒng)計是兩種不同的需求。業(yè)務方問現(xiàn)在在線多少人其實不要求100%精確但他們的真實心理預期是很快看到大概趨勢。rea的做法是兩條路并行。一條路是實時計數(shù)。聚合服務從NATS消費事件后按照事件名分鐘級時間窗口在Redis里做自增計數(shù)器。比如keyrea:count:button_click:202506131420值就是這一分鐘內這個事件的數(shù)量。另一個定時任務每分鐘把Redis中的值異步寫入PostgreSQL的匯總表作為長期趨勢的依據(jù)。這樣查詢端想看最近五分鐘的趨勢直接查Redis毫秒級返回。另一條路是明細歸檔。同樣從NATS消費事件但這條消費者專門負責把原始事件寫入PostgreSQL的rea_events表供精確查詢和下鉆使用。為什么要拆成兩條消費鏈路而不是消費一條然后既聚合又入庫因為兩種操作的耗時差異很大。寫數(shù)據(jù)庫涉及磁盤IO和索引維護耗時不穩(wěn)定Redis自增是純內存操作耗時非常穩(wěn)定。如果把它們混在一個管道里一次慢查詢就能拖住實時計數(shù)導致報表的實時名存實亡。分而治之讓實時鏈路盡可能短是rea一個很核心的設計決策。3.4 查詢API三個緩存的配合查詢API直接面向內部看板穩(wěn)定性必須保證。rea的做法是三層緩存。第一層是本進程內的LRU緩存適合查同一個事件、同一個時間窗口的重復請求。第二層是Redis存的是分鐘級別的預聚合數(shù)據(jù)查詢時把時間段拆成分鐘做聚合再在內存里合并。第三層才是數(shù)據(jù)庫只有當窗口跨度過大或者需要按事件名做聯(lián)合過濾時才會執(zhí)行SQL查詢。為什么不能只用數(shù)據(jù)庫我實測過當明細表數(shù)據(jù)到了幾百萬行一條按事件名時間范圍做COUNT的SQL在PostgreSQL里大約需要200毫秒到1秒這對于網頁接口來說還可以接受。但活動大屏上的實時看板可能每5秒自動刷新一次還疊加了三四個圖表如果全部打到數(shù)據(jù)庫高峰期查詢線程會被占滿連采集服務的連接都會被拖累。加入緩存之后大部分查詢落到了內存數(shù)據(jù)庫壓力直線下降。一個典型的API返回結構{ event: button_click, granularity: 1m, points: [ { time: 2025-06-13T14:20:00Z, count: 1200 }, { time: 2025-06-13T14:21:00Z, count: 1345 } ], queryTimeMs: 12 }3.5 權限與數(shù)據(jù)治理內部工具也有底線內部工具最容易犯的毛病是能用就行權限和安全往后放。rea雖然只對內部開放我還是花了一點時間做了幾件基礎的事避免以后變成大麻煩。第一事件接入按項目隔離。不同的業(yè)務項目使用不同的token事件數(shù)據(jù)里帶上project_id查詢時默認按當前用戶可訪問的項目過濾。第二userId在采集端就做哈希處理數(shù)據(jù)庫里不存明文。雖然會影響精確識別單個用戶的能力但對內部事件分析來說我們更關心群體行為哈希后的ID足夠算留存和漏斗。第三數(shù)據(jù)保留策略。明細表只保留90天匯總表保留兩年過期數(shù)據(jù)每天由定時任務清理。這條策略不是為了省存儲而是為了在有人問你們憑什么存著我兩年前的操作記錄時我們有一個明確且合規(guī)的答案。4. 上線前后踩過的三個坑完整排查鏈路4.1 時間戳錯亂設備本地時間把報表搞成了亂碼第一個坑在試運行第二天就出現(xiàn)了。我打開看板發(fā)現(xiàn)凌晨兩三點出現(xiàn)了一大片page_view事件而當時明顯沒有多少用戶在訪問。再看明細表某些事件的occurred_at比received_at晚了整整8小時還有一些事件的occurred_at在未來。第一反應是數(shù)據(jù)源有問題。我檢查了采集服務和數(shù)據(jù)庫的系統(tǒng)時間同步服務器時間都是準的又看了消費服務的日志發(fā)現(xiàn)它寫入的received_at是服務端接收時間正確無誤。問題只能出在事件本身的time字段。前端埋點代碼用的是Date.now()也就是設備本地時間。如果用戶手機的時間設置錯誤或者人在海外沒有校準時區(qū)事件時間就會偏差很大。修復方案分兩步。第一步將所有看板的時間計算基準改為received_at也就是服務端接收時間保證什么時間到達系統(tǒng)是可信的。第二步仍然保留occurred_at作為業(yè)務分析用的體驗發(fā)生時間但是消費端增加了一個清洗邏輯如果occurred_at和received_at相差超過24小時就把occurred_at視為非法用received_at覆蓋并給事件打上一個clock_skew標記。這樣既保住了絕大多數(shù)準確的時間戳又不會讓少數(shù)設備問題污染全局報表。這個坑給我的啟示是任何帶有設備端時間戳的系統(tǒng)都必須建立一個接收時間和發(fā)生時間的雙軌制并且要有明確的校準策略。只信設備時間早晚會被雷到。4.2 高峰期寫入阻塞隊列共享帶來的連鎖問題第二個坑出現(xiàn)在一次推廣活動流量高峰。數(shù)據(jù)庫連接的等待時間瞬間拉高表象是明細表寫入變慢進而導致看板上的實時指標在高峰期掉了20分鐘左右的數(shù)據(jù)。我當時的排查鏈路是先看消費服務日志發(fā)現(xiàn)大量batch flush timeout再看NATS上的堆積數(shù)量超過正常水位十倍接著排查采集服務的隊列積壓情況發(fā)現(xiàn)內存隊列已經滿了并且Drop日志在大量刷屏說明非核心事件正在被丟棄。按理說丟棄之后積壓應該緩解但指標還是掉。最后我打開了整個采集服務的線程池統(tǒng)計才發(fā)現(xiàn)問題根源不是消費端慢而是采集服務里接收線程和flush線程共用同一個線程池高峰期幾條大SQL查詢把線程池占滿之后連事件寫入NATS的線程也被阻塞了。也就是說慢查詢通過線程池競爭反向拖住了整個入口鏈路形成連鎖反應。修復措施是把IO密集的寫管道和計算密集的SQL查詢徹底分離。采集服務只用獨立的一小簇線程處理接收事件寫NATS另一簇線程專門處理查詢或批量聚合。同時為NATS生產通道單獨設置積壓上限超過閾值時優(yōu)先丟棄非核心事件而不是讓所有線程一起去搶連接。這次改動之后再遇到流量高峰實時鏈路基本穩(wěn)住了。排查線上問題一定要先畫出請求的完整鏈路看看事件從入口到落庫要經過哪幾個隊列、哪幾個線程池所有共享資源的競爭都可能成為牽一發(fā)動全身的瓶頸。4.3 指標翻倍事件重放和缺少冪等鍵第三個坑是報表數(shù)據(jù)變成真實的四倍。我排查了一個下午才找到原因事后看相當?shù)湫?。某天活動看板上顯示按鈕點擊量突然漲到了平日的四倍直覺告訴我這個暴漲不合理但前端流量統(tǒng)計并沒有明顯異常。排查過程是從明細數(shù)據(jù)開始倒查的。我先查最近一小時哪個事件增長最猛很快鎖定了click_feed_button然后對這一小時的事件按事件ID做去重發(fā)現(xiàn)ID的重復率接近75%。也就是說看板上大部分數(shù)字是重復上報帶來的。繼續(xù)往前看采集和消費服務都沒有重復消費的邏輯消費確認機制也正常于是我打開了埋點的發(fā)送日志。真相浮出水面頁面在弱網環(huán)境下fetch失敗后SDK會自動重試但重試時沒有重新生成事件ID每次失敗重試都會把同一個事件再次發(fā)送。更糟的是在某些瀏覽器里頁面關閉時多次觸發(fā)重發(fā)邏輯一條事件能被送出去三四次。修復很簡單前端SDK在創(chuàng)建事件的時候保留同一個id但消費端做冪等寫入。數(shù)據(jù)庫的rea_events表給id字段加了主鍵重復插入直接沖突報錯匯總計數(shù)那里也按事件ID做了一次Redis的布隆過濾器重復事件不會再進入計數(shù)。后端的冪等設計看起來像是多此一舉但在有網絡重試機制的分布式系統(tǒng)里這是人命關天的事。5. 實測效果和我的最終取舍5.1 一組可復現(xiàn)的壓測數(shù)字項目上線穩(wěn)定運行一個月后我做了一組簡單的壓測目標是回答這套輕量級方案到底扛不扛得住。測試環(huán)境是兩臺2核4G的云主機一臺跑采集服務和NATS一臺跑PostgreSQL和聚合服務。壓測工具模擬客戶端以每秒100到5000的速率發(fā)送事件持續(xù)10分鐘。結果匯總如下發(fā)送速率事件/秒采集服務CPU數(shù)據(jù)庫CPU端到端P50延遲端到端P95延遲2005%8%400ms800ms100015%20%600ms1.2s300030%45%800ms2.8s500045%70%1.2s4.1s注意這里的端到端延遲指事件從瀏覽器發(fā)出到進入聚合計數(shù)的時間整體在秒級滿足當初秒級延遲的設定。一旦速率超過5000數(shù)據(jù)庫IO開始吃緊采集服務內存也會上漲但這已經超出rea預期的目標范圍了。如果你的業(yè)務量級遠高于這個數(shù)就該考慮ClickHouse和真正的流處理框架了。5.2 和商業(yè)方案和開源重方案的對比賬項目做完之后我把rea和一個中等價位的商業(yè)SaaS方案做了個粗略對比。按照我們每個月大約5000萬事件的體量商業(yè)方案的年費大約在五位數(shù)到六位數(shù)人民幣這個量級還不包括數(shù)據(jù)出域可能帶來的合規(guī)評估成本。rea的成本主要是兩臺云主機和一個人一個月大約30%的工作量整體低一個數(shù)量級。當然商業(yè)方案帶來的價值也不能光看錢比如它有一堆現(xiàn)成的漏斗、留存、熱力圖分析不用自己開發(fā)。但那些功能對當時的我們來說是低頻功能我們真正高頻用的只有實時趨勢、事件排行、基礎漏斗三個。為了低頻功能付出高頻成本不劃算。開源重方案那邊也是一樣的道理。如果當初直接上完整的流計算棧光是把環(huán)境撐起來、保證數(shù)據(jù)不丟就得花掉比業(yè)務開發(fā)更多的時間。這也引出了我的一個核心觀點實時分析系統(tǒng)的復雜度應該跟著業(yè)務量級走而不是跟著技術潮流走。5.3 項目后續(xù)的擴展思路rea目前是能用狀態(tài)但我知道它離一個完整的數(shù)據(jù)平臺還差很多。這里列幾個我接下來打算做的方向。第一是漏斗分析。目前只能對單個事件做計數(shù)還沒有把多個事件按用戶ID串聯(lián)起來算轉化率。我打算基于明細表寫一個專門的事件序列查詢接口用哈希后的用戶ID做關聯(lián)預計三到五天能完成。第二是異常檢測。既然有了Redis里的分鐘級計數(shù)完全可以寫一個簡單的檢測器當當前分鐘的事件數(shù)和過去7天同一分鐘的中位數(shù)相比偏離超過三倍時推送一條告警到內部群里。這個功能對活動期實時監(jiān)控特別有用不必等數(shù)據(jù)部門發(fā)現(xiàn)異常。第三是歷史數(shù)據(jù)遷移到ClickHouse。等到明細表超過一億行PostgreSQL的聚合查詢會越來越吃力屆時把rea_events同步到ClickHouse查詢接口只在掃描型查詢時切換數(shù)據(jù)源其余邏輯不用動。因為表結構從一開始就是按分析場景設計的這個遷移成本很低。到這個階段我已經把rea從臨時救火的腳本正式變成了一件持續(xù)迭代的工具。最后分享一點個人的實際體會。做這類內部工具最大的瓶頸不是技術而是需求邊界。一開始我也想把功能做全后來發(fā)現(xiàn)每個新增的順手功能都會帶來額外的復雜度。rea之所以能在三周內上線并穩(wěn)定運行靠的正是開始時那句邊界宣言——輕量級、秒級延遲、關鍵事件、面向內部。如果你也想搭一套類似的實時分析系統(tǒng)不妨先寫下這三行邊界再開始選型。很多時候知道什么不做比知道做什么更值錢。