精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

B站大型開播平臺重構(gòu)

開發(fā) 前端
大可不必氣餒,回到我們在引言中談到的遺留系統(tǒng)定義,有些系統(tǒng)時間雖長,但如果一直堅持現(xiàn)代化的開發(fā)方式,在代碼質(zhì)量、架構(gòu)合理性、測試策略、DevOps 等方面都保持先進(jìn)性,就算將來需要進(jìn)行架構(gòu)的進(jìn)一步演進(jìn),這樣"整潔"的老系統(tǒng)也會幫助我們規(guī)避眾多的問題,甚至可以讓演進(jìn)周期縮短、演進(jìn)風(fēng)險降低。

1.背景

"凡事預(yù)則立,不預(yù)則廢。"

——《禮記·中庸》

在文章的開頭,我們可以先來了解一下直播業(yè)務(wù)的大致業(yè)務(wù)架構(gòu)。將直播業(yè)務(wù)簡單分為兩大類場景"看播"、"開播",前者主要面向C端觀看用戶,后者主要面向B端開播主播。主播通過"開播工具"的開播產(chǎn)品功能,經(jīng)由"開播平臺"完成一系列開播動作,最后將媒體信息采集推送到多媒體服務(wù)器,C端觀看用戶就可以從CDN看到直播的視頻流內(nèi)容。

從數(shù)據(jù)流向來講,"開播"場景是產(chǎn)生數(shù)據(jù)和觸發(fā)關(guān)鍵事件的源頭。這些數(shù)據(jù)或事件會涉及多個領(lǐng)域,如安全合規(guī)信息、房間信息、主播信息、開播場次信息、安全審計信息、多媒體信息等。

打個不太準(zhǔn)確的比喻。開播系統(tǒng)對于直播平臺的重要性,等同于訂單系統(tǒng)對于交易平臺的重要性。開播工具作為播端功能入口,直接面向官方開播工具(直播姬、粉版大加號、三方工具如OBS開播)的用戶以及內(nèi)部平臺方的用戶(其他業(yè)務(wù)線、產(chǎn)品&運(yùn)營),對開播體驗負(fù)責(zé)。開播平臺在其中的職責(zé),是向開播工具和其他平臺方提供開播相關(guān)的平臺化業(yè)務(wù)能力,如開關(guān)播、開通直播間、切換分區(qū)等。

同時,開播平臺與同級的業(yè)務(wù)平臺一起協(xié)作,才能支撐起完整的開播工具產(chǎn)品能力,如語聊房業(yè)務(wù)需要開播工具管理平臺(開播工具類支持)、主播互動平臺(主播互動能力支持)、流媒體服務(wù)端共同參與才能完成,從不同的維度幫助開播工具生態(tài)完善化。

圖片圖片

一些涉及到的業(yè)務(wù)/技術(shù)名詞,在此我們也做出列舉并做出簡單介紹:

名詞

名詞簡述

領(lǐng)域驅(qū)動設(shè)計(DDD)

DDD 是 Domain-Driven Design 的縮寫,是 Eric Evans 于 2004 年提出的一種軟件設(shè)計方法和理念。

其主要的思想是,利用確定的業(yè)務(wù)模型來指導(dǎo)業(yè)務(wù)與應(yīng)用的設(shè)計和實現(xiàn)。主張開發(fā)人員與業(yè)務(wù)人員持續(xù)地溝通和模型的持續(xù)迭代式演化,以保證業(yè)務(wù)模型與代碼實現(xiàn)的一致性,從而實現(xiàn)有效管理業(yè)務(wù)復(fù)雜度,優(yōu)化軟件設(shè)計的目的。

領(lǐng)域知識

(領(lǐng)域驅(qū)動設(shè)計概念)指能準(zhǔn)確傳達(dá)業(yè)務(wù)規(guī)則的描述,也是領(lǐng)域中業(yè)務(wù)知識的集中體現(xiàn)。

理想狀態(tài)下,領(lǐng)域?qū)<液途幋a人員對業(yè)務(wù)的認(rèn)知應(yīng)該完全一致,就算不同的人寫代碼也應(yīng)該偏差不大。

領(lǐng)域事件(Domain Event)

領(lǐng)域事件,是在業(yè)務(wù)上真實發(fā)生的客觀事實,這些事實對系統(tǒng)會產(chǎn)生關(guān)鍵影響,是觀察業(yè)務(wù)系統(tǒng)變化的關(guān)鍵點。對于開播而言,"房間已經(jīng)被主播主動流轉(zhuǎn)為開播了"就是一個領(lǐng)域事件。

視頻云(直播)

一般指廣義的直播流媒體業(yè)務(wù),提供主播推流、觀眾拉流的基礎(chǔ)能力。

看播

一般指廣義的直播觀看業(yè)務(wù)域,涉及進(jìn)房、彈幕互動、禮物打賞等業(yè)務(wù)場景,一般面向C端(觀眾)

直播姬

一般指移動端APP"嗶哩嗶哩直播姬",以及Windows系統(tǒng)下的"嗶哩嗶哩直播姬"應(yīng)用。(注:嗶哩嗶哩粉版APP和Web頁面也提供了開播能力)

測試驅(qū)動開發(fā)(TDD)

測試驅(qū)動開發(fā)是一種軟件開發(fā)過程中的應(yīng)用方法,由極限編程倡導(dǎo),以其倡導(dǎo)先寫測試程序,然后編碼實現(xiàn)其功能得名。本文中主要涉及ATDD集成測試驅(qū)動和UTDD單元測試驅(qū)動。

戰(zhàn)略設(shè)計

戰(zhàn)略設(shè)計也稱為戰(zhàn)略建模,是指對業(yè)務(wù)進(jìn)行高層次的抽象和歸類。主要手段包括理清上下文和進(jìn)行子域的劃分。

戰(zhàn)術(shù)設(shè)計

戰(zhàn)術(shù)設(shè)計也稱為戰(zhàn)術(shù)建模,是指對特定上下文下的模型進(jìn)行詳細(xì)設(shè)計。我們對開播新的微服務(wù)中各個模塊職責(zé)的編排,就是戰(zhàn)術(shù)設(shè)計的一部分。

開播平臺/開播服務(wù)平臺

一般指狹義的后端業(yè)務(wù),即提供開播房間狀態(tài)流轉(zhuǎn)、直接對客戶端提供推流信息的服務(wù)端業(yè)務(wù)。提供如開關(guān)播接口、推流地址獲取的通用業(yè)務(wù)接口。

開播工具

一般指廣義的可進(jìn)行開播的業(yè)務(wù)場景,如直播姬、Web主播中心、粉APP開播等,相較于開播更偏向于端到端場景

開播

一般指廣義的開播業(yè)務(wù)域,涉及開播、關(guān)播等業(yè)務(wù)場景,一般面向B端(主播)

事件風(fēng)暴(Event Storming)

事件風(fēng)暴是一種捕獲行為需求的方法,類似傳統(tǒng)軟件的開發(fā)用例分析。所有人員(領(lǐng)域?qū)<液图夹g(shù)專家) 對業(yè)務(wù)行為進(jìn)行一次發(fā)散,并最終收斂達(dá)到業(yè)務(wù)的統(tǒng)一。

事件溯源(Event Sourcing)

事件溯源是一種用事件日志追溯狀態(tài)的方法,因此事件溯源的關(guān)鍵在于事件日志。對于開播而言只是借用了"溯源"這種思想,用于保證新舊開播鏈路的關(guān)鍵狀態(tài)完全一致。

SOP

Standard Operating Procedure,標(biāo)準(zhǔn)作業(yè)程序。本次重構(gòu)過程,發(fā)布、應(yīng)急處理、故障處理都使用此種方式進(jìn)行推進(jìn)。

room / room-service

PHP歷史服務(wù),本次被遷移的主角。眾多歷史業(yè)務(wù)在該服務(wù)中。

live-streaming / streaming

新開播微服務(wù),今后承載開播領(lǐng)域主要業(yè)務(wù)的落地實體。

app-blink

開播工具網(wǎng)關(guān)層微服務(wù),直接承載客戶端的請求。

1.1 現(xiàn)狀和挑戰(zhàn)

直播開播系統(tǒng),伴隨著B站直播的成長貫穿始終。

發(fā)展初期所有的直播業(yè)務(wù)基本都在一套php代碼里完成,包括開播部分。之后的直播高速發(fā)展中,很多模塊已經(jīng)順利完成遷移。

開播部分也嘗試過遷移,但是未能成功完成。還不太幸運(yùn)的出了比較嚴(yán)重的線上故障。(這給后面的再次重構(gòu)積累了寶貴的經(jīng)驗。)

1.1.1 債務(wù)清單

  • 業(yè)務(wù)積累厚:最初的代碼大致是從2017年開始的,要問里面的門道究竟有多少,可能另起一篇文章也難以詳盡。
  • 代碼可讀性差:php是弱類型+動態(tài)類型特點,代碼可讀性方面有非常大的挑戰(zhàn)。同時因為涉及到跨語言遷移,需要有機(jī)制能檢查兩邊邏輯和數(shù)據(jù)的一致性。
  • 開發(fā)模式陳舊:php代碼在整個開發(fā)架構(gòu)上,也是偏"事務(wù)腳本模式"。多個領(lǐng)域混雜在一起,互相耦合調(diào)用,解耦異常困難。
  • 質(zhì)量配套欠缺:單元測試和自動化測試方面也比較缺乏。要想順利完成重構(gòu)遷移,這塊是重要的前置工作。
  • 技術(shù)棧滯后:php技術(shù)棧,已經(jīng)不符合公司的整個技術(shù)棧主路線。各種lib、中間件支持方面欠缺,急需技術(shù)棧升級。

1.1.2 遺留系統(tǒng)特征

業(yè)界對遺留系統(tǒng)的普遍定義中有4個關(guān)鍵字:舊、過時、重要、仍在使用。

圖片圖片

事實并非完全如此:有些系統(tǒng)時間雖長,但如果一直堅持現(xiàn)代化的開發(fā)方式,在代碼質(zhì)量、架構(gòu)合理性、測試策略、DevOps 等方面都保持先進(jìn)性,這樣的系統(tǒng)就像陳年的老酒一樣,歷久彌香。而有些系統(tǒng)雖然剛剛開發(fā)完成,但如果在上述幾個方面都做得不好,我們也可以把它叫做遺留系統(tǒng)。遺留系統(tǒng)在維護(hù)成本、合規(guī)性、安全性、集成性等方面都會給企業(yè)造成巨大的負(fù)擔(dān),但同時也蘊(yùn)含著豐富的數(shù)據(jù)和業(yè)務(wù)資產(chǎn)。我們應(yīng)該對遺留系統(tǒng)進(jìn)行現(xiàn)代化,讓它重新煥發(fā)青春。

