乾貨 | 餘額寶大規模服務化的技術創新
來自專欄 sofastack15 人贊了文章
本文作者:李鑫,天弘基金移動平台部任技術總監兼首架,主要負責天弘移動直銷平台的整體技術架構和技術團隊管理。
在此之前,在華為的中間件技術團隊,任六級技術專家,主導了多款華為軟體的雲計算產品的規劃、設計、構建及落地工作,包括 APaaS、ASPaaS、服務治理平台、分散式服務調測框架等幾款產品;更早之前,在噹噹網的運作產品中心做技術負責人,主要負責電商中後台的倉儲、物流、客服等系統的重構優化及技術管理工作。 個人從業十多年,做過很多技術方向,也是從項目版本研發慢慢到中間件研發,再到平台研發,涉及的技術領域比較多。在並行計算、大規模分散式服務及治理、中間件雲化及服務化(PaaS)、APM 監控、基礎開發平台、數據集成等領域都有些技術積累,如果大家在這些領域有疑問或好的建議,歡迎加我的微信號共同探討。
七月初,李鑫在 ArchSummit 深圳大會上做了題為《餘額寶大規模服務化的技術創新》的分享,現場反響熱烈。現將 PPT 和講稿整理出來,分享給大家,希望能給大家一些啟發,也歡迎留言討論。
分享分為三部分:- 餘額寶的整體架構變遷歷史;
- 講一講我們是如何進行基金實時銷售平台及大數據平台的服務化改造的;
- 「服務化」對我們運維及研發模式的影響及我們的應對策略;
當時的系統容量設計上限是能支持千萬級用戶,傳統基金銷售模式是走代銷機構的方式,投資基金用戶也多以理財為目的。所以每天可能處理的帳戶開戶也就是幾萬到幾十萬的規模。由於餘額寶對接是支付寶,支付寶的用戶規模達到千萬級,這是設計產品時對需求的定位。按照預估,這個設計容量怎麼都能扛個幾年。結果,我們低估了餘額寶的熱度,上線短短10天左右,新開戶數已經突破100W了,按這個速度,3個月左右資料庫容量上線就要被突破。事實也確實如此,我們的客戶量三個月就達到一千萬,餘額寶一期系統的生命周期只有三個月!
所以一期系統一上線,團隊就不得不開始考慮擴容,計劃將容量擴充30~50倍,計算規模也同步增加。如果還是採用一期系統的純商用軟硬體的模式進行橫向擴展的話,成本要達到1~2個億,這樣的成本對於當時天弘這樣一家小型基金公司而言,是不可負擔之重!因此,在阿里的建議下,我們決定整體上雲。 當時做出這個決定的壓力還是非常大的,因為當時沒有任何一家金融公司和基金公司這麼玩,我們成了第一個吃螃蟹的人。但在巨大的成本壓力之下,我們走出了這一步,這就是餘額寶 2.0! 餘額寶 2.0 的架構中用阿里雲上的軟負載均衡 SLB 替換了硬體負載均衡;用阿里雲上的虛擬計算單元 ECS 替換小型機成為前置服務及中間件服務的伺服器;用阿里雲的 web 服務替換了前置伺服器上的 weblogic。最大的一個變化是資料庫層面,原來的 Oracle 單庫被替換成了阿里雲上的一個 50 個節點的 RDS 集群,實際上就是進行了分庫分表的拆分,經過測算,需要使用 50 組業務節點,但在拆分時,考慮到擴展性,並未簡單地拆分成 50 份,而是根據用戶帳號 ID 作為分片主鍵將核心業務表拆分成 1000 份,然後每個節點處理 20 份數據(物理子表)。這樣做的好處是將來如果系統遇到瓶頸,需要擴容時,不需要對拆分演算法進行修改,而且數據平均遷移時只需要以庫為級別進行,從而避免了拆表。
上雲後,不僅資料庫總容量有了幾十倍的提升,交易處理效率也從一期的 120W 筆/小時提升到了近 2000W 筆/小時;最大清算時間從之前的7個多小時降低到了不到 2 個小時;而總成本,才增加了不到 1 倍。所以,上雲對我們系統整體效率的提升和成本的降低有非常明顯的作用。餘額寶 2.0 的架構穩定運行了近 3 年,中間經歷了幾次小的升級優化,有力支撐了這個過程中的各種業務創新,但也在支撐業務快速發展中埋下了一些「坑」。 2016 年,我們決定對系統進行一次大升級。這次升級的主要基調就是「業務邏輯重構,優化清算流程」,這就是餘額寶 3.0! 這一階段的業務邏輯複雜度要遠遠高於 3 年前,因此 3.0 的架構中,計算單元比 2.0 有了明顯的擴充。由於資料庫前期預留的 buffer 比較大,並沒有成為本階段的瓶頸,依然維持了 50 個節點的規模。這次升級之後,總體計算能力有了較大幅度的提高。處理效率比起 2.0 的時候增長了 2 倍多,也支撐了 2016 年春節期間日交易4億多筆的峰值。但是,清算時間比 2.0 時期有較大增長,這成了系統的一個「隱患」。2017 年,為了配合支付寶拓寬線下支付場景,並將業務推廣覆蓋到三、四線城市,同時,也要解決清算時間過長的隱患,我們規划了餘額寶 4.0 的升級。 這一階段的系統規模已經很大了,如果還是按 3.0 的架構進行橫向擴充的話,成本將呈線性增長。經過綜合考量,決定在升級前,先優化單節點的處理性能,提高效率及負載後再擴容,以降低總體的升級成本。 因此,這次升級首先將直銷和代銷業務合二為一,同一台計算單元既處理直銷業務,也處理代銷業務,提高了單點的處理效率。在此基礎上,再進行擴容,將計算單元從 340 節點擴充到 480 節點,資料庫擴容4倍。通過這兩步優化及擴充動作,最終系統的容量及計算能力均有了 4~8 倍的提高,這套新的架構支撐我們平穩度過了 2017 年雙十一及春節的交易高峰。
看到這裡,大家可能會有疑問:一般在系統服務化改造中,服務是會被拆的越來越細,為什麼我們反其道而行,反而對服務進行了整合?這是因為,餘額寶發展到現在,業務模式已經比較成熟,通過細粒度的服務模態來保證可擴展性的需求已經不是那麼強烈;另一方面,系統規模龐大,擴容的成本成為重點考慮因素;綜合這兩方面的考量,適當增加單點的複雜度,可以降低整體成本。
目前,針對餘額寶這單只基金已經建立起了一套完整的技術生態體系,它的核心是餘額寶的產品、帳號、交易、清算等模塊,在結合實時調用和文件交互兩套介面的基礎上,構建了電商大數據的分析體系及一系列輔助支撐系統。同時,餘額寶系統和其它第三方系統也有大量的交互,包括支付寶、監管、直代銷渠道等等。 我們是如何進行基金實時銷售平台及大數據平台的服務化改造的餘額寶系統的建設直接鍛煉了天弘的技術團隊,讓我們明白了大型互聯網應用是一個什麼樣的玩法,這也直接推動了天弘自有的基金直銷平台的服務化改造。接下來,我將從基金實時交易平台及大數據平台的服務化改造這兩方面來對此分別做詳細介紹。在開始這塊的內容之前,先簡單的給大家介紹一下基金公司的業務。
基金公司最主要的就是「買賣」基金,我們從直銷和代銷渠道把交易請求「接」進來,在我們的核心交易系統進行申購、認購、定投、贖回、轉換等等操作,這個過程里,會涉及與支付渠道、銀行等一系列的交付;同時,還有大量的清結算和TA清算,這個過程里,還要和銀證監會、中登等一系列監管機構有數據上的交互。聚攏過來的巨量的資金會被統一管控、並投入到股市、債市、貨幣市場等投資市場去賺錢收益。圍繞這個業務還有相應的投研、基金產品管理、風控、客服等中後台的業務支持。以上,就是基金公司的日常業務模式。在天弘早期的基金銷售系統的建設中,其實沒有什麼服務化的概念,當時的模式是有什麼類型的業務,就針對這種業務單獨開發一套獨立的銷售及清結算系統。由於業務量普遍不大,這些系統往往採用單體架構模式,不考慮橫向擴展性。經過多年的發展和積累,內部多套直銷及代銷交易系統並存,系統間帳號沒有打通,用戶的資產數據無法統一,用戶體驗差;另一方面,各系統間功能重複的現象嚴重,不僅重複佔用軟硬體資源,版本的控制也很麻煩。這種狀況甚至在我們整體遷移到雲上之後還存在了很長的一段時間,所以,所謂的「服務化」,並不是僅僅上雲那麼簡單,它更多的還是涉及到架構思維的轉變。在這裡,要重點強調的是數據質量監控。對於離線分析,數據的精度及實時性普遍要求不高;但對於二次使用的匯總數據,則有很高的精度要求和實時性要求,比如用於用戶競賽排行並有獎勵的一些運營活動,由於每筆交易記錄都與錢掛鉤,用戶對準確性很敏感,稍有不慎就會引發大規模的客訴,因此我們在自構建的規則引擎基礎上,利用數百條預先定義的規則,對數據進行抽樣或者全量的檢測,一旦檢測到異常,則會自動觸發人工訂正或者自動化數據訂正的任務。
我們目前使用的服務化的底層框架是螞蟻金融雲提供的 SOFARPC。SOFARPC 提供了相對簡便的服務暴露及服務接入的方式。如上圖所示,它可以基於Spring的擴展標籤機制,把一個 Spring 的 bean 實例以特定的協議暴露為遠程服務,也可以以介面的形式把一個遠程服務接入進來,同時,它還提供了鏈式過濾器的機制,對所有的服務調用請求進行一個串列式的處理,我們也可以通過它來實現一些自定義的服務管控策略,包括服務 Mock,線上數據採集等能力。單有分散式服務框架,還不足以保證服務化的平穩落地。企業服務化之路要走的順暢,一定是要兩條腿走路的,一條腿是分散式服務框架,另一條腿是服務治理,只有兩條腿都健壯,路才能走的順暢。阿里雲結合它線上的資源編排和資源調度能力,為 SOFARPC 提供了相對完善的服務生命周期管理的能力,能夠實現諸如服務上線、下線,擴容、縮容等操作。同時,螞蟻還提供了一個叫 Guardian 的組件,通過它可以實現對線上服務的熔斷限流的自動保護機制,類似 Netflix 提供的 Hystrix 組件,大家有興趣可以去了解一下。我所在的移動開發部門,採用了螞蟻的移動開發平台 mPaaS 框架,mPaaS 是類似 OSGi的一套模塊化的插件管理框架,APP 應用需要的各種基礎能力都可以以插件的形式集成進來,它提供的服務遠程調用能力就是基於 SOFARPC。我們看一下上面的這個圖,mpaaS提供了統一的服務網關,可以實現對服務端的 SOFA 服務的遠程調用能力;同時,它還提供了日誌網關和消息網關,可以實現對 APP 上的埋點信息的採集服務及消息的推送服務。通過 mPaaS 這套框架,我們可以相對方便的實現移動應用的開發工作。 以上就是目前我們基金實時銷售平台的整體服務化的一個狀況,接下來,我們再介紹一下服務化對我們研發和運維的影響。 「服務化」對我們運維及研發模式的影響及我們的應對策略服務化的本質就是一個「拆」字,原來的單體應用被拆成了大大小小的應用集群和服務集群,並被分散到網路的各個節點,由不同的團隊負責,每個團隊各管一段。在這個背景下,運維和研發都會遭遇一系列新的問題和挑戰,包括故障的定界定位、調用關係的梳理、集群環境下的調試及分散式環境下的事務一致性的保障等等。 接下來就來看看,我們是如何破解這些困局的。【只有更高效的收集線上服務的日誌,才能更好的對服務進行監控和管控】傳統的日誌收集一般採用諸如 log4j 這類的日誌組件進行日誌的落盤,再通過 logstash 或者flume 這類的日誌採集組件進行落盤日誌的增量收集,通過這種方式進行日誌採集存在大量的磁碟 IO。對於線上伺服器來說,最大的性能瓶頸就是磁碟 IO,尤其是在高並發和高負載環境下,磁碟 IO 對系統性能的影響會被成倍放大。我們的測試顯示,在整個系統負載被打滿的前提下,日誌採集所產生的整體性能消耗佔了總資源的 40% 左右。 為了降低系統資源佔用,同時更高效的採集服務日誌,我們開發了無磁碟IO的日誌採集方式。具體流程是:採用類似 Spring AOP 的方式,對服務的請求進行擋截,採集服務的調用延時、服務狀態等信息;同時,根據自定義的配置,抓取特定的入參和出參數據,所有這些信息都會被封裝到一個消息對象之中,並扔到一個內存消息隊列之中進行緩存;與此同時,有獨立的線程對這些消息進行預處理(如果需要的話),預處理結果也會被壓入內存消息隊列中再次進行緩存;最後,由獨立的發送線程將這些內存消息隊列中的原始日誌或者預處理數據發送到遠程的日誌手機端。 在日誌的收集端,接進來的日誌統一被扔到內存消息隊列中緩存,再被分散到不同時間片段對應的二級消息隊列中,由獨立的分析器實例集合進行分析和落盤存儲,通過這種純內存+全非同步的處理方式,我們就可以最大限度的避免資源鎖的競爭,並榨取伺服器的性能,從而實現對日誌的高效的處理。 通過這套體系,在不堵塞的情況下,任何一個服務節點的故障,在1~2秒之內就能被我們的分析器捕捉到。如果把分散式服務框架比作是「咖啡」的話,那應用性能管理中的調用鏈監控及分析就是「奶昔」了,咖啡和奶昔是什麼,是絕配! 調用鏈比常規的日誌收集方式更關注日誌之間的關係,它通過一個統一的 traceId 把不同服務節點上的日誌聚合在一起,來完整描述一個請求的調用過程,通過這個調用鏈路,我們可以發現服務的性能瓶頸在哪裡、埋點的缺失情況、網路的質量等等一系列信息,通過調用鏈的聚合,還可以獲取到服務集群的負載和健康度等更複雜的信息。 調用鏈能夠有效解決分散式環境下的監控需求,但在使用調用鏈的過程中,也要平衡全採集還是抽樣採集、自動插碼埋點還是手動埋點、實時統計還是預統計等等這些問題,如何權衡,需要根據自身的特點及技術實力來做決策。今天由於時間關係,不在這裡展開了,如果有感興趣的同學,可以關注我在大會後兩天的深度培訓《微服務治理的探索與實踐》。我們前面說了,企業服務化落地要兩條「腿」走路,一條是服務框架,另外一條就是服務治理,服務化之路要走的順暢,一定是兩條腿都健壯。 通過幾年的努力,我們已經初步構建了服務化的治理體系,能夠覆蓋到服務監控和服務管控的大部分需求,其中管控的大部分能力是依託於螞蟻金融雲的能力來構建的,而監控這部分能力則是在 SOFARPC 的基礎上,通過整合常規日誌體系及 APM 監控的能力來綜合獲取的。服務監控這塊,我們從各個服務節點抽取服務調用延時、調用狀態、調用異常等信息,並匯總到日誌中心進行綜合統計,得到各類的監控報表和監控大盤。通過錯誤信息,我們可以進行線上的故障定位定界;通過調用量的各級匯總,我們可以獲取線上實時「水位」,進而進行客觀的容量規劃;通過調用延時和錯誤率,我們可以推斷線上服務的健康度;在這些數據的基礎上,基於時間維度,還可以獲取到服務隨時間的質量演進情況,這樣的話,我們對整個服務集群就能有一個全面而實時的了解,並在此基礎上,做出正確的管控決策。 通過服務管控,可以將服務生命周期管理的調度指令下發到發布系統,進行服務的上線、下線、擴容、縮容等操作,另外限流、降級、調整負載的指令則會直接下發到各個服務節點中,由服務框架的 SDK 進行相應的調整動作。 這樣,通過服務的監控(左邊)和服務的管控(右邊),就形成了針對服務治理的一個閉環的操作。 在服務化的過程中,研發遇到的第一個困難,一定是調試。原來單體應用中的服務被拆分到不同團隊,並部署在不同的伺服器上,而本地只有一個服務介面。這時候要做調試,要麼做 P2P 直連,要麼做 Mock 。採用傳統的 Mock 手段,要寫一堆的 Mock 語句,比如用 mockito,就要寫一堆的 when…..thenReturn….的語句,耦合度非常的高。 我們是利用分散式服務框架提供的過濾器機制,開發了一個Mock 過濾器,並通過 Mock 數據文件來詳細定義要被 Mock 的服務的名稱、入參及出參。這樣,當請求過來的時候,將服務名及入參和 Mock 數據中的定義進行比對,結果吻合,則直接將 Mock 數據文件中的出參反序列化後作為服務的調用結果直接返回,同時遠程調用的所有後續操作被終止。這樣,就通過 Mock 數據模擬了一個真實的遠程服務。 Mock 過濾器的啟用可以通過配置文件來實現「開關控制」,可以只在開發和測試環境啟用,生產環境關閉。 通過這種方式來構建服務的 Mock 能力,我們就不需要寫一堆的 Mock 代碼了,而且整個過程對業務邏輯來說完全無感知,完全把 Mock 能力下沉到底層的服務框架。通過這種方式構建的分散式服務的 Mock 能力,除了 Mock 過濾器,最核心的就是 Mock數據的構建。Mock 數據的質量直接決定了調測的質量!說起 Mock 數據,它所做的無非就是匹配哪個服務、輸入的參數是什麼,輸出的結果又是什麼。但實際的情況往往更複雜,你不可能通過靜態數據去構建一個所謂的「當前時間」吧!因此,Mock 數據除了支持靜態輸入輸出數據的比對,還需要支持動態匹配模式,也就是支持腳本匹配,我們所構建的服務 Mock 框架,支持在 Mock 數據中同時使用 bsh 和 groovy這兩種腳本。另外,一個服務集群中,往往會存在同一服務的不同版本,因此要真實模擬現實情況的話,Mock 數據也必須有版本的概念。 除了以上兩種匹配模式,我們針對實際情況,還開發了第三種 Mock 模式,就是可以針對一個真實請求,部分修改它的回參來模擬出一個第三方的結果,這種方式在參數量非常多的情況下非常有用。 所以,要構建好一個 Mock 數據是需要投入不少工作量的,那麼誰來做這個事情呢?這實際上牽涉到管理規範了。我們的規定是,服務誰提供,就由誰來構建這個mock數據,服務調用方可以在這個基礎上做修改或者替換。但這還不夠,由於服務會非常多,因此,對 Mock 數據的管理一定要體系化和工程化。我的建議是,可以採用獨立的項目工程對 Mock 數據進行獨立的管理和發布。 目前,我們針對前端和服務端都開發了完整的 Mock 能力,可以讓開發人員在基本「無感」的狀態下進行本地化的功能調測,同時提供一些自研的小工具來自動生成 Mock 文件,以降低構建 Mock 的難度和人力投入。 為了有效降低製作 Mock 文件的成本,我們還基於服務框架的過濾器機制開發了「在線數據抓取過濾器」,它可以將指定的服務請求的入參和返回結果都抓取下來,並直接寫成 Mock數據文件。通過抓取方式獲得的 Mock 數據文件,往往有更好的數據質量,畢竟反映的是更加真實的業務場景。當然了,這裡還有一個合規性的問題,對線上數據的抓取是種敏感行為,大部分公司這麼干都會很謹慎,一般都要做好數據脫敏的處理工作。對於我們,目前只在測試環境中進行數據抓取操作。事務的一致性和可用性問題是分散式環境下「老生常談」的問題了,相信大家也或多或少遇到過這類問題。針對分散式事務,我們採取了 3 級應對的策略。 首先,在分庫分表操作中,我們會堅持「實體組」優先的策略,盡量按照統一的「片鍵」進行分庫分表操作,比如說,如果統一按「用戶賬戶ID」作為分庫分表鍵的話,那麼用戶相關的交易、資產、支付等相關信息都會落到同一個物理庫實例之中,這樣的話,針對此用戶的相關操作,本地事務就可以生效,從而避免了分散式事務的使用。因為對於任何分散式事務而言,不管做不做資源鎖定,為了有效保障事務狀態,都需要額外的資源處理消耗。 另外,我們還提供了自研的支持多級事務的 TCC 服務,以應對不可避免的分散式事務需求。採用 TCC 的原因最主要還是在於相對其它資源管理器而言,它相對簡單,我們不用關注資源層面,只需要關注服務介面即可。上面的第二張圖是 TCC 的典型架構模式圖,相信只要研究過 TCC 的同學們一定看過這張圖,第三張則是我們自研的 TCC 的一個更詳細的交互架構圖,能體現更多技術細節,希望能對大家有所參考借鑒。總的來說,從我個人的理解而言,自己實現一個 TCC 框架(獨立服務)並不麻煩,最核心的是要解決兩大核心問題,一個是參與事務的業務數據的緩存和回放問題,我們在做 TRY 操作的時候,就需要將事務數據緩存起來,可以存到資料庫中,當框架做 Confirm 和 Cancel 操作時,再用緩存的事務數據去運行特定的服務邏輯,所以,這就要求在 TRY、Confirm 和 Cancel 的方法構造上要有一定的約束,也就是相互之間要能夠識別哪些入參是事務數據;另一個是父子事務的事務 ID 的傳遞問題,我們通過分散式服務框架的「非業務傳參」來解決這個問題,一旦某一個事務構建了一個事務 ID,這個事務 ID 就會被放置到環境上下文之中,並隨著 RPC 調用傳遞到遠程服務,遠程服務如果偵測到上下文中已經存在事務 ID 了,則就不再構建新的事務 ID,這樣的話,父子事務之間就通過同一個事務 ID 關聯在一起。最後,最終一致性的保障一定是「對賬、對賬、對賬」,這是最傳統,也是最可靠的最後一道防線,這也是金融公司的基礎能力,這裡我就不展開詳細說了。服務化之後,每個團隊負責一部分的服務,經常一個業務會涉及多個團隊之間的協同配合,如何讓團隊內部、團隊之間的協作更高效,天弘內部也做了不同的嘗試,從綜合效果來說,敏捷模式會更適合一些。以我們移動平台團隊舉例,我們目前採用兩周一迭代、固定發版的模式,同時每個迭代之內,採用「火車發布模式」,實行班車制,準點發車,這樣的話,其它協作部門在很早之前就能大概知道我們的一個發布計劃,產品方面也大概知道要把需求放入哪個迭代之中。這樣,能夠有效減輕部門間的溝通成本。在每期工作量評估的時候,我們一般會預留一些工作量 buffer,以應對一些臨時性需求,這類需求不受版本約束,按需發布。如果這個迭代周期內沒有這類緊急需求的話,我們會從 backlog 中撈一些架構優化的需求來填補這些 buffer。 對每個迭代而言,最不可控的就是 UI 的設計了,UI 的設計過程中,感性化的因素會更多一些,可能會反覆修改多次,不像程序代碼那麼明確。所以,我們一般不將 UI 設計納入迭代之中,而是將其作為需求的一部分,在每個迭代開始之前的工作量評估中,要求必須提供完整的UI物料,否則不予評估工作量,此需求也不會被納入迭代之中。要保證敏捷模式平穩推進,還需要一套與之匹配的 DevOps 研發工具體系去支撐它。這方面螞蟻金融雲有相對完善的研發管理工具體系,但我們目前暫時沒有使用,畢竟團隊規模不一樣。我們團隊目前規模還比較小,因此還是採用業界最通用的一些開源的產品(包括Jekins、Jira、Wiki 等)來整合構建我們自己的 DevOps 的工具鏈,但我們會在我們的研發Pipeline 中通過腳本來整合金融雲的一系列能力,包括包上傳、發布能等一系列 IaaS 的能力。這樣,就將雲下的研發和雲上的發布能力整合在了一起。同時,我們會收集 DevOps工具鏈中各個環節的數據,並通過自研的精益看板來進行各個維度的數據匯總統計和呈現,從而實現對研發的推進狀況及質量的嚴格把控。 總結以上就是本次分享的主要內容,前面我們已經介紹了,我們很多服務化的能力都是在螞蟻金融雲的I層和 P 層能力基礎之上構建起來的,螞蟻目前已經將它的雲原生架構引擎 —— SOFA 中間件進行逐步開源,尤其是我之前介紹的 SOFARPC,各位同學如果感興趣的話,可以關注本公眾號了解。http://weixin.qq.com/r/YymEnETE8xmMrQD-93xx (二維碼自動識別)
長按關注,獲取最新分散式架構乾貨歡迎大家共同打造 SOFAStack https://github.com/alipay推薦閱讀:
※面試必備:什麼是一致性Hash演算法?
※redis學習系列(五)--JedisPool與spring集成的實現及一致性哈希分析和基於Redis的分散式鎖
※ZBS:SmartX 分散式塊存儲 -- 元數據篇
※計算機論文精選-20180725
※乾貨| 支付系統如何進行分散式改造