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