1. 程式人生 > >微服務的接入層設計與動靜資源隔離

微服務的接入層設計與動靜資源隔離

此文已由作者劉超授權網易雲社群釋出。

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。

這個系列是微服務高併發設計,所以我們先從最外層的接入層入手,看都有什麼樣的策略保證高併發。

接入層的架構畫一個簡圖來講包括下面的部分

201811080917223670f4c6-3d59-49ad-8b78-21b49051af1d.png 

接下來我們依次解析各個部分以及可以做的優化。

一、資料中心之外:DNS,HttpDNS,GSLB

當我們要訪問一個網站的服務的時候,首先訪問的肯定是一個域名,然後由DNS,將域名解析為IP地址。

我們首先先通過DNS訪問資料中心中的物件儲存上的靜態資源為例子,看一看整個過程。

我們建議將例如檔案,圖片,視訊,音訊等靜態資源放在物件儲存中,直接通過CDN下發,而非放在伺服器上,和動態資源繫結在一起。

假設全國有多個數據中心,託管在多個運營商,每個資料中心三個可用區Available Zone,物件儲存通過跨可用區部署,實現高可用性,在每個資料中心中,都至少部署兩個內部負載均衡器,內部負載均衡器後面對接多個物件儲存的前置服務proxy-server。

20181108091735a94d3a3f-2c52-491b-abdf-545fc6b1e312.jpg

(1) 當一個客戶端要訪問object.yourcompany.com的時候,需要將域名轉換為IP地址進行訪問,所以他要請求本地的resolver幫忙

(2) 本地的resolver看本地的快取是否有這個記錄呢?如果有則直接使用

(3) 如果本地無快取,則需要請求本地的Name Server

(4) 本地的Name Server一般部署在客戶的資料中心或者客戶所在的運營商的網路中,本地Name Server看本地是否有快取,如果有則返回

(5) 如果本地沒有,本地Name Server需要從Root Name Server開始查起,Root Name Server會將.com Name Server的地址返回給本地Name Server

(6) 本地的Name Server接著訪問.com的Name Server,他會將你們公司的yourcompany.com的Name Server給本地Name Server

(7) 本地的Name Server接著訪問yourcompany.com的Name Server,按說這一步就應該返回真實要訪問的IP地址。

對於不需要做全域性負載均衡的簡單應用來講,yourcompany.com的Name Server可以直接將object.yourcompany.com這個域名解析為一個或者多個IP地址,然後客戶端可以通過多個IP地址,進行簡單的輪詢,實現簡單的負載均衡即可。

但是對於複雜的應用,尤其是跨地域跨運營商的大型應用,則需要更加複雜的全域性負載均衡機制,因而需要專門的裝置或者伺服器來做這件事情,這就是GSLB,全域性負載均衡器

從yourcompany.com的Name Server中,一般是通過配置CNAME的方式,給object.yourcompany.com起一個別名,例如object.vip.yourcomany.com,然後告訴本地Name Server,讓他去請求GSLB去解析這個域名,則GSLB就可以在解析這個域名的過程中,通過自己的策略實現負載均衡。

圖中畫了兩層的GSLB,是因為分運營商和分地域,我們希望將屬於不同運營商的客戶,訪問相同運營商機房中的資源,這樣不用跨運營商訪問,有利於提高吞吐量,減少時延。

(8) 第一層GSLB通過檢視請求他的本地Name Server所在的運營商,就知道了使用者所在的運營商,假設是移動,然後通過CNAME的方式,通過另一個別名object.yd.yourcompany.com,告訴本地Name Server去請求第二層的GSLB

(9) 第二層的GSLB通過檢視請求他的本地Name Server所在的地址,就知道了使用者所在的地理位置,然後將距離使用者位置比較近的Region的裡面的內部負載均衡SLB的地址共六個返回給本地Name Server

(10) 本地Name Server將結果返回給resolver

(11) resolver將結果快取後,返回給客戶端

(12) 客戶端開始訪問屬於相同運營商的距離較近的Region1中的物件儲存,當然客戶端得到了六個IP地址,他可以通過負載均衡的方式,隨機或者輪詢選擇一個可用區進行訪問,物件儲存一般會有三份備份,從而可以實現對儲存讀寫的負載均衡。

