本文來自網易雲社區。前端
蜂巢計費系統爲網易雲計算基礎服務(網易蜂巢)提供總體的計費服務,業務範圍涵蓋完整的產品售賣流程,包含訂價、訂單、支付、計費、結算、優惠、帳單等主體功能,支持十幾種不一樣產品的售賣,產品形態上貫穿了IaaS、PaaS和SaaS類別。同時,計費方式還提供了了按量、包年包月、資源包等多種方式。該項目的業務範圍之廣,玩法種類之多,數據要求之嚴註定了它將成爲一個燙手的山芋,並且仍是一個吃力不討好的工做。git
該項目在人員上已經幾經易手,就我所知,已經換過兩撥完整的開發和測試團隊了,並且已經所有離職。不得不說,該項目已經變得使人談之色變,讓人敬而遠之。在這樣的背景下,後期接手的開發和QA不得不硬着頭皮上,踩着雷過河,當心翼翼的應對着不斷涌來的業務需求。隨之而來的是高居不下的bug率,愈來愈難以維護的代碼,沒法擴展的架構問題,咱們開始意識到這樣下去是不行的。因而咱們從8月份開始了漫漫的架構升級之路。github
在咱們開始優化架構以前,咱們從新梳理了計費系統完整的業務,獲得了以下圖所示的業務領域:web
梳理之後發現,計費系統承載了太多非計費的業務,包含訂單、帳單、結算和代金券等,這些業務代碼散落在各處,沒有嚴格地業務邊界劃分,而是「奇蹟般」的融合在了一個工程裏面。形成這個局面的緣由在於計費系統第一版設計時,根本沒有考慮到這些問題,固然也不可能考慮到,而在後面逐步地迭代過程當中,也未能去及時地調整架構,架構腐化不是一天內完成的。固然,這方面有部分技術的緣由,也有部分人爲的緣由所在,由於當時負責計費系統的開發就只有一人,仍是剛畢業的同窗。目前看來,也是難爲這位同窗了。數據庫
技術債務的問題不是小事,千里之堤毀於蟻穴。既然咱們找到了問題的癥結所在,那麼解決的方式也就顯而易見了,一個字:拆!咱們分析了全部的業務,訂單是最大也是最複雜的一個業務,而結算和帳單考慮到後期有可能遷移到雲支付團隊,咱們決定優先把訂單系統拆分出去!緩存
訂單拆分提及來容易,作起來難。套用一句業界常說的話,就是開着飛機換輪胎。由於在咱們拆分的同時,不斷地有新的業務需求進來,還有一些bug須要處理,因此不太可能讓咱們專門進行拆分的工做。所以,爲了避免影響正常的業務迭代,咱們決定拉出獨立分支進行開發。咱們分出兩人專門處理拆分的工做。網絡
爲了最小化風險,訂單拆分咱們分了兩步進行:一,模塊獨立;二:系統獨立。架構
模塊獨立是將訂單的代碼首先在工程內部獨立出來,咱們採用獨立Module的形式,將訂單獨立成了一個Order的模塊。它擁有徹底獨立的服務層、業務層以及持久化層。其餘模塊能夠依賴Order,而Order不能依賴除公共模塊外的其餘業務模塊。總體的模塊劃分以下圖所示。模塊的拆分過程當中咱們也發現了原先不少不合理的地方,例如:其餘服務直接操做訂單的持久化層(DAO)、模塊直接依賴關係混亂、Service所在的Pacakge不合理、存在大量無用的代碼和邏輯、隨意的命名等。咱們邊拆分邊重構,雖然進度比預期要緩慢一些,但總體上在向着合理的方向進行。框架
模塊獨立的過程當中咱們遇到了業務層級關係的問題。因爲訂單模塊再也不依賴於其餘業務模塊,而又有一些業務邏輯是由訂單觸發的,須要在計費模塊完成,咱們又不能直接調用計費模塊的Service。針對這個問題,咱們採用了領域事件
的方式來解耦,簡單來講就是訂單經過發佈事件的方式來與其餘模塊進行通訊,當時實現的代碼其實也至關簡單。運維
咱們並無獨立拆分web層,由於系統尚未獨立,web層做爲統一的打包入口也承載着訂單的流量。並且,Controller層的邏輯相對比較簡單,徹底能夠在系統獨立時再作。經過你們的努力,8月底訂單已獨立模塊的方式上線了,一切正常。
模塊拆分完成後,僅接着就是系統獨立,此時咱們須要將訂單系統獨立部署。這裏一個關鍵的問題是,獨立部署意味着單獨提供服務,而依賴訂單系統的業務方很是之多,包含前端、主站、大部分的PaaS業務和計費,都有須要直接依賴訂單接口的地方,冒然獨立風險很大。針對這個問題,咱們採用使用haproxy七層轉發代理來將流量分發到不一樣的vip來解決。雖然,在上線過程當中遇到了一些坎坷,但最終仍是成功了。如今看來這個選擇是很是對的,由於這樣能夠在業務方無感知的狀況下平滑升級。但長遠來看,最終咱們仍是以獨立的vip對外保留服務。
訂單和計費直接咱們採用RabbitMQ來完成主體通訊,關於採用MQ仍是HTTP調用咱們內部還進行了一番爭論。之因此最終仍是採用MQ來進行通訊,是由於咱們發現不少業務流程並不須要計費系統當即響應(大部分流程都是訂單觸發的),也就是咱們常說的弱依賴。另外,職責上計費系統的響應的質量也不該影響到訂單的主體流程,舉個例子:用戶支付了一個雲主機的訂單,若是計費系統此時沒法響應,業務上相對來講能夠接受過一小會兒計費再處理,而不是把訂單直接退款給用戶。MQ的引入在技術和職責層面都將訂單和計費分的更開了。固然,強依賴的服務是咱們沒法避免的,其中之一就是結算模塊還留在計費中,訂單須要經過接口調用結算服務來完成支付。
前期,咱們在模塊獨立時採用事件解耦的方式,在此時也得到了收穫。咱們經過一個統一的轉化層,將那些事件直接轉化層RabbitMQ能夠識別的消息,這樣代碼的改造工做就大大減小了。
系統獨立後一個直接的表象就是每一個系統的代碼行數大大下降了。獨立前,總體的代碼行數已經達到了12W行以上(包含配置文件),獨立後,計費系統下降到了10W如下,訂單維持在4W如下。代碼行數的下降將直接提升系統的可維護性。我的認爲若是一個工程裏的代碼超過10W行,那麼維護性將大大下降,除非是那些有着嚴格自律意識的團隊,不然,我建議仍是儘可能下降代碼行數。
通過你們一個月的努力,訂單系統終於已獨立的姿態提供服務了。過程很艱辛,可是收穫良多。
訂單獨立後,一個直接的好處就是咱們能獨立的思考問題了,這在之前是很難作到的一件事情,由於你們不得不當心翼翼的處理那些依賴,作事會畏手畏腳的。另一個好處就是,咱們的工做能夠有側重點的進行了。訂單業務能夠說是產品最爲關注的業務,也是計費對外暴露的主要入口。 下圖就是咱們在拆分後規劃訂單的業務架構,你們對後期的訂單規劃充滿期待。
公有云產商面臨的一大挑戰就是多Region環境的支持。普通的互聯網行業出於高可用的考慮,每每會把核心系統部署到多個機房,而後根據本身的實際應用場景選擇冷備、雙活甚至三活。咱們常常聽到的「兩地三中心」、「三地五中心」等等高大上的名詞就是代多機房高可用的縮影。這些行業作多機房部署的主要目的是爲了提升系統的可用性,不是其業務的必須屬性。換句話說,他們不作多機房部署也能夠,作了固然更好。而公有云產商不同,多Region部署就是其行業屬性之一。若是哪一個雲產商不提供多region產品的支持,那麼它確定是不完整的。不得不認可,咱們在這方面的經驗是比較欠缺的,在多Region的支持上走了一些彎路。
今年上半年的時候,蜂巢開始計劃啓動北京Region,預計年中交付,當時對咱們橫向業務提出了很大地技術挑戰。一是在於橫向系統設計之初並無考慮到對Region環境的支持,咱們很被動;二是咱們並無跨Region系統設計的經驗,咱們很着急。計費系統面臨的問題更加嚴重,由於它對數據的一致性要求更高,並且出錯地影響範圍也更大。並且當時計費的技術債務已經很高了,產品的需求列表也拍了很長,套用一句很形象的話說,「留給咱們的時間很少了」。
在這種狀況下,咱們「膽戰心驚」的給出了初版的多Region設計方案,主體架構以下所示:
由於當時計費系統尚未拆分,全部的業務都在一個系統中完成的,就是咱們常說的「大泥球」系統。這種狀況下咱們很難作到多Region部署,訂單和帳單其實只有在一個Region部署就能夠了,而計費的數據採集和請求分發是要下沉到各個Region的,而計算過程能夠集中完成。採用"雙主"同步複製的方案實則是無奈之舉。數據庫的同步只能基於實例級別,而沒法細分到表,咱們各Region中計費數據庫中存在資源的計量表,這個數據須要同步到杭州Region來完成。爲了不「腦裂」的問題,咱們特別將該表的主鍵採用UUID的形式。存量表由於沒法作大規模修改,咱們經過限制北京MySQL用戶的權限來避免寫入和修改全局表。
這個設計很糟糕,可是當時的條件限制,咱們也拿不出更好的設計了。雖然上線的過程有些曲折,當這個架構仍是成功運行了,這是令咱們最爲欣慰的事情。由於爲了適配這個架構,團隊的小夥伴作了不少工做。不能否認,這個架構存在諸多弊端,其中最大的隱患就在於數據庫的「雙主」同步,這就像一顆隨時會爆的炸彈縈繞在咱們心頭。當時專線尚未搭建好,全部的流量均經過外網隧道代理,糟糕的網絡質量無疑放大了這個風險。爲此,DBA們向咱們吐槽了很久,幸虧咱們抗打擊能力很強。
在作完雙Region的支持之後,計費團隊就繼續作產品需求了,由於架構調整致使需求列表已經很長了。並且當時也說的是,短時間內(至少今年)不會再有第三個Region了,咱們也想着快點作完,多花點精力投入到重構中。可是計劃趕不上變化,9月底咱們被通知到第三個Region來了,並且已經被提升到第一優先級支持了。
有了初版雙Region的經驗,這一次咱們淡定了不少。固然,咱們不可能在沿用初版的設計了,由於DBA就會跟咱們拼命的。回過頭來梳理多Region支持面臨的問題時,我發現一開始咱們就本身給本身挖了一個坑,而後往裏面跳。橫向支撐系統顯然都須要對全部Region提供支持,但這並不表明其須要在各個Region內部署(我還與團隊其餘的小夥伴分享了這方面的想法,網上應該還能找到這一次分享的ppt——《跨Region實踐初探》)。由於公有云產商常常會提供多個Region的服務,有得甚至達到幾十個Region,若是橫向支持系統每一個Region都要全量部署的話,那麼咱們花在運維上的精力就能夠拖垮咱們,更不要說還有最爲困難的數據的一致性問題。
其實多Region的支持的問題咱們總結出主要表如今一下兩個方面,一是應用層面的接口互通;二是底層數據庫的同步。
咱們先說底層數據庫的同步,對計費系統而言,數據的一致性是相當重要的,但多機房部署是在挑戰CAP定律。是否是就沒有了這樣的數據庫方案了呢,有,那就是Google的Spanner,號稱能夠在全球作到強一致的數據庫。可是咱們沒有這樣的數據庫。其實咱們也考慮使用NoSQL數據庫——Cassandra,可是這個數據庫運維起來太複雜,咱們也沒有這方面的經驗,也就放棄了。仍是迴歸到MySQL,受限於傳統關係型數據庫在擴展性方面的問題,咱們不可能把整個庫在各個Region都同步一份。可是計費原始數據又必須在各個Region內收集,因而咱們決定——拆,把計費拆層兩個部分,分爲bill-agent(數據採集)和bill-central(數據計算)兩個部分。
經過這樣的拆分,架構就清晰多了。再多加Region,咱們只須要部署Bill-Agent就能夠了。Bill-Agent將處理過的計費數據寫入本地庫的一張資源表,利用NDC(馬進在網上分享過關於這個中間件的介紹)將資源表單向同步到Bill-Central的中央庫,而後Bill-Central統一在對計費數據進行處理。有意思的是,這張資源表就是咱們在初版設計中新建的資源表,由於咱們將主鍵修改成UUID,全部使用NDC同步表的方案是至關順利的。固然,NDC在咱們其餘項目的跨Region支持上也發揮了重要做用,好比:跨機房緩存更新的問題。這一版的數據庫方案在技術評審時你們都比較滿意,DBA也確定了咱們的方案。
如今再來看跨Region調用的問題。在多Region的橫向系統中,咱們發現或多或少的存在着機房間的接口調用問題。這些問題有多是某些Region的庫不能寫須要路由到主庫來寫致使的,也有多是全局緩存的問題,還有就是Global業務向Region內服務發送指令。計費屬於最後一種場景,咱們有一些業務場景須要由杭州Region觸發,而後調用各個Region內的服務的接口。在初版的實現中,計費系統本身實現了跨Region代理部分,可是實現的不是很好,代碼的可維護性比較差,加劇了調試的難度。這一版的設計中,咱們決定把跨Region接口代理單獨拿出來從新作,結合多Region的應用場景,而後封裝一些非功能性的特性,這就成了後面咱們很重要的一個組件——RegionProxy。
RegionProxy最開始是爲了解決跨Region調用的非功能性問題,簡化應用系統處理的成本。可是設計上經歷了比較大的調整。最開始的設計咱們是但願Region內全部跨Region的HTTP調用都能經過RegionProxy來代理,RegionProxy之間可以發現對方而且相互通訊,那麼Region內的應用系統就只須要與本Region的RegionProxy通訊就能夠調到任意一個Region的應用系統了。可是在方案評審的過程當中,咱們發現若是都用RegionProxy代理,可能會致使跨Region調用多出一跳或者兩跳,調試可能會比較困難。後來,咱們放棄了這個方案。再後來,咱們發現ServiceMesh的方案和咱們最初RegionProxy的方案是十分類似的。
在RegionProxy的設計上咱們進行了簡化處理,咱們將全部Region的業務系統錄入到一個全局的配置中心(咱們本身開發的ConfigCenter)中,而後經過一個本身開發的一個HttpProxy的Java庫來與ConfigCenter通訊來完成跨Region的調用。這樣作的好處就是使用方用起來比較輕量,可是在網絡連通性方面咱們須要與全部Region的系統作到互通。在開發Proxy庫的時候,咱們不只對跨Region的HTTP調用進行了封裝,並且對普通的HTTP調用也加入了非功能性的封裝,這樣系統能夠經過Proxy庫完成全部的HTTP調用請求,極大的簡化了代碼的維護成本。後面,咱們使用RegionProxy來代理請求後,確實刪除了不少之前的無用代碼,總體流程上也清晰了許多。
通過兩版多Region的改造,咱們確實收貨了不少寶貴的經驗,很是可貴。實際上,在多Region的支持上,你們須要清晰地認識到爲何要支持多Region,以何種方式去支持多Region,多Region支持與高可用的關係等基本問題。若是這些問題回到很差,或者不清楚,那麼很容易就會掉到陷阱中去。另一個感悟就是結合業務的實際場景,第二版的多Region架構咱們之因此可以這麼設計,就在於計費系統不須要實時出帳,咱們徹底能夠把數據保存下來,離線計算之後再出帳,這是能夠接受的。但這並不適用與所用狀況,有些性能要求很高的橫向業務就不適合這種場景。
前面提過幾回技術債務的問題,有些問題是能夠經過工具來解決了,有些只能經過內部重構來解決。左耳朵耗子曾經說過一句話對我感觸很大,大意是說有些公司在解決問題時偏流程,有些公司偏技術。我想咱們既然是技術團隊,在解決問題時能經過技術方式解決的就應該儘可能用技術解決,流程和人都是不可靠的。
計費項目面臨的諸多問題之一就有配置文件的管理,由於業務流程的緣由,計費系統有着大量的各類各樣的配置。之前咱們把配置文件放到工程裏面,經過自動化部署平臺來指定使用不一樣的配置文件。這樣作的一個顯著問題就是代碼和配置耦合起來了,每次修改什麼配置都得提交代碼,而咱們提交又有着一套嚴格地流程,致使總體效率不高。另一個問題就是可視化的問題。每每QA在線下環境測試都是經過的,而上線之後出了問題,基本上都是配置致使的問題。針對這幾個的問題,咱們決定使用Apollo來管理咱們的配置,經過整合Apollo,咱們的兩個項目(訂單和計費)都作到了工程零配置,全部的配置都放到Apollo上進行管理,好處良多。
計費系統嚴重依賴於定時任務,有許多流程須要經過定時任務來推進。之前咱們使用QUARTZ+MYSQL來做爲咱們分佈式定時任務框架,可是這種作法的可維護性太差,並且對數據庫侵入很高,對測試也不友好。在QA的不斷吐槽中,咱們決定替換掉現有的定時任務框架。在調研開源的定時任務框架後咱們決定使用Elastic-Job來做爲咱們的分佈式定時任務框架。目前,咱們的兩個項目的全部定時任務(除bill-agent外)都已遷移到Elastic-Job上來了。
若是你要問我作蜂巢計費最困難的地方是什麼?個人回答確定是業務太複雜了。這種複雜性不是由於咱們架構設計的很差致使的複雜,而是業務自己就是十分複雜的。如今計費系統須要支持十幾種產品的售賣形式,涵蓋IaaS、PaaS和SaaS的絕大部分產品,同時各個產品的售賣和計費模式都存在或多或少的差別,這讓咱們很難經過一個統一的模型就涵蓋全部的場景。咱們找到了一條緩解這個問題的方式——抽象化。
橫向系統或者支持系統若是須要服務多個產品,那麼抽象化設計是不可或缺的一個緩解。若是越早進行抽象化,那麼後期對接和維護的成本也就會越低,還能把系統的邊界劃分得更清晰。計費系統早期的設計在抽象化方面沒有過多的規劃,在後期的對接方面又處於比較弱勢的一方,致使計費系統出現了大量的特化代碼。這些特化代碼對一個服務十幾個產品的支持系統無疑是傷害巨大的。如今咱們已經意識到了問題的嚴重性,也着手在作這方面的重構工做了。可是挑戰依然很大,由於業務的複雜性是沒法經過技術手段就能下降的,這方面咱們只有和產品、運營和銷售各方面一塊兒努力,打造一個合理、靈活、穩定的新計費。
抽象化設計由於咱們還在進行中,後期有機會再分享。
從八月底加入計費團隊以來,收穫良多,不管是在技術上,仍是在對業務的理解上,都有了許多新的認識。最爲給力的仍是團隊的小夥伴們,由於計費自己的需求很是多,處理這些需求的人都只剛剛夠。後來咱們又作了兩版跨Region改造、訂單拆分、框架替換、抽象化等優化工做,迭代週期從兩週一次壓縮到了一週一次,開發和QA的小夥伴也都是不辭辛苦。固然,你們能在這個過程當中有所收穫纔是最關鍵的。
計費系統能夠說是我接觸過的最爲複雜的一個系統,越是複雜的系統越須要清晰的頭腦和良好的設計。雲計算產商的博弈已經到了白熱化階段了,你們拼的不光光是每一個產品的質量和體驗,還有整個雲平臺的內功。公有云平臺自己就是一個龐大、複雜的系統,如何把這個系統建設好,用戶體驗作好、服務質量提升、穩定性獲得保障,這自己就是極爲有難度的一件事情。計費系統做爲公有云平臺一個重要的組成部分,能夠說扮演着一個極爲關鍵的角色。作得好能夠對整個平臺提供助力,而作的差則會拖慢總體的發展進程。咱們已經找到了適合本身的一條道路,相信會走上正軌!
網易雲計算基礎服務深度整合了 IaaS、PaaS 及容器技術,提供彈性計算、DevOps 工具鏈及微服務基礎設施等服務,幫助企業解決 IT、架構及運維等問題,使企業更聚焦於業務,是新一代的雲計算平臺。
本文來自網易雲社區,經做者蔣文康受權發佈。
原文:蜂巢計費系統架構升級之路