在 LinkedIn,為用户資料和會員設置提供數據的身份服務是一個非常關鍵的系統。在本文中,我們將分享我們如何通過合併身份服務(每秒處理超過 50 萬個查詢請求)將延遲減少了 10%,並極大降低了年度服務成本。內容將涉及我們所使用的基於數據驅動的架構、用到的工具以及從舊架構總結出的經驗教訓。
1背景
LinkedIn 的會員系統採用了面向服務架構,以此來提供各種不同的體驗。服務隱藏了內部領域模型的複雜性,並通過定義良好的服務 API 把功能暴露出來。這種抽象保證了系統的可演化性和可組合性。
下圖是合併之前的身份服務的總體架構圖,包括服務、客户端和下游服務。客户端調用中間層服務獲取資料和設置信息,中間層服務依賴數據服務,數據服務對 Espresso(LinkedIn 的分佈式 NoSQL 數據庫)數據存儲執行 CRUD 操作。數據服務只實現了有限的邏輯,比如數據驗證(例如數據類型驗證、字符串長度驗證等)。中間層服務還調用了其他服務,這些服務由 LinkedIn 的其他團隊負責開發,提供了重要的領域數據。這些下游服務提供了垃圾信息過濾和阻塞、會員邀請、會員連接等功能。中間層服務基於這些信息實現業務邏輯,確保用户可以自由設置自己的資料以及與 LinkedIn 和其他第三方應用程序交互。
2動機
隨着應用程序規模的增長和系統功能不斷增加,開發團隊開始把關注點放在了性能、服務成本和運維開銷上。另外,我們也開始重新評估和思考之前的一些設計和開發方式。
我們發現,繼續讓數據服務和中間層服務獨立運行存在一些問題:
將數據服務和中間層服務分離,這樣的設計可能沒有當初想象得那麼有價值。我們發現,大部分伸縮性方面的問題都可以在存儲層解決,也就是在 Espresso 數據存儲端。況且,對 Espresso 讀寫操作實際上都是來自數據服務。
將數據服務作為單獨的服務增加了運維開銷和代碼複雜性。為此,我們分配了 1000 個應用程序實例。另外,我們需要單獨為中間層提供 API,涉及數據建模、API 演化和安全等方面的工作。
數據服務裏只有很少的業務邏輯,大部分都與數據驗證有關。
對於客户端來説,中間層服務和數據服務的分離增加了網絡跳數。
基於以上這些考慮,我們打算在保持 API 不變的情況下把中間層服務和數據服務合併成一個服務。從面相服務架構的角度來看,這樣做有點違反直覺,因為面相服務架構的意義在於將大系統拆分成小系統,以此來解決複雜性問題。不過,我們相信可以找到一個平衡點,合併服務從性能、服務成本和運維開銷方面得到的好處比合並服務帶來的複雜性要大得多。
3實現
得益於微服務架構,我們可以在不影響客户端的情況下把兩個服務合併成一個。我們保持中間層接口不變,把數據服務的代碼合併到中間層,讓中間層直接操作數據存儲層。我們的一個重要的目標是儘量讓新舊架構的功能和性能保持不變。另外,我們也要注意在合併兩個重要系統時可能會遇到的風險,並最小化合並的開發成本。
我們分四步實現服務的合併。
第一步:我們有兩種方式來合併代碼庫。最直接的方式是把數據服務的代碼拷貝到中間層,這樣就可以執行數據驗證和調用數據存儲。不過,雖然這種方式最為直接,但在確定可行之前需要做很多前期的開發工作。於是,我們選擇了另外一種“取巧”的方式,我們直接將數據服務的 REST API 作為中間層的一個本地庫。
第二步:我們逐步執行在第一步中定下的方案。LinkedIn 有一個非常厲害的 AB 測試框架,叫作 T-REX,我們用它生成統計報告,基於風險等級和變更影響範圍來安排進度。我們可以邊觀察邊做出修改,在必要的情況下可以進行快速回滾(在幾分鐘內)。因為我們合併的是兩個非常關鍵的系統,風險較高,影響較大,所以在安排進度時也非常謹慎。我們一個數據中心接一個數據中心地遷移,在每一個數據中心裏也是先從小比例開始,再逐漸加大,確保有足夠的時間生成統計報告。
https://engineering.linkedin.com/teams/data/analytics-platform-apps/data-applications/t-rex
第三步:下線數據服務的主機。
第四步:因為第一步的方案走了捷徑,直接將 REST API 作為本地庫調用,所以現在需要清理這些代碼。在 LinkedIn,工匠精神是我們文化的一個重要組成部分。我們把暴露 REST 服務需要的類和接口移除掉,只保留訪問數據存儲需要的類。
下圖顯示了架構變更前後的區別。
4性能分析
為了比較合併前和合並後的性能,我們採用了一種叫作“Dark Canary”的機制。我們以一種可控的方式把真實的生產環境的只讀流量導給測試主機。例如,我們可以把一台生產主機的流量加倍並導給一台測試主機,這些是在不影響生產主機的情況下進行的。也就是説,我們可以在不影響業務的情況下使用生產流量進行性能測試。下面是我們的 Dark Canary 架構。
下面的兩張圖顯示了常規生產流量和測試流量的 p90 延遲區別。p90 延遲平均從 26.67 毫秒下降到 24.84 毫秒(6.9% 的下降幅度)。
通常,因為影響因素太多,p99 指標是很難提升的。不過,我們確實做到了。總體來説,合併後的服務分別將 p50、p90、p99 提升了 14%、6.9% 和 9.6%。
5內存分配
為了瞭解性能的特徵,我們基於 GC 日誌分析了內存分配情況。這些日誌來自三種主機:中間層服務所在的測試主機、中間層所在的生產主機和數據服務主機。GC 日誌提供了非常有價值的有關對象分配模式的信息,這些信息通常可以説明應用程序的性能情況以及它們是如何使用內存的。下圖顯示了中間層所在的生產主機的內存分配情況,平均內存分配率是每秒 350MB。
在合併之後,中間層服務的內存分配率比之前每秒減少了 100MB 左右(28.6%)。這不僅改進了性能,也降低了服務成本。
6服務成本
服務成本是業務決策的一個參考因素。在 LinkedIn,我們使用了一種內部框架,基於硬件和運維成本來計算服務成本。我們的團隊也使用這個框架對合並後的服務進行了分析統計,在將數據服務所在的主機移除之後,在物理資源方面節省了超過 12000 個核心和 13000GB 的內存,相當於每年節省了相當大一筆費用。