從上面的過程可以看出,基於DNS域名的GSLB實現全域性的負載均衡,可是現在跨運營商和跨地域的流量排程,但是由於不同運營商的DNS快取策略不同,會造成GSLB的工作實效。

有的使用者的DNS會將域名解析的請求轉發給其他的運營商的DNS進行解析,導致到GSLB的時候,錯誤的判斷了使用者所在的運營商。

有的運營商的DNS出口會做NAT,導致GSLB判斷錯誤使用者所在的運營商。

所以不同於傳統的DNS,有另一種機制稱為httpDNS,可以在使用者的手機App裡面嵌入SDK,通過http的方式訪問一個httpDNS伺服器,由於手機App可以精確的獲得自己的IP地址,可以將IP地址傳給httpDNS伺服器,httpDNS伺服器完全由應用的服務商提供,可以實現完全自主的全網流量排程。

二、資料中心之外:CDN

對於靜態資源來講,其實在真實的訪問機房內的物件儲存之前,在最最接近使用者的地方,可以先通過CDN進行快取,這也是高併發應用的一個總體的思路,能接近客戶,儘量接近客戶。

CDN廠商的覆蓋範圍往往更廣,在每個運營商,每個地區都有自己的POP點,所以總有更加靠近使用者的相同運營商和相近地點的CDN節點就近獲取靜態資料,避免了跨運營商和跨地域的訪問。

在使用了CDN之後,使用者訪問資源的時候,和上面的過程類似,但是不同的是,DNS解析的時候,會將域名的解析權交給CDN廠商的DNS伺服器,而CDN廠商的DNS伺服器可以通過CDN廠商的GSLB,找到最最接近客戶的POP點,將資料返回給使用者。

20181108091752d1711f87-a5a1-4d98-a3fc-493c28b88956.jpg 

當CDN中沒有找到快取資料的時候,則需要到真正的伺服器中去拿,這個稱為回源,僅僅非常少數的流量需要回源,大大減少了伺服器的壓力。

三、資料中心邊界與核心:邊界路由,核心交換,等價路由

如果真的需要回源,或者訪問的壓根就不是靜態資源,而是動態資源,則需要進入資料中心了。

剛才第一節中說到,最終GSLB返回了6個IP地址,都是內部負載均衡SLB的IP地址,說明這6個IP地址都是公網可以訪問的,那麼公網如何知道這些IP地址的呢?

這就要看機房的結構了

20181108091759edadd6a2-baef-4c73-9969-538a560b23fd.jpg

一個機房一般會有邊界路由器,核心交換機,每個AZ有匯聚交換機,6個SLB是在AZ裡面的,所以他們的IP地址是通過iBGP協議告知邊界路由器的。

當用戶從六個IP裡面選擇了一個IP地址進行訪問的時候,可以通過公網上面的路由,找到機房的邊界路由器,邊界路由器知道當時這個路由是從哪個AZ裡面給他的,於是就通過核心交換一層,將請求轉發給某一個AZ,這個AZ的匯聚交換機會將請求轉發給這個SLB。

如果一個AZ出現了問題,是否可以讓對某個公網IP的訪問給另一個AZ呢?當然是可以的,在核心路由和核心交換之間,可以做ECMP等價路由。當然也可以在邊界路由上將外部地址NAT稱為內部的一個VIP地址,通過等價路由實現跨AZ的流量分擔。

四、資料中心可用區中:負載均衡SLB,LVS,Haproxy

進入一個可用區AZ之後,首先到達的是負載均衡SLB,可以購買商用的SLB,也可以自己搭建,例如通過LVS實現基本的負載均衡功能。

LVS的效能比較好,很多工作通過核心模組ipvs完成。

20181108091808535ef9dd-9729-400e-95df-4cbf97b11ae5.png

LVS可使用keepalived實現雙機熱備,也可以通過OSPF使用等價路由的方式,在多個LVS之間進行流量分擔,往往作為統一的負載均衡入口,承載大的流量。

20181108091814bd5e51a8-90f3-46a9-a0ae-d2a52e755960.jpg

有時候需要更加複雜的4層和7層負載均衡,則會在LVS後面加上haproxy叢集,也即將LVS匯入的流量,分發到一大批haproxy上,這些haproxy可以根據不同的應用或者租戶進行隔離,每個租戶獨享單獨的haproxy,但是所有的租戶共享LVS叢集。

