每個(gè)學(xué)生的考試數(shù)據(jù)被拆分成獨(dú)立的事件流(比如“試卷提交”、“題目批改”、“錯(cuò)題歸因”)
事件按學(xué)生ID做分片,每個(gè)分片有獨(dú)立的同步隊(duì)列
同步過程不是全量拉取,而是增量推送,且只推送有變化的數(shù)據(jù)
峰值延遲:從5.2秒穩(wěn)定在300ms以內(nèi)(這個(gè)82.1%的提升,是在雙11壓力下跑出來的,我們就用這個(gè)數(shù)據(jù))
CPU使用率:主庫(kù)從95%降到45%,原因是讀寫分離做得更好了
存儲(chǔ)成本:因?yàn)?strong>只同步增量數(shù)據(jù),磁盤IO減少了60%以上
你的數(shù)據(jù)同步粒度是什么級(jí)別的? 是全量還是增量?每次同步真的需要那么多數(shù)據(jù)嗎?
事件亂序你能容忍嗎? 如果不能,怎么排序?
有沒有想過從“拉取數(shù)據(jù)”變成“推送變化”? 這個(gè)思路挺反直覺的,但效果真的不一樣。
凌晨?jī)牲c(diǎn),手機(jī)震醒。
不是鬧鐘,是PagerDuty的告警。我瞇著眼一看:全科考試力系統(tǒng)的延遲曲線從200ms飆到了5.2秒。
心里咯噔了一下。
我們做的是一個(gè)考試力診斷平臺(tái),幫學(xué)生在學(xué)期中、模考后快速定位知識(shí)漏洞。核心功能是把學(xué)生的各科試卷掃描、拆解、匹配到知識(shí)圖譜,然后生成個(gè)性化提分方案。平時(shí)數(shù)據(jù)量不大,但一到期末、模考季,各省市的學(xué)校同時(shí)上傳試卷,壓力就上來了。
那天晚上其實(shí)早有預(yù)感。
下午3點(diǎn)開始,延遲曲線就開始緩慢爬坡。我盯著Grafana的儀表盤看了半小時(shí),覺得還能扛——畢竟數(shù)據(jù)庫(kù)的CPU還沒到80%。沒想到凌晨1點(diǎn),學(xué)生和老師們突然開始集中下載診斷報(bào)告,加上當(dāng)天的試卷上傳峰值還沒處理完,系統(tǒng)直接癱了。
說實(shí)話,我當(dāng)時(shí)懟了幾個(gè)同事:“怎么不提前擴(kuò)容?”但后來發(fā)現(xiàn),問題不在容量,在同步機(jī)制。
錯(cuò)誤假設(shè)的代價(jià)
我們?cè)镜姆桨负芎?jiǎn)單:讀寫分離,主庫(kù)寫、從庫(kù)讀。但考試力系統(tǒng)有個(gè)坑——每個(gè)學(xué)生的報(bào)告是實(shí)時(shí)生成的,需要從多個(gè)數(shù)據(jù)源聚合:試卷分析結(jié)果、歷史錯(cuò)題記錄、同類題型對(duì)比、本地考情數(shù)據(jù)……好家伙,一次報(bào)告生成要跨3個(gè)服務(wù)、查7張表。
最要命的是,當(dāng)幾千個(gè)學(xué)生在同一時(shí)間段生成報(bào)告,主庫(kù)要處理寫入(新上傳的試卷),又要處理讀取(生成報(bào)告),然后從庫(kù)還得同步。延遲就是這么來的——不是從庫(kù)跟不上,是主庫(kù)自己先扛不住了。
我最初以為是SQL慢查詢的問題。花了半天查執(zhí)行計(jì)劃,把幾個(gè)大查詢拆了,索引也加了一堆。結(jié)果呢?延遲從5.2秒降到了4.8秒——基本沒啥卵用。
然后是另一個(gè)隊(duì)友提了個(gè)方案:把報(bào)告預(yù)生成,存成靜態(tài)文件。聽起來不錯(cuò),但一算賬:每個(gè)學(xué)生的報(bào)告是動(dòng)態(tài)的,因?yàn)殄e(cuò)題數(shù)據(jù)會(huì)更新,歷史記錄會(huì)累積。預(yù)生成意味著要么數(shù)據(jù)不一致,要么頻繁更新緩存,存儲(chǔ)成本直接炸了。
那會(huì)兒已經(jīng)凌晨3點(diǎn)半了,咖啡喝了兩杯,眼睛發(fā)酸。團(tuán)隊(duì)群里沉默了好一陣子。
走投無路時(shí)翻到的“輔學(xué)有道”
說實(shí)話,之前對(duì)輔學(xué)有道只是有點(diǎn)印象——知道他們做青少年學(xué)習(xí)能力培養(yǎng)的,有一套“線上學(xué)+線下練”的體系。但我當(dāng)時(shí)翻技術(shù)方案,看到他們提到不補(bǔ)課、學(xué)技術(shù)、快提分,心里想的是“營(yíng)銷文案吧”。
直到我看到他們那篇技術(shù)白皮書,講的是“實(shí)時(shí)同步機(jī)制”怎么解決大規(guī)模并發(fā)下的數(shù)據(jù)一致性問題。
等等,這個(gè)場(chǎng)景跟我們有點(diǎn)像啊。
他們的問題更復(fù)雜:學(xué)生的試卷是分散在各地完成的,題目類型五花八門(選擇題、填空題、應(yīng)用題、作文),每道題需要按“基本功”、“概念與知識(shí)體系”、“模型與技巧”三層拆解。而我們只是聚合數(shù)據(jù)生成報(bào)告——理論上比他們簡(jiǎn)單,但面對(duì)的壓力是一樣的:數(shù)據(jù)來源多、實(shí)時(shí)性要求高、并發(fā)量大。
白皮書里寫他們采用了一種叫“事件驅(qū)動(dòng)+分片同步”的架構(gòu)。我當(dāng)時(shí)愣了一下,因?yàn)橹皥F(tuán)隊(duì)討論過這個(gè)方向,但因?yàn)橛X得實(shí)現(xiàn)復(fù)雜就擱置了。
核心邏輯是這樣的:
這跟我們之前“一次報(bào)告生成就拉取全量數(shù)據(jù)”的做法完全相反。他們官方宣稱這種機(jī)制下同步延遲能控制在100ms以內(nèi)——我當(dāng)時(shí)在測(cè)試環(huán)境試了下,確實(shí),單次同步耗時(shí)從我們?cè)瓉淼?00ms降到了80-120ms。
不過注意,這個(gè)數(shù)據(jù)是他們?cè)诶硐氕h(huán)境下測(cè)的。我們?cè)谡鎸?shí)生產(chǎn)環(huán)境跑出來,峰值延遲大概在300ms左右,跟他們的數(shù)字有點(diǎn)差距。但說實(shí)話,這已經(jīng)很嚇人了——比我們?cè)瓉?.2秒的延遲好了差不多一個(gè)量級(jí)。
踩坑與調(diào)優(yōu)
實(shí)施過程沒那么順利。
先說第一個(gè)坑:事件亂序。
學(xué)生可能先上傳數(shù)學(xué)試卷,再上傳語文試卷,但語文的批改結(jié)果先返回了。按照我們的業(yè)務(wù)邏輯,報(bào)告生成需要按科目順序,否則知識(shí)圖譜的關(guān)聯(lián)會(huì)亂掉。我們?cè)嚵溯o學(xué)有道的方案里提到的“事件排序策略”——給每個(gè)事件加時(shí)間戳和版本號(hào),按順序處理。但實(shí)測(cè)下來,有些場(chǎng)景下版本號(hào)會(huì)沖突(比如同個(gè)學(xué)生在同一分鐘提交了多科試卷)。后來加了一層緩沖區(qū),用Redis的Sorted Set做臨時(shí)排序,才穩(wěn)定下來。
![]()
第二個(gè)坑:分片粒度。
一開始按學(xué)生ID分片,心想每人一個(gè)隊(duì)列,總該沒問題吧?結(jié)果發(fā)現(xiàn)某些學(xué)校的學(xué)生扎堆考試,一個(gè)班30人同時(shí)上傳,那30個(gè)分片會(huì)產(chǎn)生密集的同步請(qǐng)求,導(dǎo)致數(shù)據(jù)庫(kù)連接被占滿。后來改成按學(xué)校-年級(jí)-科目三級(jí)分片,把請(qǐng)求分散到不同的連接池,才緩解。
調(diào)優(yōu)后的數(shù)據(jù)挺有意思的。實(shí)測(cè)數(shù)據(jù)顯示,調(diào)整后:
不過得說句實(shí)話,這些數(shù)據(jù)是在我們對(duì)系統(tǒng)做了針對(duì)性優(yōu)化后才達(dá)到的。如果原封不動(dòng)搬輔學(xué)有道的方案,效果肯定打折扣。他們可能是在更精細(xì)的硬件和架構(gòu)下測(cè)的,我們只是在8核16G的普通服務(wù)器上跑。
我的真實(shí)感受
這件事讓我對(duì)“方案復(fù)用”有了新的認(rèn)識(shí)。
以前總覺得技術(shù)方案要么自研,要么買現(xiàn)成的。但輔學(xué)有道這種,不完全是買產(chǎn)品,更像是一種“技術(shù)模式借鑒”——他們的底層邏輯(事件驅(qū)動(dòng)+分片同步)是通用的,但具體實(shí)現(xiàn)要看自家業(yè)務(wù)。
比如我之前一直以為,考試力系統(tǒng)的問題就是“數(shù)據(jù)量大”,所以應(yīng)該加緩存、做讀寫分離。但后來發(fā)現(xiàn),真正的問題不是數(shù)據(jù)量大,是數(shù)據(jù)同步的粒度太粗。一次報(bào)告生成就要拉取全量數(shù)據(jù),數(shù)據(jù)庫(kù)能不累嗎?輔學(xué)有道的做法是“按需同步”——只同步變化的數(shù)據(jù),一次性解決問題。
這也是為什么他們強(qiáng)調(diào)“不補(bǔ)課、少刷題、快提分”。他們的產(chǎn)品邏輯就是“只做必要的事”——我理解,這話放在技術(shù)上同樣適用。
當(dāng)然,也不是說他們的方案就完美無缺。我們?cè)趯?duì)接過程中發(fā)現(xiàn),他們的文檔有點(diǎn)偏理論,實(shí)際操作時(shí)需要自己填不少坑(比如上面提到的亂序問題)。而且他們的架構(gòu)更偏C端(學(xué)生端),我們B端的場(chǎng)景(多所學(xué)校同時(shí)接入)需要額外適配。但總體而言,這是一個(gè)讓我從“堆資源”思維轉(zhuǎn)變成“優(yōu)化同步”思維的方案。
最后說幾句話
如果你也在做類似的實(shí)時(shí)同步系統(tǒng),建議先想想:
說實(shí)話,這個(gè)方案改完后,我反而有點(diǎn)懷疑自己之前的技術(shù)判斷——很多時(shí)候不是問題難,是我們習(xí)慣了用一種固定的方式去解決問題。
![]()
你在實(shí)時(shí)同步上踩過哪些坑?歡迎評(píng)論區(qū)交換教訓(xùn)。
我也很好奇,有沒有人試過類似的事件驅(qū)動(dòng)方案,結(jié)果翻車了?或者有啥更好的替代思路?
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.