阿里雙11狂歡如何承載大型系統的千萬級訪問?
一、問題起源
Spring Cloud微服務架構體系中,Eureka是一個至關重要的元件,它扮演著微服務註冊中心的角色,所有的服務註冊與服務發現,都是依賴Eureka的。
不少初學Spring Cloud的朋友在落地公司生產環境部署時,經常會問:
- Eureka Server到底要部署幾臺機器?
- 我們的系統那麼多服務,到底會對Eureka Server產生多大的訪問壓力?
- Eureka Server能不能抗住一個大型系統的訪問壓力?
如果你也有這些疑問,彆著急!咱們這就一起去看看,Eureka作為微服務註冊中心的核心原理
下面這些問題,大家先看看,有個大概印象。帶著這些問題,來看後面的內容,效果更佳!
- Eureka註冊中心使用什麼樣的方式來儲存各個服務註冊時傳送過來的機器地址和埠號?
- 各個服務找Eureka Server拉取登錄檔的時候,是什麼樣的頻率?
- 各個服務是如何拉取登錄檔的?
- 一個有幾百個服務,部署了上千臺機器的大型分散式系統,會對Eureka Server造成多大的訪問壓力?
- Eureka Server從技術層面是如何抗住日千萬級訪問量的?
先給大家說一個基本的知識點,各個服務內的Eureka Client元件,預設情況下,每隔30秒會發送一個請求到Eureka Server,來拉取最近有變化的服務資訊
舉個例子:
- 庫存服務原本部署在1臺機器上,現在擴容了,部署到了3臺機器,並且均註冊到了Eureka Server上。
- 然後訂單服務的Eureka Client會每隔30秒去找Eureka Server拉取最近登錄檔的變化,看看其他服務的地址有沒有變化。
除此之外,Eureka還有一個心跳機制,各個Eureka Client每隔30秒會發送一次心跳到Eureka Server,通知人家說,哥們,我這個服務例項還活著!
如果某個Eureka Client很長時間沒有傳送心跳給Eureka Server,那麼就說明這個服務例項已經掛了。
光看上面的文字,大家可能沒什麼印象。老規矩!咱們還是來一張圖,一起來直觀的感受一下這個過程。
二、Eureka Server設計精妙的登錄檔儲存結構
現在咱們假設手頭有一套大型的分散式系統,一共100個服務,每個服務部署在20臺機器上,機器是4核8G的標準配置。
也就是說,相當於你一共部署了100 * 20 = 2000個服務例項,有2000臺機器。
每臺機器上的服務例項內部都有一個Eureka Client元件,它會每隔30秒請求一次Eureka Server,拉取變化的登錄檔。
此外,每個服務例項上的Eureka Client都會每隔30秒傳送一次心跳請求給Eureka Server。
那麼大家算算,Eureka Server作為一個微服務註冊中心,每秒鐘要被請求多少次?一天要被請求多少次?
- 按標準的演算法,每個服務例項每分鐘請求2次拉取登錄檔,每分鐘請求2次傳送心跳
- 這樣一個服務例項每分鐘會請求4次,2000個服務例項每分鐘請求8000次
- 換算到每秒,則是8000 / 60 = 133次左右,我們就大概估算為Eureka Server每秒會被請求150次
- 那一天的話,就是8000 * 60 * 24 = 1152萬,也就是每天千萬級訪問量
好!經過這麼一個測算,大家是否發現這裡的奧祕了?
- 首先,對於微服務註冊中心這種元件,在一開始設計它的拉取頻率以及心跳傳送頻率時,就已經考慮到了一個大型系統的各個服務請求時的壓力,每秒會承載多大的請求量。
- 所以各服務例項每隔30秒發起請求拉取變化的登錄檔,以及每隔30秒傳送心跳給Eureka Server,其實這個時間安排是有其用意的。
按照我們的測算,一個上百個服務,幾千臺機器的系統,按照這樣的頻率請求Eureka Server,日請求量在千萬級,每秒的訪問量在150次左右。
即使算上其他一些額外操作,我們姑且就算每秒鐘請求Eureka Server在200次~300次吧。
所以通過設定一個適當的拉取登錄檔以及傳送心跳的頻率,可以保證大規模系統裡對Eureka Server的請求壓力不會太大。
現在關鍵的問題來了,Eureka Server是如何保證輕鬆抗住這每秒數百次請求,每天千萬級請求的呢?
要搞清楚這個,首先得清楚Eureka Server到底是用什麼來儲存登錄檔的?三個字,看原始碼!
接下來咱們就一起進入Eureka原始碼裡一探究竟:
- 如上圖所示,圖中這個名字叫做registry的CocurrentHashMap,就是登錄檔的核心結構。看完之後忍不住先讚歎一下,精妙的設計!
- 從程式碼中可以看到,Eureka Server的登錄檔直接基於純記憶體,即在記憶體裡維護了一個數據結構。
- 各個服務的註冊、服務下線、服務故障,全部會在記憶體裡維護和更新這個登錄檔。
- 各個服務每隔30秒拉取登錄檔的時候,Eureka Server就是直接提供記憶體裡儲存的有變化的登錄檔資料給他們就可以了。
同樣,每隔30秒發起心跳時,也是在這個純記憶體的Map資料結構裡更新心跳時間。
一句話概括:維護登錄檔、拉取登錄檔、更新心跳時間,全部發生在記憶體裡!這是Eureka Server非常核心的一個點。
搞清楚了這個,咱們再來分析一下registry這個東西的資料結構,大家千萬別被它複雜的外表唬住了,沉下心來,一層層的分析!
- 首先,這個ConcurrentHashMap的key就是服務名稱,比如“inventory-service”,就是一個服務名稱。
- value則代表了一個服務的多個服務例項。
- 舉例:比如“inventory-service”是可以有3個服務例項的,每個服務例項部署在一臺機器上。
再來看看作為value的這個Map:Map<String, Lease<InstanceInfo>>
- 這個Map的key就是服務例項的id
- value是一個叫做Lease的類,它的泛型是一個叫做InstanceInfo的東東,你可能會問,這倆又是什麼鬼?
- 首先說下InstanceInfo,其實啊,我們見名知義,這個InstanceInfo就代表了服務例項的具體資訊,比如機器的ip地址、hostname以及埠號。
- 而這個Lease,裡面則會維護每個服務最近一次傳送心跳的時間
三、Eureka Server端優秀的多級快取機制
假設Eureka Server部署在4核8G的普通機器上,那麼基於記憶體來承載各個服務的請求,每秒鐘最多可以處理多少請求呢?
- 根據之前的測試,單臺4核8G的機器,處理純記憶體操作,哪怕加上一些網路的開銷,每秒處理幾百請求也是輕鬆加愉快的。
- 而且Eureka Server為了避免同時讀寫記憶體資料結構造成的併發衝突問題,還採用了多級快取機制來進一步提升服務請求的響應速度。
- 在拉取登錄檔的時候:
- 首先從ReadOnlyCacheMap裡查快取的登錄檔。
- 若沒有,就找ReadWriteCacheMap裡快取的登錄檔。
- 如果還沒有,就從記憶體中獲取實際的登錄檔資料。
- 在登錄檔發生變更的時候:
- 會在記憶體中更新變更的登錄檔資料,同時過期掉ReadWriteCacheMap。
- 此過程不會影響ReadOnlyCacheMap提供人家查詢登錄檔。
- 一段時間內(預設30秒),各服務拉取登錄檔會直接讀ReadOnlyCacheMap
- 30秒過後,Eureka Server的後臺執行緒發現ReadWriteCacheMap已經清空了,也會清空ReadOnlyCacheMap中的快取
- 下次有服務拉取登錄檔,又會從記憶體中獲取最新的資料了,同時填充各個快取。
多級快取機制的優點是什麼?
- 儘可能保證了記憶體登錄檔資料不會出現頻繁的讀寫衝突問題。
- 並且進一步保證對Eureka Server的大量請求,都是快速從純記憶體走,效能極高。
為方便大家更好的理解,同樣來一張圖,大家跟著圖再來回顧一下這整個過程:
四、總結
- 通過上面的分析可以看到,Eureka通過設定適當的請求頻率(拉取登錄檔30秒間隔,傳送心跳30秒間隔),可以保證一個大規模的系統每秒請求Eureka Server的次數在幾百次。
- 同時通過純記憶體的登錄檔,保證了所有的請求都可以在記憶體處理,確保了極高的效能
- 另外,多級快取機制,確保了不會針對記憶體資料結構發生頻繁的讀寫併發衝突操作,進一步提升效能。
上述就是Spring Cloud架構中,Eureka作為微服務註冊中心可以承載大規模系統每天千萬級訪問量的原理。
推薦一個學習圈子:697-57-9-751 裡面會分享一些資深架構師錄製的視訊錄影:有Spring,MyBatis,Netty原始碼分析,高併發、高效能、分散式、微服務架構的原理,JVM效能優化這些成為架構師必備的知識體系。還能領取免費的學習資源,目前受益良多