顯然在知曉了舊開播系統(tǒng)有諸多歷史債務(wù)后,我們可以認(rèn)為它確實是一個搖搖欲墜的遺留系統(tǒng)。而我們本次的目標(biāo),就是將開播平臺這個重要的遺留系統(tǒng)進(jìn)行重構(gòu),讓它"煥發(fā)新生",并讓他在可預(yù)見的未來中都維持現(xiàn)代化系統(tǒng)的標(biāo)準(zhǔn)。

1.2 安全生產(chǎn)

在開播系統(tǒng)的維護(hù)、迭代、演進(jìn)中,我們也致力于系統(tǒng)的"安全生產(chǎn)"問題:

  • 如何降低研發(fā)的業(yè)務(wù)認(rèn)知成本、溝通成本,降低復(fù)雜度,從而提高"卡車系數(shù)",保證團(tuán)隊內(nèi)部能保證形成快速backup?
  • 如何通過技術(shù)演進(jìn),增加開播系統(tǒng)的可拓展性/魯棒性/可測試性?
  • 遷移新系統(tǒng)時,新老系統(tǒng)如何優(yōu)雅安全切換、過程中的新舊系統(tǒng)數(shù)據(jù)是否可以進(jìn)行白盒對比?

2.開播系統(tǒng)架構(gòu)演進(jìn)

每個士兵在上戰(zhàn)場前必須清楚的明白,他這場小小的戰(zhàn)斗在大局中起的作用。——伯納德 · L · 蒙哥馬利(英國)

2.1 審視:問題出在哪里?

在著手進(jìn)行改造升級之前,不妨先從整體業(yè)務(wù)的迭代流程和已有架構(gòu)中找到問題,以確定真正值得樹立的目標(biāo),避免陷入"只見樹木不見森林"的狹小視野中。

我們不難發(fā)現(xiàn),這個日積月累的遺留系統(tǒng)當(dāng)中,它的業(yè)務(wù)研發(fā)流程種種令人難以忽視的問題:業(yè)務(wù)知識、業(yè)務(wù)架構(gòu)的認(rèn)識遺失、產(chǎn)研語言的不統(tǒng)一等等。

2.1.1 業(yè)務(wù)知識與業(yè)務(wù)架構(gòu)的生命周期

開播域作為播端的核心業(yè)務(wù)域,由于其悠久的歷史和維護(hù)團(tuán)隊同學(xué)的變更,在幾經(jīng)周折后,領(lǐng)域知識已經(jīng)處于混沌狀態(tài)。這種情況下,顯然比起遺留代碼和不合理的實現(xiàn)邏輯而言,更大的bug可能最終會發(fā)生在人身上,也就是我們對業(yè)務(wù)知識本身的認(rèn)識:對業(yè)務(wù)知識缺乏了解,往往是拖慢業(yè)務(wù)迭代甚至是釀成線上事故的罪魁禍?zhǔn)住?/p>

對于業(yè)務(wù)架構(gòu)的認(rèn)知遺失,則會導(dǎo)致業(yè)務(wù)域內(nèi)職責(zé)的混亂:“這個新增業(yè)務(wù)是否應(yīng)該由我們負(fù)責(zé)?”。落實到開發(fā)者身上就變成了應(yīng)用架構(gòu)的混亂:“這個業(yè)務(wù)我們到底應(yīng)該寫在哪個微服務(wù)里?”

最明顯最集中的問題會爆發(fā)在端到端用例中:戰(zhàn)略設(shè)計上一個實體上業(yè)務(wù)行為的不清晰往往代表著一個甚至多個端到端用例的認(rèn)知缺失,映射到戰(zhàn)術(shù)實現(xiàn)上就會演變成災(zāi)難性的"需求引入變更時,未考慮到某個用戶用例",最終在上線前的驗收環(huán)節(jié)甚至是上線后,發(fā)現(xiàn)這個需求的引入導(dǎo)致了bug的產(chǎn)生。

我們當(dāng)然可以把這種事故歸結(jié)為“歷史遺留問題”,但是對于功能的使用者而言,這種糟糕體驗會直接讓平臺被貼上“不專業(yè)”的負(fù)面標(biāo)簽。對平臺本身而言,這種災(zāi)難性的錯誤堆砌也只會讓系統(tǒng)不斷熵增,復(fù)雜程度愈發(fā)不可收拾,最終花費(fèi)在處理問題、歷史代碼考古上的人力一增再增缺無濟(jì)于事。

這部分無疑是開播重構(gòu)項目中,最迫切需要解決的問題。

2.1.2 描述語言不統(tǒng)一

在業(yè)務(wù)人員和產(chǎn)品的角度來看,"開播"這個用例往往和各端開發(fā)人員所說的"開播"又有著某種微妙的差別。業(yè)務(wù)視角下的開播,往往是用戶一次完整的開播體驗,比如,打開移動直播姬,調(diào)整好各種用戶設(shè)置,點擊開播,最終看到自己的畫面被正確投放到b站直播間,并且可以完成后續(xù)和觀眾的互動。

而在技術(shù)視角下的開播,"開播"是各執(zhí)行方的橫切面組成的:客戶端完成最直接的ui/ux互動、直播服務(wù)端進(jìn)行用戶請求校驗、視頻流和直播業(yè)務(wù)數(shù)據(jù)的協(xié)調(diào)、視頻云負(fù)責(zé)接收用戶的上行視頻流;每一方對"開播"的這個詞解釋就產(chǎn)生了差異:客戶端進(jìn)入到直播界面并點擊開播叫開播,服務(wù)端的開播接口被調(diào)用了也被視為開播,視頻流被推送到視頻云上行服務(wù)器的時候也可能被視為開播。

泛泛而談的話,各方的解釋都沒有太大問題,但是這樣的解釋無法確切指定它在業(yè)務(wù)里處于哪一部分,會造成什么結(jié)果。最終呈現(xiàn)在一位新進(jìn)入技術(shù)團(tuán)隊的同學(xué)的眼中可能是這樣的場景:

舉例

客服:主播反饋線上無法開播。【問題平臺】PC直播姬; 【一級分類】開播; 【二級分類】無法開播; 【問題描述】主播反饋進(jìn)入移動直播姬開播界面后,點擊開播后,不能正常推流;

開發(fā)1:是不是開播了多次?

客服:不是,主播開播了一次

開發(fā)1:那可以讓用戶重試

開發(fā)2:是不是視頻云推流服務(wù)出了問題?@視頻云

客服:用戶已經(jīng)重試了,還是不能正常開播(其實是在另一臺設(shè)備上已經(jīng)推流了,還在嘗試使用其他設(shè)備推流)

開發(fā)3:視頻云看到用戶推流是正常的 (推流監(jiān)控圖)

開發(fā)1:哦,原來是重復(fù)開播了

假設(shè)我是一位團(tuán)隊新成員,在看到最終輸出的"重復(fù)開播"結(jié)論之前,得到的都是點狀的信息,沒有完整的用例以供參考,難以理解線上問題的癥結(jié)在何處。如果這個時候甚至沒有文檔來描述開播領(lǐng)域相關(guān)業(yè)務(wù),或者是開播流程的場景快照,那更是一場新人的災(zāi)難——可能需要專門請教團(tuán)隊中熟悉開播領(lǐng)域的資深開發(fā)為他進(jìn)行講解才能瞥見開播業(yè)務(wù)的一隅,且授課效果還要取決于講述人的結(jié)構(gòu)化敘述能力,這是我們從效率考量上不愿意見到的。

可以舉一個貼近實際開發(fā)人員的例子,"請教了3位同事才知道了開播記錄是怎么產(chǎn)生的"、"請教了3位同事才本地構(gòu)建成功",諸如此類的尷尬在日常工作中屢見不鮮的,實際上這類問題只會對程序員了解業(yè)務(wù)和編碼的積極性,以及商業(yè)化產(chǎn)品開發(fā)落地的效率起反作用。

2.1.3 對程序員的"人文關(guān)懷"

一個貼近實際開發(fā)人員的例子,"請教了3位同事才知道了開播記錄是怎么產(chǎn)生的"、"請教了3位同事才本地構(gòu)建歷史服務(wù)成功",諸如此類的尷尬在日常工作中屢見不鮮的,實際上這類問題只會對程序員了解業(yè)務(wù)和編碼的積極性、產(chǎn)品開發(fā)落地的效率起反作用。

要解決這種效率抑或積極性問題,還是需要解決根源上的"知識共享"問題。

 2.2 引入領(lǐng)域驅(qū)動設(shè)計

在前文敘述問題的時候,熟悉的讀者可能就已經(jīng)想到了某個熱度經(jīng)久不衰的架構(gòu)思想:領(lǐng)域驅(qū)動設(shè)計。是的,我們在開播平臺的重構(gòu)中決定使用這種方式來解決現(xiàn)有的諸多痛點。依靠領(lǐng)域驅(qū)動設(shè)計的設(shè)計思想,通過事件風(fēng)暴建立領(lǐng)域模型,合理劃分領(lǐng)域邏輯和物理邊界,建立與現(xiàn)實世界相映射的領(lǐng)域?qū)ο蠛头?wù)架構(gòu)圖,定義符合DDD分層架構(gòu)思想的代碼結(jié)構(gòu)模型,保證業(yè)務(wù)模型與代碼模型的一致性。

相對的,對于最終的效果,也是可以預(yù)期到的:

  • 統(tǒng)一業(yè)務(wù)模型和代碼模型,領(lǐng)域知識全體共享,提升協(xié)助效率;
  • 通過邊界劃分將復(fù)雜業(yè)務(wù)領(lǐng)域簡單化,設(shè)計出清晰的領(lǐng)域和應(yīng)用邊界,實現(xiàn)業(yè)務(wù)和技術(shù)統(tǒng)一的架構(gòu)演進(jìn),提高人效,拒絕一加功能排期一個月;
  • 通過職責(zé)劃分合理的職責(zé)邊界,降低架構(gòu)腐敗速度;

2.3 領(lǐng)域驅(qū)動視角看開播

回到"開播"這個待解決的問題域本身,對開播業(yè)務(wù)中最核心的"開播"用例,核心的業(yè)務(wù)問題包括以下幾點需要明確:

  • 如何確定房間是否可以開始直播。
  • 如何讓房間開始直播。
  • 如何通知外界房間已經(jīng)開始直播。

也有一些非功能性的考慮:

  • 如何使技術(shù)實現(xiàn)貼近現(xiàn)實中的業(yè)務(wù)原貌,從而降低認(rèn)知成本
  • 如何提高我們對業(yè)務(wù)和產(chǎn)品的認(rèn)知程度和積極性
  • 如何提高開播功能的魯棒性和性能

