新聞中心
1.背景
"凡事預(yù)則立,不預(yù)則廢。"

東昌網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián)公司,東昌網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為東昌上千多家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)營(yíng)銷網(wǎng)站建設(shè)要多少錢(qián),請(qǐng)找那個(gè)售后服務(wù)好的東昌做網(wǎng)站的公司定做!
——《禮記·中庸》
在文章的開(kāi)頭,我們可以先來(lái)了解一下直播業(yè)務(wù)的大致業(yè)務(wù)架構(gòu)。將直播業(yè)務(wù)簡(jiǎn)單分為兩大類場(chǎng)景"看播"、"開(kāi)播",前者主要面向C端觀看用戶,后者主要面向B端開(kāi)播主播。主播通過(guò)"開(kāi)播工具"的開(kāi)播產(chǎn)品功能,經(jīng)由"開(kāi)播平臺(tái)"完成一系列開(kāi)播動(dòng)作,最后將媒體信息采集推送到多媒體服務(wù)器,C端觀看用戶就可以從CDN看到直播的視頻流內(nèi)容。
從數(shù)據(jù)流向來(lái)講,"開(kāi)播"場(chǎng)景是產(chǎn)生數(shù)據(jù)和觸發(fā)關(guān)鍵事件的源頭。這些數(shù)據(jù)或事件會(huì)涉及多個(gè)領(lǐng)域,如安全合規(guī)信息、房間信息、主播信息、開(kāi)播場(chǎng)次信息、安全審計(jì)信息、多媒體信息等。
打個(gè)不太準(zhǔn)確的比喻。開(kāi)播系統(tǒng)對(duì)于直播平臺(tái)的重要性,等同于訂單系統(tǒng)對(duì)于交易平臺(tái)的重要性。開(kāi)播工具作為播端功能入口,直接面向官方開(kāi)播工具(直播姬、粉版大加號(hào)、三方工具如OBS開(kāi)播)的用戶以及內(nèi)部平臺(tái)方的用戶(其他業(yè)務(wù)線、產(chǎn)品&運(yùn)營(yíng)),對(duì)開(kāi)播體驗(yàn)負(fù)責(zé)。開(kāi)播平臺(tái)在其中的職責(zé),是向開(kāi)播工具和其他平臺(tái)方提供開(kāi)播相關(guān)的平臺(tái)化業(yè)務(wù)能力,如開(kāi)關(guān)播、開(kāi)通直播間、切換分區(qū)等。
同時(shí),開(kāi)播平臺(tái)與同級(jí)的業(yè)務(wù)平臺(tái)一起協(xié)作,才能支撐起完整的開(kāi)播工具產(chǎn)品能力,如語(yǔ)聊房業(yè)務(wù)需要開(kāi)播工具管理平臺(tái)(開(kāi)播工具類支持)、主播互動(dòng)平臺(tái)(主播互動(dòng)能力支持)、流媒體服務(wù)端共同參與才能完成,從不同的維度幫助開(kāi)播工具生態(tài)完善化。
圖片
一些涉及到的業(yè)務(wù)/技術(shù)名詞,在此我們也做出列舉并做出簡(jiǎn)單介紹:
|
名詞 |
名詞簡(jiǎn)述 |
|
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD) |
DDD 是 Domain-Driven Design 的縮寫(xiě),是 Eric Evans 于 2004 年提出的一種軟件設(shè)計(jì)方法和理念。 其主要的思想是,利用確定的業(yè)務(wù)模型來(lái)指導(dǎo)業(yè)務(wù)與應(yīng)用的設(shè)計(jì)和實(shí)現(xiàn)。主張開(kāi)發(fā)人員與業(yè)務(wù)人員持續(xù)地溝通和模型的持續(xù)迭代式演化,以保證業(yè)務(wù)模型與代碼實(shí)現(xiàn)的一致性,從而實(shí)現(xiàn)有效管理業(yè)務(wù)復(fù)雜度,優(yōu)化軟件設(shè)計(jì)的目的。 |
|
領(lǐng)域知識(shí) |
(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)概念)指能準(zhǔn)確傳達(dá)業(yè)務(wù)規(guī)則的描述,也是領(lǐng)域中業(yè)務(wù)知識(shí)的集中體現(xiàn)。 理想狀態(tài)下,領(lǐng)域?qū)<液途幋a人員對(duì)業(yè)務(wù)的認(rèn)知應(yīng)該完全一致,就算不同的人寫(xiě)代碼也應(yīng)該偏差不大。 |
|
領(lǐng)域事件(Domain Event) |
領(lǐng)域事件,是在業(yè)務(wù)上真實(shí)發(fā)生的客觀事實(shí),這些事實(shí)對(duì)系統(tǒng)會(huì)產(chǎn)生關(guān)鍵影響,是觀察業(yè)務(wù)系統(tǒng)變化的關(guān)鍵點(diǎn)。對(duì)于開(kāi)播而言,"房間已經(jīng)被主播主動(dòng)流轉(zhuǎn)為開(kāi)播了"就是一個(gè)領(lǐng)域事件。 |
|
視頻云(直播) |
一般指廣義的直播流媒體業(yè)務(wù),提供主播推流、觀眾拉流的基礎(chǔ)能力。 |
|
看播 |
一般指廣義的直播觀看業(yè)務(wù)域,涉及進(jìn)房、彈幕互動(dòng)、禮物打賞等業(yè)務(wù)場(chǎng)景,一般面向C端(觀眾) |
|
直播姬 |
一般指移動(dòng)端APP"嗶哩嗶哩直播姬",以及Windows系統(tǒng)下的"嗶哩嗶哩直播姬"應(yīng)用。(注:?jiǎn)袅▎袅ǚ郯鍭PP和Web頁(yè)面也提供了開(kāi)播能力) |
|
測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(TDD) |
測(cè)試驅(qū)動(dòng)開(kāi)發(fā)是一種軟件開(kāi)發(fā)過(guò)程中的應(yīng)用方法,由極限編程倡導(dǎo),以其倡導(dǎo)先寫(xiě)測(cè)試程序,然后編碼實(shí)現(xiàn)其功能得名。本文中主要涉及ATDD集成測(cè)試驅(qū)動(dòng)和UTDD單元測(cè)試驅(qū)動(dòng)。 |
|
戰(zhàn)略設(shè)計(jì) |
戰(zhàn)略設(shè)計(jì)也稱為戰(zhàn)略建模,是指對(duì)業(yè)務(wù)進(jìn)行高層次的抽象和歸類。主要手段包括理清上下文和進(jìn)行子域的劃分。 |
|
戰(zhàn)術(shù)設(shè)計(jì) |
戰(zhàn)術(shù)設(shè)計(jì)也稱為戰(zhàn)術(shù)建模,是指對(duì)特定上下文下的模型進(jìn)行詳細(xì)設(shè)計(jì)。我們對(duì)開(kāi)播新的微服務(wù)中各個(gè)模塊職責(zé)的編排,就是戰(zhàn)術(shù)設(shè)計(jì)的一部分。 |
|
開(kāi)播平臺(tái)/開(kāi)播服務(wù)平臺(tái) |
一般指狹義的后端業(yè)務(wù),即提供開(kāi)播房間狀態(tài)流轉(zhuǎn)、直接對(duì)客戶端提供推流信息的服務(wù)端業(yè)務(wù)。提供如開(kāi)關(guān)播接口、推流地址獲取的通用業(yè)務(wù)接口。 |
|
開(kāi)播工具 |
一般指廣義的可進(jìn)行開(kāi)播的業(yè)務(wù)場(chǎng)景,如直播姬、Web主播中心、粉APP開(kāi)播等,相較于開(kāi)播更偏向于端到端場(chǎng)景 |
|
開(kāi)播 |
一般指廣義的開(kāi)播業(yè)務(wù)域,涉及開(kāi)播、關(guān)播等業(yè)務(wù)場(chǎng)景,一般面向B端(主播) |
|
事件風(fēng)暴(Event Storming) |
事件風(fēng)暴是一種捕獲行為需求的方法,類似傳統(tǒng)軟件的開(kāi)發(fā)用例分析。所有人員(領(lǐng)域?qū)<液图夹g(shù)專家) 對(duì)業(yè)務(wù)行為進(jìn)行一次發(fā)散,并最終收斂達(dá)到業(yè)務(wù)的統(tǒng)一。 |
|
事件溯源(Event Sourcing) |
事件溯源是一種用事件日志追溯狀態(tài)的方法,因此事件溯源的關(guān)鍵在于事件日志。對(duì)于開(kāi)播而言只是借用了"溯源"這種思想,用于保證新舊開(kāi)播鏈路的關(guān)鍵狀態(tài)完全一致。 |
|
SOP |
Standard Operating Procedure,標(biāo)準(zhǔn)作業(yè)程序。本次重構(gòu)過(guò)程,發(fā)布、應(yīng)急處理、故障處理都使用此種方式進(jìn)行推進(jìn)。 |
|
room / room-service |
PHP歷史服務(wù),本次被遷移的主角。眾多歷史業(yè)務(wù)在該服務(wù)中。 |
|
live-streaming / streaming |
新開(kāi)播微服務(wù),今后承載開(kāi)播領(lǐng)域主要業(yè)務(wù)的落地實(shí)體。 |
|
app-blink |
開(kāi)播工具網(wǎng)關(guān)層微服務(wù),直接承載客戶端的請(qǐng)求。 |
1.1 現(xiàn)狀和挑戰(zhàn)
直播開(kāi)播系統(tǒng),伴隨著B(niǎo)站直播的成長(zhǎng)貫穿始終。
發(fā)展初期所有的直播業(yè)務(wù)基本都在一套php代碼里完成,包括開(kāi)播部分。之后的直播高速發(fā)展中,很多模塊已經(jīng)順利完成遷移。
開(kāi)播部分也嘗試過(guò)遷移,但是未能成功完成。還不太幸運(yùn)的出了比較嚴(yán)重的線上故障。(這給后面的再次重構(gòu)積累了寶貴的經(jīng)驗(yàn)。)
1.1.1 債務(wù)清單
- 業(yè)務(wù)積累厚:最初的代碼大致是從2017年開(kāi)始的,要問(wèn)里面的門(mén)道究竟有多少,可能另起一篇文章也難以詳盡。
- 代碼可讀性差:php是弱類型+動(dòng)態(tài)類型特點(diǎn),代碼可讀性方面有非常大的挑戰(zhàn)。同時(shí)因?yàn)樯婕暗娇缯Z(yǔ)言遷移,需要有機(jī)制能檢查兩邊邏輯和數(shù)據(jù)的一致性。
- 開(kāi)發(fā)模式陳舊:php代碼在整個(gè)開(kāi)發(fā)架構(gòu)上,也是偏"事務(wù)腳本模式"。多個(gè)領(lǐng)域混雜在一起,互相耦合調(diào)用,解耦異常困難。
- 質(zhì)量配套欠缺:?jiǎn)卧獪y(cè)試和自動(dòng)化測(cè)試方面也比較缺乏。要想順利完成重構(gòu)遷移,這塊是重要的前置工作。
- 技術(shù)棧滯后:php技術(shù)棧,已經(jīng)不符合公司的整個(gè)技術(shù)棧主路線。各種lib、中間件支持方面欠缺,急需技術(shù)棧升級(jí)。
1.1.2 遺留系統(tǒng)特征
業(yè)界對(duì)遺留系統(tǒng)的普遍定義中有4個(gè)關(guān)鍵字:舊、過(guò)時(shí)、重要、仍在使用。
圖片
事實(shí)并非完全如此:有些系統(tǒng)時(shí)間雖長(zhǎng),但如果一直堅(jiān)持現(xiàn)代化的開(kāi)發(fā)方式,在代碼質(zhì)量、架構(gòu)合理性、測(cè)試策略、DevOps 等方面都保持先進(jìn)性,這樣的系統(tǒng)就像陳年的老酒一樣,歷久彌香。而有些系統(tǒng)雖然剛剛開(kāi)發(fā)完成,但如果在上述幾個(gè)方面都做得不好,我們也可以把它叫做遺留系統(tǒng)。遺留系統(tǒng)在維護(hù)成本、合規(guī)性、安全性、集成性等方面都會(huì)給企業(yè)造成巨大的負(fù)擔(dān),但同時(shí)也蘊(yùn)含著豐富的數(shù)據(jù)和業(yè)務(wù)資產(chǎn)。我們應(yīng)該對(duì)遺留系統(tǒng)進(jìn)行現(xiàn)代化,讓它重新煥發(fā)青春。
顯然在知曉了舊開(kāi)播系統(tǒng)有諸多歷史債務(wù)后,我們可以認(rèn)為它確實(shí)是一個(gè)搖搖欲墜的遺留系統(tǒng)。而我們本次的目標(biāo),就是將開(kāi)播平臺(tái)這個(gè)重要的遺留系統(tǒng)進(jìn)行重構(gòu),讓它"煥發(fā)新生",并讓他在可預(yù)見(jiàn)的未來(lái)中都維持現(xiàn)代化系統(tǒng)的標(biāo)準(zhǔn)。
1.2 安全生產(chǎn)
在開(kāi)播系統(tǒng)的維護(hù)、迭代、演進(jìn)中,我們也致力于系統(tǒng)的"安全生產(chǎn)"問(wèn)題:
- 如何降低研發(fā)的業(yè)務(wù)認(rèn)知成本、溝通成本,降低復(fù)雜度,從而提高"卡車系數(shù)",保證團(tuán)隊(duì)內(nèi)部能保證形成快速backup?
- 如何通過(guò)技術(shù)演進(jìn),增加開(kāi)播系統(tǒng)的可拓展性/魯棒性/可測(cè)試性?
- 遷移新系統(tǒng)時(shí),新老系統(tǒng)如何優(yōu)雅安全切換、過(guò)程中的新舊系統(tǒng)數(shù)據(jù)是否可以進(jìn)行白盒對(duì)比?
2.開(kāi)播系統(tǒng)架構(gòu)演進(jìn)
每個(gè)士兵在上戰(zhàn)場(chǎng)前必須清楚的明白,他這場(chǎng)小小的戰(zhàn)斗在大局中起的作用。——伯納德 · L · 蒙哥馬利(英國(guó))
2.1 審視:?jiǎn)栴}出在哪里?
在著手進(jìn)行改造升級(jí)之前,不妨先從整體業(yè)務(wù)的迭代流程和已有架構(gòu)中找到問(wèn)題,以確定真正值得樹(shù)立的目標(biāo),避免陷入"只見(jiàn)樹(shù)木不見(jiàn)森林"的狹小視野中。
我們不難發(fā)現(xiàn),這個(gè)日積月累的遺留系統(tǒng)當(dāng)中,它的業(yè)務(wù)研發(fā)流程種種令人難以忽視的問(wèn)題:業(yè)務(wù)知識(shí)、業(yè)務(wù)架構(gòu)的認(rèn)識(shí)遺失、產(chǎn)研語(yǔ)言的不統(tǒng)一等等。
2.1.1 業(yè)務(wù)知識(shí)與業(yè)務(wù)架構(gòu)的生命周期
開(kāi)播域作為播端的核心業(yè)務(wù)域,由于其悠久的歷史和維護(hù)團(tuán)隊(duì)同學(xué)的變更,在幾經(jīng)周折后,領(lǐng)域知識(shí)已經(jīng)處于混沌狀態(tài)。這種情況下,顯然比起遺留代碼和不合理的實(shí)現(xiàn)邏輯而言,更大的bug可能最終會(huì)發(fā)生在人身上,也就是我們對(duì)業(yè)務(wù)知識(shí)本身的認(rèn)識(shí):對(duì)業(yè)務(wù)知識(shí)缺乏了解,往往是拖慢業(yè)務(wù)迭代甚至是釀成線上事故的罪魁禍?zhǔn)住?/p>
對(duì)于業(yè)務(wù)架構(gòu)的認(rèn)知遺失,則會(huì)導(dǎo)致業(yè)務(wù)域內(nèi)職責(zé)的混亂:“這個(gè)新增業(yè)務(wù)是否應(yīng)該由我們負(fù)責(zé)?”。落實(shí)到開(kāi)發(fā)者身上就變成了應(yīng)用架構(gòu)的混亂:“這個(gè)業(yè)務(wù)我們到底應(yīng)該寫(xiě)在哪個(gè)微服務(wù)里?”
最明顯最集中的問(wèn)題會(huì)爆發(fā)在端到端用例中:戰(zhàn)略設(shè)計(jì)上一個(gè)實(shí)體上業(yè)務(wù)行為的不清晰往往代表著一個(gè)甚至多個(gè)端到端用例的認(rèn)知缺失,映射到戰(zhàn)術(shù)實(shí)現(xiàn)上就會(huì)演變成災(zāi)難性的"需求引入變更時(shí),未考慮到某個(gè)用戶用例",最終在上線前的驗(yàn)收環(huán)節(jié)甚至是上線后,發(fā)現(xiàn)這個(gè)需求的引入導(dǎo)致了bug的產(chǎn)生。
我們當(dāng)然可以把這種事故歸結(jié)為“歷史遺留問(wèn)題”,但是對(duì)于功能的使用者而言,這種糟糕體驗(yàn)會(huì)直接讓平臺(tái)被貼上“不專業(yè)”的負(fù)面標(biāo)簽。對(duì)平臺(tái)本身而言,這種災(zāi)難性的錯(cuò)誤堆砌也只會(huì)讓系統(tǒng)不斷熵增,復(fù)雜程度愈發(fā)不可收拾,最終花費(fèi)在處理問(wèn)題、歷史代碼考古上的人力一增再增缺無(wú)濟(jì)于事。
這部分無(wú)疑是開(kāi)播重構(gòu)項(xiàng)目中,最迫切需要解決的問(wèn)題。
2.1.2 描述語(yǔ)言不統(tǒng)一
在業(yè)務(wù)人員和產(chǎn)品的角度來(lái)看,"開(kāi)播"這個(gè)用例往往和各端開(kāi)發(fā)人員所說(shuō)的"開(kāi)播"又有著某種微妙的差別。業(yè)務(wù)視角下的開(kāi)播,往往是用戶一次完整的開(kāi)播體驗(yàn),比如,打開(kāi)移動(dòng)直播姬,調(diào)整好各種用戶設(shè)置,點(diǎn)擊開(kāi)播,最終看到自己的畫(huà)面被正確投放到b站直播間,并且可以完成后續(xù)和觀眾的互動(dòng)。
而在技術(shù)視角下的開(kāi)播,"開(kāi)播"是各執(zhí)行方的橫切面組成的:客戶端完成最直接的ui/ux互動(dòng)、直播服務(wù)端進(jìn)行用戶請(qǐng)求校驗(yàn)、視頻流和直播業(yè)務(wù)數(shù)據(jù)的協(xié)調(diào)、視頻云負(fù)責(zé)接收用戶的上行視頻流;每一方對(duì)"開(kāi)播"的這個(gè)詞解釋就產(chǎn)生了差異:客戶端進(jìn)入到直播界面并點(diǎn)擊開(kāi)播叫開(kāi)播,服務(wù)端的開(kāi)播接口被調(diào)用了也被視為開(kāi)播,視頻流被推送到視頻云上行服務(wù)器的時(shí)候也可能被視為開(kāi)播。
泛泛而談的話,各方的解釋都沒(méi)有太大問(wèn)題,但是這樣的解釋無(wú)法確切指定它在業(yè)務(wù)里處于哪一部分,會(huì)造成什么結(jié)果。最終呈現(xiàn)在一位新進(jìn)入技術(shù)團(tuán)隊(duì)的同學(xué)的眼中可能是這樣的場(chǎng)景:
舉例
客服:主播反饋線上無(wú)法開(kāi)播?!締?wèn)題平臺(tái)】PC直播姬; 【一級(jí)分類】開(kāi)播; 【二級(jí)分類】無(wú)法開(kāi)播; 【問(wèn)題描述】主播反饋進(jìn)入移動(dòng)直播姬開(kāi)播界面后,點(diǎn)擊開(kāi)播后,不能正常推流;
開(kāi)發(fā)1:是不是開(kāi)播了多次?
客服:不是,主播開(kāi)播了一次
開(kāi)發(fā)1:那可以讓用戶重試
開(kāi)發(fā)2:是不是視頻云推流服務(wù)出了問(wèn)題?@視頻云
客服:用戶已經(jīng)重試了,還是不能正常開(kāi)播(其實(shí)是在另一臺(tái)設(shè)備上已經(jīng)推流了,還在嘗試使用其他設(shè)備推流)
開(kāi)發(fā)3:視頻云看到用戶推流是正常的 (推流監(jiān)控圖)
開(kāi)發(fā)1:哦,原來(lái)是重復(fù)開(kāi)播了
假設(shè)我是一位團(tuán)隊(duì)新成員,在看到最終輸出的"重復(fù)開(kāi)播"結(jié)論之前,得到的都是點(diǎn)狀的信息,沒(méi)有完整的用例以供參考,難以理解線上問(wèn)題的癥結(jié)在何處。如果這個(gè)時(shí)候甚至沒(méi)有文檔來(lái)描述開(kāi)播領(lǐng)域相關(guān)業(yè)務(wù),或者是開(kāi)播流程的場(chǎng)景快照,那更是一場(chǎng)新人的災(zāi)難——可能需要專門(mén)請(qǐng)教團(tuán)隊(duì)中熟悉開(kāi)播領(lǐng)域的資深開(kāi)發(fā)為他進(jìn)行講解才能瞥見(jiàn)開(kāi)播業(yè)務(wù)的一隅,且授課效果還要取決于講述人的結(jié)構(gòu)化敘述能力,這是我們從效率考量上不愿意見(jiàn)到的。
可以舉一個(gè)貼近實(shí)際開(kāi)發(fā)人員的例子,"請(qǐng)教了3位同事才知道了開(kāi)播記錄是怎么產(chǎn)生的"、"請(qǐng)教了3位同事才本地構(gòu)建成功",諸如此類的尷尬在日常工作中屢見(jiàn)不鮮的,實(shí)際上這類問(wèn)題只會(huì)對(duì)程序員了解業(yè)務(wù)和編碼的積極性,以及商業(yè)化產(chǎn)品開(kāi)發(fā)落地的效率起反作用。
2.1.3 對(duì)程序員的"人文關(guān)懷"
一個(gè)貼近實(shí)際開(kāi)發(fā)人員的例子,"請(qǐng)教了3位同事才知道了開(kāi)播記錄是怎么產(chǎn)生的"、"請(qǐng)教了3位同事才本地構(gòu)建歷史服務(wù)成功",諸如此類的尷尬在日常工作中屢見(jiàn)不鮮的,實(shí)際上這類問(wèn)題只會(huì)對(duì)程序員了解業(yè)務(wù)和編碼的積極性、產(chǎn)品開(kāi)發(fā)落地的效率起反作用。
要解決這種效率抑或積極性問(wèn)題,還是需要解決根源上的"知識(shí)共享"問(wèn)題。
2.2 引入領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)
在前文敘述問(wèn)題的時(shí)候,熟悉的讀者可能就已經(jīng)想到了某個(gè)熱度經(jīng)久不衰的架構(gòu)思想:領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)。是的,我們?cè)陂_(kāi)播平臺(tái)的重構(gòu)中決定使用這種方式來(lái)解決現(xiàn)有的諸多痛點(diǎn)。依靠領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的設(shè)計(jì)思想,通過(guò)事件風(fēng)暴建立領(lǐng)域模型,合理劃分領(lǐng)域邏輯和物理邊界,建立與現(xiàn)實(shí)世界相映射的領(lǐng)域?qū)ο蠛头?wù)架構(gòu)圖,定義符合DDD分層架構(gòu)思想的代碼結(jié)構(gòu)模型,保證業(yè)務(wù)模型與代碼模型的一致性。
相對(duì)的,對(duì)于最終的效果,也是可以預(yù)期到的:
- 統(tǒng)一業(yè)務(wù)模型和代碼模型,領(lǐng)域知識(shí)全體共享,提升協(xié)助效率;
- 通過(guò)邊界劃分將復(fù)雜業(yè)務(wù)領(lǐng)域簡(jiǎn)單化,設(shè)計(jì)出清晰的領(lǐng)域和應(yīng)用邊界,實(shí)現(xiàn)業(yè)務(wù)和技術(shù)統(tǒng)一的架構(gòu)演進(jìn),提高人效,拒絕一加功能排期一個(gè)月;
- 通過(guò)職責(zé)劃分合理的職責(zé)邊界,降低架構(gòu)腐敗速度;
2.3 領(lǐng)域驅(qū)動(dòng)視角看開(kāi)播
回到"開(kāi)播"這個(gè)待解決的問(wèn)題域本身,對(duì)開(kāi)播業(yè)務(wù)中最核心的"開(kāi)播"用例,核心的業(yè)務(wù)問(wèn)題包括以下幾點(diǎn)需要明確:
- 如何確定房間是否可以開(kāi)始直播。
- 如何讓房間開(kāi)始直播。
- 如何通知外界房間已經(jīng)開(kāi)始直播。
也有一些非功能性的考慮:
- 如何使技術(shù)實(shí)現(xiàn)貼近現(xiàn)實(shí)中的業(yè)務(wù)原貌,從而降低認(rèn)知成本
- 如何提高我們對(duì)業(yè)務(wù)和產(chǎn)品的認(rèn)知程度和積極性
- 如何提高開(kāi)播功能的魯棒性和性能
首先,我們可以采用事件驅(qū)動(dòng)開(kāi)發(fā)方法,結(jié)合領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中的事件風(fēng)暴方法論,來(lái)梳理開(kāi)播用例中的關(guān)鍵事件和參與者:
事件風(fēng)暴的核心流程就是由用戶執(zhí)行了命令,從而產(chǎn)生了事件?;谶@個(gè)事件的結(jié)果,與之前相同或是其他的用戶會(huì)執(zhí)行另一個(gè)命令,產(chǎn)生新類型的事件,以此類推。而順序是按照業(yè)務(wù)邏輯而定的。所以我們?cè)谡黹_(kāi)播涉及的時(shí)間風(fēng)暴時(shí),工作流如下:
- 確定用例的發(fā)起者,即主語(yǔ)。在開(kāi)播場(chǎng)景中,可以是主播或者運(yùn)營(yíng)。
- 確定主語(yǔ)的動(dòng)作,比如"開(kāi)啟直播"。
- 確定動(dòng)作的流程中涉及的命令以及執(zhí)行命令產(chǎn)生的事件、后果,例如"新場(chǎng)次已被主播創(chuàng)建"。
- 補(bǔ)充流程中涉及的業(yè)務(wù)知識(shí),例如"房間"、各種各樣的"檢查規(guī)則"以及外部系統(tǒng)。
- 當(dāng)一個(gè)完整的業(yè)務(wù)流程通過(guò)上述方式寫(xiě)完之后,對(duì)于每個(gè)用戶,命令,事件進(jìn)行組合,就能獲得聚合,用事件風(fēng)暴的描述開(kāi)播場(chǎng)次創(chuàng)建就是"主播在場(chǎng)次聚合上進(jìn)行了創(chuàng)建場(chǎng)次操作,導(dǎo)致了新場(chǎng)次創(chuàng)建事件",此事件發(fā)生后,用戶會(huì)在房間聚合上執(zhí)行房間開(kāi)播狀態(tài)流轉(zhuǎn)操作的新命令。
圖片
事件風(fēng)暴中"定義領(lǐng)域模型"是最重要的一步,這一步需要了解實(shí)際業(yè)務(wù)形態(tài)后團(tuán)隊(duì)內(nèi)大量討論,從而達(dá)成共識(shí)。在這個(gè)階段中我們提煉了諸多的業(yè)務(wù)表現(xiàn)并且屏蔽技術(shù)實(shí)現(xiàn)細(xì)節(jié),提取出了關(guān)鍵的實(shí)體、值對(duì)象、聚合根。緊接著就可以著手對(duì)事件風(fēng)暴中的概念進(jìn)行進(jìn)一步的歸納。
通過(guò)以上步驟,我們可以清晰地梳理出開(kāi)播用例中的關(guān)鍵事件和參與者,為后續(xù)的設(shè)計(jì)和開(kāi)發(fā)工作奠定基礎(chǔ)。
業(yè)務(wù)描述拆解成主語(yǔ)和動(dòng)詞的形式后,可以發(fā)現(xiàn)"房間"和"場(chǎng)次"是這個(gè)問(wèn)題域的兩個(gè)主要元素。在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中,需要將這兩個(gè)支撐域進(jìn)行集成,最終形成"開(kāi)播域"的基本解決方案。為了確保開(kāi)播業(yè)務(wù)流程的完整性,還需要將"安全管控"、"分區(qū)"、"賬號(hào)"等子域或外部系統(tǒng)的知識(shí)參與其中,并將其作為業(yè)務(wù)規(guī)則和值對(duì)象等等的形式進(jìn)行表達(dá)。
圖片
根據(jù)近一年的業(yè)務(wù)現(xiàn)狀,我們參考領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)模式,進(jìn)行了領(lǐng)域上下文的劃分:
|
子域 |
能力 |
業(yè)務(wù)子域重點(diǎn) |
|
[核心域] 開(kāi)播域 |
集成開(kāi)播相關(guān)的所有上下文,形成對(duì)開(kāi)播場(chǎng)景下業(yè)務(wù)用例的實(shí)現(xiàn)方案。如開(kāi)播、關(guān)播等。 |
開(kāi)播 = 直播場(chǎng)次被創(chuàng)建 + 房間狀態(tài)變?yōu)殚_(kāi)播 + 開(kāi)播領(lǐng)域事件被發(fā)出 |
|
安全管控 = 開(kāi)播常態(tài)化管控能力 + 應(yīng)急管控能力,如僅允許官方直播間開(kāi)播 |
||
|
[通用域] 房間域 |
基礎(chǔ)業(yè)務(wù)能力,僅處理播端房間基礎(chǔ)業(yè)務(wù),如變更房間開(kāi)播狀態(tài)。 |
房間狀態(tài)的增刪改查 |
|
發(fā)送開(kāi)關(guān)播領(lǐng)域事件 |
||
|
[通用域] 場(chǎng)次域 |
基礎(chǔ)業(yè)務(wù)能力,僅處理開(kāi)播領(lǐng)域產(chǎn)生的開(kāi)關(guān)播場(chǎng)次信息 |
開(kāi)關(guān)播場(chǎng)次信息、子場(chǎng)次信息(同一場(chǎng)直播下不同分區(qū)) |
|
[支撐子域] 房間管理域 |
保障房間域和開(kāi)播域之間集成業(yè)務(wù)的業(yè)務(wù)完整性,如房間的推流管理。 |
房間關(guān)鍵依賴對(duì)賬:保障新開(kāi)通房間存在視頻云上行推流地址 |
|
直播CQRS對(duì)賬:保障播端、觀看端的房間基礎(chǔ)信息一致 |
||
|
視頻云流管理能力封裝 |
||
|
[支撐子域] 開(kāi)播安全管控域 |
開(kāi)通直播間、預(yù)開(kāi)播、開(kāi)播、切換分區(qū)、推流管控策略 |
提供可配置、產(chǎn)品化的常態(tài)化策略管控能力,如是否允許某些地區(qū)開(kāi)播,某分區(qū)是否需要延遲 |
|
必須具備快速應(yīng)對(duì)重要事件的能力。 |
||
|
外部域 |
視頻云管理 |
視頻云所屬領(lǐng)域,提供上行推流相關(guān)能力。開(kāi)播主要使用上行推流管理、查詢上行推流地址 |
|
直播分區(qū) |
看端所屬領(lǐng)域,提供分區(qū)基礎(chǔ)知識(shí)。開(kāi)播僅使用其查詢分區(qū)元信息,作為開(kāi)播安全管控的輸入 |
|
|
賬號(hào)上下文 |
主站賬號(hào)所屬領(lǐng)域,提供實(shí)名、等級(jí)相關(guān)知識(shí)。開(kāi)播需要使用賬號(hào)信息、粉絲數(shù)等,作為開(kāi)播安全管控的輸入 |
明確領(lǐng)域上下文和解決域的劃分后,緊接著就可以進(jìn)行DDD指導(dǎo)下的解決域戰(zhàn)術(shù)落地了。
領(lǐng)域劃分落實(shí)到戰(zhàn)術(shù)上的一個(gè)方案就是微服務(wù),微服務(wù)將直接作為開(kāi)播域這個(gè)核心域與其他子域的實(shí)際界限。
在下文中,我們會(huì)講述如何將當(dāng)前的PHP遺留服務(wù),這個(gè)不滿足領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的開(kāi)發(fā)架構(gòu),演進(jìn)為受領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)指導(dǎo)的、貼合業(yè)務(wù)的、使用Golang搭建的整潔架構(gòu)。
3.開(kāi)發(fā)架構(gòu)
設(shè)計(jì)不只是感觀,設(shè)計(jì)就是產(chǎn)品的工作方式?!返俜颉滩妓?/p>
開(kāi)門(mén)見(jiàn)山地講,經(jīng)過(guò)了多年積累后的舊版開(kāi)播的遺留代碼是工程導(dǎo)向的,里面不乏炫技的代碼、大段冗長(zhǎng)而缺乏業(yè)務(wù)注釋的代碼,這讓"開(kāi)播"這個(gè)重要的業(yè)務(wù)領(lǐng)域在技術(shù)實(shí)現(xiàn)上,與業(yè)務(wù)方的實(shí)際描述漸行漸遠(yuǎn)。每當(dāng)我們提及一些業(yè)務(wù)場(chǎng)景,都需要絞盡腦汁才能回想起這個(gè)場(chǎng)景到底與哪些代碼有一些聯(lián)系。這樣的開(kāi)發(fā)架構(gòu)和技術(shù)實(shí)現(xiàn)方式,不論對(duì)團(tuán)隊(duì)的知識(shí)共享還是對(duì)業(yè)務(wù)的正常迭代,都是一筆不可忽視的成本。
同時(shí),由于陳年代碼經(jīng)手多代程序員,導(dǎo)致代碼風(fēng)格不統(tǒng)一、領(lǐng)域邏輯和UI邏輯耦合的情況幾乎隨處可見(jiàn),DAO代碼更是可能隨時(shí)出現(xiàn)在各個(gè)層次,這樣的耦合對(duì)可拓展性和可測(cè)試性都帶來(lái)了不小的麻煩。
本次重構(gòu)在戰(zhàn)術(shù)落地層面所面臨的挑戰(zhàn),就是如何在保證業(yè)務(wù)邏輯幾乎不變的情況下,讓業(yè)務(wù)描述與代碼實(shí)現(xiàn)更貼切、認(rèn)知負(fù)荷更低從而加強(qiáng)業(yè)務(wù)知識(shí)的地位,以及如何優(yōu)雅地解耦原本雜亂耦合的各層次代碼,讓他們變得整潔、可測(cè)試、可拓展。
3.1 設(shè)計(jì)模式
我們不妨先管中窺豹,看一個(gè)簡(jiǎn)化版的新舊版本開(kāi)播的時(shí)序圖對(duì)比:
圖片
圖片
很顯然,前者的描述充斥著純技術(shù)屬性的描述,大部分篇幅集中于諸如area_id、uid之類的屬性,難以直接和實(shí)際業(yè)務(wù)中的描述對(duì)應(yīng)上。
而后者的描述則是有業(yè)務(wù)上的主客體描述的,如"房間是否屬于該用戶"、"分區(qū)是否允許該房間開(kāi)播"。在代碼的編排描述上,很容易就可以看出,后者的可理解性要比前者高出一截,這便引出了下文要討論的話題:新舊版本開(kāi)播服務(wù)的設(shè)計(jì)模式。
3.1.1 舊版設(shè)計(jì)模式:事務(wù)腳本
事務(wù)腳本模式也叫做面條代碼或者膠水代碼;它有一些顯著的特點(diǎn):面向過(guò)程,易于編寫(xiě),難以應(yīng)對(duì)變更,復(fù)雜事務(wù)腳本可讀性低&可維護(hù)性低。顯然,舊版php開(kāi)播代碼在多年缺乏系統(tǒng)性維護(hù)的業(yè)務(wù)迭代后,已經(jīng)幾乎退化為這樣的模式。
根據(jù)Fowler在PoEAA中對(duì)事務(wù)腳本的描述,我們用上文的舊版開(kāi)播進(jìn)行分析。初次讀這段代碼的體驗(yàn),可能是如下的:
- 從表示層/服務(wù)層獲得輸入(開(kāi)播請(qǐng)求的一些參數(shù),比如room_id、uid)
- 中間有大量的過(guò)程是用來(lái)做單純的獲取某一條數(shù)據(jù)(area_info分區(qū)信息)
- 獲取對(duì)這些獲取的單一數(shù)據(jù)進(jìn)行某些字段的判斷,或者多個(gè)單條數(shù)據(jù)聯(lián)合判斷(如,檢查房間開(kāi)播狀態(tài)、檢查分區(qū)狀態(tài)是否為online)
- 之后調(diào)用其他系統(tǒng)或者存儲(chǔ)數(shù)據(jù)到數(shù)據(jù)庫(kù)(多次更新房間的多條信息,live_start_time/area_id)
- 過(guò)程中,不斷將單條操作后新增的數(shù)據(jù)合并到響應(yīng)值中
開(kāi)播這個(gè)動(dòng)作,在舊版代碼中乍一看,是一個(gè)過(guò)程驅(qū)動(dòng),由許多僅有技術(shù)含義的動(dòng)作完成的純技術(shù)操作,缺乏了對(duì)業(yè)務(wù)的基本感知和描述。這樣的模式,對(duì)業(yè)務(wù)中的場(chǎng)景業(yè)務(wù)歸納能力較弱,當(dāng)我們提到某個(gè)場(chǎng)景時(shí),往往需要把這些生硬的代碼在腦海中轉(zhuǎn)譯一次,才能對(duì)應(yīng)上業(yè)務(wù)方的實(shí)際描述。
當(dāng)我們擁有了更多動(dòng)作時(shí),就會(huì)有若干過(guò)程需要做相似的動(dòng)作,通常就要使多個(gè)過(guò)程中包含某些相同的代碼,這些類似的副本會(huì)讓?xiě)?yīng)用程序變成一張極度雜亂無(wú)章的網(wǎng)。
換一個(gè)角度,從OOP的角度上看該模式,實(shí)體的概念并不能完全表現(xiàn),甚至只是充當(dāng)了業(yè)務(wù)邏輯層和數(shù)據(jù)訪問(wèn)層之間的輔助角色,只空有屬性,沒(méi)有行為。這樣實(shí)體在 業(yè)務(wù)行為上難以和代碼實(shí)現(xiàn) 對(duì)應(yīng),更難以復(fù)用。
3.1.2 新版設(shè)計(jì)模式:領(lǐng)域模型
反之,對(duì)比起原來(lái)的事務(wù)腳本模式,我們的新服務(wù)中,包含有多個(gè)有血有肉的對(duì)象,比如房間和賬號(hào)。
對(duì)于應(yīng)用服務(wù)需要完成的開(kāi)播用例而言,相比起純過(guò)程的各個(gè)字段和子過(guò)程的串聯(lián),更關(guān)心每一個(gè)對(duì)象應(yīng)該做出什么行為。
圖片
圖片
3.2 戰(zhàn)術(shù)設(shè)計(jì)
3.2.1 戰(zhàn)術(shù)設(shè)計(jì)的思考:引入六邊形架構(gòu)
既然是遺留系統(tǒng)現(xiàn)代化演進(jìn),我們不妨先提一些工程質(zhì)量方面的提升預(yù)期:
業(yè)務(wù)領(lǐng)域的邊界更加清晰、更好的可擴(kuò)展性、對(duì)測(cè)試的友好支持、更容易實(shí)施DDD...
看到既定目標(biāo),再結(jié)合領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)指導(dǎo)的前情提要,相信對(duì)此熟悉的讀者已經(jīng)會(huì)心一笑了,解決方案呼之欲出:六邊形架構(gòu)。
3.2.2 領(lǐng)域模型與六邊形架構(gòu)
怎么寫(xiě)開(kāi)發(fā)架構(gòu)相對(duì)整潔、看起來(lái)就可測(cè)試的代碼?相信大家或多或少都了解過(guò)"六邊形架構(gòu)"、"整潔架構(gòu)"或者"洋蔥架構(gòu)"。我們?cè)賮?lái)稍微復(fù)習(xí)一下它的定義:
六邊形架構(gòu),也被稱為端口和適配器架構(gòu)(Ports and Adapters Architecture),是由Alistair Cockburn于2005年首次提出的。這個(gè)架構(gòu)模式的主要目標(biāo)是將應(yīng)用程序的核心業(yè)務(wù)邏輯與外部依賴分離開(kāi)來(lái),從而提高可測(cè)試性、可維護(hù)性和可擴(kuò)展性。
在六邊形架構(gòu)中,應(yīng)用程序被劃分為以下幾個(gè)關(guān)鍵部分:
- 應(yīng)用程序核心:這是應(yīng)用程序的主要業(yè)務(wù)邏輯,它包含了所有的用例和業(yè)務(wù)規(guī)則。核心不依賴于具體的外部組件或技術(shù),因此它是高度可測(cè)試的。
- 端口:端口是定義應(yīng)用程序與外部依賴之間的接口。它們定義了應(yīng)用程序需要的功能,但不實(shí)現(xiàn)具體的實(shí)現(xiàn)細(xì)節(jié)。
- 適配器:適配器是實(shí)際實(shí)現(xiàn)端口的組件,它們負(fù)責(zé)將外部依賴集成到應(yīng)用程序中。適配器將外部依賴的細(xì)節(jié)隱藏在內(nèi)部,以確保核心業(yè)務(wù)邏輯保持獨(dú)立性。
通過(guò)將應(yīng)用程序核心與外部依賴分離,六邊形架構(gòu)提供了以下優(yōu)勢(shì):
- 可測(cè)試性:由于核心業(yè)務(wù)邏輯與外部依賴分離,開(kāi)發(fā)人員可以輕松地編寫(xiě)單元測(cè)試,而無(wú)需依賴外部資源。
- 可維護(hù)性:應(yīng)用程序的核心業(yè)務(wù)邏輯保持簡(jiǎn)單和獨(dú)立,因此更容易理解和維護(hù)。
- 可擴(kuò)展性:通過(guò)添加新的端口和適配器,您可以輕松地?cái)U(kuò)展應(yīng)用程序,以滿足不斷變化的需求。
本次我們新搭建的開(kāi)播平臺(tái),遵循了端口和適配器的架構(gòu)風(fēng)格,將服務(wù)拆分為了以下的層次:
- Transporter Layer 外部請(qǐng)求適配器,適配外部用例
- Data Source Adapters 內(nèi)部資源適配器,適配內(nèi)部資源(Repository、Infrastructure)
- Application 應(yīng)用程序?qū)?,集成領(lǐng)域邏輯為用例,如"用戶使用直播姬開(kāi)播"
- Domain 領(lǐng)域?qū)?,業(yè)務(wù)邏輯核心,眾多重要邏輯在這里實(shí)現(xiàn),如"房間狀態(tài)流轉(zhuǎn)為開(kāi)播"
圖片
解決的問(wèn)題
- 在PHP的老設(shè)計(jì)中,開(kāi)播接口的ui/領(lǐng)域內(nèi)業(yè)務(wù)邏輯耦合較重,大量客戶端參數(shù)校驗(yàn)、特定客戶端返回特定響應(yīng)的邏輯耦合在多個(gè)地方,可能是controller,也可能是service,甚至可能是dao層。這部分與領(lǐng)域知識(shí)本身無(wú)關(guān),在新版本的開(kāi)播代碼中,需要與應(yīng)用程序?qū)雍皖I(lǐng)域?qū)痈綦x起來(lái),保護(hù)后者的邏輯不受污染。
- 歷史遺留代碼中,DAO耦合在代碼中,對(duì)業(yè)務(wù)邏輯本身的可拓展性和可測(cè)試性產(chǎn)生了阻礙;新版代碼中也需要將這部分解耦,以便未來(lái)技術(shù)演進(jìn)和單元測(cè)試的開(kāi)展。
3.2.3 模塊設(shè)計(jì)
按照上文的開(kāi)發(fā)架構(gòu)設(shè)計(jì),本次新開(kāi)播服務(wù)的代碼分包結(jié)構(gòu)代碼實(shí)現(xiàn)如下。
圖片
因?yàn)镚olang本身OOP的鴨子類型特性和諸多原因,我們的編碼風(fēng)格顯得沒(méi)有那么嚴(yán)格,選擇了相對(duì)松散的代碼分包結(jié)構(gòu)。大致區(qū)分為了領(lǐng)域?qū)?、防腐層、?yīng)用程序?qū)印}(cāng)儲(chǔ)/基礎(chǔ)設(shè)施層
Domain 領(lǐng)域?qū)?/h4>
作為領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)指導(dǎo)下的工程,我們的首要目標(biāo)就是保障領(lǐng)域邏輯的正確性。
最核心的領(lǐng)域?qū)?,svc/pkg/domain包含了領(lǐng)域服務(wù)和各個(gè)領(lǐng)域?qū)ο蟮膇nterface契約聲明和具體實(shí)現(xiàn)。開(kāi)播涉及到的領(lǐng)域?qū)ο蠖荚谶@里集中實(shí)現(xiàn)。
在開(kāi)播的戰(zhàn)略設(shè)計(jì)中,我們提到了幾個(gè)上下文,在這里會(huì)作為聚合或?qū)ο蟮姆绞竭M(jìn)行實(shí)現(xiàn),他們的關(guān)系如下:
圖片
Facade 防腐層
對(duì)應(yīng)設(shè)計(jì)中的Transporter Layer 外部請(qǐng)求適配器,適配外部用例:
用例上,開(kāi)播和用于調(diào)試開(kāi)播的請(qǐng)求,都需要在用例層面適配他們,所以自然需要適配器來(lái)適配他們的grpc請(qǐng)求,以及考慮到今后多種接口形式的接入( http or mq),如果之后grpc定義出現(xiàn)變更,或者新請(qǐng)求形式的接入,不會(huì)對(duì) Application 層的用例帶來(lái)滲透和影響。
設(shè)計(jì)原則
需要提供多組外部適配器,適配各種場(chǎng)景的開(kāi)播請(qǐng)求(理論上可能是grpc/http/mq ...,本文僅限于直播姬開(kāi)播的場(chǎng)景),并轉(zhuǎn)化為應(yīng)用程序?qū)涌山邮艿挠美?jí)別請(qǐng)求。并且作為防腐層,不應(yīng)該有過(guò)多業(yè)務(wù)邏輯,僅實(shí)現(xiàn)必要的特定端到端場(chǎng)景的UI邏輯。
- 處于防腐層的適配器需要有自己的合法校驗(yàn)邏輯(必要的靜態(tài)參數(shù)檢查)。
- 防腐層約定上層(controller的 grpc server)以及下層應(yīng)用的(application)的交互協(xié)議,開(kāi)播中對(duì)應(yīng)開(kāi)播的gRPC請(qǐng)求轉(zhuǎn)換為Application層可接受的請(qǐng)求。
- 歷史邏輯中只與UI相關(guān)的邏輯也需要在這一層收斂。(etc.開(kāi)播失敗情況下的端上提示、彈窗的相關(guān)返回值組裝)
對(duì)外契約:
- 適配網(wǎng)關(guān)層開(kāi)播接口的請(qǐng)求:需要有靜態(tài)參數(shù)檢查、將請(qǐng)求構(gòu)造為application層可接受的ACL請(qǐng)求。
- 開(kāi)播返回結(jié)構(gòu)體組裝:將application層開(kāi)播用例的結(jié)果,根據(jù)端到端的UI邏輯,組裝成業(yè)務(wù)預(yù)期中的返回值(防止客戶端UI相關(guān)的邏輯滲透到下面的層次)
Application 應(yīng)用程序?qū)?/h4>
應(yīng)用層作為場(chǎng)景用例的主體部分,充當(dāng)了實(shí)體、聚合、領(lǐng)域服務(wù)的膠水層,將房間、場(chǎng)次、賬號(hào)的行為集成到一起,最終形成"直播姬開(kāi)播"用例的業(yè)務(wù)邏輯。最終,每一個(gè)用例會(huì)對(duì)應(yīng)application的一個(gè)接口,如"直播姬開(kāi)播"、"直播姬關(guān)播"、"后臺(tái)開(kāi)播"、"后臺(tái)關(guān)播"等等,包裝成用例提供到外部。
Repository / Infrastructure 倉(cāng)儲(chǔ)/ 基礎(chǔ)設(shè)施
對(duì)應(yīng)Data Source Adapters 內(nèi)部資源適配器。同樣的,六邊形架構(gòu)中,對(duì)下游依賴的約束也是依靠 接口與適配器 這一風(fēng)格進(jìn)行解耦和契約。
設(shè)計(jì)原則
- 內(nèi)部資源適配器應(yīng)該依照上層契約實(shí)現(xiàn)
- 內(nèi)部資源適配器實(shí)現(xiàn)的變更不會(huì)影響到聲明的契約本身,其交付的能力應(yīng)當(dāng)是不變的
- 倉(cāng)儲(chǔ) Repository,按照Domain層協(xié)商的倉(cāng)儲(chǔ)能力進(jìn)行實(shí)現(xiàn),該層只對(duì)領(lǐng)域邏輯要求的倉(cāng)儲(chǔ)能力負(fù)責(zé),如"發(fā)布開(kāi)播領(lǐng)域事件"
- 基礎(chǔ)設(shè)施層 Infrastructure,領(lǐng)域無(wú)關(guān)的基礎(chǔ)設(shè)施,如分布式鎖、上報(bào)組件等
3.3 測(cè)試驅(qū)動(dòng)開(kāi)發(fā) TDD
當(dāng)露營(yíng)結(jié)束離開(kāi)的時(shí)候,要打掃營(yíng)地,讓它比你來(lái)的時(shí)候更干凈。—— 童子軍原則,《97 Things Every Programmer Should Know》
3.3.1 動(dòng)機(jī)
從項(xiàng)目角度出發(fā),可以提供持續(xù)的項(xiàng)目進(jìn)度反饋。開(kāi)播平臺(tái)重構(gòu)作為一個(gè)大型項(xiàng)目,需要從業(yè)務(wù)和項(xiàng)目的量化成一個(gè)個(gè)可操作的任務(wù)寫(xiě)到 to-do list,然后使用測(cè)試驅(qū)動(dòng)編碼,可以在每一個(gè)預(yù)期用例完成后進(jìn)行標(biāo)記,那么我們的工作目標(biāo)將變得非常清晰,因?yàn)楣て?、待辦事項(xiàng)、難點(diǎn)都非常明確,可以在持續(xù)細(xì)微的反饋中有意識(shí)地做一些適當(dāng)?shù)恼{(diào)整,比如添加新的任務(wù),刪除冗余的測(cè)試;還有一點(diǎn)更加讓人振奮,可以知道大概什么時(shí)候可以完工,對(duì)開(kāi)發(fā)進(jìn)度可以更精確的把握。
從工程角度出發(fā),可以確保代碼質(zhì)量,也保障重構(gòu)的安全性。一個(gè)軟件的自動(dòng)化測(cè)試,可以從內(nèi)部表達(dá)這個(gè)軟件的質(zhì)量,我們通常管它叫做內(nèi)建質(zhì)量(Build Quality In)。開(kāi)發(fā)人員如果忽視編寫(xiě)自動(dòng)化測(cè)試,就放棄了將質(zhì)量?jī)?nèi)建到軟件(也就是自己證明自己質(zhì)量)的機(jī)會(huì),把質(zhì)量的控制完全托付給了測(cè)試人員。這種靠人力去保證質(zhì)量的方式,永遠(yuǎn)也不可能代表"技術(shù)先進(jìn)性"。在用例級(jí)別保障了內(nèi)建質(zhì)量后,倘若將來(lái)有一天需要重構(gòu),由于有全面的測(cè)試套件作為保障,開(kāi)發(fā)人員可以放心地對(duì)代碼進(jìn)行優(yōu)化、改進(jìn)結(jié)構(gòu)或增加新功能,而不用擔(dān)心引入潛在的問(wèn)題。
3.3.2 實(shí)踐
一句話來(lái)概括就是先設(shè)計(jì)用例,再寫(xiě)代碼。
鑒于六邊形架構(gòu)符合 端口與適配器 風(fēng)格的契約,我們很容易知道:
- 一個(gè)端口(interface)提供的能力是什么
- 業(yè)務(wù)邏輯應(yīng)該使用什么端口(interface),他們應(yīng)該在業(yè)務(wù)中表現(xiàn)如何
那么以下的TDD工作流就應(yīng)該被遵守:
- 先明確interface的能力,定義所需的行為,并編寫(xiě)可讀性良好的注釋文檔來(lái)聲明它們的作用;
- 根據(jù)上一步的契約,編寫(xiě)interface的測(cè)試用例。
- 實(shí)現(xiàn)interface的業(yè)務(wù)邏輯,并實(shí)現(xiàn)接口以使其能夠通過(guò)。
- 業(yè)務(wù)邏輯測(cè)試,并使用上文編寫(xiě)的用例進(jìn)行測(cè)試,驗(yàn)證預(yù)期行為是否在待測(cè)的interface中產(chǎn)生。
- 根據(jù)結(jié)果調(diào)整代碼,直到可以通過(guò)測(cè)試用例。
由于六邊形架構(gòu)中,接口與其實(shí)現(xiàn)天然存在接縫(seam),對(duì)于某個(gè)業(yè)務(wù)邏輯中對(duì)Repository甚至領(lǐng)域?qū)ο蟮那闆r,我們也可以輕松通過(guò)mock的方式進(jìn)行依賴處理。
以房間聚合的開(kāi)播狀態(tài)流轉(zhuǎn)作為舉例:
圖片
第一步,我們根據(jù)"開(kāi)播狀態(tài)流轉(zhuǎn)"這個(gè)領(lǐng)域?qū)ο蟮膭?dòng)作,進(jìn)行需求分析,得出該動(dòng)作的目的就是"將房間狀態(tài)流轉(zhuǎn)為開(kāi)播中",一些關(guān)聯(lián)的知識(shí)就包括,"必須聲明開(kāi)播時(shí)間"、"狀態(tài)流轉(zhuǎn)為開(kāi)播的同時(shí)需要與一場(chǎng)直播綁定"、"開(kāi)播的房間必須已經(jīng)選擇了分區(qū)"。明確需求后,從業(yè)務(wù)邏輯中得到想要的用例可以得到如下的用例:
- 完全符合預(yù)期,開(kāi)播的動(dòng)作中包含所選的開(kāi)播時(shí)間、場(chǎng)次、分區(qū),所以房間狀態(tài)可以流轉(zhuǎn)為開(kāi)播
- 空操作,不合法輸入,失敗
- 沒(méi)有聲明開(kāi)播時(shí)間,不合法輸入,失敗
- 沒(méi)有選擇分區(qū),不合法輸入,失敗
- 沒(méi)有綁定一場(chǎng)直播,不合法輸入,失敗
- Repository倉(cāng)儲(chǔ)方法調(diào)用錯(cuò)誤,失敗
同時(shí)也可以注意到,在開(kāi)播狀態(tài)流轉(zhuǎn)中,我們只關(guān)注依賴的Repository的倉(cāng)儲(chǔ)方法是否失敗,而不關(guān)心它如何實(shí)現(xiàn)的、為何失敗的。因?yàn)檫@對(duì)于房間對(duì)象而言并不是職責(zé)范圍內(nèi)的知識(shí),而是倉(cāng)儲(chǔ)方法的職責(zé)范圍,所以在這個(gè)場(chǎng)景下,我們只關(guān)心倉(cāng)儲(chǔ)是否交付成功即可。
簡(jiǎn)單舉例,測(cè)試用例代碼如下:
圖片
第二步,根據(jù)這些已知用例實(shí)現(xiàn)"房間狀態(tài)流轉(zhuǎn)為開(kāi)播",也就是實(shí)現(xiàn) IRoomStatus 這個(gè)對(duì)象的 ChangeRoomStatusToStartLive 方法。
圖片
第三步,運(yùn)行第一步編寫(xiě)的測(cè)試用例,查看是否符合預(yù)期。對(duì)于領(lǐng)域?qū)ο缶唧w依賴的Repository,由于我們事先在六邊形架構(gòu)中聲明了依賴的interface契約,所以可以較為簡(jiǎn)單地使用mock處理這些依賴。
圖片
第四步,運(yùn)行完整的測(cè)試用例集合,如果不符合預(yù)期,則重回第二步,開(kāi)始新一輪的修改和測(cè)試流程。
至此,一套完整的 UTDD 流程就良好地運(yùn)作起來(lái)了,在實(shí)際的開(kāi)發(fā)過(guò)程中,我們的每一個(gè)領(lǐng)域?qū)ο蟆}(cāng)儲(chǔ)方法、基礎(chǔ)設(shè)施的實(shí)現(xiàn)流程都是按照該流程進(jìn)行的,在很大程度上保障了新開(kāi)播的內(nèi)建質(zhì)量。
對(duì)于更為大型的場(chǎng)景,比如application層對(duì)開(kāi)播接口的測(cè)試,本質(zhì)上在六邊形架構(gòu)中也可以將集成的多個(gè)領(lǐng)域?qū)ο笸ㄟ^(guò)端口-適配器的解耦,將涉及的領(lǐng)域?qū)ο笾苯舆M(jìn)行mock,從而以較低的心智成本編寫(xiě)出可讀性較高的集成測(cè)試,一個(gè)典型的集成測(cè)試集合如下:
圖片
在TDD思想的指導(dǎo)和開(kāi)發(fā)流程下,我們的新服務(wù)整體單元測(cè)試覆蓋率達(dá)到了70+%,部分關(guān)鍵領(lǐng)域邏輯的覆蓋率達(dá)到100%。
如此的覆蓋率,不論在業(yè)務(wù)理解層面還是內(nèi)建質(zhì)量方面都產(chǎn)生了莫大的幫助——不必?fù)?dān)心一些改動(dòng)導(dǎo)致的重要影響無(wú)法被開(kāi)發(fā)者捕捉到,這無(wú)疑在未來(lái)的業(yè)務(wù)迭代和進(jìn)一步重構(gòu)中都會(huì)起到關(guān)鍵作用。
4 安全的系統(tǒng)遷移
兵馬未動(dòng),糧草先行。
——《孫子兵法》
一艘巨輪建造完成后終究需要下水,而往往船下水的方案設(shè)計(jì)是先于船體本身的建造的。開(kāi)播能被稱為遺留系統(tǒng),那么它背后的歷史邏輯和技術(shù)債務(wù)一定不容小覷,我們對(duì)新開(kāi)播系統(tǒng)"完工下水"這件事,顯然就要謹(jǐn)慎對(duì)待了,從新開(kāi)播的實(shí)現(xiàn)本身、到中間的開(kāi)發(fā)執(zhí)行和驗(yàn)證,以及最后的部署灰度,都需要進(jìn)行細(xì)致的考慮,保證這艘新船能順利接觸水面。
前期對(duì)業(yè)務(wù)邏輯進(jìn)行最細(xì)致的歸納,這其中包括了代碼的逐行校對(duì)和每個(gè)邏輯分支的業(yè)務(wù)邏輯梳理,甚至也包括了PHP和Golang基礎(chǔ)組件的源碼對(duì)比。
中期在代碼編寫(xiě)的過(guò)程中逐步明確"檢查點(diǎn)"和事件溯源的全貌,設(shè)計(jì)并完善了驗(yàn)證方案:流量復(fù)制和事件溯源,并構(gòu)建完善的新舊開(kāi)播檢查點(diǎn)對(duì)比系統(tǒng),保證關(guān)鍵的邏輯節(jié)點(diǎn)上,新舊服務(wù)的表現(xiàn)完全一致。
后期在服務(wù)部署和灰度策略上,也做了最周密的準(zhǔn)備,包括網(wǎng)關(guān)級(jí)別萬(wàn)分位的灰度放量規(guī)則和業(yè)務(wù)級(jí)別的重要房間退避方案。
4.1 業(yè)務(wù)邏輯
業(yè)務(wù)邏輯通常是最沒(méi)有邏輯的東西。
—— Martin Fowler,《企業(yè)應(yīng)用架構(gòu)模式》
4.1.1 歷史邏輯
面對(duì)已存在多年的業(yè)務(wù)邏輯,不論它是否容易閱讀、我們是否熟悉跨語(yǔ)言的寫(xiě)法,都應(yīng)該心存敬畏,逐個(gè)分支、逐個(gè)業(yè)務(wù)場(chǎng)景進(jìn)行盤(pán)點(diǎn),最終形成對(duì)此業(yè)務(wù)場(chǎng)景的正確理解。
面對(duì)這種高準(zhǔn)確度要求的表達(dá)訴求,我們選擇了已有的接口自動(dòng)化測(cè)試用例結(jié)合手動(dòng)端到端驗(yàn)證 + 逐行閱讀對(duì)比代碼的方式進(jìn)行梳理驗(yàn)證,最后以時(shí)序圖的方式,將舊開(kāi)播服務(wù)的PHP實(shí)現(xiàn)邏輯呈現(xiàn)到施工方案中。
圖片
(涉及具體業(yè)務(wù)流程,僅展示縮略圖)
既然是"重構(gòu)",我們選擇盡量保持原有的邏輯流和數(shù)據(jù)流,先將主邏輯大部分遷移完成,再進(jìn)行下一步的改造。
所以在新版本的重構(gòu)中,涉及的業(yè)務(wù)邏輯流,實(shí)際上并沒(méi)有過(guò)大的改變,從而保障了邏輯分支在端到端表現(xiàn)上可以完全一致。
4.1.2 轉(zhuǎn)化漏斗圖
針對(duì)上述邏輯分支眾多的用例場(chǎng)景,我們也嘗試使用最直觀的圖形形式,展現(xiàn)給對(duì)開(kāi)播領(lǐng)域不甚熟悉的研發(fā)同學(xué),甚至是產(chǎn)運(yùn)同學(xué)進(jìn)行參考,最終選擇了漏斗圖的形式。
最頂層為開(kāi)播接口的入口,對(duì)應(yīng)直播姬點(diǎn)擊"開(kāi)始直播"按鈕后對(duì)服務(wù)端開(kāi)播接口的請(qǐng)求。而后的一系列漏斗層,則代表了服務(wù)端的行為,中途不斷有檢查項(xiàng)攔截不符合開(kāi)播條件的請(qǐng)求,直到底部的成功開(kāi)播。
圖片
4.2 流量復(fù)制 & 事件溯源
以上文歸納的"業(yè)務(wù)邏輯"為指導(dǎo),我們著手構(gòu)建了一套為開(kāi)播業(yè)務(wù)邏輯遷移量身打造的流量復(fù)制和數(shù)據(jù)驗(yàn)證方案。
作為核心場(chǎng)景,開(kāi)播日均承載的流量大,且邏輯流具有不確定性:不同的開(kāi)播賬號(hào)、開(kāi)播場(chǎng)景,甚至是網(wǎng)絡(luò)環(huán)境,都可能會(huì)導(dǎo)致會(huì)走入某一個(gè)上述復(fù)雜的開(kāi)播邏輯分支中,可能有業(yè)務(wù)邏輯拒絕開(kāi)播直接中斷開(kāi)播流程的情況,也有可能發(fā)生內(nèi)部錯(cuò)誤但繼續(xù)執(zhí)行開(kāi)播流程的情況。所以如何在眾多的業(yè)務(wù)分支中識(shí)別出新舊開(kāi)播服務(wù)的數(shù)據(jù)流和邏輯流完全一致,是本次工程中的難點(diǎn)之一。
對(duì)此,我們?cè)O(shè)計(jì)了一套"流量復(fù)制"和"事件溯源"的驗(yàn)證方案。
4.2.1 流量復(fù)制
圖片
在舊版和新版開(kāi)播正式進(jìn)行切換之前,必須保證新版舊版開(kāi)播邏輯和數(shù)據(jù)鏈路和業(yè)務(wù)邏輯一致,為此我們?cè)O(shè)計(jì)了"流量復(fù)制"和"事件溯源/對(duì)賬"的機(jī)制。
- 流量復(fù)制:網(wǎng)關(guān)層復(fù)制開(kāi)播接口請(qǐng)求,分發(fā)給舊服務(wù)和新服務(wù)
- 事件溯源/對(duì)賬:
- 開(kāi)播接口邏輯中的重要事件節(jié)點(diǎn)(包括了開(kāi)播接口最終返回到端上的響應(yīng)值)上報(bào)到數(shù)據(jù)平臺(tái)
- 對(duì)每一條開(kāi)播請(qǐng)求的事件進(jìn)行新舊服務(wù)對(duì)比
對(duì)于復(fù)制過(guò)來(lái)的一組流量,我們期望它是冪等的,不能對(duì)下游數(shù)據(jù)產(chǎn)生任何影響。
在上文開(kāi)發(fā)架構(gòu)中提到,Repository和Infrastructure在六邊形架構(gòu)中,可以通過(guò)不同的方式實(shí)現(xiàn)契約,那么對(duì)于"不真實(shí)執(zhí)行"這一實(shí)現(xiàn)方式,是天然可以實(shí)現(xiàn)支持的——新增一組"假寫(xiě)"的適配器即可。
為保證這些檢查點(diǎn)在新舊服務(wù)完全一致,在驗(yàn)證方案中設(shè)計(jì)了以下三個(gè)階段:
圖片
圖片
圖片
- 舊服務(wù)進(jìn)行開(kāi)播事件上報(bào)
- 主要邏輯仍然由舊服務(wù)處理,網(wǎng)關(guān)服務(wù)復(fù)制流量到新服務(wù)。新服務(wù)只執(zhí)行冪等邏輯,不進(jìn)行真實(shí)的寫(xiě)操作。新舊服務(wù)均上報(bào)關(guān)鍵事件檢查點(diǎn),統(tǒng)一在數(shù)據(jù)平臺(tái)進(jìn)行每一條請(qǐng)求的檢查。
- 重構(gòu)部分的關(guān)鍵事件檢查點(diǎn)驗(yàn)證完成后,舊服務(wù)不再上報(bào),而新服務(wù)切換為真實(shí)寫(xiě)入的模式,并且繼續(xù)保留關(guān)鍵事件上報(bào)能力。
4.2.2 事件溯源
借助戰(zhàn)略設(shè)計(jì)章節(jié)中的"事件風(fēng)暴"整理出的關(guān)鍵路徑和事件,稍加整理就可以得到一組關(guān)鍵事件鏈路,借助事件溯源(Event Sourcing)的思想,我們可以將開(kāi)播流程中的重要節(jié)點(diǎn)上報(bào)并持久化。
根據(jù)事件風(fēng)暴和業(yè)務(wù)邏輯的時(shí)序圖,我們?cè)O(shè)定了以下關(guān)鍵事件檢查點(diǎn):
圖片
根據(jù)這些檢查點(diǎn),我們?cè)谛屡f版本的開(kāi)播代碼中進(jìn)行改造,在對(duì)應(yīng)的點(diǎn)位埋樁進(jìn)行數(shù)據(jù)上報(bào)。由于他們可以被聚合在同一條trace下,所以針對(duì)每一條開(kāi)播接口的請(qǐng)求,都可以被完整地記錄在案。
從事件溯源中,我們也可以獲取到一個(gè)意料之中的收獲:開(kāi)播鏈路在服務(wù)端鏈路的業(yè)務(wù)可觀測(cè)性。
圖片
4.3 自動(dòng)化測(cè)試
4.3.1 UTDD 單元測(cè)試&集成測(cè)試
如 3.3(測(cè)試驅(qū)動(dòng)開(kāi)發(fā))部分所述,新開(kāi)播服務(wù)在開(kāi)發(fā)時(shí)就采用了 TDD 工作流,單測(cè)覆蓋率70%以上,關(guān)鍵邏輯的行覆蓋率達(dá)到100%。
單元測(cè)試覆蓋率檢查集成到CI中,保證后續(xù)業(yè)務(wù)迭代質(zhì)量。
圖片
4.3.2 ATDD 測(cè)試共建自動(dòng)化測(cè)試用例
在本次重構(gòu)中,我們與測(cè)試團(tuán)隊(duì)持續(xù)合作,共建了200+條開(kāi)播接口的自動(dòng)化集成測(cè)試用例,覆蓋了大部分的請(qǐng)求參數(shù)檢查、用戶身份和狀態(tài)、特殊開(kāi)播場(chǎng)景、安全管控策略、分區(qū)和場(chǎng)次狀態(tài)等正常和異常用例,并對(duì)對(duì)應(yīng)預(yù)期接口返回結(jié)果、數(shù)據(jù)和消息寫(xiě)入結(jié)果等檢查。同時(shí)在自動(dòng)化測(cè)試中引入diff能力,相同參數(shù)輸入下新舊服務(wù)接口響應(yīng)進(jìn)行對(duì)比,覆蓋80%以上開(kāi)播場(chǎng)景。
圖片
整個(gè)重構(gòu)的遷移過(guò)程中,我們通過(guò)接口自動(dòng)化測(cè)試,發(fā)現(xiàn)并修復(fù)問(wèn)題10+個(gè)。
4.4 部署計(jì)劃
整體上線(包括流量復(fù)制&實(shí)際灰度階段)分為了三個(gè)階段:
- 舊開(kāi)播服務(wù)事件上報(bào)
- 新舊開(kāi)播服務(wù),線上流量復(fù)制對(duì)比
- 新開(kāi)播服務(wù)正式灰度切流
整個(gè)部署發(fā)布不同階段,都嚴(yán)格制定SOP按照計(jì)劃執(zhí)行,避免遺漏或切換過(guò)程中對(duì)線上開(kāi)播服務(wù)的影響:
圖片
4.5 結(jié)果
在精細(xì)的驗(yàn)證計(jì)劃、部署計(jì)劃和嚴(yán)格的流程把控下,開(kāi)播在整個(gè)遷移過(guò)程中未出現(xiàn)任何事故。
其中一些驗(yàn)證操作的功效是很直觀的:
- ATDD 接口自動(dòng)化發(fā)現(xiàn)差異:10+個(gè)
- 流量復(fù)制&事件溯源發(fā)現(xiàn)差異:20+個(gè)
- 歷史邏輯&代碼對(duì)比發(fā)現(xiàn)差異:30+個(gè)
同時(shí)我們?cè)谝粋€(gè)月時(shí)間內(nèi),逐步進(jìn)行了精細(xì)到單個(gè)用戶粒度-萬(wàn)分位-千分位-十分位-全量的灰度,在途中也優(yōu)化了10+性能問(wèn)題。
圖片
最終順利全量上線。
5 生產(chǎn)配套
“君之所以明者,兼聽(tīng)也;其所以暗者,偏信也?!薄獫h·王符《潛夫論·明暗》
一個(gè)運(yùn)作良好的系統(tǒng)首先必須具備良好的可觀測(cè)性,倘若都無(wú)法觀測(cè)到各個(gè)零件運(yùn)作是否良好,又談何算得上一輛好車。
對(duì)于開(kāi)播這種不容有失的系統(tǒng),萬(wàn)萬(wàn)不可寫(xiě)完代碼就萬(wàn)事大吉。我們需要更加謹(jǐn)慎地將系統(tǒng)的運(yùn)作狀態(tài)觀測(cè)納入設(shè)計(jì)考慮,讓觀測(cè)變得更加直觀,使?jié)撛诘南到y(tǒng)性風(fēng)險(xiǎn)可以快速暴露,也便于在緊急情況下做出恰當(dāng)?shù)臎Q策。
5.1 系統(tǒng)監(jiān)控
對(duì)于開(kāi)播服務(wù)的整體鏈路,我們通過(guò)前文的事件溯源上報(bào)方案結(jié)合司內(nèi)的監(jiān)控解決方案,對(duì)開(kāi)播成功、開(kāi)播拒絕的情況進(jìn)行了上報(bào)統(tǒng)計(jì),對(duì)開(kāi)播整體大盤(pán)的開(kāi)播成功率、被拒絕開(kāi)播的原因和發(fā)生率形成直觀感受。
若開(kāi)播系統(tǒng)出現(xiàn)了某種業(yè)務(wù)異動(dòng),比如被拒絕開(kāi)播的突增,我們可以借助監(jiān)控大盤(pán)和告警體系在第一時(shí)間感知到。
5.2 系統(tǒng)排障
伴隨著"事件溯源"體系的建設(shè),自然可以衍生出眾多提升系統(tǒng)可觀測(cè)性的輔助工具。這些工具在未來(lái)的業(yè)務(wù)運(yùn)維和業(yè)務(wù)迭代過(guò)程中可以節(jié)省大量的人力。
如可以實(shí)時(shí)驗(yàn)證是否指定房間是否滿足開(kāi)播條件的"模擬開(kāi)播":
圖片
以及針對(duì)每一條歷史開(kāi)播請(qǐng)求可以追溯關(guān)鍵事件,排查開(kāi)播為何成功/失敗的"開(kāi)播事件問(wèn)診":
圖片
6 結(jié)果
回顧文章開(kāi)篇時(shí)提到的歷史債務(wù)上來(lái),我們從業(yè)務(wù)層面和技術(shù)層面來(lái)進(jìn)行一些簡(jiǎn)單的復(fù)盤(pán)。
6.1 業(yè)務(wù)收益
知識(shí)共享:在開(kāi)播平臺(tái)重構(gòu)的一系列工作中,首當(dāng)其沖的是對(duì)開(kāi)播歷史邏輯的完整梳理,這無(wú)疑提高了產(chǎn)研對(duì)開(kāi)播業(yè)務(wù)的理解程度,降低溝通成本;在過(guò)程中,我們也已與產(chǎn)品溝通了眾多不曾關(guān)注到的功能細(xì)節(jié),幫助產(chǎn)品更好地建設(shè)開(kāi)播工具生態(tài)。伴隨著產(chǎn)研對(duì)業(yè)務(wù)知識(shí)的理解成本降低,一些客訴問(wèn)題的排查也會(huì)變得容易起來(lái)——從前一些只有代碼編寫(xiě)者才能描述的邊緣情況,現(xiàn)在更容易被產(chǎn)品甚至熟悉的運(yùn)營(yíng)所得知,進(jìn)而減低對(duì)開(kāi)播功能的疑惑,最終使產(chǎn)研協(xié)作效率提升。
開(kāi)發(fā)提效:在PHP舊服務(wù)的開(kāi)發(fā)過(guò)程中,用例梳理、PHP代碼晦澀的Coding過(guò)程、復(fù)雜代碼的反復(fù)Review、PHP的遠(yuǎn)古工具鏈?zhǔn)褂枚紩?huì)占用大量的開(kāi)發(fā)時(shí)間;相較舊版,新版開(kāi)播接口不存在這些歷史包袱,極大提高了開(kāi)發(fā)效率。
業(yè)務(wù)SRE:"開(kāi)播事件溯源"提供的接口請(qǐng)求級(jí)別的問(wèn)診能力,不同于以往排查開(kāi)播問(wèn)題時(shí)需要手動(dòng)翻閱每一條關(guān)鍵日志,新版本的一鍵查詢溯源記錄能力可以大大降低研發(fā)的問(wèn)題排查成本。
6.2 系統(tǒng)性風(fēng)險(xiǎn)優(yōu)化
在過(guò)去,開(kāi)播系統(tǒng)運(yùn)行于"房間服務(wù)"的 PHP 服務(wù)之中,該服務(wù)除了承載開(kāi)播業(yè)務(wù),也承載了大量和直播有關(guān)的周邊業(yè)務(wù)接口;
從技術(shù)角度,跨語(yǔ)言的遷移解決了較多的風(fēng)險(xiǎn):
- 一個(gè)相當(dāng)?shù)湫偷陌咐涸鹊目蚣芑?Swoole 二次開(kāi)發(fā)(Worker 模式),在突發(fā)并發(fā)流量較大時(shí)會(huì)出現(xiàn)單實(shí)例 Worker 滿載的情況,造成請(qǐng)求超時(shí);且由于請(qǐng)求堆積、Worker 釋放和重建、內(nèi)存回收之間存在一定時(shí)間差,瞬時(shí) Swoole Worker 進(jìn)程超出內(nèi)存限制導(dǎo)致請(qǐng)求失敗時(shí)有發(fā)生。該問(wèn)題造成了原開(kāi)播系統(tǒng)的穩(wěn)定性不足,無(wú)法支撐直播業(yè)務(wù)快速發(fā)展的需要,構(gòu)成了系統(tǒng)性風(fēng)險(xiǎn)。而這種在PHP框架下難以根治的問(wèn)題,遷移到Golang后就自然不存在了。
- 重構(gòu)后,開(kāi)播業(yè)務(wù)屬于單獨(dú)的 Go 微服務(wù),性能和可用性上有了大幅提升,接口可用性 SLA 從 99.xx% 提升到 99.99xx%,接口 P99 響應(yīng)速度提高50+%。
- 從語(yǔ)言層面,由于 PHP 本身是弱類型和解釋型語(yǔ)言,在開(kāi)發(fā)和編譯過(guò)程中較難發(fā)現(xiàn)潛在問(wèn)題,導(dǎo)致研發(fā)自測(cè)和測(cè)試成本上升,在過(guò)去也曾因?yàn)檫@些特性的處理不慎導(dǎo)致開(kāi)播系統(tǒng)的線上問(wèn)題;Go服務(wù)中對(duì)單元測(cè)試、故障測(cè)試有較好的支持,可以及時(shí)發(fā)現(xiàn)問(wèn)題。
- PHP 的內(nèi)部工具鏈相對(duì)缺少維護(hù),與之對(duì)比的 Golang 是公司后端研發(fā)最主流的選型,公司級(jí)監(jiān)控、告警、觀測(cè)、服務(wù)治理、持續(xù)集成等系統(tǒng)都為 Go 提供了相對(duì)更好的支持,這也使得新的開(kāi)播系統(tǒng)也有更好的可維護(hù)性和事件響應(yīng)能力。
圖片
從業(yè)務(wù)角度看,也提高了業(yè)務(wù)的系統(tǒng)穩(wěn)定性:
- 重構(gòu)過(guò)程中,梳理了整個(gè)開(kāi)播鏈路中的服務(wù)、接口、配置、存儲(chǔ)依賴,并將數(shù)十個(gè)依賴項(xiàng)區(qū)分為強(qiáng)依賴和弱依賴。
- 對(duì)于強(qiáng)依賴場(chǎng)景,梳理了對(duì)應(yīng)的失敗表現(xiàn),并編寫(xiě)了應(yīng)急SOP;對(duì)于弱依賴調(diào)用失敗的場(chǎng)景,采用補(bǔ)償任務(wù)等手段處理,不阻塞用戶開(kāi)播,進(jìn)一步提升了開(kāi)播系統(tǒng)的可用性。
6.3 技術(shù)資產(chǎn)
一個(gè)好的技術(shù)項(xiàng)目,不僅需要達(dá)成業(yè)務(wù)和技術(shù)上的硬性目標(biāo),還需要有所積累和成長(zhǎng)。我們?cè)陂_(kāi)播重構(gòu)的旅途中,也摸索出了一套行之有效、可復(fù)用的觀測(cè)模式和遷移模式。
6.3.1 更細(xì)粒度的業(yè)務(wù)可觀測(cè)
上文中提到的業(yè)務(wù)鏈路可觀測(cè),沉淀后也成為了開(kāi)播問(wèn)診臺(tái)的通用事件溯源功能。
可查看某個(gè)房間過(guò)往不同開(kāi)播場(chǎng)次,過(guò)程 & 結(jié)果關(guān)鍵事件的數(shù)據(jù)信息,更快的定位到線上每一次具體開(kāi)播的情況。
6.3.2 可復(fù)用的遷移模式
經(jīng)過(guò)本次開(kāi)播接口遷移的歷練,開(kāi)播平臺(tái)獲得了可復(fù)用的PHP轉(zhuǎn)Go的工程經(jīng)驗(yàn),我們也可以嘗試用DDD的觀點(diǎn)來(lái)總結(jié):
圖片
一些沉淀的能力如下:
- 業(yè)務(wù)流
- 業(yè)務(wù)邏輯梳理:邏輯分支級(jí)別梳理,落實(shí)到標(biāo)準(zhǔn)設(shè)計(jì)圖上(時(shí)序圖、數(shù)據(jù)流圖),分支級(jí)別的測(cè)試用例覆蓋(判定表、單測(cè)、自動(dòng)化測(cè)試)
- 事件溯源:按照事件風(fēng)暴、測(cè)試用例標(biāo)定的關(guān)鍵事件,進(jìn)行開(kāi)播流程中的事件上報(bào),保證請(qǐng)求可完全回溯。
- 工程保障
- 流量復(fù)制/切換:新舊接口的流量復(fù)制和流量控制。
- SOP:嚴(yán)格的部署計(jì)劃以及SOP,減少人為因素的不穩(wěn)定性。
那么套用回“開(kāi)播平臺(tái)遷移”這個(gè)問(wèn)題域匯總,我們可以得到以下的解法:
- 統(tǒng)一領(lǐng)域知識(shí):對(duì)于產(chǎn)研測(cè)需要達(dá)成的共識(shí),一套可讀性高的業(yè)務(wù)邏輯梳理可以滿足訴求。
- 白盒驗(yàn)證:業(yè)務(wù)邏輯梳理提供單元測(cè)試、自動(dòng)化測(cè)試用例;事件溯源和流量復(fù)制共同提供新舊服務(wù)的關(guān)鍵事件上報(bào)、數(shù)據(jù)對(duì)比。
- 服務(wù)部署:SOP提供嚴(yán)格的準(zhǔn)出和操作步驟;流量復(fù)制/切換提供新舊服務(wù)的切換能力。
- 戰(zhàn)術(shù)落地:借助于上述的所有能力,逐步完善開(kāi)播系統(tǒng)。
一個(gè)完整的迭代可能是這樣的:確保項(xiàng)目組內(nèi)產(chǎn)研認(rèn)知一致后,按照TDD方法編寫(xiě)出初版代碼;通過(guò)眾多測(cè)試用例后,進(jìn)行流量復(fù)制和事件溯源,通過(guò)關(guān)鍵事件對(duì)比保障關(guān)鍵檢查點(diǎn)和數(shù)據(jù)鏈路完全一致,最終按照SOP進(jìn)行上線。如果中途發(fā)現(xiàn)了修改點(diǎn),需要回退到初版代碼編寫(xiě),乃至同一領(lǐng)域知識(shí)的步驟進(jìn)行項(xiàng)目組認(rèn)知的對(duì)齊。
通過(guò)業(yè)務(wù)流的完整評(píng)估,再有嚴(yán)謹(jǐn)?shù)墓こ舔?yàn)證計(jì)劃保障,在事實(shí)上極大降低了出現(xiàn)嚴(yán)重遷移事故的概率(開(kāi)播遷移過(guò)程中未出現(xiàn)PX以上事故)。
7 后日談:可演進(jìn)的“遺留”系統(tǒng)
重構(gòu)和微服務(wù)的締造者,軟件開(kāi)發(fā)領(lǐng)域的泰斗,Martin Fowler 曾經(jīng)說(shuō)過(guò)這樣一句話:
Let's face it, all we are doing is writing tomorrow's legacy software today.
是的,可以毫不夸張地說(shuō),你現(xiàn)在所寫(xiě)的每一行代碼,都是未來(lái)的遺留系統(tǒng)。這聽(tīng)上去有點(diǎn)讓人沮喪,但卻是血淋淋的事實(shí),一個(gè)軟件系統(tǒng)的生命周期終歸會(huì)符合業(yè)務(wù)演進(jìn)的客觀規(guī)律。
不過(guò)大可不必氣餒,回到我們?cè)谝灾姓劦降倪z留系統(tǒng)定義,有些系統(tǒng)時(shí)間雖長(zhǎng),但如果一直堅(jiān)持現(xiàn)代化的開(kāi)發(fā)方式,在代碼質(zhì)量、架構(gòu)合理性、測(cè)試策略、DevOps 等方面都保持先進(jìn)性,就算將來(lái)需要進(jìn)行架構(gòu)的進(jìn)一步演進(jìn),這樣"整潔"的老系統(tǒng)也會(huì)幫助我們規(guī)避眾多的問(wèn)題,甚至可以讓演進(jìn)周期縮短、演進(jìn)風(fēng)險(xiǎn)降低。
相信我們今天在開(kāi)播平臺(tái)遷移中花費(fèi)的心血和留下的基石,終會(huì)為"歷久彌新"的系統(tǒng)打下基礎(chǔ)。
參考:
[1] Vernon, V. (2013) Implementing domain-driven design.
[2] Martraire, C. (2019) Living documentation: Continuous knowledge sharing by design. Boston: Addison-Wesley.
[3] Just enough software architecture: A risk-driven approach. Boulder: Marshall & Brainerd, 2010.
[4] Fowler, M. (2019) Refactoring: Improving the design of existing code. Boston: Addison-Wesley.
[5] Qilin, Y. (2021) 遺留系統(tǒng)
網(wǎng)頁(yè)題目:B站大型開(kāi)播平臺(tái)重構(gòu)
標(biāo)題URL:http://fisionsoft.com.cn/article/dhjisih.html


咨詢
建站咨詢
