SpringCloud入坑記-深入Eureka

在這裏插入圖片描述

前言

如果還不瞭解Eureka,那麼應該先從上一篇開始 SpringCloud入坑記-初識Eureka。搭建一個Eureka Server已經註冊一個實例,纔算有一個初步的認知。

自我保護

在實踐過程中,很可能會在頁面遇到下面一段紅色文字:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

這是Eureka server的一種自我保護機制。

Eureka Server與Client之間是通過心跳確認對方是否存活。而現實環境中,網絡的抖動或者閃斷可能導致心跳未能如約而至,而服務本身是健康的,如果此時Eureka server將實例去除,就會導致正常的服務無法被調用到,要是網絡抖動比較頻繁會使得程序很不穩定。極端一點,發送心跳時網絡掉線導致註冊的服務信息被刪除,所有服務都失效無法訪問了。
Eureka引入了自我保護老解決這個問題,當最近一分鐘接收到的心跳次數小於一個值的時候,就禁止服務被刪除,保護註冊信息,同時顯示一段文字提示。

不過也有幾個問題:服務停掉了,還可以在Eureka Server上看到;服務上線了,Eureka Server上還不能及時獲取到。
在項目開發時,這個功能會干擾我們的集成,可以通過下面的參數關掉:
eureka.server.enableSelfPreservation=false

Eureka Server HA 高可用

在分佈式系統中,服務註冊中心是最重要的基礎部分

單個Eureka Server

上一節已經實現的Eureka Server與Client可以通過下面這個圖表示:

在這裏插入圖片描述

可以看到,Client註冊到Server上。
如果多個服務的話,可以都註冊到Server上,就可以用下面這個圖表示:

在這裏插入圖片描述

很容易就發現,Eureka Server是很重要的一環,服務ClientA要與服務ClientB通信,不直接找對方,而是通過中間橋樑 Eureka Server。要是Eureka Server宕機,服務Client A、B、C…就無法註冊,於是需要做高可用的Eureka Server

兩個Eureka Server

Eureka Server支持一種P2P模式,可以在ServerA中註冊ServerB,它們就會相互協同工作。

在這裏插入圖片描述

複製之前的項目ms-a1-eureka-server,命名爲ms-b1-eureka-server,僅僅修改其中的配置文件application.yml

server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8762/eureka

可以看到,對於ServerA(端口8761)註冊ServerB的信息(端口8762)。
再次複製之前的項目ms-a1-eureka-server,命名爲ms-b2-eureka-server,僅僅修改其中的配置文件application.yml

server:
  port: 8762
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8761/eureka

ServerB(端口8762)註冊ServerA的信息(端口8761)。

分別啓動兩個項目,在Eureka Server的控制檯可以看到如下信息:

在這裏插入圖片描述

和之前的控制面板顯示的信息不一樣。

對於客戶端,也需要相應的配置來適應Eureka Server的高可用部署。

複製之前的項目ms-a1-product-plain,命名爲ms-b1-product-plain,僅僅修改其中的配置文件application.yml

server:
  port: 8080
spring:
  application:
    name: product-plain
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka

將所有的Eureka Server列表都寫在了配置裏,用逗號隔開。啓動該項目,可以在兩個Eureke Server的頁面看到註冊的信息。停掉任意一個Eureka Server,對於商品服務"product-plain"沒有影響。
在這裏插入圖片描述
如果有多個服務的話,可以都註冊到Server上
在這裏插入圖片描述

三個Eureka Server

在實際的生產中,2臺Eureka Server是不夠的,一般需要3臺或者更多。
對於三臺Eureka Server,需要Eureka Server兩兩之間相互配合。

在這裏插入圖片描述

這時,對於ServerA就需要如下配置application.yml

server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8762/eureka,http://localhost:8763/eureka

在Client端,需要將所有的Server配置好:

server:
  port: 8080
spring:
  application:
    name: product-plain
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka,http://localhost:8763/eureka

Eureka Server故障分析

上面講過Eureka Server可以做高可用,但是這就能保證萬無一失了嗎?
有一個理論叫墨菲定律,它的一個觀點可以解釋爲:如果你擔心某種情況會發生,那麼它極有可能會發生,久而久之一定會發生。

Eureka Server 全部宕機

最壞的情況就是所有的Eureka Server全部宕機,如果此時Client還沒有啓動,那麼服務可以啓動,但是會一直打印如下錯誤信息:
com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused: connect
com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
這樣該服務無法被其他服務感知到,不能調用其他服務,也不會被其他服務調用到。屬於一個孤立的服務,一般屬於無用服務。