首先,我們可以采用事件驅(qū)動開發(fā)方法,結(jié)合領(lǐng)域驅(qū)動設(shè)計中的事件風(fēng)暴方法論,來梳理開播用例中的關(guān)鍵事件和參與者:

事件風(fēng)暴的核心流程就是由用戶執(zhí)行了命令,從而產(chǎn)生了事件。基于這個事件的結(jié)果,與之前相同或是其他的用戶會執(zhí)行另一個命令,產(chǎn)生新類型的事件,以此類推。而順序是按照業(yè)務(wù)邏輯而定的。所以我們在整理開播涉及的時間風(fēng)暴時,工作流如下:

  1. 確定用例的發(fā)起者,即主語。在開播場景中,可以是主播或者運(yùn)營。
  2. 確定主語的動作,比如"開啟直播"。
  3. 確定動作的流程中涉及的命令以及執(zhí)行命令產(chǎn)生的事件、后果,例如"新場次已被主播創(chuàng)建"。
  4. 補(bǔ)充流程中涉及的業(yè)務(wù)知識,例如"房間"、各種各樣的"檢查規(guī)則"以及外部系統(tǒng)。
  5. 當(dāng)一個完整的業(yè)務(wù)流程通過上述方式寫完之后,對于每個用戶,命令,事件進(jìn)行組合,就能獲得聚合,用事件風(fēng)暴的描述開播場次創(chuàng)建就是"主播在場次聚合上進(jìn)行了創(chuàng)建場次操作,導(dǎo)致了新場次創(chuàng)建事件",此事件發(fā)生后,用戶會在房間聚合上執(zhí)行房間開播狀態(tài)流轉(zhuǎn)操作的新命令。

圖片圖片

事件風(fēng)暴中"定義領(lǐng)域模型"是最重要的一步,這一步需要了解實際業(yè)務(wù)形態(tài)后團(tuán)隊內(nèi)大量討論,從而達(dá)成共識。在這個階段中我們提煉了諸多的業(yè)務(wù)表現(xiàn)并且屏蔽技術(shù)實現(xiàn)細(xì)節(jié),提取出了關(guān)鍵的實體、值對象、聚合根。緊接著就可以著手對事件風(fēng)暴中的概念進(jìn)行進(jìn)一步的歸納。

通過以上步驟,我們可以清晰地梳理出開播用例中的關(guān)鍵事件和參與者,為后續(xù)的設(shè)計和開發(fā)工作奠定基礎(chǔ)。

業(yè)務(wù)描述拆解成主語和動詞的形式后,可以發(fā)現(xiàn)"房間"和"場次"是這個問題域的兩個主要元素。在領(lǐng)域驅(qū)動設(shè)計中,需要將這兩個支撐域進(jìn)行集成,最終形成"開播域"的基本解決方案。為了確保開播業(yè)務(wù)流程的完整性,還需要將"安全管控"、"分區(qū)"、"賬號"等子域或外部系統(tǒng)的知識參與其中,并將其作為業(yè)務(wù)規(guī)則和值對象等等的形式進(jìn)行表達(dá)。

圖片圖片

根據(jù)近一年的業(yè)務(wù)現(xiàn)狀,我們參考領(lǐng)域驅(qū)動設(shè)計模式,進(jìn)行了領(lǐng)域上下文的劃分:

子域

能力

業(yè)務(wù)子域重點

[核心域] 開播域

集成開播相關(guān)的所有上下文,形成對開播場景下業(yè)務(wù)用例的實現(xiàn)方案。如開播、關(guān)播等。

開播 = 直播場次被創(chuàng)建 + 房間狀態(tài)變?yōu)殚_播 + 開播領(lǐng)域事件被發(fā)出

安全管控 = 開播常態(tài)化管控能力 + 應(yīng)急管控能力,如僅允許官方直播間開播

[通用域] 房間域

基礎(chǔ)業(yè)務(wù)能力,僅處理播端房間基礎(chǔ)業(yè)務(wù),如變更房間開播狀態(tài)。

房間狀態(tài)的增刪改查

發(fā)送開關(guān)播領(lǐng)域事件

[通用域] 場次域

基礎(chǔ)業(yè)務(wù)能力,僅處理開播領(lǐng)域產(chǎn)生的開關(guān)播場次信息

開關(guān)播場次信息、子場次信息(同一場直播下不同分區(qū))

[支撐子域] 房間管理域

保障房間域和開播域之間集成業(yè)務(wù)的業(yè)務(wù)完整性,如房間的推流管理。

房間關(guān)鍵依賴對賬:保障新開通房間存在視頻云上行推流地址

直播CQRS對賬:保障播端、觀看端的房間基礎(chǔ)信息一致

視頻云流管理能力封裝

[支撐子域] 開播安全管控域


開通直播間、預(yù)開播、開播、切換分區(qū)、推流管控策略

提供可配置、產(chǎn)品化的常態(tài)化策略管控能力,如是否允許某些地區(qū)開播,某分區(qū)是否需要延遲

必須具備快速應(yīng)對重要事件的能力。

外部域

視頻云管理

視頻云所屬領(lǐng)域,提供上行推流相關(guān)能力。開播主要使用上行推流管理、查詢上行推流地址

直播分區(qū)

看端所屬領(lǐng)域,提供分區(qū)基礎(chǔ)知識。開播僅使用其查詢分區(qū)元信息,作為開播安全管控的輸入

賬號上下文

主站賬號所屬領(lǐng)域,提供實名、等級相關(guān)知識。開播需要使用賬號信息、粉絲數(shù)等,作為開播安全管控的輸入

明確領(lǐng)域上下文和解決域的劃分后,緊接著就可以進(jìn)行DDD指導(dǎo)下的解決域戰(zhàn)術(shù)落地了。

領(lǐng)域劃分落實到戰(zhàn)術(shù)上的一個方案就是微服務(wù),微服務(wù)將直接作為開播域這個核心域與其他子域的實際界限。

在下文中,我們會講述如何將當(dāng)前的PHP遺留服務(wù),這個不滿足領(lǐng)域驅(qū)動設(shè)計的開發(fā)架構(gòu),演進(jìn)為受領(lǐng)域驅(qū)動設(shè)計指導(dǎo)的、貼合業(yè)務(wù)的、使用Golang搭建的整潔架構(gòu)。

3.開發(fā)架構(gòu)

設(shè)計不只是感觀,設(shè)計就是產(chǎn)品的工作方式。——史蒂夫·喬布斯

開門見山地講,經(jīng)過了多年積累后的舊版開播的遺留代碼是工程導(dǎo)向的,里面不乏炫技的代碼、大段冗長而缺乏業(yè)務(wù)注釋的代碼,這讓"開播"這個重要的業(yè)務(wù)領(lǐng)域在技術(shù)實現(xiàn)上,與業(yè)務(wù)方的實際描述漸行漸遠(yuǎn)。每當(dāng)我們提及一些業(yè)務(wù)場景,都需要絞盡腦汁才能回想起這個場景到底與哪些代碼有一些聯(lián)系。這樣的開發(fā)架構(gòu)和技術(shù)實現(xiàn)方式,不論對團(tuán)隊的知識共享還是對業(yè)務(wù)的正常迭代,都是一筆不可忽視的成本。

同時,由于陳年代碼經(jīng)手多代程序員,導(dǎo)致代碼風(fēng)格不統(tǒng)一、領(lǐng)域邏輯和UI邏輯耦合的情況幾乎隨處可見,DAO代碼更是可能隨時出現(xiàn)在各個層次,這樣的耦合對可拓展性和可測試性都帶來了不小的麻煩。

本次重構(gòu)在戰(zhàn)術(shù)落地層面所面臨的挑戰(zhàn),就是如何在保證業(yè)務(wù)邏輯幾乎不變的情況下,讓業(yè)務(wù)描述與代碼實現(xiàn)更貼切、認(rèn)知負(fù)荷更低從而加強(qiáng)業(yè)務(wù)知識的地位,以及如何優(yōu)雅地解耦原本雜亂耦合的各層次代碼,讓他們變得整潔、可測試、可拓展。

3.1 設(shè)計模式

我們不妨先管中窺豹,看一個簡化版的新舊版本開播的時序圖對比:

圖片圖片

圖片圖片

很顯然,前者的描述充斥著純技術(shù)屬性的描述,大部分篇幅集中于諸如area_id、uid之類的屬性,難以直接和實際業(yè)務(wù)中的描述對應(yīng)上。

而后者的描述則是有業(yè)務(wù)上的主客體描述的,如"房間是否屬于該用戶"、"分區(qū)是否允許該房間開播"。在代碼的編排描述上,很容易就可以看出,后者的可理解性要比前者高出一截,這便引出了下文要討論的話題:新舊版本開播服務(wù)的設(shè)計模式。

3.1.1 舊版設(shè)計模式:事務(wù)腳本

事務(wù)腳本模式也叫做面條代碼或者膠水代碼;它有一些顯著的特點:面向過程,易于編寫,難以應(yīng)對變更,復(fù)雜事務(wù)腳本可讀性低&可維護(hù)性低。顯然,舊版php開播代碼在多年缺乏系統(tǒng)性維護(hù)的業(yè)務(wù)迭代后,已經(jīng)幾乎退化為這樣的模式。

根據(jù)Fowler在PoEAA中對事務(wù)腳本的描述,我們用上文的舊版開播進(jìn)行分析。初次讀這段代碼的體驗,可能是如下的:

  1. 從表示層/服務(wù)層獲得輸入(開播請求的一些參數(shù),比如room_id、uid)
  2. 中間有大量的過程是用來做單純的獲取某一條數(shù)據(jù)(area_info分區(qū)信息)
  3. 獲取對這些獲取的單一數(shù)據(jù)進(jìn)行某些字段的判斷,或者多個單條數(shù)據(jù)聯(lián)合判斷(如,檢查房間開播狀態(tài)、檢查分區(qū)狀態(tài)是否為online)
  4. 之后調(diào)用其他系統(tǒng)或者存儲數(shù)據(jù)到數(shù)據(jù)庫(多次更新房間的多條信息,live_start_time/area_id)
  5. 過程中,不斷將單條操作后新增的數(shù)據(jù)合并到響應(yīng)值中

開播這個動作,在舊版代碼中乍一看,是一個過程驅(qū)動,由許多僅有技術(shù)含義的動作完成的純技術(shù)操作,缺乏了對業(yè)務(wù)的基本感知和描述。這樣的模式,對業(yè)務(wù)中的場景業(yè)務(wù)歸納能力較弱,當(dāng)我們提到某個場景時,往往需要把這些生硬的代碼在腦海中轉(zhuǎn)譯一次,才能對應(yīng)上業(yè)務(wù)方的實際描述。