如果有云環境,則haproxy可以部署在虛擬機器裡面,可以根據流量的情況和租戶的請求進行動態的建立和刪除。

20181108091826f22b5c3e-34dd-4e75-b2ec-55e5c72a4bf7.png 

五、資料中心可用區中:接入層nginx,接入層快取

在負載均衡之後,是接入閘道器,或者API閘道器,往往需要實現很多靈活的轉發策略,這裡會選擇使用nginx+lua或者openresty做這一層。

由於nginx本身也有負載均衡機制,有的時候會將haproxy這一層和nginx這一層合併,LVS後面直接跟nginx叢集。

接入層作用一:API的聚合。

使用微服務之後,後端的服務會拆分的非常的細,因而前端應用如果要獲取整個頁面的顯示,往往需要從多個服務獲取資料,將資料做一定的聚合後,方能夠顯示出來。

20181108091841b55246d1-2748-4d92-af1e-4dcf5c397d99.jpg 

如果是網頁其實還好,如果你用chrome的debug模式下,開啟一個複雜的電商主頁的時候,你會發現這個頁面同時會發出很多的http的請求,最終聚合稱為一個頁面。

如果是APP的話,其實也沒有問題,但是會有大量的工作要在客戶端做,這樣會非常的耗電,使用者體驗非常不好,因而最好有一個地方可以將請求聚合,這就是API閘道器的職責之一。這樣對於前端APP來講,後端接是似乎是一個統一的入口,則後端的服務的拆分和聚合,灰度釋出,快取策略等全部被遮蔽了。

20181108091853689a14db-ef45-4f12-85c6-4d88e52afe65.jpg 

接入層作用二:服務發現與動態負載均衡

既然統一的入口變為了接入層,則接入層就有責任自動的發現後端拆分,聚合,擴容,縮容的服務叢集,當後端服務有所變化的時候,能夠實現健康檢查和動態的負載均衡。

對於微服務來講,服務之間也是需要做服務發現的,常見的框架是dubbo和springcloud,服務的註冊中心可以是zookeeper, consul, etcd, eureka等。

我們以consul為例子,既然服務之間的呼叫已經註冊到consul上,則nginx自然也可以通過consul來獲取後端服務的狀態,實現動態的負載均衡。

nginx可以整合consul-template,可監聽consul的事件, 當已註冊service列表或key/value 發生變化時, consul-template會修改配置檔案同時可執行一段shell, 如 nginx reload

consul-template \    -template "/tmp/nginx.hcl:/var/nginx/nginx.conf:service nginx reload" \

consul-template模式配置相對複雜,需要reload nginx。

另一種整合consul的方式是nginx-upsync-module,可以同步consul的服務列表或key/value儲存,需要重新編譯nginx,不需要reload nginx。

upstream test {
        server 127.0.0.1:11111;
        # 所有的後端服務列表會從consul拉取, 並刪除上面的佔位server
        upsync 127.0.0.1:8500/v1/catelog/service/test upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off;
        # 備份的地址, 保證nginx不強依賴consul
        upsync_dump_path /usr/local/nginx/conf/servers/servers_test.conf;
}

還有一種方式是openresty+lua,相對nginx-upsync-module, 可以加入更多自己的邏輯, init_*_by_lua 階段通過http api 獲取服務列表載入Nginx 記憶體, 並設定timer輪訓更新列表,balancer_by_lua 階段 讀取記憶體的列表, 設定後端伺服器。

Lua實現 同樣可以不reload nginx, 相比nginx-upsync-module 來說更加可擴充套件。

接入層作用三:動靜資源隔離,靜態頁面快取,頁面靜態化

為什麼靜態資源需要隔離呢,靜態資源往往變化較少,但是卻往往比較大,如果每次都載入,則影響效能,浪費頻寬。其實靜態資源可以預載入,並且可以進行快取,甚至可以推送到CDN。

所以應該在接入層nginx中配置動態資源和靜態資源的分離,將靜態資源的url匯入到nginx的本地快取或者單獨的快取層如varnish或者squid,將動態的資源訪問後端的應用或者動態資源的快取。

在nginx中,可以通過配置expires,cache-control,if-modified-since來控制瀏覽器端的快取控制。使得瀏覽器端在一段時間內,對於靜態資源,不會重複請求服務端。這一層稱為瀏覽器端的快取。

