在2015-2016,咱們將單體應用程序從新設計爲微服務,並選擇Spring Cloud Netflix做爲基礎。git
Spring Cloud Netflix 是經過自動配置, Spring 環境以加上其餘 Spring 編程模型習慣用法的一個對Netflix OSS 開源軟件進行集成的 Spring Boot 應用程序。
時間流逝,咱們逐漸對框架更加熟悉,甚至還貢獻了一些代碼。我認爲我已經知道了夠多了, 便開始研究 Eureka 冗餘和故障轉移。當一個概念的快速證實不起做用時,我開始深刻挖掘,遇到了其餘可憐的靈魂, 在互聯網上搜索相似的信息。不幸的是, 網上啥也找不到。值得讚賞的是,Spring Cloud 文檔至關不錯,並且還有 Netflix Wiki,但都沒有達到我想要的詳細程度。這篇文章試圖彌合這一差距。我假設您對 Eureka 和服務發現有一些基本的瞭解,因此若是您是新手,請在閱讀 Spring Cloud 文檔後再回來。github
Netflix 的高級架構,遵循 Apache License v2.0 許可。spring
Eureka 有兩個基本組件,服務器和客戶端。引用 Netflix Wiki:編程
Eureka 是一種基於 REST(Representational State Transfer)的服務,主要用於 AWS 雲,用於定位服務,以實現中間層服務器的負載均衡和故障轉移。咱們將此服務稱爲 Eureka Server。Eureka 還附帶了一個基於Java 的客戶端組件 Eureka Client,它使與服務的交互變得更加容易。客戶端還有一個內置的負載均衡器,能夠進行基本的循環負載均衡。緩存
Eureka 客戶端應用程序稱爲實例。客戶端應用程序和 Eureka 客戶端之間存在細微差異; 前者是您的應用程序,後者是框架提供的組件。服務器
Netflix 設計的 Eureka 具備高度動態性。有通常屬性,也有一些專門屬性, 用於定義了在通常屬性的更新查詢時間間隔。這是一種常見的作法,這意味着大多數這些屬性能夠在運行時更改,並在下一個刷新週期中被感知。例如,客戶端用於向 Eureka 註冊的 URL 能夠更改,並在5分鐘後被感知(可經過eureka.client.eurekaServiceUrlPollIntervalSeconds來配置)。大多數用戶不須要這樣的動態更新,但若是你想這樣作,能夠找到全部配置選項,以下所示:架構
org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean 實現com.netflix.eureka.EurekaServerConfig。全部屬性都具備eureka.server 前綴。併發
org.springframework.cloud.netflix.eureka.EurekaClientConfigBean 實現 com.netflix.discovery.EurekaClientConfig。全部屬性都有 eureka.client 前綴。app
org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean 實現(間接)com.netflix.appinfo.EurekaInstanceConfig。全部屬性都有 eureka.instance 前綴。
有關更多體系結構的詳細信息,請參閱 Netflix Wiki。負載均衡
在 Eureka 中經過 eureka.instance.instanceId 識別實例, 若是不存在則用eureka.instance.metadataMap.instanceId。實例發現彼此使用 eureka.instance.appName,該值在 Spring Cloud 默認使用 spring.application.name 或者 UNKNOWN, 若是前者未定義。您須要進行設置,spring.application.name 由於具備相同名稱的應用程序在 Eureka 服務器中彙集在一塊兒。不須要設置eureka.instance.instanceId,由於它默認值設置爲 CLIENT IP:PORT,但若是你想設置它,則appName必須在集羣範圍內是惟一的。還有一個 eureka.instance.virtualHostName,但它沒有被 Spring 使用,而且設置爲 spring.application.name 或 UNKNOWN 如上所述。
若是 registerWithEureka 是 true,則實例使用給定的 URL 向 Eureka 服務器註冊; 而後,它每30秒發送一次心跳(可配置 eureka.instance.leaseRenewalIntervalInSeconds)。若是服務器沒有收到心跳,則在eureka.instance.leaseExpirationDurationInSeconds 從註冊表中刪除實例以前等待90秒(可配置),而後禁止該實例的流量。發送心跳是一項異步任務; 若是操做失敗,則以指數方式後退2倍,直到eureka.instance.leaseRenewalIntervalInSeconds * eureka.client.heartbeatExecutorExponentialBackOffBound 達到最大延遲。註冊 Eureka 的重試次數沒有限制。
心跳與將實例信息更新到 Eureka 服務器不一樣。每一個實例都由 com.netflix.appinfo.InstanceInfo 表示,它是關於實例的一堆信息。它將 InstanceInfo 會按期發送到Eureka 服務器,從啓動後40s開始(可配置eureka.client.initialInstanceInfoReplicationIntervalSeconds),而後每30秒開始一次(可配置eureka.client.instanceInfoReplicationIntervalSeconds)。
若是 eureka.client.fetchRegistry 是 true,則客戶端在啓動時獲取 Eureka 服務器註冊表並在本地進行緩存。從那時起,它只是獲取增量(這能夠經過設置來關閉 eureka.client.shouldDisableDelta 到 false,儘管這會是浪費帶寬)。註冊表獲取是每30秒調度一次的異步任務(可配置eureka.client.registryFetchIntervalSeconds)。若是操做失敗,則按指數2倍後退,直到eureka.client.registryFetchIntervalSeconds * eureka.client.cacheRefreshExecutorExponentialBackOffBound 達到最大延遲。獲取註冊表信息的重試次數沒有限制。
客戶端任務由 com.netflix.discovery.DiscoveryClient 安排, 這個類是對 Spring Cloud 的org.springframework.cloud.netflix.eureka.CloudEurekaClient 的擴展實現。
大部分都是從spring-cloud-netflix#373複製而來的。
第一次心跳在啓動後發生30s(如前所述),所以實例在此間隔以前不會出如今 Eureka 註冊表中。
服務器維護一個每30秒更新一次的響應緩存(可配置 eureka.server.responseCacheUpdateIntervalMs)。所以,即便實例剛剛註冊,它也不會出如今對 /eureka/apps
REST 端點的調用結果中。可是,實例可能會在註冊後出如今 Eureka Dashboard 上。這是由於儀表板繞過了 REST API 使用的響應緩存。若是您知道instanceId,您仍然能夠經過調用從 Eureka 獲取有關它的一些詳細信息/eureka/apps/<appName>/<instanceId>
。此端點不使用響應緩存。
所以,其餘客戶端可能須要另外30秒來發現新註冊的實例。
Eurekac客戶端維護註冊表信息的緩存。此緩存每30秒刷新一次(如前所述)。所以,在客戶端決定刷新其本地緩存並發現其餘新註冊的實例以前,可能還須要30秒。
Ribbon 使用的負載均衡器從本地 Eureka 客戶端獲取其信息。Ribbon 還維護一份本地緩存,以免爲每一個請求調用客戶端。此緩存每30秒刷新一次(可配置 ribbon.ServerListRefreshInterval)。所以,在 Ribbon 可使用新註冊的實例以前可能還須要30秒。
最後,在新註冊的實例開始接收來自其餘實例的流量以前,可能須要2分鐘。
Eureka 服務器具備對等感知模式,在該模式下,它跨其餘 Eureka 服務器複製服務註冊表,以提供負載平衡和彈性。對等感知模式是默認模式,所以 Eureka 服務器也充當 Eureka 客戶端向對等體上註冊給定的 URL。這就是你應該如何在生產中運行 Eureka,可是對於演示或概念驗證,你能夠經過設置 registerWithEureka 爲 false 採用獨立模式運行。
當 Eureka 服務器啓動時,它會嘗試從對等的 Eureka 節點獲取全部註冊表信息。對每一個對等體重試此操做5次(可配置 eureka.server.numberRegistrySyncRetries)。若是因爲某種緣由此操做失敗,則服務器不容許客戶端獲取註冊表信息5分鐘(可配置 eureka.server.getWaitTimeInMsWhenSyncEmpty)。
Eureka 的對等感知意識,經過所謂的「自我保護」的概念引入一個全新水平的複雜性(能夠經過設置eureka.server.enableSelfPreservation 爲 false 來關閉)。事實上,在網上看,這是我看到大多數人絆倒的地方。來自 Netflix Wiki:
當 Eureka 服務器啓動時,它會嘗試從相鄰節點獲取全部實例註冊表信息。若是從某個節點獲取信息時出現問題,服務器會嘗試全部對等體直到放棄。若是服務器可以成功獲取全部實例,則會根據該信息設置應接收的續訂閾值。若是有任什麼時候間,續訂低於爲該值配置的百分比,則服務器會中止使用 實例過時機制, 以保護當前實例註冊表信息。
數學運算以下:若是有兩個客戶端註冊到 Eureka 實例,每一個客戶端每30秒發送一次心跳,該實例應該在一分鐘內收到4次心跳。Spring 爲此添加了一個較低的最小值1(可配置eureka.instance.registry.expectedNumberOfRenewsPerMin),所以實例但願每分鐘接收5個心跳。而後將其乘以0.85(可配置eureka.server.renewalPercentThreshold)並四捨五入到下一個整數,這使咱們再次回到 5。若是 Eureka 在15分鐘內收到的心跳少於5次(可配置 eureka.server.renewalThresholdUpdateIntervalMs),它將進入自我保護模式並再也不讓已經註冊的實例過時。
Eureka 服務器隱含假設客戶端以每30秒固定的速率發送心跳。若是註冊了兩個實例,則服務器但願(2 2 + 1 ) 0.85 = 5每分鐘都接收一次心跳。若是續訂率低於此值,則激活自保護模式。如今,若是客戶端發送心跳的速度要快得多(例如,每隔10秒),則服務器每分鐘接收12次心跳,而且即便其中一個實例發生故障,也會持續接收6次/分鐘。所以,即便應該是自保護模式也不會被激活。這就是改變 eureka.client.instanceInfoReplicationIntervalSeconds 不可取的緣由。若是必須的話, 你能夠修改一下 eureka.server.renewalPercentThreshold 的值。
Eureka 對等實例不考慮預期的續約數量,但他們的心跳被計入在最後一分鐘收到的續約數量。在對等感知模式下,心跳能夠轉到任何 Eureka 實例; 這在運行負載均衡器或 Kubernetes 服務後很重要,其中心跳以循環模式(一般)發送到每一個實例。
在更新超過閾值以前,Eureka服務器不會退出自我保護模式。這可能致使客戶端獲取再也不存在的實例。請參閱瞭解Eureka Peer to Peer Communication.
還有一件事:在同一主機上運行多個 Eureka 服務器。Netflix 代碼(com.netflix.eureka.cluster.PeerEurekaNodes.isThisMyUrl)過濾掉同一主機上的對等 URL。這多是爲了防止服務器註冊爲本身的對等體(我在這裏猜想),但因爲它們不檢查端口,所以除非 Eureka 主機名eureka.client.serviceUrl.defaultZone 不一樣,不然對等感知不起做用。這種狀況的Hack解決方法是定義惟一的主機名,而後在/etc/hosts
文件(或它的Windows等價物)將它們映射到 127.0.0.1 了。Spring Cloud doc討論了這種解決方法,但沒有提到爲何須要它。如今你知道了。
AWS Regions 和可用 Zones。
Eureka 旨在在 AWS 中運行,並使用許多特定於 AWS 的概念和術語。Regions 和 Zones 是兩個這樣的東西。來自 AWS doc:
Amazon EC2 託管在全球多個地點。這些位置由 Regions 和可用 Zones 組成。每一個地區都是一個獨立的地理區域。每一個區域都有多個孤立的位置,稱爲可用Zones...每一個區域都是徹底獨立的。每一個可用 Zones 都是隔離的,但區域中的可用 Zones 經過低延遲連接鏈接。
Eureka 儀表板顯示環境和數據中心。的值被分別設置爲 test 與 default,經過使用com.netflix.config.ConfigurationManager 設置 org.springframework.cloud.netflix.eureka.server.EurekaServerBootstrap。有各類查找和回退,所以若是因爲某種緣由須要更改它們,請參閱上述類的源代碼。
Eureka 客戶端默認狀況下更喜歡相同的區域(可配置eureka.client.preferSameZone)。來自com.netflix.discovery.endpoint.EndpointUtils.getServiceUrlsFromDNS Java doc:
從DNS獲取全部 Eureka 服務 URL 的列表,以便 Eureka 客戶端與之交談。客戶端從其區域中獲取服務 URL,而後隨機故障轉移到其餘區域。若是同一區域中有多個服務器,則客戶端會再次隨機選擇一個服務器。這樣,流量將在發生故障時分發。
Ticket spring-cloud-netflix#203 在撰寫本文時是開放的,其中有幾我的談論 Regions和 Zones。我沒有驗證,因此我沒法評論Regions和 Zones如何與 Eureka一塊兒使用。
大部分都是從 spring-cloud-netflix#203 中複製而來的。
HA 策略彷佛是一個主要的 Eureka 服務器(server1)與備份(server2)。
經過配置(或 DNS 或 /etc/hosts
)向客戶端提供 Eureka 服務器列表
客戶端嘗試鏈接server1; 在這一點上,server2坐着閒着。
若是server1不可用,客戶端將從列表中嘗試下一個。
當server1從新上線時,客戶端會從新使用server1。
固然server1,server2能夠在對等感知模式下運行,而且能夠複製其註冊表。但這與客戶註冊正交。
做者:阿比吉特 薩卡(Abhijit Sarkar) 軟件工程師,攝影師,一般是個好人。
Updated: January 18, 2017