當(dāng)我們擁有了更多動作時,就會有若干過程需要做相似的動作,通常就要使多個過程中包含某些相同的代碼,這些類似的副本會讓應(yīng)用程序變成一張極度雜亂無章的網(wǎng)。

換一個角度,從OOP的角度上看該模式,實體的概念并不能完全表現(xiàn),甚至只是充當(dāng)了業(yè)務(wù)邏輯層和數(shù)據(jù)訪問層之間的輔助角色,只空有屬性,沒有行為。這樣實體在 業(yè)務(wù)行為上難以和代碼實現(xiàn) 對應(yīng),更難以復(fù)用。

3.1.2 新版設(shè)計模式:領(lǐng)域模型

反之,對比起原來的事務(wù)腳本模式,我們的新服務(wù)中,包含有多個有血有肉的對象,比如房間和賬號。

對于應(yīng)用服務(wù)需要完成的開播用例而言,相比起純過程的各個字段和子過程的串聯(lián),更關(guān)心每一個對象應(yīng)該做出什么行為。

圖片圖片

圖片圖片

3.2 戰(zhàn)術(shù)設(shè)計

3.2.1 戰(zhàn)術(shù)設(shè)計的思考:引入六邊形架構(gòu)

既然是遺留系統(tǒng)現(xiàn)代化演進(jìn),我們不妨先提一些工程質(zhì)量方面的提升預(yù)期:

業(yè)務(wù)領(lǐng)域的邊界更加清晰、更好的可擴(kuò)展性、對測試的友好支持、更容易實施DDD...

看到既定目標(biāo),再結(jié)合領(lǐng)域驅(qū)動設(shè)計指導(dǎo)的前情提要,相信對此熟悉的讀者已經(jīng)會心一笑了,解決方案呼之欲出:六邊形架構(gòu)。

3.2.2 領(lǐng)域模型與六邊形架構(gòu)

怎么寫開發(fā)架構(gòu)相對整潔、看起來就可測試的代碼?相信大家或多或少都了解過"六邊形架構(gòu)"、"整潔架構(gòu)"或者"洋蔥架構(gòu)"。我們再來稍微復(fù)習(xí)一下它的定義:

六邊形架構(gòu),也被稱為端口和適配器架構(gòu)(Ports and Adapters Architecture),是由Alistair Cockburn于2005年首次提出的。這個架構(gòu)模式的主要目標(biāo)是將應(yīng)用程序的核心業(yè)務(wù)邏輯與外部依賴分離開來,從而提高可測試性、可維護(hù)性和可擴(kuò)展性。

在六邊形架構(gòu)中,應(yīng)用程序被劃分為以下幾個關(guān)鍵部分:

  • 應(yīng)用程序核心:這是應(yīng)用程序的主要業(yè)務(wù)邏輯,它包含了所有的用例和業(yè)務(wù)規(guī)則。核心不依賴于具體的外部組件或技術(shù),因此它是高度可測試的。
  • 端口:端口是定義應(yīng)用程序與外部依賴之間的接口。它們定義了應(yīng)用程序需要的功能,但不實現(xiàn)具體的實現(xiàn)細(xì)節(jié)。
  • 適配器:適配器是實際實現(xiàn)端口的組件,它們負(fù)責(zé)將外部依賴集成到應(yīng)用程序中。適配器將外部依賴的細(xì)節(jié)隱藏在內(nèi)部,以確保核心業(yè)務(wù)邏輯保持獨(dú)立性。

通過將應(yīng)用程序核心與外部依賴分離,六邊形架構(gòu)提供了以下優(yōu)勢:

  • 可測試性:由于核心業(yè)務(wù)邏輯與外部依賴分離,開發(fā)人員可以輕松地編寫單元測試,而無需依賴外部資源。
  • 可維護(hù)性:應(yīng)用程序的核心業(yè)務(wù)邏輯保持簡單和獨(dú)立,因此更容易理解和維護(hù)。
  • 可擴(kuò)展性:通過添加新的端口和適配器,您可以輕松地擴(kuò)展應(yīng)用程序,以滿足不斷變化的需求。

本次我們新搭建的開播平臺,遵循了端口和適配器的架構(gòu)風(fēng)格,將服務(wù)拆分為了以下的層次:

  • Transporter Layer 外部請求適配器,適配外部用例
  • Data Source Adapters 內(nèi)部資源適配器,適配內(nèi)部資源(Repository、Infrastructure)
  • Application 應(yīng)用程序?qū)樱深I(lǐng)域邏輯為用例,如"用戶使用直播姬開播"
  • Domain 領(lǐng)域?qū)樱瑯I(yè)務(wù)邏輯核心,眾多重要邏輯在這里實現(xiàn),如"房間狀態(tài)流轉(zhuǎn)為開播"

圖片圖片

解決的問題

  • 在PHP的老設(shè)計中,開播接口的ui/領(lǐng)域內(nèi)業(yè)務(wù)邏輯耦合較重,大量客戶端參數(shù)校驗、特定客戶端返回特定響應(yīng)的邏輯耦合在多個地方,可能是controller,也可能是service,甚至可能是dao層。這部分與領(lǐng)域知識本身無關(guān),在新版本的開播代碼中,需要與應(yīng)用程序?qū)雍皖I(lǐng)域?qū)痈綦x起來,保護(hù)后者的邏輯不受污染。
  • 歷史遺留代碼中,DAO耦合在代碼中,對業(yè)務(wù)邏輯本身的可拓展性和可測試性產(chǎn)生了阻礙;新版代碼中也需要將這部分解耦,以便未來技術(shù)演進(jìn)和單元測試的開展。

3.2.3 模塊設(shè)計

按照上文的開發(fā)架構(gòu)設(shè)計,本次新開播服務(wù)的代碼分包結(jié)構(gòu)代碼實現(xiàn)如下。

圖片圖片

因為Golang本身OOP的鴨子類型特性和諸多原因,我們的編碼風(fēng)格顯得沒有那么嚴(yán)格,選擇了相對松散的代碼分包結(jié)構(gòu)。大致區(qū)分為了領(lǐng)域?qū)印⒎栏瘜印?yīng)用程序?qū)印}儲/基礎(chǔ)設(shè)施層

Domain 領(lǐng)域?qū)?/h4>

作為領(lǐng)域驅(qū)動設(shè)計指導(dǎo)下的工程,我們的首要目標(biāo)就是保障領(lǐng)域邏輯的正確性。

最核心的領(lǐng)域?qū)樱瑂vc/pkg/domain包含了領(lǐng)域服務(wù)和各個領(lǐng)域?qū)ο蟮膇nterface契約聲明和具體實現(xiàn)。開播涉及到的領(lǐng)域?qū)ο蠖荚谶@里集中實現(xiàn)。

在開播的戰(zhàn)略設(shè)計中,我們提到了幾個上下文,在這里會作為聚合或?qū)ο蟮姆绞竭M(jìn)行實現(xiàn),他們的關(guān)系如下:

圖片圖片

Facade 防腐層

對應(yīng)設(shè)計中的Transporter Layer 外部請求適配器,適配外部用例:

用例上,開播和用于調(diào)試開播的請求,都需要在用例層面適配他們,所以自然需要適配器來適配他們的grpc請求,以及考慮到今后多種接口形式的接入( http or mq),如果之后grpc定義出現(xiàn)變更,或者新請求形式的接入,不會對 Application 層的用例帶來滲透和影響。

設(shè)計原則

需要提供多組外部適配器,適配各種場景的開播請求(理論上可能是grpc/http/mq ...,本文僅限于直播姬開播的場景),并轉(zhuǎn)化為應(yīng)用程序?qū)涌山邮艿挠美墑e請求。并且作為防腐層,不應(yīng)該有過多業(yè)務(wù)邏輯,僅實現(xiàn)必要的特定端到端場景的UI邏輯。

  • 處于防腐層的適配器需要有自己的合法校驗邏輯(必要的靜態(tài)參數(shù)檢查)。
  • 防腐層約定上層(controller的 grpc server)以及下層應(yīng)用的(application)的交互協(xié)議,開播中對應(yīng)開播的gRPC請求轉(zhuǎn)換為Application層可接受的請求。
  • 歷史邏輯中只與UI相關(guān)的邏輯也需要在這一層收斂。(etc.開播失敗情況下的端上提示、彈窗的相關(guān)返回值組裝)

對外契約:

  • 適配網(wǎng)關(guān)層開播接口的請求:需要有靜態(tài)參數(shù)檢查、將請求構(gòu)造為application層可接受的ACL請求。
  • 開播返回結(jié)構(gòu)體組裝:將application層開播用例的結(jié)果,根據(jù)端到端的UI邏輯,組裝成業(yè)務(wù)預(yù)期中的返回值(防止客戶端UI相關(guān)的邏輯滲透到下面的層次)

Application 應(yīng)用程序?qū)?/h4>

應(yīng)用層作為場景用例的主體部分,充當(dāng)了實體、聚合、領(lǐng)域服務(wù)的膠水層,將房間、場次、賬號的行為集成到一起,最終形成"直播姬開播"用例的業(yè)務(wù)邏輯。最終,每一個用例會對應(yīng)application的一個接口,如"直播姬開播"、"直播姬關(guān)播"、"后臺開播"、"后臺關(guān)播"等等,包裝成用例提供到外部。

Repository / Infrastructure 倉儲/ 基礎(chǔ)設(shè)施 

對應(yīng)Data Source Adapters 內(nèi)部資源適配器。同樣的,六邊形架構(gòu)中,對下游依賴的約束也是依靠 接口與適配器 這一風(fēng)格進(jìn)行解耦和契約。

設(shè)計原則

  • 內(nèi)部資源適配器應(yīng)該依照上層契約實現(xiàn)
  • 內(nèi)部資源適配器實現(xiàn)的變更不會影響到聲明的契約本身,其交付的能力應(yīng)當(dāng)是不變的
  • 倉儲 Repository,按照Domain層協(xié)商的倉儲能力進(jìn)行實現(xiàn),該層只對領(lǐng)域邏輯要求的倉儲能力負(fù)責(zé),如"發(fā)布開播領(lǐng)域事件"
  • 基礎(chǔ)設(shè)施層 Infrastructure,領(lǐng)域無關(guān)的基礎(chǔ)設(shè)施,如分布式鎖、上報組件等

3.3 測試驅(qū)動開發(fā) TDD

當(dāng)露營結(jié)束離開的時候,要打掃營地,讓它比你來的時候更干凈。—— 童子軍原則,《97 Things Every Programmer Should Know》

3.3.1 動機(jī)

