1、前言
公司內考慮到服務器資源成本的問題,目前業務上還在進行服務的容器化改造和遷移,計劃將容器化后的服務,以及一些中間件(MQ、DB、ES、Redis等)盡量都遷移到其他機房,
那你們為什么不用阿里云啊,騰訊云啊,還用自己的機房?
的確是這樣,公司內部目前還是有專門的運維團隊,也是因為歷史原因,當時業務發展比較迅猛,考慮到資料的安全性也是自建機房的,對于中小型公司這樣做,顯然成本太高了,所以一般都用阿里云,對于中大型企業或者對資料安全性要求高的公司,自建機房維護的也不再少數,
對于中間件來說,比如 Redis 快取,有的業務也是因為歷史原因,當時上線后都是單獨申請,并部署的一套集群,但是量并不是很大,所以類似這種情況的,可以考慮跟其他專案使用的集群合并為一個,這樣就可能節省了一部分服務器資源,
現在大多數企業都已經微服務化,容器化了,
所以,將非容器化的業務要求都遷移到容器中,這里的容器基本都是指 Kubernetes 平臺了,通過容器發布調度服務,對于運維來說,維護變得更加便捷,高效,
對于研發來說,業務需要部署服務,不再需要重新提 JIRA 工單,走一系列審核流程,最后給到你的可能還是一臺虛擬機,依賴的軟體單獨安裝部署,用了容器,只要在 集裝箱 中提前安裝好所需軟體環境,按照發布規范打好鏡像,發布服務的程序一路就是 點點點...,

2、線上業務場景介紹
繼續來說今天的主題,
有一個專案是 SpringCloud 架構的,其中使用到了 網關 Zuul,并且也使用了到了 Eureka 作為注冊中心,
因為該專案提前已經遷移到北京機房節點部署的容器環境,我們最終目標是遷移到其他機房(如:天津機房),
北京有兩個機房:A機房、B機房,因為都在北京,所以兩個機房之間的 網路延時 是可以接受的,
微服務也同樣在這兩個機房之間都有部署,

此時,如果只是將微服務部署到 天津機房,會變成如下圖所示的關系:

問題很明顯,就是網關服務只有北京的,而微服務新增了天津機房的,此時會導致 跨機房呼叫,即北京網關呼叫到了天津微服務,
盡管北京到天津 ping 的網路延時僅有 3 毫秒 之差,但是服務與服務之間的呼叫,可就不止這 3 毫秒了,
其中包括服務器與服務器之間 TCP連接的建立、資料傳輸的網路開銷,如果資料包過大,跨機房訪問耗時就會很明顯了,
所以呢,盡量避免跨機房訪問,當然要將網關也要遷移到天津機房,

但是,大家看 粉紅色粗體 的線條,仍然存在跨機房呼叫,天津網關呼叫到北京微服務,
對于線上并發訪問量稍微大點,或者有些介面回應體大的,又或者網路抖動等場景下,可能就會導致介面回應時間變長了,
如何解決呢?
因大部分業務都部署到天津,可以將天津機房的服務權重調高
SLB配置 (類Nginx):
upstream {
server 北京機房網關IP 20;
server 天津機房網關IP 80;
}
網關與微服務之間,都是通過 Eureka 注冊中心媒介來溝通,即 注冊服務 拉取服務,
僅僅在網關層配置好權重還不夠,此時還會存在天津網關路由到北京微服務上,
Eureka 內部是基于 Ribbon 實作負載均衡的,自行實作按權重的負載均衡策略,Eureka做一點改造,界面上支持權重的修改,
下圖截圖了部分示例:

