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

網易雲社群發表於2018-04-26

本文由 網易雲 釋出。


作者:劉超,網易雲解決方案架構師

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

接入層的架構如下圖所示:

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

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

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

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

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

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

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

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

(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點,將資料返回給使用者。

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

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

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

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

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

這就要看機房的結構了:

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

一個機房一般會有邊界路由器,核心交換機,每個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完成。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

對於微服務來講,服務之間也是需要做服務發現的,常見的框架是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,對動態資源進行快取。

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

接入層作用五:資源隔離

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

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

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

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

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

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

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

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

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

接入層作用七:限流

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

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

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

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

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

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

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

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

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

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

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

延伸閱讀

微服務化的基石——持續整合


瞭解網易雲:

網易雲官網:www.163yun.com/

新使用者大禮包:www.163yun.com/gift

網易雲社群:sq.163yun.com/




相關文章