從項目角度出發(fā),可以提供持續(xù)的項目進(jìn)度反饋。開播平臺重構(gòu)作為一個大型項目,需要從業(yè)務(wù)和項目的量化成一個個可操作的任務(wù)寫到 to-do list,然后使用測試驅(qū)動編碼,可以在每一個預(yù)期用例完成后進(jìn)行標(biāo)記,那么我們的工作目標(biāo)將變得非常清晰,因為工期、待辦事項、難點都非常明確,可以在持續(xù)細(xì)微的反饋中有意識地做一些適當(dāng)?shù)恼{(diào)整,比如添加新的任務(wù),刪除冗余的測試;還有一點更加讓人振奮,可以知道大概什么時候可以完工,對開發(fā)進(jìn)度可以更精確的把握。

從工程角度出發(fā),可以確保代碼質(zhì)量,也保障重構(gòu)的安全性。一個軟件的自動化測試,可以從內(nèi)部表達(dá)這個軟件的質(zhì)量,我們通常管它叫做內(nèi)建質(zhì)量(Build Quality In)。開發(fā)人員如果忽視編寫自動化測試,就放棄了將質(zhì)量內(nèi)建到軟件(也就是自己證明自己質(zhì)量)的機(jī)會,把質(zhì)量的控制完全托付給了測試人員。這種靠人力去保證質(zhì)量的方式,永遠(yuǎn)也不可能代表"技術(shù)先進(jìn)性"。在用例級別保障了內(nèi)建質(zhì)量后,倘若將來有一天需要重構(gòu),由于有全面的測試套件作為保障,開發(fā)人員可以放心地對代碼進(jìn)行優(yōu)化、改進(jìn)結(jié)構(gòu)或增加新功能,而不用擔(dān)心引入潛在的問題。

3.3.2 實踐

一句話來概括就是先設(shè)計用例,再寫代碼。

鑒于六邊形架構(gòu)符合 端口與適配器 風(fēng)格的契約,我們很容易知道:

  • 一個端口(interface)提供的能力是什么
  • 業(yè)務(wù)邏輯應(yīng)該使用什么端口(interface),他們應(yīng)該在業(yè)務(wù)中表現(xiàn)如何

那么以下的TDD工作流就應(yīng)該被遵守:

  1. 先明確interface的能力,定義所需的行為,并編寫可讀性良好的注釋文檔來聲明它們的作用;
  2. 根據(jù)上一步的契約,編寫interface的測試用例。
  3. 實現(xiàn)interface的業(yè)務(wù)邏輯,并實現(xiàn)接口以使其能夠通過。
  4. 業(yè)務(wù)邏輯測試,并使用上文編寫的用例進(jìn)行測試,驗證預(yù)期行為是否在待測的interface中產(chǎn)生。
  5. 根據(jù)結(jié)果調(diào)整代碼,直到可以通過測試用例。

由于六邊形架構(gòu)中,接口與其實現(xiàn)天然存在接縫(seam),對于某個業(yè)務(wù)邏輯中對Repository甚至領(lǐng)域?qū)ο蟮那闆r,我們也可以輕松通過mock的方式進(jìn)行依賴處理。

以房間聚合的開播狀態(tài)流轉(zhuǎn)作為舉例:

圖片圖片

第一步,我們根據(jù)"開播狀態(tài)流轉(zhuǎn)"這個領(lǐng)域?qū)ο蟮膭幼鳎M(jìn)行需求分析,得出該動作的目的就是"將房間狀態(tài)流轉(zhuǎn)為開播中",一些關(guān)聯(lián)的知識就包括,"必須聲明開播時間"、"狀態(tài)流轉(zhuǎn)為開播的同時需要與一場直播綁定"、"開播的房間必須已經(jīng)選擇了分區(qū)"。明確需求后,從業(yè)務(wù)邏輯中得到想要的用例可以得到如下的用例:

  1. 完全符合預(yù)期,開播的動作中包含所選的開播時間、場次、分區(qū),所以房間狀態(tài)可以流轉(zhuǎn)為開播
  2. 空操作,不合法輸入,失敗
  3. 沒有聲明開播時間,不合法輸入,失敗
  4. 沒有選擇分區(qū),不合法輸入,失敗
  5. 沒有綁定一場直播,不合法輸入,失敗
  6. Repository倉儲方法調(diào)用錯誤,失敗

同時也可以注意到,在開播狀態(tài)流轉(zhuǎn)中,我們只關(guān)注依賴的Repository的倉儲方法是否失敗,而不關(guān)心它如何實現(xiàn)的、為何失敗的。因為這對于房間對象而言并不是職責(zé)范圍內(nèi)的知識,而是倉儲方法的職責(zé)范圍,所以在這個場景下,我們只關(guān)心倉儲是否交付成功即可。

簡單舉例,測試用例代碼如下:

圖片圖片

第二步,根據(jù)這些已知用例實現(xiàn)"房間狀態(tài)流轉(zhuǎn)為開播",也就是實現(xiàn) IRoomStatus 這個對象的 ChangeRoomStatusToStartLive 方法。

圖片圖片

第三步,運(yùn)行第一步編寫的測試用例,查看是否符合預(yù)期。對于領(lǐng)域?qū)ο缶唧w依賴的Repository,由于我們事先在六邊形架構(gòu)中聲明了依賴的interface契約,所以可以較為簡單地使用mock處理這些依賴。

圖片圖片

第四步,運(yùn)行完整的測試用例集合,如果不符合預(yù)期,則重回第二步,開始新一輪的修改和測試流程。

至此,一套完整的 UTDD 流程就良好地運(yùn)作起來了,在實際的開發(fā)過程中,我們的每一個領(lǐng)域?qū)ο蟆}儲方法、基礎(chǔ)設(shè)施的實現(xiàn)流程都是按照該流程進(jìn)行的,在很大程度上保障了新開播的內(nèi)建質(zhì)量。

對于更為大型的場景,比如application層對開播接口的測試,本質(zhì)上在六邊形架構(gòu)中也可以將集成的多個領(lǐng)域?qū)ο笸ㄟ^端口-適配器的解耦,將涉及的領(lǐng)域?qū)ο笾苯舆M(jìn)行mock,從而以較低的心智成本編寫出可讀性較高的集成測試,一個典型的集成測試集合如下:

圖片圖片

在TDD思想的指導(dǎo)和開發(fā)流程下,我們的新服務(wù)整體單元測試覆蓋率達(dá)到了70+%,部分關(guān)鍵領(lǐng)域邏輯的覆蓋率達(dá)到100%。

如此的覆蓋率,不論在業(yè)務(wù)理解層面還是內(nèi)建質(zhì)量方面都產(chǎn)生了莫大的幫助——不必?fù)?dān)心一些改動導(dǎo)致的重要影響無法被開發(fā)者捕捉到,這無疑在未來的業(yè)務(wù)迭代和進(jìn)一步重構(gòu)中都會起到關(guān)鍵作用。

4 安全的系統(tǒng)遷移

兵馬未動,糧草先行。

——《孫子兵法》

一艘巨輪建造完成后終究需要下水,而往往船下水的方案設(shè)計是先于船體本身的建造的。開播能被稱為遺留系統(tǒng),那么它背后的歷史邏輯和技術(shù)債務(wù)一定不容小覷,我們對新開播系統(tǒng)"完工下水"這件事,顯然就要謹(jǐn)慎對待了,從新開播的實現(xiàn)本身、到中間的開發(fā)執(zhí)行和驗證,以及最后的部署灰度,都需要進(jìn)行細(xì)致的考慮,保證這艘新船能順利接觸水面。

前期對業(yè)務(wù)邏輯進(jìn)行最細(xì)致的歸納,這其中包括了代碼的逐行校對和每個邏輯分支的業(yè)務(wù)邏輯梳理,甚至也包括了PHP和Golang基礎(chǔ)組件的源碼對比。

中期在代碼編寫的過程中逐步明確"檢查點"和事件溯源的全貌,設(shè)計并完善了驗證方案:流量復(fù)制和事件溯源,并構(gòu)建完善的新舊開播檢查點對比系統(tǒng),保證關(guān)鍵的邏輯節(jié)點上,新舊服務(wù)的表現(xiàn)完全一致。

后期在服務(wù)部署和灰度策略上,也做了最周密的準(zhǔn)備,包括網(wǎng)關(guān)級別萬分位的灰度放量規(guī)則和業(yè)務(wù)級別的重要房間退避方案。

 4.1 業(yè)務(wù)邏輯

業(yè)務(wù)邏輯通常是最沒有邏輯的東西。

—— Martin Fowler,《企業(yè)應(yīng)用架構(gòu)模式》

4.1.1 歷史邏輯

面對已存在多年的業(yè)務(wù)邏輯,不論它是否容易閱讀、我們是否熟悉跨語言的寫法,都應(yīng)該心存敬畏,逐個分支、逐個業(yè)務(wù)場景進(jìn)行盤點,最終形成對此業(yè)務(wù)場景的正確理解。

面對這種高準(zhǔn)確度要求的表達(dá)訴求,我們選擇了已有的接口自動化測試用例結(jié)合手動端到端驗證 + 逐行閱讀對比代碼的方式進(jìn)行梳理驗證,最后以時序圖的方式,將舊開播服務(wù)的PHP實現(xiàn)邏輯呈現(xiàn)到施工方案中。

圖片圖片

(涉及具體業(yè)務(wù)流程,僅展示縮略圖)

既然是"重構(gòu)",我們選擇盡量保持原有的邏輯流和數(shù)據(jù)流,先將主邏輯大部分遷移完成,再進(jìn)行下一步的改造。

所以在新版本的重構(gòu)中,涉及的業(yè)務(wù)邏輯流,實際上并沒有過大的改變,從而保障了邏輯分支在端到端表現(xiàn)上可以完全一致。

4.1.2 轉(zhuǎn)化漏斗圖

針對上述邏輯分支眾多的用例場景,我們也嘗試使用最直觀的圖形形式,展現(xiàn)給對開播領(lǐng)域不甚熟悉的研發(fā)同學(xué),甚至是產(chǎn)運(yùn)同學(xué)進(jìn)行參考,最終選擇了漏斗圖的形式。

最頂層為開播接口的入口,對應(yīng)直播姬點擊"開始直播"按鈕后對服務(wù)端開播接口的請求。而后的一系列漏斗層,則代表了服務(wù)端的行為,中途不斷有檢查項攔截不符合開播條件的請求,直到底部的成功開播。

圖片圖片

4.2 流量復(fù)制 & 事件溯源

以上文歸納的"業(yè)務(wù)邏輯"為指導(dǎo),我們著手構(gòu)建了一套為開播業(yè)務(wù)邏輯遷移量身打造的流量復(fù)制和數(shù)據(jù)驗證方案。

