原文連接:微服務的接入層設計與動靜資源隔離(來源:劉超的通俗雲計算)前端
這個系列是微服務高併發設計,因此咱們先從最外層的接入層入手,看都有什麼樣的策略保證高併發。
接入層的架構畫一個簡圖來說包括下面的部分。nginx
接下來咱們依次解析各個部分以及能夠作的優化。shell
當咱們要訪問一個網站的服務的時候,首先訪問的確定是一個域名,而後由DNS,將域名解析爲IP地址。
咱們首先先經過DNS訪問數據中心中的對象存儲上的靜態資源爲例子,看一看整個過程。
咱們建議將例如文件、圖片、視頻、音頻等靜態資源放在對象存儲中,直接經過CDN下發,而非放在服務器上,和動態資源綁定在一塊兒。
假設全國有多個數據中心,託管在多個運營商,每一個數據中心三個可用區Available Zone,對象存儲經過跨可用區部署,實現高可用性,在每一個數據中心中,都至少部署兩個內部負載均衡器,內部負載均衡器後面對接多個對象存儲的前置服務proxy-server。後端
一、當一個客戶端要訪問object.yourcompany.com的時候,須要將域名轉換爲IP地址進行訪問,因此他要請求本地的resolver幫忙
二、本地的resolver看本地的緩存是否有這個記錄呢?若是有則直接使用
三、若是本地無緩存,則須要請求本地的Name Server
四、本地的Name Server通常部署在客戶的數據中心或者客戶所在的運營商的網絡中,本地Name Server看本地是否有緩存,若是有則返回
五、若是本地沒有,本地Name Server須要從Root Name Server開始查起,Root Name Server會將.com Name Server的地址返回給本地Name Server
六、本地的Name Server接着訪問.com的Name Server,他會將大家公司的yourcompany.com的Name Server給本地Name Server
七、本地的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,是由於分運營商和分地域,咱們但願將屬於不一樣運營商的客戶,訪問相同運營商機房中的資源,這樣不用跨運營商訪問,有利於提升吞吐量,減小時延。
八、第一層GSLB經過查看請求他的本地Name Server所在的運營商,就知道了用戶所在的運營商,假設是移動,而後經過CNAME的方式,經過另外一個別名object.yd.yourcompany.com,告訴本地Name Server去請求第二層的GSLB
九、第二層的GSLB經過查看請求他的本地Name Server所在的地址,就知道了用戶所在的地理位置,而後將距離用戶位置比較近的Region的裏面的內部負載均衡SLB的地址共六個返回給本地Name Server
十、本地Name Server將結果返回給resolver
十一、resolver將結果緩存後,返回給客戶端
十二、客戶端開始訪問屬於相同運營商的距離較近的Region1中的對象存儲,固然客戶端獲得了六個IP地址,他能夠經過負載均衡的方式,隨機或者輪詢選擇一個可用區進行訪問,對象存儲通常會有三份備份,從而能夠實現對存儲讀寫的負載均衡。
從上面的過程能夠看出,基於DNS域名的GSLB實現全局的負載均衡,但是如今跨運營商和跨地域的流量調度,可是因爲不一樣運營商的DNS緩存策略不一樣,會形成GSLB的工做實效。
有的用戶的DNS會將域名解析的請求轉發給其餘的運營商的DNS進行解析,致使到GSLB的時候,錯誤的判斷了用戶所在的運營商。
有的運營商的DNS出口會作NAT,致使GSLB判斷錯誤用戶所在的運營商。
因此不一樣於傳統的DNS,有另外一種機制稱爲httpDNS,能夠在用戶的手機App裏面嵌入SDK,經過http的方式訪問一個httpDNS服務器,因爲手機App能夠精確的得到本身的IP地址,能夠將IP地址傳給httpDNS服務器,httpDNS服務器徹底由應用的服務商提供,能夠實現徹底自主的全網流量調度。api
對於靜態資源來說,其實在真實的訪問機房內的對象存儲以前,在最最接近用戶的地方,能夠先經過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的流量分擔。cookie
進入一個可用區AZ以後,首先到達的是負載均衡SLB,能夠購買商用的SLB,也能夠本身搭建,例如經過LVS實現基本的負載均衡功能。
LVS的性能比較好,不少工做經過內核模塊ipvs完成。網絡
LVS可以使用keepalived實現雙機熱備,也能夠經過OSPF使用等價路由的方式,在多個LVS之間進行流量分擔,每每做爲統一的負載均衡入口,承載大的流量。
有時候須要更加複雜的4層和7層負載均衡,則會在LVS後面加上HAProxy集羣,也即將LVS導入的流量,分發到一大批HAProxy上,這些HAProxy能夠根據不一樣的應用或者租戶進行隔離,每一個租戶獨享單獨的HAProxy,可是全部的租戶共享LVS集羣。
若是有云環境,則HAProxy能夠部署在虛擬機裏面,能夠根據流量的狀況和租戶的請求進行動態的建立和刪除。
在負載均衡以後,是接入網關,或者API網關,每每須要實現不少靈活的轉發策略,這裏會選擇使用Nginx+Lua或者OpenResty作這一層。
因爲Nginx自己也有負載均衡機制,有的時候會將HAProxy這一層和Nginx這一層合併,LVS後面直接跟Nginx集羣。
使用微服務以後,後端的服務會拆分的很是的細,於是前端應用若是要獲取整個頁面的顯示,每每須要從多個服務獲取數據,將數據作必定的聚合後,方可以顯示出來。
若是是網頁其實還好,若是你用Chrome的debug模式下,打開一個複雜的電商主頁的時候,你會發現這個頁面同時會發出不少的http的請求,最終聚合稱爲一個頁面。
若是是APP的話,其實也沒有問題,可是會有大量的工做要在客戶端作,這樣會很是的耗電,用戶體驗很是很差,於是最好有一個地方能夠將請求聚合,這就是API網關的職責之一。這樣對於前端APP來說,後端接是彷佛是一個統一的入口,則後端的服務的拆分和聚合,灰度發佈,緩存策略等所有被屏蔽了。
既然統一的入口變爲了接入層,則接入層就有責任自動的發現後端拆分、聚合、擴容、縮容的服務集羣,當後端服務有所變化的時候,可以實現健康檢查和動態的負載均衡。
對於微服務來說,服務之間也是須要作服務發現的,常見的框架是Dubbo和Spring Cloud,服務的註冊中心能夠是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測試,同時上線兩套系統,經過切入部分流量的方式,測試新上系統的穩定性或者是否更受歡迎。