要是服務在成功啓動,並且註冊到了Eureka Server以後,全部的Eureka Server才宕機,此時客戶端依然會報錯,但是行爲略有不同。客戶端本地會維護一個所有服務的註冊列表信息,在源代碼DiscoveryClient中可以看到如下聲明
private final AtomicReference<Applications> localRegionApps;

它保存了一份其他服務的註冊信息,通過CacheRefreshThread來定時刷新,時間間隔爲renewalIntervalInSecs,默認爲30S。如果刷新時,所有的Eureka Server不可用,那麼就不刷新列表。服務間可以正常通信,但是無法感知變化,要是有服務下線,其他服務無法獲知。

Eureka Server 部分宕機

Eureka Server之間是Peer to Peer的同步,它比Master-Slave模式更爲穩定一些。Master-Slave中Slave宕機不影響系統,要是Master宕機,需要重新選舉Master機器,Zookeeper便是這種機制。P2P模式不涉及到選舉,只是Eureka Server之間的同步會報錯,不影響正常使用。

客戶端這邊,在配置文件中存有所有Eureka Server的地址:
defaultZone: http://server1/eureka,http://server2/eureka,http://server3/eureka

在拉取註冊列表信息的時候,客戶端會嘗試依次從這些服務器中獲取信息,獲取不到就去下一個服務器中找。目前Eureka Server 部分宕機,所以肯定可以獲取到。其次,在客戶端還維護了一個不可達的Eureka Server列表,之後就不從這些服務器拉取信息。要是該列表中的Eureka Server數目達到一定的值,就將其清空
還有一點,客戶端在拉取註冊信息的時候,不是每次都從第一個開始,這樣所有的請求壓力都集中在第一臺服務器上,而其他的Eureka Server不幹活。實際上,這個請求是隨機的,保證所有的Eureka Server能均勻地處理客戶端的拉取請求。

Eureka Server的REST API

Eureka Server暴露了一些API可以直接調用,大部分時候,我們用不到這些API,但是在某些場景下,可以定製化一些東西。

先列舉幾個接口:

操作 http method http request
註冊新服務 POST /eureka/apps/{appId}
註銷服務 DELETE /eureka/apps/{appId}/{instanceId}
心跳 PUT /eureka/apps/{appId}/{instanceId}
查詢所有實例 GET /eureka/apps
查詢單個實例 GET /eureka/apps/{appId}

還有很多接口可以調用,在Eureka的GitHub上有詳細的API列表。

有什麼用呢?試想一個場景,我們要定製化Eureka Server的頁面,將其嵌入到我們現有的監控平臺,直接把Eureka Server現有的頁面加入其中顯得風格不搭,就可以通過上面提供的API來自定義界面,還可以添加更多的功能。

例如,我訪問http://localhost:8761/eureka/apps/PRODUCT-PLAIN,就可以拿到如下信息:

<application>
<name>PRODUCT-PLAIN</name>
<instance>
<instanceId>192.168.2.200:product-plain:8080</instanceId>
<hostName>192.168.2.200</hostName>
<app>PRODUCT-PLAIN</app>
<ipAddr>192.168.2.200</ipAddr>
<status>UP</status>
<overriddenstatus>UNKNOWN</overriddenstatus>
<port enabled="true">8080</port>
<securePort enabled="false">443</securePort>
<countryId>1</countryId>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwn</name>
</dataCenterInfo>
<leaseInfo>
<renewalIntervalInSecs>30</renewalIntervalInSecs>
<durationInSecs>90</durationInSecs>
<registrationTimestamp>1555842871524</registrationTimestamp>
<lastRenewalTimestamp>1555848452274</lastRenewalTimestamp>
<evictionTimestamp>0</evictionTimestamp>
<serviceUpTimestamp>1555842870859</serviceUpTimestamp>
</leaseInfo>
<metadata>
<management.port>8080</management.port>
<jmx.port>52858</jmx.port>
</metadata>
<homePageUrl>http://192.168.2.200:8080/</homePageUrl>
<statusPageUrl>http://192.168.2.200:8080/actuator/info</statusPageUrl>
<healthCheckUrl>http://192.168.2.200:8080/actuator/health</healthCheckUrl>
<vipAddress>product-plain</vipAddress>
<secureVipAddress>product-plain</secureVipAddress>
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
<lastUpdatedTimestamp>1555842871524</lastUpdatedTimestamp>
<lastDirtyTimestamp>1555842870720</lastDirtyTimestamp>
<actionType>ADDED</actionType>
</instance>
</application>

未完待續

Eureka還有很多東西可以討論,需要在實際運用中慢慢體會。在下一節中,我將引入Ribbon這一組件。

項目代碼託管於Github

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章