作為核心場景,開播日均承載的流量大,且邏輯流具有不確定性:不同的開播賬號、開播場景,甚至是網(wǎng)絡(luò)環(huán)境,都可能會導(dǎo)致會走入某一個上述復(fù)雜的開播邏輯分支中,可能有業(yè)務(wù)邏輯拒絕開播直接中斷開播流程的情況,也有可能發(fā)生內(nèi)部錯誤但繼續(xù)執(zhí)行開播流程的情況。所以如何在眾多的業(yè)務(wù)分支中識別出新舊開播服務(wù)的數(shù)據(jù)流和邏輯流完全一致,是本次工程中的難點之一。

對此,我們設(shè)計了一套"流量復(fù)制"和"事件溯源"的驗證方案。

4.2.1 流量復(fù)制

圖片圖片

在舊版和新版開播正式進(jìn)行切換之前,必須保證新版舊版開播邏輯和數(shù)據(jù)鏈路和業(yè)務(wù)邏輯一致,為此我們設(shè)計了"流量復(fù)制"和"事件溯源/對賬"的機(jī)制。

  • 流量復(fù)制:網(wǎng)關(guān)層復(fù)制開播接口請求,分發(fā)給舊服務(wù)和新服務(wù)
  • 事件溯源/對賬:
  • 開播接口邏輯中的重要事件節(jié)點(包括了開播接口最終返回到端上的響應(yīng)值)上報到數(shù)據(jù)平臺
  • 對每一條開播請求的事件進(jìn)行新舊服務(wù)對比

對于復(fù)制過來的一組流量,我們期望它是冪等的,不能對下游數(shù)據(jù)產(chǎn)生任何影響。

在上文開發(fā)架構(gòu)中提到,Repository和Infrastructure在六邊形架構(gòu)中,可以通過不同的方式實現(xiàn)契約,那么對于"不真實執(zhí)行"這一實現(xiàn)方式,是天然可以實現(xiàn)支持的——新增一組"假寫"的適配器即可。

為保證這些檢查點在新舊服務(wù)完全一致,在驗證方案中設(shè)計了以下三個階段:

圖片圖片

圖片圖片

圖片圖片

  1. 舊服務(wù)進(jìn)行開播事件上報
  2. 主要邏輯仍然由舊服務(wù)處理,網(wǎng)關(guān)服務(wù)復(fù)制流量到新服務(wù)。新服務(wù)只執(zhí)行冪等邏輯,不進(jìn)行真實的寫操作。新舊服務(wù)均上報關(guān)鍵事件檢查點,統(tǒng)一在數(shù)據(jù)平臺進(jìn)行每一條請求的檢查。
  3. 重構(gòu)部分的關(guān)鍵事件檢查點驗證完成后,舊服務(wù)不再上報,而新服務(wù)切換為真實寫入的模式,并且繼續(xù)保留關(guān)鍵事件上報能力。

4.2.2 事件溯源

借助戰(zhàn)略設(shè)計章節(jié)中的"事件風(fēng)暴"整理出的關(guān)鍵路徑和事件,稍加整理就可以得到一組關(guān)鍵事件鏈路,借助事件溯源(Event Sourcing)的思想,我們可以將開播流程中的重要節(jié)點上報并持久化。

根據(jù)事件風(fēng)暴和業(yè)務(wù)邏輯的時序圖,我們設(shè)定了以下關(guān)鍵事件檢查點:

圖片圖片

根據(jù)這些檢查點,我們在新舊版本的開播代碼中進(jìn)行改造,在對應(yīng)的點位埋樁進(jìn)行數(shù)據(jù)上報。由于他們可以被聚合在同一條trace下,所以針對每一條開播接口的請求,都可以被完整地記錄在案。

從事件溯源中,我們也可以獲取到一個意料之中的收獲:開播鏈路在服務(wù)端鏈路的業(yè)務(wù)可觀測性。

圖片圖片

4.3 自動化測試

4.3.1 UTDD 單元測試&集成測試

如 3.3(測試驅(qū)動開發(fā))部分所述,新開播服務(wù)在開發(fā)時就采用了 TDD 工作流,單測覆蓋率70%以上,關(guān)鍵邏輯的行覆蓋率達(dá)到100%。

單元測試覆蓋率檢查集成到CI中,保證后續(xù)業(yè)務(wù)迭代質(zhì)量。

圖片圖片

4.3.2 ATDD 測試共建自動化測試用例

在本次重構(gòu)中,我們與測試團(tuán)隊持續(xù)合作,共建了200+條開播接口的自動化集成測試用例,覆蓋了大部分的請求參數(shù)檢查、用戶身份和狀態(tài)、特殊開播場景、安全管控策略、分區(qū)和場次狀態(tài)等正常和異常用例,并對對應(yīng)預(yù)期接口返回結(jié)果、數(shù)據(jù)和消息寫入結(jié)果等檢查。同時在自動化測試中引入diff能力,相同參數(shù)輸入下新舊服務(wù)接口響應(yīng)進(jìn)行對比,覆蓋80%以上開播場景。

圖片圖片

整個重構(gòu)的遷移過程中,我們通過接口自動化測試,發(fā)現(xiàn)并修復(fù)問題10+個。

4.4 部署計劃

整體上線(包括流量復(fù)制&實際灰度階段)分為了三個階段:

  • 舊開播服務(wù)事件上報
  • 新舊開播服務(wù),線上流量復(fù)制對比
  • 新開播服務(wù)正式灰度切流

整個部署發(fā)布不同階段,都嚴(yán)格制定SOP按照計劃執(zhí)行,避免遺漏或切換過程中對線上開播服務(wù)的影響:

圖片圖片

4.5 結(jié)果

在精細(xì)的驗證計劃、部署計劃和嚴(yán)格的流程把控下,開播在整個遷移過程中未出現(xiàn)任何事故。

其中一些驗證操作的功效是很直觀的:

  • ATDD 接口自動化發(fā)現(xiàn)差異:10+個
  • 流量復(fù)制&事件溯源發(fā)現(xiàn)差異:20+個
  • 歷史邏輯&代碼對比發(fā)現(xiàn)差異:30+個

同時我們在一個月時間內(nèi),逐步進(jìn)行了精細(xì)到單個用戶粒度-萬分位-千分位-十分位-全量的灰度,在途中也優(yōu)化了10+性能問題。

圖片圖片

最終順利全量上線。

5 生產(chǎn)配套

“君之所以明者,兼聽也;其所以暗者,偏信也。”——漢·王符《潛夫論·明暗》

一個運(yùn)作良好的系統(tǒng)首先必須具備良好的可觀測性,倘若都無法觀測到各個零件運(yùn)作是否良好,又談何算得上一輛好車。

對于開播這種不容有失的系統(tǒng),萬萬不可寫完代碼就萬事大吉。我們需要更加謹(jǐn)慎地將系統(tǒng)的運(yùn)作狀態(tài)觀測納入設(shè)計考慮,讓觀測變得更加直觀,使?jié)撛诘南到y(tǒng)性風(fēng)險可以快速暴露,也便于在緊急情況下做出恰當(dāng)?shù)臎Q策。

5.1 系統(tǒng)監(jiān)控

對于開播服務(wù)的整體鏈路,我們通過前文的事件溯源上報方案結(jié)合司內(nèi)的監(jiān)控解決方案,對開播成功、開播拒絕的情況進(jìn)行了上報統(tǒng)計,對開播整體大盤的開播成功率、被拒絕開播的原因和發(fā)生率形成直觀感受。

若開播系統(tǒng)出現(xiàn)了某種業(yè)務(wù)異動,比如被拒絕開播的突增,我們可以借助監(jiān)控大盤和告警體系在第一時間感知到。

圖片

 5.2 系統(tǒng)排障

伴隨著"事件溯源"體系的建設(shè),自然可以衍生出眾多提升系統(tǒng)可觀測性的輔助工具。這些工具在未來的業(yè)務(wù)運(yùn)維和業(yè)務(wù)迭代過程中可以節(jié)省大量的人力。

如可以實時驗證是否指定房間是否滿足開播條件的"模擬開播":

圖片圖片

以及針對每一條歷史開播請求可以追溯關(guān)鍵事件,排查開播為何成功/失敗的"開播事件問診":

圖片圖片

6 結(jié)果

回顧文章開篇時提到的歷史債務(wù)上來,我們從業(yè)務(wù)層面和技術(shù)層面來進(jìn)行一些簡單的復(fù)盤。

6.1 業(yè)務(wù)收益

知識共享:在開播平臺重構(gòu)的一系列工作中,首當(dāng)其沖的是對開播歷史邏輯的完整梳理,這無疑提高了產(chǎn)研對開播業(yè)務(wù)的理解程度,降低溝通成本;在過程中,我們也已與產(chǎn)品溝通了眾多不曾關(guān)注到的功能細(xì)節(jié),幫助產(chǎn)品更好地建設(shè)開播工具生態(tài)。伴隨著產(chǎn)研對業(yè)務(wù)知識的理解成本降低,一些客訴問題的排查也會變得容易起來——從前一些只有代碼編寫者才能描述的邊緣情況,現(xiàn)在更容易被產(chǎn)品甚至熟悉的運(yùn)營所得知,進(jìn)而減低對開播功能的疑惑,最終使產(chǎn)研協(xié)作效率提升。

開發(fā)提效:在PHP舊服務(wù)的開發(fā)過程中,用例梳理、PHP代碼晦澀的Coding過程、復(fù)雜代碼的反復(fù)Review、PHP的遠(yuǎn)古工具鏈?zhǔn)褂枚紩加么罅康拈_發(fā)時間;相較舊版,新版開播接口不存在這些歷史包袱,極大提高了開發(fā)效率。

業(yè)務(wù)SRE:"開播事件溯源"提供的接口請求級別的問診能力,不同于以往排查開播問題時需要手動翻閱每一條關(guān)鍵日志,新版本的一鍵查詢溯源記錄能力可以大大降低研發(fā)的問題排查成本。

6.2 系統(tǒng)性風(fēng)險優(yōu)化

在過去,開播系統(tǒng)運(yùn)行于"房間服務(wù)"的 PHP 服務(wù)之中,該服務(wù)除了承載開播業(yè)務(wù),也承載了大量和直播有關(guān)的周邊業(yè)務(wù)接口;