IP后面的就是權重值,可以在界面上輸入權重值進行調整,
我們可以將北京微服務權重調低,天津微服務權重調高,
相當于網關以及微服務兩側都是通過基于 權重 的負載均衡演算法來盡量減少跨機房呼叫的,但是無法避免跨機房呼叫,
使用 Eureka 的磁區改進
上面描述的方案對于 20% 的流量仍然存在跨機房訪問,我們能不能做到先訪問同一機房的服務,如果同一機房的服務都不可用了,再訪問其他機房的呢?
答案是 可以的,
我們可以借助于 Eureka 注冊中心里提供了 region 和 zone 的概念來實作,
region 和 zone 兩個概念均來自亞馬遜的 AWS:
region:簡單理解為地理上的磁區,比如亞洲地區,或者華北地區等等,沒有具體大小的限制,根據專案情況,自行合理劃分 region,
zone:簡單理解為 region 內的具體機房,比如說 zone 劃分為北京、天津,且北京有兩個機房,就可以在 region 內劃分為三個zone,北京劃分為zone1、zone2,天津為zone3,
結合上面的示例,假設僅設定一個 region 為京津地區,
然后我們給這個區域下的網關服務、微服務打上 zone 機房標簽,在系統運維上將機房也稱作 IDC 資料中心,
網關服務打上zone標簽:

微服務打上zone標簽:

這個功能都是在 Eureka注冊中心 上實作的,在給服務配置 zone 前,呼叫路徑如下所示:

給服務配置 zone 之后,框架內部的路由機制的實作下,呼叫路徑如下所示:

當前使用的 Eureka 是部署在北京,如果想讓服務在注冊、續約、拉取 動作時也能實作 就近機房訪問,部署架構就變成如下這個樣子:

北京區域不同機房假設認為網路延時小,所以北京兩個機房可以使用同一個 Eureka 集群;天津可以單獨再部署一套 Eureka 集群,這樣就可以實作優先路由到同機房訪問,
服務注冊的關鍵配置
基本原理就是這樣,貼上一段 Eureka 使用 region 和 zone 的配置供大家參考:
spring:
application:
name: mananger
server:
port: ${EUREKA_SERVER_PORT:8011}
eureka:
instance:
# 全網服務實體唯一標識
instance-id: ${EUREKA_SERVER_IP:127.0.0.1}:${server.port}
# 服務實體的meta資料鍵值對集合,可由注冊中心進行服務實體間傳遞
metadata-map:
# [HA-P配置]-當前服務實體的zone
zone: ${EUREKA_SERVER_ZONE:tz-1}
profiles: ${spring.profiles.active}
# 開啟ip,默認為false=》hostname
prefer-ip-address: true
ip-address: ${EUREKA_SERVER_IP:127.0.0.1}
# [HA-P配置]-當前服務實體的region
client:
region: ${EUREKA_SERVER_REGION:cn-bj}
# [HA-P配置]-開啟當前服務實體優先發現同zone的注冊中心,默認為true
prefer-same-zone-eureka: true
# [服務注冊]-允許當前服務實體注冊,默認為true
register-with-eureka: true
# [服務續約]-允許當前服務實體獲取注冊資訊,默認為true
fetch-registry: true
# [HA-P配置]-可用region下zone集合
availability-zones:
cn-bj: ${eureka.instance.metadata-map.zone},zone-bj,zone-tj
service-url:
# [HA-P配置]-各zone下注冊中心地址串列
zone-bj: http://BJIP1:8011/eureka,http://BJIP2:8012/eureka
zone-tj: http://TJIP1:8013/eureka,http://TJIP2:8014/eureka
prefer-same-zone-eureka :
默認就為true,首先會通過 region 找到 availability-zones 內的第一個 zone,然后通過這個 zone 找到 service-url 對應該機房的注冊中心地址串列,并向該串列內的 第一個URL 地址發起注冊和心跳,不會再向其它的URL地址發起操作,只有當第一個URL地址注冊失敗的情況下,才會依次向其它的URL發起操作,重試一定次數仍然失敗,會間隔一段心跳時間繼續重試,
eureka.instance.metadata-map.zone:
服務提供者和消費者都要配置該引數,表示自己屬于哪一個機房的,網關服務也屬于消費者,從注冊中心拉取到注冊表之后會根據這個引數中指定的 zone 進行過濾,過濾后向同 zone 內的服務會有多個實體 ,通過 Ribbon 來實作負載均衡呼叫,如果同一 zone內的所有服務都不可用時,會其他 zone 的服務發起呼叫,
另外注意一點 availability-zones 下 region 的配置是 ${eureka.instance.metadata-map.zone},... 這樣配置的好處是,你只要指定好了 eureka.instance.metadata-map.zone,優先會將這個引數放到可用磁區下作為第一個 zone 來訪問,
Zuul 網關路由磁區原始碼分析
網關使用的 zuul,其內部也是通過 ribbon 和 eureka 的結合來實作服務之間的呼叫,因為網關實際也是個服務消費者,同樣會注冊到 eureka 上,被網關拉取過來的注冊表里的服務,作為服務提供者,同樣會注冊到eureka上,
通過一張圖把控整個請求的大致脈絡:

上述圖示中部分核心原始碼如下所示:
PollServerListUpdater#start(final UpdateAction action) 啟動后會每隔30秒(默認)去Eureka注冊中心拉取一次注冊表資訊,更新本地快取的資料結構,

呼叫到了DyamicServerListLoadBalancer匿名實作類中,

通過DyamicServerListLoadBalancer類呼叫了 updateListOfServer() 方法更新服務串列,serverListImpl的實作是DiscoveryEnabledNIWSServerList類

在DiscoveryEnabledNIWSServerList類內部會呼叫 obtainServersViaDiscovery() 方法,其內部通過 EurekaClient 來實作從 Eureka 注冊中心拉取服務串列,

過濾器內部獲取同一機房(zone)的服務串列,先后會呼叫 ZonePreferenceServerListFilter 和 ZoneAffinityServerListFilter 兩個過濾器實作 zone 的過濾,

最開始獲取的Servers一共是有4條記錄,根據除錯的代碼看,我們是為了獲取 zone 為2的服務,所以得到的結果是一條,即 zone = "2",說明找到了同 zone 服務,
請求介面后會呼叫到 LoadBalancerContext#getServerFromLoadBalancer(...),內部會呼叫到ILoadBalancer 具體實作的 chooseServer() 方法,最侄訓獲取到 zone="2" 里的一個Server,

那么這里是如何選擇的Server呢?
本地除錯時,只配置了已給可用的zone,所以這里條件滿足會直接呼叫 super.chooseServer(key) 父類的方法:

BaseLoadBalancer#chooseServer(…) 父類的選擇Server的方法,其內部通過 IRule#choose(key) 會呼叫到具體的負載均衡器的實作:

上述截圖中,能看到 MetadataWeightedRule ,這個類是我們自行基于權重負載均衡實作,

該實作類是繼承了 ZoneAviodanceRule ,目的就是利用了 zone 的概念,所重寫的 choose(Object key) 方法,呼叫了 this.getPredicate().getEligibleServers(...) 會走同樣的過濾規則獲取到同一機房(zone)下的所有服務串列,然后在基于每個服務配置的權重篩選一個Server,
獲取到 Server 后,拼接介面的URI請求地址 http://IP:PORT/api/.../xxx.json ,通過底層的 OkHttp 實作完成 Http 介面的呼叫程序,

好了,到此基本就分析完了,從網關請求,通過 ribbon 組件從 eureka 注冊中心拉取服務串列,如何基于 zone 磁區來實作多資料中心的訪問,
對于 服務注冊,要保證服務能注冊到同一個 zone 內的注冊中心,如果跨 zone 注冊,會導致網路延時較大,出現拉取注冊表,心跳超時等問題,
對于 服務呼叫,要保證優先呼叫同一個 zone 內的服務,當無法找到同 zone 或者 同 zone 內的服務不可用時,才會轉向呼叫其他 zone 里的服務,
本文提到的只是網關到微服務之間的呼叫,實際專案中,微服務還會呼叫其他第三方的服務,也要同時考慮到跨機房呼叫的問題,盡量都讓各服務之間在同機房呼叫,減少網路延時,提高服務的穩定性,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/298310.html
標籤:其他