當有的請求的確到達了接入層nginx的時候,也不用總是去應用層獲取頁面,可以在接入層nginx先攔截一部分熱點的請求。在這裡可以有兩層快取。一是nginx本身的快取proxy_cache,二是快取層的varnish或者squid。

在使用接入層快取的時候,需要注意的是快取key的選擇,不應該包含於使用者相關的資訊,如使用者名稱,地理資訊,cookie,deviceid等,這樣相當於每個使用者單獨的一份快取,使得快取的命中率比較低。

在分離了靜態和動態資源之後,就存在組合的問題,可以通過ajax訪問動態資源,在瀏覽器端進行組合,也可以在接入層進行組合。

如果在接入層聚合,或者varnish進行聚合,則可以讓接入層快取定時輪詢後端的應用,當有資料修改的時候,進行動態頁面靜態化,這樣使用者訪問的資料到接入層就會被攔截,缺點是更新的速度有些慢,對於大促場景下的併發訪問高的頁面,可以進行如此的處理。

接入層作用四:動態資源快取

在動靜分離之後,靜態頁面可以很好的快取,而動態的資料還是會向後端請求,而動態頁面靜態化延時相對比較高,而且頁面數目多的時候,靜態化的工作量也比較大,因而在接入層還可以通過redis或者memcached,對動態資源進行快取。

201811080919419a41d646-0622-4a56-86da-1eea13740756.png 

接入層作用五:資源隔離

接入層的nginx叢集不是一個,而是不同的請求可以有獨立的nginx叢集。

例如搶券或者秒殺系統,會成為熱點中的熱點,因而應該有獨立的nginx叢集。

接入層作用六:統一鑑權,認證,過濾

API Gateway的另一個作用是統一的認證和鑑權。

一種是基於session的,當客戶端輸入使用者名稱密碼之後,API Gateway會向後端服務提交認證和鑑權,成功後生成session,session統一放在redis裡面,則接下來的訪問全部都帶著session進行。

另一種方式是通過統一的認證鑑權中心,分配token的方式進行。

20181108091958b3d91891-f6d5-48e4-bf8d-540e9ef07510.png

這是一個三角形的結構,當API Gateway接收到登陸請求的時候,去認證中心請求認證和授權,如果成功則返回token,token是一個加密過的字串,裡面包含很多的認證資訊,接下來的訪問中,API Gateway可以驗證這個token是否有效來認證,而真正的服務可以根據token來鑑權。

20181108092008631cd793-243f-4a23-baaa-6c75fc06e8fd.jpg

接入層作用七:限流

在大促過程中,常常會遇到真實的流量遠遠大於系統測試下來的可承載流量,如果這些流量都進來,則整個系統一定垮掉,最後誰也別玩。所以長做的方式是限流。

限流是從上到下貫穿整個應用的,當然接入層作為最外面的屏障,需要做好整個系統的限流。

對於nginx來講,限流有多種方式,可以進行連線數限制limit_conn,可以進行訪問頻率限制limit_req,可以啟用過載保護sysgurad模組。

對請求的目標URL進行限流(例如:某個URL每分鐘只允許呼叫多少次)

對客戶端的訪問IP進行限流(例如:某個IP每分鐘只允許請求多少次)

對於被限流的使用者,可以進行相對友好的返回,不同的頁面的策略可以不同。

對於首頁和活動頁,是讀取比較多的,可以返回快取中的老的頁面,或者APP定時重新整理。

對於加入購物車,下單,支付等寫入請求被限流的,可以返回等待頁面,或者返回一個圈圈轉啊轉,如果過了一段時間還轉不出來,就可以返回擠爆了。

對於支付結果返回,如果被限流,需要馬上返回錯誤頁面。

接入層作用八:灰度釋出與AB測試

在接入層,由於可以配置訪問路由,以及訪問權重,可以實現灰度釋出,或者AB測試,同時上線兩套系統,通過切入部分流量的方式,測試新上系統的穩定性或者是否更受歡迎。

網易雲端計算基礎服務深度整合了 IaaS、PaaS 及容器技術,提供彈性計算、DevOps 工具鏈及微服務基礎設施等服務,幫助企業解決 IT、架構及運維等問題,使企業更聚焦於業務,是新一代的雲端計算平臺,點選可免費試用

更多網易技術、產品、運營經驗分享請點選