從技術(shù)角度,跨語言的遷移解決了較多的風(fēng)險:

  • 一個相當(dāng)?shù)湫偷陌咐涸鹊目蚣芑?Swoole 二次開發(fā)(Worker 模式),在突發(fā)并發(fā)流量較大時會出現(xiàn)單實例 Worker 滿載的情況,造成請求超時;且由于請求堆積、Worker 釋放和重建、內(nèi)存回收之間存在一定時間差,瞬時 Swoole Worker 進(jìn)程超出內(nèi)存限制導(dǎo)致請求失敗時有發(fā)生。該問題造成了原開播系統(tǒng)的穩(wěn)定性不足,無法支撐直播業(yè)務(wù)快速發(fā)展的需要,構(gòu)成了系統(tǒng)性風(fēng)險。而這種在PHP框架下難以根治的問題,遷移到Golang后就自然不存在了。
  • 重構(gòu)后,開播業(yè)務(wù)屬于單獨(dú)的 Go 微服務(wù),性能和可用性上有了大幅提升,接口可用性 SLA 從 99.xx% 提升到 99.99xx%,接口 P99 響應(yīng)速度提高50+%。
  • 從語言層面,由于 PHP 本身是弱類型和解釋型語言,在開發(fā)和編譯過程中較難發(fā)現(xiàn)潛在問題,導(dǎo)致研發(fā)自測和測試成本上升,在過去也曾因為這些特性的處理不慎導(dǎo)致開播系統(tǒng)的線上問題;Go服務(wù)中對單元測試、故障測試有較好的支持,可以及時發(fā)現(xiàn)問題。
  • PHP 的內(nèi)部工具鏈相對缺少維護(hù),與之對比的 Golang 是公司后端研發(fā)最主流的選型,公司級監(jiān)控、告警、觀測、服務(wù)治理、持續(xù)集成等系統(tǒng)都為 Go 提供了相對更好的支持,這也使得新的開播系統(tǒng)也有更好的可維護(hù)性和事件響應(yīng)能力。

圖片圖片

從業(yè)務(wù)角度看,也提高了業(yè)務(wù)的系統(tǒng)穩(wěn)定性:

  • 重構(gòu)過程中,梳理了整個開播鏈路中的服務(wù)、接口、配置、存儲依賴,并將數(shù)十個依賴項區(qū)分為強(qiáng)依賴和弱依賴。
  • 對于強(qiáng)依賴場景,梳理了對應(yīng)的失敗表現(xiàn),并編寫了應(yīng)急SOP;對于弱依賴調(diào)用失敗的場景,采用補(bǔ)償任務(wù)等手段處理,不阻塞用戶開播,進(jìn)一步提升了開播系統(tǒng)的可用性。

6.3 技術(shù)資產(chǎn)

一個好的技術(shù)項目,不僅需要達(dá)成業(yè)務(wù)和技術(shù)上的硬性目標(biāo),還需要有所積累和成長。我們在開播重構(gòu)的旅途中,也摸索出了一套行之有效、可復(fù)用的觀測模式和遷移模式。

6.3.1 更細(xì)粒度的業(yè)務(wù)可觀測

上文中提到的業(yè)務(wù)鏈路可觀測,沉淀后也成為了開播問診臺的通用事件溯源功能。

可查看某個房間過往不同開播場次,過程 & 結(jié)果關(guān)鍵事件的數(shù)據(jù)信息,更快的定位到線上每一次具體開播的情況。

6.3.2 可復(fù)用的遷移模式

經(jīng)過本次開播接口遷移的歷練,開播平臺獲得了可復(fù)用的PHP轉(zhuǎn)Go的工程經(jīng)驗,我們也可以嘗試用DDD的觀點來總結(jié):

圖片圖片

一些沉淀的能力如下:

  • 業(yè)務(wù)流
  • 業(yè)務(wù)邏輯梳理:邏輯分支級別梳理,落實到標(biāo)準(zhǔn)設(shè)計圖上(時序圖、數(shù)據(jù)流圖),分支級別的測試用例覆蓋(判定表、單測、自動化測試)
  • 事件溯源:按照事件風(fēng)暴、測試用例標(biāo)定的關(guān)鍵事件,進(jìn)行開播流程中的事件上報,保證請求可完全回溯。
  • 工程保障
  • 流量復(fù)制/切換:新舊接口的流量復(fù)制和流量控制。
  • SOP:嚴(yán)格的部署計劃以及SOP,減少人為因素的不穩(wěn)定性。

那么套用回“開播平臺遷移”這個問題域匯總,我們可以得到以下的解法:

  • 統(tǒng)一領(lǐng)域知識:對于產(chǎn)研測需要達(dá)成的共識,一套可讀性高的業(yè)務(wù)邏輯梳理可以滿足訴求。
  • 白盒驗證:業(yè)務(wù)邏輯梳理提供單元測試、自動化測試用例;事件溯源和流量復(fù)制共同提供新舊服務(wù)的關(guān)鍵事件上報、數(shù)據(jù)對比。
  • 服務(wù)部署:SOP提供嚴(yán)格的準(zhǔn)出和操作步驟;流量復(fù)制/切換提供新舊服務(wù)的切換能力。
  • 戰(zhàn)術(shù)落地:借助于上述的所有能力,逐步完善開播系統(tǒng)。

一個完整的迭代可能是這樣的:確保項目組內(nèi)產(chǎn)研認(rèn)知一致后,按照TDD方法編寫出初版代碼;通過眾多測試用例后,進(jìn)行流量復(fù)制和事件溯源,通過關(guān)鍵事件對比保障關(guān)鍵檢查點和數(shù)據(jù)鏈路完全一致,最終按照SOP進(jìn)行上線。如果中途發(fā)現(xiàn)了修改點,需要回退到初版代碼編寫,乃至同一領(lǐng)域知識的步驟進(jìn)行項目組認(rèn)知的對齊。

通過業(yè)務(wù)流的完整評估,再有嚴(yán)謹(jǐn)?shù)墓こ舔炞C計劃保障,在事實上極大降低了出現(xiàn)嚴(yán)重遷移事故的概率(開播遷移過程中未出現(xiàn)PX以上事故)。

7 后日談:可演進(jìn)的“遺留”系統(tǒng)

重構(gòu)和微服務(wù)的締造者,軟件開發(fā)領(lǐng)域的泰斗,Martin Fowler 曾經(jīng)說過這樣一句話:

Let's face it, all we are doing is writing tomorrow's legacy software today.

是的,可以毫不夸張地說,你現(xiàn)在所寫的每一行代碼,都是未來的遺留系統(tǒng)。這聽上去有點讓人沮喪,但卻是血淋淋的事實,一個軟件系統(tǒng)的生命周期終歸會符合業(yè)務(wù)演進(jìn)的客觀規(guī)律。

不過大可不必氣餒,回到我們在引言中談到的遺留系統(tǒng)定義,有些系統(tǒng)時間雖長,但如果一直堅持現(xiàn)代化的開發(fā)方式,在代碼質(zhì)量、架構(gòu)合理性、測試策略、DevOps 等方面都保持先進(jìn)性,就算將來需要進(jìn)行架構(gòu)的進(jìn)一步演進(jìn),這樣"整潔"的老系統(tǒng)也會幫助我們規(guī)避眾多的問題,甚至可以讓演進(jìn)周期縮短、演進(jìn)風(fēng)險降低。

相信我們今天在開播平臺遷移中花費(fèi)的心血和留下的基石,終會為"歷久彌新"的系統(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)現(xiàn)代化實戰(zhàn), 極客時間. Available at: https://time.geekbang.org/column/intro/100111101 (Accessed: 22 November 2023). 

本期作者

趙書彬嗶哩嗶哩高級開發(fā)工程師趙書彬嗶哩嗶哩高級開發(fā)工程師


王清培 嗶哩嗶哩資深開發(fā)工程師王清培 嗶哩嗶哩資深開發(fā)工程師


傅志超 嗶哩嗶哩資深開發(fā)工程師傅志超 嗶哩嗶哩資深開發(fā)工程師


郭宇霆 嗶哩嗶哩資深開發(fā)工程師郭宇霆 嗶哩嗶哩資深開發(fā)工程師


朱德江 嗶哩嗶哩資深開發(fā)工程師朱德江 嗶哩嗶哩資深開發(fā)工程師

責(zé)任編輯:武曉燕 來源: 嗶哩嗶哩技術(shù)
相關(guān)推薦

2023-03-29 23:34:16

2023-02-16 07:24:27

VPA技術(shù)

2024-01-24 07:36:29

2021-03-01 21:32:49

HTTP2 QUIC

2022-12-07 07:35:20

B站裁員隱情

2022-09-15 15:18:23

計算實踐

2024-02-28 07:50:36

大數(shù)據(jù)標(biāo)簽系統(tǒng)AB 實驗

2025-03-05 00:00:55

2023-02-09 07:38:39

配置中心架構(gòu)組件

2022-07-05 15:08:52

機(jī)房架構(gòu)

2022-07-29 14:53:09

數(shù)據(jù)實踐

2023-06-25 07:31:15

2025-09-28 04:00:00

RAG數(shù)據(jù)智能會員中心

2021-07-14 07:41:54

B站A站服務(wù)器

2023-05-26 06:45:08

2023-06-12 17:24:40

Web網(wǎng)絡(luò)

2020-03-02 16:24:45

戴爾

2024-03-05 18:36:21

轉(zhuǎn)換引擎用戶體驗

2024-08-13 12:54:20

點贊
收藏

51CTO技術(shù)棧公眾號

