摘要:8月24日,阿里雲資料庫技術峰會到來,本次技術峰會邀請到了阿里集團和阿里雲資料庫老司機們,為大家分享了一線資料庫實踐經驗和技術乾貨。在本次峰會上,特邀嘉賓映客直播架構師王振濤分享了映客直播作為創業公司從0至日活千萬的資料庫架構變遷,資料庫在直播中的經典應用場景,資料庫儲存的優化思路,以及如何構建一個高可用資料庫架構。
以下內容根據演講嘉賓現場視訊以及PPT整理而成。
本次分享的內容將主要圍繞以下四個部分:
一、映客直播發展歷程
二、直播遇上雲資料庫
三、風口上的資料庫架構變遷
四、直播典型應用場景分析
一、映客直播發展歷程映客直播是2015年5月份成立的一家公司,在移動直播領域,映客算是比較早成立的公司了。如上圖中所展示的就是映客APP上的一些頁面,左圖展示的是映客APP中的熱門內容,這裡是某一個反串演員主播正在進行直播,並且此時有5萬多觀眾正在觀看;右圖則是映客直播的發現頻道,發現頻道里面主要會在熱門時段對於平臺內的優質內容進行展示推送。在映客APP中會有很多類似於前面提到的這些頁面,包括了遊戲直播、健身直播以及小視訊等等。作為一個剛剛成立兩年的公司而言,映客直播的發展速度還是非常迅速的,從2015年5月映客成立之初,剛剛釋出APP時的DAU只有200,到當年10月份的時候DAU就已經達到了10萬+,再到12月份短短兩個月的時間,映客直播的DAU就已經突破了100萬,而在2016年中下旬的時候,DAU峰值就已經達到了千萬級別。以上就是映客直播大致的整體發展歷程。
二、直播遇上雲資料庫業務起步:映客APP釋出
接下來為大家介紹映客直播從開始到現在的架構發展演變過程。當映客APP剛釋出的時候,整個後臺其實只有三個人負責,兩個開發和一個運維,這時的架構如下圖所示。起初的架構其實是非常簡單的,後臺是由8臺虛擬機器搭建而成的,其中的6臺虛擬機器做了服務,而剩下的2臺做了儲存,儲存這部分則是使用了自己搭建的MySQL和Redis。在這個起步階段,對於後臺系統要求最高的一點就是當出現了產品想法就需要儘快地實現,這樣的架構非常適合在業務起步階段的需求條件下實現一個直播APP,當時第一版本的開發工作只花費了2周的時間。
業務發展:規模化
而隨著映客直播業務量的提升,從2015年的5月份到10月份,映客的DAU達到了10萬+,到12月份的時候就已經達到了100萬+,此時的業務發展已經達到了規模化的階段,這個階段的整體服務端的架構設計如下圖所示。此時的架構也是分為了接入層、業務層、基礎層以及資料層,但是此時的架構與第一版的架構相比,其整體的框架已經基本成型了,這一版的架構實現了一些業務拆分、資料拆分以及模組化複用,此時服務的底層仍舊是依賴於Redis和MySQL資料庫的。這個階段最為顯著的變化就是引入了雲,也就是說此時的服務已經上雲了。
對於映客的服務上雲,當時的考慮主要基於以下幾點:
DDoS,因為雲主機本身是可以防止DDoS攻擊的,這樣就可以將代理層全部都部署到雲上。直播存在一個天然的屬性就是有很多的明星活動以及其他大V的活動,因為這些活動所造成的瞬時壓力是非常大的,如果使用自建機房就需要為這些可能僅僅持續幾個小時的活動而囤積很多臺伺服器,需要購買很多預留資源。而使用在雲之後,雲主機本身是按照使用量進行收費的,這樣可以將很多核心業務都部署到雲上,在出現瞬時活動需要進行擴容的時候就可以直接進行擴容,當活動結束之後就可以釋放這些多餘的資源,通過這樣的彈性伸縮可以極大地降低直播平臺的運營成本。雲主機以及雲資料庫等這些雲資源使用的都採用的是叢集模式的部署,而其叢集模式往往也是對於使用者透明的,這樣就可以理解成雲本身幫助我們實現了一層高可用。平滑可伸縮,比如現在的資料庫的配置或者效能不能滿足需求了,就可以使用雲上的這些資源進行平滑地擴容,並且雲上的擴容對於服務是沒有任何影響的,而且包括SLB負載均衡器這些對於雲的使用者而言都是比較友好的。綜合以上四點的考慮,映客直播在業務規模化的階段就使用了雲服務。
而使用雲服務之後首先需要面對的一個問題就是:整體遷移。對於整體遷移而言,雲服務本身也提供了一些工具可以實現對於儲存進行實時地同步以及在最後進行切換,對於雲服務本身可以實現隔離以及製作映象之後就可以打包釋出到雲上去。映客直播當時的業務量也不是太大,所以整體而言遷移上雲的過程還是比較順利的。但是任何事情可能都不只會有好的一面,當使用了雲服務之後同時也會遇到一些問題,映客在上雲的過程中也遇到了一些不可控的因素,也就是“水土不服”。比如當時的雲服務的MySQL連線數是有上限為2000的限制的,而映客當時採用的很多服務框架都是屬於多程序的框架,這樣就非常容易造成連線數不夠用的情況;並且當時外網頻寬限制是200M,很容易就跑滿了,而且內網頻寬也是一樣的情況;另外,在使用雲主機的時候也會出現一些資源搶佔或者宕機的情況。綜上所述,在上雲之後有好的一面,也存在著不是特別舒服的一面。在這個過程中,映客不再僅僅是一個雲服務的使用者了,也演變成為雲服務的產品經理或者QA,映客與阿里雲也是從開始的接觸、了解到後來的“相愛相殺”,並且一起來推進雲服務的優化,一直到雙方都達到比較好的狀態。
業務爆發:千萬日活
其實雲服務本身是作為基礎設施的,它可以為我們提供高品質的運維。但是當業務的日活、流量還在爆發性增長的時候,單單依靠雲服務這種基礎設施的能力還是不夠的,所以映客的服務框架也在不斷地迭代中。如下圖所示的架構框架是在映客上雲之後,進行了一次比較大的改造之後的框架,這個框架就已經實現了一些分層,主要分為了使用者層、應用層以及資料層,並且在雲服務基礎設施上實現了很多服務化的重構、儲存的優化等的一些技術中介軟體,做了方方面面的工作來保障當業務爆發達到千萬日活的時候,服務還依舊能夠支撐住,這樣的框架本身的穩定性也還是比較不錯的。近期,映客在做的一件事情就是在目前體量已經非常大的情況下,如何去保證資源的獨佔和安全,目前也正在基於雲服務設施進行VPC的部署,而且這項工作目前推進得也還不錯。
雲上架構
其實大家可以看到,映客的整體架構是構建在雲服務基礎設施之上的。目前映客已經達到今天千萬日活這樣的體量還在使用雲服務其實主要基於以下幾個方面的考慮:首先,我們認為在現階段,雲服務的發展已經相對比較成熟了,並且也是比較安全的,雲服務在內部可以保障一些高可用的支撐;另外,使用者使用的所有服務基本上都是叢集化的,並且是支援平滑伸縮的,這些對於有活動運營需求的直播平臺而言都是非常有價值的;並且雲服務的使用者也不需要預留太多資源來支撐可伸縮的容量;除此之外,如果使用自建機房,那麼運維團隊需要足夠了解每一個環節,需要能夠支撐從硬體設施到監控以及上線等各個方面,這對於一個快速迭代的公司的運維團隊而言則是非常困難的,而使用雲服務則可以保障基礎設施的高品質運維。以上這些就是映客直播到目前千萬日活的體量依舊選擇在雲上架構未來的很重要的原因。
其實很多服務以及系統到最後歸結起來都是資料,所以雲服務的重中之重或者基礎的基礎就是雲資料庫,這也是映客的架構中一直在不斷地迭代和優化的技術層面。雲資料庫作為基礎,也經歷了從單機時代到叢集時代,再到分散式框架的發展演變。
三、風口上的資料庫架構變遷接下來為大家分享映客從0到千萬日活過程中資料庫架構變遷的歷程。談到資料庫,無論是像Oracle、MySQL這樣的關係型資料庫,還是像MongoDB這樣的NoSQL的資料庫,大家想到的第一點就是資料庫的效能。而資料庫主機的效能主要和以下的這幾個方面相關:CPU、儲存、索引、IO、鎖、記憶體以及內部的執行緒模型等,這些都是確保資料庫設計本身效能非常關鍵的方面。
在另一個層面,當資料庫實現了叢集化或者分散式之後,可能會面臨著另外的一個問題,也就是所謂的CAP理論。CAP理論就是當想要在一致性、可用性以及分割槽可容忍性這三個方面進行權衡時,需要結合具體的業務場景,針對於具體不同的業務場景需要有選擇地捨棄一些東西來保障核心的訴求。
在映客的資料庫設計演變的過程中,大致經歷了如下這樣的變化:使用者量從千級到了億級,日活從百級到了千萬級,大V粉絲從百級到了千萬級,送禮峰值的QPS從十級到了萬級,關係資料從千級到了千億級。大家可能會產生疑問,為什麼關係資料一下子從千級到了千億級。其實直播本身存在一些社交屬性,比如當用戶喜歡一個主播的時候就可能去關注他,每一條關注就可能產生兩條記錄,所以當用戶量已經達到億級的時候,關係資料的量就會是非常大並且也非常重要的。所以在這樣的過程中,千億級別關係資料的儲存所面對的挑戰還是非常大的。另外,直播本身也存在大V屬性,很多大主播可能具有千萬量級的粉絲,那麼在大V每開一次直播的時候,千萬粉絲都需要收到這個大V的開播提醒。而對於千萬粉絲而言,他們的關注列表中都要出現這個大V正在直播的關注資訊。同樣的,在大V關閉直播時,關注列表中的直播資訊就需要立即消失,這些對於實時性、高併發、大容量的要求是非常高的。除此之外,非常重要的一點就是映客直播本身是支援送禮的,而且映客還獨創性地支援了禮物連送,也就是比如我喜歡一個主播,可能送的禮物的價值是一毛錢,但是可以進行連續送禮,可以連續送幾萬次,雖然這對於使用者的體驗而言是非常爽的,但是對於伺服器而言則會造成非常大的壓力,所以萬級QPS的送禮的金融資料的壓力也是映客之前所面臨的很大的挑戰。
在這個過程中就涉及到基礎資料和雲資料庫之間的碰撞。一方面,基礎資料在爆發性地增長;另外一方面,雲資料庫需要能夠支撐資料的指數級增長的量級。
在資料庫架構演變過程中,無論是MySQL、Redis還是MongoDB這些資料庫層面的服務都經歷如下圖所示的變化,從單主機的模式到後來獨立主機,到後來的主從讀寫分離,再後來根據使用者、房間、資料等維度進行垂直拆分,而在維度已經很大比如像關係資料已經到達千億量級的時候還進行了水平拆分。
關鍵技術點-資料遷移工具
在資料庫架構演變過程中涉及到一些技術點,在這裡可以與大家分享一下。在資料遷移工具這部分在資料庫架構演變過程一直都扮演者極為重要的角色。對於同構資料的遷移而言,其實有比較成熟的方案,同構資料遷移相當於上圖中的從單主機一直到垂直拆分這部分,這部分由於所涉及到資料表的結構以及資料的拆分策略都沒有什麼變化,所以這部分工作直接依賴雲資料庫的遷移工具,就可以實現資料的平滑遷移,並且業務上基本是無感知的。這部分適用的場景就是伺服器級別、例項級別的擴容或者讀寫分離以及業務的垂直拆分等。但是當體量已經足夠大到需要做很多水平層面的拆分的時候,很多現有的成熟的工具就已經不太適用了。這個過程中,映客直播的技術團隊就基於binlog等日誌層面的東西開發了自己的一套異構資料遷移中介軟體,這個中介軟體比較適用於異構資料庫或者是做分庫分表的遷移。因為在分庫分表的過程中要想做到業務的儘量的無感知還是比較困難的,特別是在一開始是單庫單表,需要分成1000張表,在分完成1000張表之後是很難再回退過去的,這個成本是非常大的。如果在這個過程中異構資料遷移元件做的足夠好,就可以將風險降到很低。
對於異構資料的遷移方面可以為大家分享一個比較成熟的方案,比如需要從單庫單表的資料維度遷移到32個庫,每個庫32個分表這樣一千多張表的資料維度,此時可以首先把分庫分表的一千多張表的資料維度當做之前單庫單表的邏輯存庫,而這個邏輯存庫在一開始是通過基礎元件binlog觸發保證資料的實時同步的,其延時基本上可以達到秒級。如果是這樣就可以先將服務中一些讀的操作遷移過去,因為對於讀操作而言,即使出現問題進行回滾也是沒有任何代價的。等讀操作遷移完成,並且通過驗證比對沒有問題之後,再將比較少的寫的操作遷移過去,這樣就能夠從整體上降低很多的風險。
關鍵技術點-分庫分表中介軟體
第二個關鍵技術點就涉及到訪問分庫分表資料的問題。在實現這部分時,我們也調研了很多業界的方案。目前業界比較成熟的方案主要有兩種,分別是基於Client的和基於Proxy的,映客技術團隊對於這兩種方案在不同的時期也都實現過,但是總的宗旨是實現分庫分表的透明化。首先,對於業務而言,不需要關心SQL到底執行到哪一個庫表上,也不需要關心分庫分表的策略是什麼。第二個就是資料庫本身配置的統一管理,這部分就是對於線上資料的統一切換以及配置都是非常有用的。第三個就是需要提供統一的監控。第四點,既然使用了這個分庫分表中介軟體,就相當於所有的業務都是依賴於這個中介軟體的,其高可用設計就是非常關鍵的。另外,分庫分表中介軟體的設計本身是一個叢集模式,所以需要做好中介軟體和資料庫中間連結的管理,通過一些連線池的模型將連結更好的管理起來。包括後面的負載均衡以及更好的擴充套件能力等都是在實現分庫分表中介軟體時需要考慮的。
結合以上的技術關鍵點,映客的技術團隊實現了兩種分庫分表中介軟體,在最開始技術棧還比較單一的時候,實現了基於Client的分庫分表中介軟體——Mdriver Client,基於其分庫分表的lab庫可以實現將之前提到的大部分技術點都在內部整合。對於使用者而言,可能只需要關心初始化這個Client,然後正常地寫SQL,最終的結果可以直接匯聚好並返回回來,包括中間的路由策略都是由Client本身通過統一配置中心來實現的。這個過程中更多的會涉及到對於多語言的支援,相對而言其優點是當中間件掛掉或者宕機了所造成的災難性後果的情況會少一些。除此之前,在最近由於考慮到對於整個公司層面而言,可能不止有一兩種語言棧,而且不是所有的業務都需要使用Client來實現,這時候就應該提供更加友好的分庫分表中介軟體,所以後來映客技術團隊調研了Atlas Proxy這種形式,也在內部開發了一個支援分庫分表的Atlas Proxy版本。
關鍵技術點-分散式事務
第三個關鍵技術點就是分散式事務,在之前可能所有的業務在一個數據庫上通過一個本地事務就可以解決問題。而現在已經按照水平拆分和垂直拆分之後分成了很多個庫表以及很多個維度的模組,這時候就涉及到了資料一致性的管理。而在這一方面,從業務層面來說業務可能處理的比較多的有兩種分散式事務,第一類就是預佔資源型,預佔資源型就是比如是A要給B送禮或者A給B轉賬的時候,對於A而言需要扣錢,這部分是需要預先將錢扣掉的,然後再給B加上,這個過程對於A而言就是預佔資源型。預佔資源型有什麼特點呢?其實就是如果想要給B加錢,那麼就一定需要確保A的錢已經被扣掉了,而且這個操作如果在最終沒有成功,那麼對於A而言是非常容易回滾的。相當於預佔資源型的分散式事務的要求是實時的、同步的、並且能夠保證可回滾。這一部分現在業界使用的比較多的模型就是基於補償的,也就是tcc的分散式事務框架,這種框架模型也是電商以及金融相關的業務中使用的比較多的。
而第二類分散式事務則是給予資源型的,還是用剛才轉賬的例子,如果A給B轉賬,使用給予資源型事務,如果事務失敗了,但是B已經把錢轉走了,這時候想要回滾其實是無法實現的。所以對於給予資源型的分散式事務而言,要麼先不要給B把錢加上,也就是通過凍結資源的方式實現,要麼就是通過一些非同步必達的方式保證一定成功。而給予資源型事務的特點是其實時性要求不像預佔資源型事務那麼強,往往可以允許一定的延遲,比如A給B轉賬,B的錢晚到一會是沒有太大影響的。所以給予資源型事務的特點就決定了其比較適合實現一些非同步操作,並且保證其成功。
以上這些就是在實現了資料庫架構演變之後,映客技術團隊在分散式事務這部分所面臨的問題以及所做的事情。
四、直播典型應用場景分析任何脫離了實際應用場景的架構都很難說它是一個好的架構,所以還是需要去分析架構的應用場景,根據其應用場景來分析究竟應該選擇什麼樣的架構模型。接下來就與大家分享在直播這個應用場景之下,與資料庫相關的技術點以及如何進行資料庫架構的設計和迭代。
下圖中展現的是一個直播大V,直播大V的特點就是其粉絲會非常多,下圖中的大V有156萬粉絲,而最多的粉絲量可能會達到千萬級別。而粉絲量比較多所帶來的問題就是無論是推送量還是關注度,還是在直播間中的活躍度都會非常高。而且本身而言,大V的直播也會比較頻繁。此時對於平臺考驗最大的就是關係系統,所以關係系統必須能夠支撐高併發、大容量,並且對於關係系統的實時性以及一致性的要求也是比較高的。如果A關注了B,而到最後沒有關注成功或者並沒有收到B直播的推送資訊,那麼這就是一個故障。所以這對於關係系統的壓力是非常大的,因為任意使用者之間都可以建立相互的關係本身就是笛卡爾積的形式。
所以映客技術團隊也對於關係系統,特別是關係資料庫進行了一些優化。對於關係資料庫的優化主要分為了幾個層面,首先是讀寫分析,也就是關係資料庫這部分的寫和讀是分離開來的;另外還實現了寫的非同步化,寫這部分的每個關係的觸發所涉及到的寫的操作會非常多,這時候就應該確保關鍵路徑的寫操作成功之後,其他的都通過寫非同步化的形式來做;當千億級的資料爆發之後,資料量已經突破了資料庫單例項的限制,這時候就必須要做很多分庫分表等資料拆分方面的工作;另外在關係系統這部分還存在一個特點,就是訊息推送需要涉及到關係,兩人聊天也需要涉及到關係,而且每一種需要關係的場景都是不一樣的,所以需要做很多異構資料的處理工作。
通過下圖大家可以看到,在關係系統的主關係服務上有很多非同步化的優化。首先,要想實現這些優化需要確保關係服務本身以及關係資料本身是收攏的,有統一的入口。在這之後就可以統一地把控這個入口,然後確保關鍵路徑寫成功,其他非關鍵路徑都可以通過訊息佇列分發出去,並組建一些異構的資料來源或者不同型別的需要做很多Cache優化、異構資料構建等的工作。
從上圖中大家可以看到,如果主播有一千萬粉絲,那麼每次主播開播的時候,就需要將這一千萬粉絲都查詢到並進行推送,那麼這對於系統所造成的壓力將會是非常大的。所以在這個過程中通過Redis,通過一些資料拆分保證即使主播有一千萬粉絲也可以實現秒級推送。有一些場景比如在關注直播裡面,假如主播有一千萬粉絲,如果每次開播推送訊息時都對於這一千萬粉絲的資料進行寫操作,這將會對關係資料庫造成非常大的壓力,其實可以通過關注列表去反查有所有正在觀看直播的觀眾進行推送,這樣就可以通過推拉結合的方式將這些對於系統性能影響非常大的點通過異構的方式解決掉。
下圖展現的也是映客平臺的一個大主播,在日榜中,這個主播一天收穫到了四千多萬的映票,而一個映票相當於一毛錢。每個使用者可以一個小禮物、一個映票地去送,並且可以連送。這樣就會造成這一個問題,因為這四千多萬的映票在一天之內,甚至在一個小時或者半個小時內都送給這個主播,勢必會對於資料庫造成很大的壓力,如果主播的資料在資料庫中僅為一條資料,那麼會涉及到很多次更新操作。所以基於這樣送禮的高併發場景,映客也做了很多層面的優化,首先支撐送禮這個場景的金融屬性中的強一致性,雖然一個映票只有一毛錢,禮物的價值雖小但是仍舊是一個金融屬性,如果這一毛錢沒有到主播賬戶上,這將會成為一個金融故障,其影響將會是非常大的。而且粉絲無論在什麼時候送給主播一個映票,都需要看到主播的映票數量是在實時地增長的,如果在短時間內主播的映票數沒有增長,使用者可能就來投訴了,所以在這個場景下對於實時性的要求也是非常高的。除了需要保證金融資料的強一致性以及實時性的前提,還需要保證在多個粉絲同時向主播送禮時的高併發特性,這對於金幣系統的壓力也是非常大的。
映客直播金幣系統
映客的金幣系統在初始時的架構如下圖所示,也是依賴於RDS的MySQL叢集,一開始是單主機的,後來進行了獨立例項的優化,在這之後還進行了讀寫的分離,實現了讀寫分離之後其實業務包括送禮、支付和體現還是可以直接操作資料庫的。這個時候即便是金幣資料庫能夠支撐的量再大,穩定性再高,當遇到剛才提到的主播一天收到四千萬映票的情況時,資料庫所產生的延遲也是無法接受的。
於是映客技術團隊對於金幣系統也做了2.0版本的優化,與之前提到的關係服務類似,對於任何的資料層面的優化的前提就是首先需要進行入口的收攏,也就是讓金幣系統負責所有的與金幣資料相關的操作。當入口收攏之後就可以去進行多方面的優化,而第一個層面就是做分庫分表的工作,第二個層面則是對於主播收禮的過程進行非同步的優化。為什麼要實現這兩個層面呢?可能分庫分表並不能解決一個主播一天收到四千多萬次映票的壓力,而它解決的其實是另一個問題,就是將整體的併發吞吐量提升上去。可能最後觀眾送出的禮物都會匯聚到這一個主播上來,但是對於觀眾而言,每個使用者都是單獨的個體,對於他們而言,其操作並不會特別快,而且觀眾都是離散地分佈在不同的資料庫上的,這時候就可以通過分庫分表的將資料進行拆分從而將觀眾這一側的壓力降到最低。另一個層面,可以通過非同步的優化,如果在主播這一側的收禮比較多,就可以通過非同步序列的處理甚至是合併性的處理來保證系統的壓力處於可控範圍之內。而這兩層優化的前提還是入口必須收攏,這就是映客對於金幣系統優化的考量。
送禮邏輯解析
這裡為大家分享一個業務中比較核心的送禮邏輯。之前也提到送禮操作和收禮操作需要分為兩塊,並且每個部分需要採用不同的策略,一種是分庫分表的策略,另外一種是使用非同步化的策略來解決。
剛才提到的分散式事務中包括了預佔資源型事務和給予資源型事務。而送禮恰恰是預佔資源型事務,這就必須保障送禮有一個比較好的實時性並且能夠進行同步操作。因為送禮所涉及的是同步操作,需要具有實時性,並且送禮時觀眾端的比較離散的操作,通過分庫分表就能很容易地保證其有一個整體吞吐量的提升。這裡的同步操作並不是簡單的更新操作,一旦更新操作失敗了,就很難實現容錯處理。這裡就涉及到會對於每個操作本身進行本地事務的日誌記錄,如果失敗了或者造成一些冪等性的問題時就有據可查了。另外在收禮端,這裡相當於分散式事務中給予資源的操作,既然是給予資源,並且本身還可以容忍一定的延遲性,就可以在扣費成功之後將這個事務投入到一個訊息佇列中,通過訊息佇列來非同步地處理收禮操作,並且可以對於大小主播進行通道的隔離,這樣就很好地滿足了收禮方的需求。總體而言,這樣的邏輯設計保障了送禮和收禮雙方的穩定性、高併發以及實時性。
總結其實,資料庫本身而言是一個基礎設施。那麼作為基礎設施,我們主要可以依賴資料庫的穩定性以及基本的效能。而云為資料庫提供了專家級的運維,這一點也是比較好的方面。但是雲資料庫也不是萬能的,在前面提到的場景中的很多情況下必須在架構層面以及具體的業務場景層面進行具體的架構梳理和優化。在資料庫架構方面,我們一直都在路上,並且將會一直努力前行。