微服務的接入層設計與動靜資源隔離
作者:劉超,網易雲解決方案架構師
這個系列是微服務高並發設計,所以我們先從最外層的接入層入手,看都有什麼樣的策略保證高並發。
接入層的架構如下圖所示:
接下來我們依次解析各個部分以及可以做的優化。
一、數據中心之外:DNS,HttpDNS,GSLB
當我們要訪問一個網站的服務的時候,首先訪問的肯定是一個域名,然後由DNS,將域名解析為IP地址。
我們首先先通過DNS訪問數據中心中的對象存儲上的靜態資源為例子,看一看整個過程。
我們建議將例如文件,圖片,視頻,音頻等靜態資源放在對象存儲中,直接通過CDN下發,而非放在伺服器上,和動態資源綁定在一起。
假設全國有多個數據中心,託管在多個運營商,每個數據中心三個可用區Available Zone,對象存儲通過跨可用區部署,實現高可用性,在每個數據中心中,都至少部署兩個內部負載均衡器,內部負載均衡器後面對接多個對象存儲的前置服務proxy-server。
(1) 當一個客戶端要訪問http://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,他會將你們公司的http://yourcompany.com的Name Server給本地Name Server;
(7) 本地的Name Server接著訪問http://yourcompany.com的Name Server,按說這一步就應該返回真實要訪問的IP地址。
對於不需要做全局負載均衡的簡單應用來講,http://yourcompany.com的Name Server可以直接將http://object.yourcompany.com這個域名解析為一個或者多個IP地址,然後客戶端可以通過多個IP地址,進行簡單的輪詢,實現簡單的負載均衡即可。
但是對於複雜的應用,尤其是跨地域跨運營商的大型應用,則需要更加複雜的全局負載均衡機制,因而需要專門的設備或者伺服器來做這件事情,這就是GSLB,全局負載均衡器。
從http://yourcompany.com的Name Server中,一般是通過配置CNAME的方式,給http://object.yourcompany.com起一個別名,例如http://object.vip.yourcomany.com,然後告訴本地Name Server,讓他去請求GSLB去解析這個域名,則GSLB就可以在解析這個域名的過程中,通過自己的策略實現負載均衡。
圖中畫了兩層的GSLB,是因為分運營商和分地域,我們希望將屬於不同運營商的客戶,訪問相同運營商機房中的資源,這樣不用跨運營商訪問,有利於提高吞吐量,減少時延。
(8) 第一層GSLB通過查看請求他的本地Name Server所在的運營商,就知道了用戶所在的運營商,假設是移動,然後通過CNAME的方式,通過另一個別名http://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測試,同時上線兩套系統,通過切入部分流量的方式,測試新上系統的穩定性或者是否更受歡迎。
延伸閱讀:
微服務化的基石——持續集成
推薦閱讀:
※使用 Istio治理微服務入門
※微服務下使用GraphQL構建BFF
※《Cloud Native Go》筆記(十二)使用React構建Web視圖
※《微服務設計》閱讀筆記(八) 監控
※【詳解】以銀行零售業務為例,一個案例說清楚可視化微服務架構
TAG:微服務架構 |