免费成人高清在线视频| 欧美熟妇交换久久久久久分类| 欧美日韩第一| 正在播放亚洲一区| 日本熟妇人妻xxxx| 国产资源在线播放| 久久99国产精品麻豆| 欧美黑人又粗大| 91精彩刺激对白露脸偷拍| 欧美成人黑人| 亚洲男同性恋视频| 国产精品免费一区二区| 蜜臀精品一区二区三区| 亚洲久久久久| 亚洲女同精品视频| 亚洲精品乱码久久久久久动漫| 国语对白在线刺激| 欧美国产视频在线| 国产青春久久久国产毛片 | **网站欧美大片在线观看| 国产精品久久亚洲7777| 中文字幕日日夜夜| 亚洲在线观看| 欧美国产日韩xxxxx| av男人的天堂av| 国产精品一区二区三区美女| 欧美日韩三级在线| 日本免费不卡一区二区| 超碰在线caoporn| 国产色综合一区| 久久综合九色综合网站| 精品国产18久久久久久| 久久精品久久精品| 国产999视频| 久久精品久久精品久久| 国产精品99久久| 正在播放国产一区| 在线不卡av电影| 欧美理伦片在线播放| 精品三级在线看| 欧美国产日韩在线视频| 国产成人精选| 欧美中文字幕一区二区三区亚洲| 无码人妻丰满熟妇区96| 牛牛精品在线| 亚洲最色的网站| 久久久久久久久久久综合| 老司机在线永久免费观看| 国产欧美日韩视频在线观看| 久久综合九色欧美狠狠| 色中色在线视频| 99综合电影在线视频| 粉嫩高清一区二区三区精品视频| 国产黄色av片| 精品一区二区影视| 国产精品视频1区| 亚洲欧美偷拍视频| 亚洲二区三区不卡| 欧美成人精品不卡视频在线观看| 新91视频在线观看| 欧美大片网址| 亚洲国产日韩精品在线| 国产人妻精品久久久久野外| 日韩综合久久| 欧美日韩精品高清| 一区二区xxx| 成人在线视频播放| 色av成人天堂桃色av| 欧美 日韩 国产在线观看| 在线免费av导航| 亚洲网友自拍偷拍| 性高湖久久久久久久久aaaaa| 成人福利在线观看视频| 亚洲人成网站精品片在线观看| 视频在线观看成人| 国产青青草在线| 中文字幕免费一区| 亚洲电影免费| 欧美三级黄网| 亚洲欧美电影一区二区| 国产在线视频综合| 国产第一页在线视频| 亚洲综合在线视频| 黄色大片中文字幕| 手机在线观看av| 色综合久久久久网| 熟妇人妻va精品中文字幕| 午夜影院一区| 在线国产亚洲欧美| 久久婷五月综合| 国产精品视频首页| 精品粉嫩aⅴ一区二区三区四区| 日韩大尺度视频| 欧美理伦片在线播放| 精品在线小视频| 在线观看福利片| 色婷婷一区二区三区| 久久色精品视频| 精品爆乳一区二区三区无码av| 激情久久久久久久| 欧美性受xxx| 中文字幕日本人妻久久久免费| 韩国一区二区在线观看| 成人毛片网站| 欧美日韩在线中文字幕| 国产精品嫩草影院com| 日本一区二区三区免费看| 不卡在线视频| 亚洲自拍偷拍欧美| av天堂永久资源网| 老司机精品视频网| 欧美精品一区二区在线观看| 日韩人妻无码精品综合区| 久久麻豆精品| 午夜精品久久久久久久久久久久久 | 成年人视频在线免费观看| 中文字幕人成不卡一区| 免费看欧美黑人毛片| 中文字幕在线直播| 9191国产精品| 亚洲一区二区乱码| 99久久婷婷国产综合精品电影√| 欧美日韩第一视频| 中文字幕在线观看欧美| 成人免费精品视频| 一区二区欧美日韩| 人成在线免费网站| 在线播放91灌醉迷j高跟美女| 扒开伸进免费视频| 日韩国产一区二区三区| 69久久夜色精品国产69| 国产女人高潮时对白| 久久青草国产手机看片福利盒子| 黄色免费高清视频| 日本免费久久| 精品国产区一区| a级在线免费观看| 欧美一区在线看| 国产精品偷伦视频免费观看国产| 丰满人妻一区二区三区免费| 国产欧美一区二区三区网站| 免费视频爱爱太爽了| 国产精品伦一区二区| 精品国产91乱码一区二区三区 | 中文字幕日韩在线观看| 精品无码人妻一区二区三区品 | 伊人婷婷欧美激情| 香蕉视频网站入口| 色狼人综合干| 久久久久国产一区二区三区| 国产成人麻豆免费观看| 99精品国产视频| 欧洲精品在线播放| 欧美成人精品午夜一区二区| 最新国产精品拍自在线播放| 天天干天天干天天干天天| 成人午夜视频在线观看| 秋霞在线一区二区| 免费视频观看成人| 国产一区二区三区在线看| 国产精品免费精品一区| 99久久久久免费精品国产 | 91精品91| 国产精品一区二区久久久| 毛片免费在线播放| 欧美日韩亚洲精品内裤| 成人在线电影网站| 黄色亚洲在线| 国产精品免费一区二区三区| 日本动漫理论片在线观看网站| 91精品国产免费| 日本高清一二三区| 国产一区二区三区在线观看精品| 中文字幕人成一区| 电影91久久久| 色综合久久精品亚洲国产 | 丁香花高清在线观看完整版| 日韩欧美中文一区| 九热这里只有精品| 国产九九视频一区二区三区| 免费网站在线观看视频 | 国产精品大全| 成人黄色动漫| 亚洲免费福利视频| 国产精品传媒在线观看| 国产精品美女久久久久久久网站| 艹b视频在线观看| 我不卡手机影院| 成人在线精品视频| 超碰porn在线| 亚洲国产成人91精品| 国产无遮挡呻吟娇喘视频| 久久久欧美精品sm网站| 国产裸体免费无遮挡| 日韩精品诱惑一区?区三区| **亚洲第一综合导航网站| 欧美6一10sex性hd| 亚洲精品久久久久| 国内av在线播放| 亚洲精品免费在线| 久久久视频6r| 国产精品资源网| 免费看又黄又无码的网站| 久操成人av| 国产欧亚日韩视频| xxxx视频在线| 夜夜躁日日躁狠狠久久88av| 国产黄色片av| 色综合一区二区| caoporn91| 91麻豆免费观看| 在线视频观看91| 136国产福利精品导航网址| 日韩久久精品一区二区三区| 麻豆视频久久| 亚洲91精品在线| 自拍视频在线免费观看| 精品国产麻豆免费人成网站| 日韩三级一区二区| 樱花草国产18久久久久| 性欧美精品中出| 懂色av一区二区三区蜜臀 | av在线播放资源| 在线午夜精品自拍| 国产成人三级在线观看视频| 一道本成人在线| 久久机热这里只有精品| 欧美国产精品中文字幕| 精品久久久久一区二区| 男女男精品视频网| 国产二区视频在线| 色乱码一区二区三区网站| 免费中文日韩| 亚洲精品午夜| 国产精品第3页| 婷婷激情一区| 91成品人片a无限观看| 成人a在线视频免费观看| 国产亚洲人成a一在线v站| 亚洲成人精品女人久久久| 欧美日韩一区三区四区| 亚洲久久在线观看| 亚洲丶国产丶欧美一区二区三区| 精品手机在线视频| 久久久影院官网| 手机在线成人av| 国产成人aaaa| 免费不卡av网站| 精油按摩中文字幕久久| 中文字幕无码不卡免费视频| 在线亚洲精品| 黄页网站大全在线观看| 欧美人成在线| 亚洲一区在线免费| 青青草综合网| 色狠狠久久av五月综合| 久久99高清| 美乳视频一区二区| 天堂综合网久久| 国产一区二区黄色| 婷婷成人av| 91视频国产高清| 91成人福利社区| 91视频国产一区| 99久久999| 91麻豆国产语对白在线观看| av在线亚洲一区| 成人在线精品视频| 高清一区二区| 国产一区二区高清视频| 欧美变态网站| 免费国产一区| 狠狠色狠狠色综合婷婷tag| 日韩三级电影网站| 久久福利影院| 国产精品美女在线播放| 亚洲激情中文| 欧美人与动牲交xxxxbbbb| 欧美在线资源| 91免费黄视频| 噜噜噜在线观看免费视频日韩 | 国内高清免费在线视频| 午夜精品一区二区三区在线| 美女高潮在线观看| 欧美综合在线第二页| 日本在线精品| 成人免费直播live| 日韩av网站在线免费观看| 日本不卡久久| 亚洲人体av| 免费国产a级片| 日本不卡视频在线| 911av视频| 高清成人在线观看| 国产精品一区二区无码对白| 国产精品色哟哟| 国产97免费视频| 精品日韩视频在线观看| 91丝袜一区二区三区| 欧美妇女性影城| 国产91免费在线观看| 亚洲午夜性刺激影院| 欧美黄色视屏| 日韩美女写真福利在线观看| 中文幕av一区二区三区佐山爱| 国产精品高清一区二区三区| 最新国产一区| 国产专区在线视频| 亚洲欧美日韩综合国产aⅴ| 精品国产一区三区| 美女免费视频一区二区| 久久精品aⅴ无码中文字字幕重口| 91在线视频18| 国产传媒免费在线观看| 日韩一区欧美一区| 九九九国产视频| 欧美美女直播网站| 五月婷婷六月色| 精品久久久999| 最新中文字幕在线播放| 91精品综合视频| 一道在线中文一区二区三区| 99热一区二区三区| 久久裸体视频| zjzjzjzjzj亚洲女人| 国产精品理伦片| 欧美一级片免费在线观看| 在线综合视频播放| 狠狠v欧美ⅴ日韩v亚洲v大胸 | 亚洲精品免费在线观看| 成人免费a视频| 亚洲国产精品999| 在线网址91| 成人在线中文字幕| 精品一区二区三| 欧美a v在线播放| 国产精品一级片在线观看| 巨胸大乳www视频免费观看| 午夜久久久影院| 国产叼嘿视频在线观看| 在线观看日韩av| 欧美舌奴丨vk视频| 国产九色精品| 欧美日韩一区二区国产| 蜜臀av免费观看| 久久久久久久久岛国免费| 国产污视频在线观看| 日韩欧美色电影| 久操视频在线观看| 国产欧美在线播放| 日韩欧美视频专区| 欧美在线观看成人| 99精品偷自拍| 中文在线观看免费网站| 日韩欧美国产一二三区| www免费视频观看在线| 成人激情视频网| 99久久婷婷| 国产女同无遮挡互慰高潮91| 亚洲一区中文在线| 精品人妻一区二区三区日产乱码| 精品国内产的精品视频在线观看| 国产精品久久久久久久久久齐齐 | 97免费在线视频| 伊人久久噜噜噜躁狠狠躁| 中国成人在线视频| 国内成人精品2018免费看| 亚洲精品电影院| 欧美吻胸吃奶大尺度电影| 国产网站在线免费观看| 91午夜在线播放| 欧美激情1区| 久久精品aⅴ无码中文字字幕重口| 亚洲国产精品尤物yw在线观看| 风流少妇一区二区三区91| 欧美激情视频一区二区三区不卡| 亚洲精品v亚洲精品v日韩精品| 日本国产中文字幕| 暴力调教一区二区三区| 国产污污视频在线观看| 亚洲欧美国内爽妇网| 欧美日韩五区| 亚洲精品自在在线观看| 国产成人免费av在线| 国产成人精品亚洲男人的天堂| 日韩av在线一区| 欧洲av一区二区| 亚洲欧洲日夜超级视频| 国产一区二区三区免费看| 亚洲国产精品免费在线观看| 日韩精品专区在线影院重磅| 日本乱码一区二区三区不卡| 欧美污视频久久久| 老司机精品视频在线| 曰本女人与公拘交酡| 欧美成人艳星乳罩| 99久久婷婷国产综合精品首页| 黄色一级片网址| 97久久精品人人爽人人爽蜜臀| 久久久999久久久|