1. 程式人生 > >Spring Cloud Eureka(服務治理)(2)

Spring Cloud Eureka(服務治理)(2)

1.服務發現與消費

下面來嘗試構建一個服務消費者,它主要完成兩個目標,發現服務以及消費服務。其中服務發現的任務由Eureka的客戶端完成,而服務消費者的任務由Ribbon完成。Ribbon是一個基於HTTP和TCP的客戶端負載均衡器,它可以在通過客戶端中配置的ribbonServerList的服務端列表去輪詢訪問以達到負載均衡的作用。當Ribbon與Eureka聯合使用時,Ribbon的服務例項清單RibbonServerrList會被DiscoveryEnabledNIWSServerList重寫,擴充套件成從Eureka註冊中心獲取服務端列表。同時它也會用NIWSDiscoveryPing來取代IPing,它將職責委託給Eureka來確定服務端是否已經啟動。

首先啟動服務註冊中心eureka-server,然後通過命令列的方式啟動兩個eureka-service(為了實驗Ribbon的客戶端負載均衡功能,通過java -jar 命令列的方式來啟動兩個不同埠的eureka-service):

java -jar eureka-service-0.0.1-SNAPSHOT.jar --server.port=8082

java -jar eureka-service-0.0.1-SNAPSHOT.jar --server.port=8083

建立一個Boot的基礎工程來實現服務消費者,取名為ribbon-consumer,並在pom.xml中引入如下依賴內容:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>eureka-ribbon</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-ribbon</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

然後給應用主類上新增@EnableDiscoveryClient註解,讓該應用註冊為Eureka客戶端應用,使其獲得服務發現的能力。同時在該主類中建立RestTemplate的SpringBean例項,並通過@LoadBalanced註解開啟客戶端負載均衡。

@EnableDiscoveryClient
@SpringBootApplication
public class EurekaRibbonApplication {

    @Bean
    @LoadBalanced
    RestTemplate restTemplate(){
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(EurekaRibbonApplication.class, args);
    }
}

建立ConsumerController類並實現ribbon-consumer介面。該介面通過上面建立的RestTemplate來實現對euerka-service服務提供的/hello介面進行呼叫。

@RestController
public class ConsumerController {

    @Autowired
    RestTemplate restTemplate;

    @RequestMapping("/ribbon-consumer")
    public  String helloConsumer(){
        return restTemplate.getForEntity("http://hello-service/hello",String.class).getBody();
    }
}

然後在application.properties中配置Eureka服務註冊中心的位置,需要跟eureka-service中的application.properties中的一樣否則無法發現該服務,同時配置埠防止埠衝突。

spring.application.name=ribbon-consumer
server.port=8099

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

而我的eureka-service的配置如下:

啟動ribbon-consumer應用後,可以在Eureka資訊面板看到:

除了hello-service還多了ribbon-consumer服務。

然後訪問http://localhost:8099/ribbon-consumer發起GET請求

頁面成功返回"Hello"。此時可以在控制檯看到如下資訊:

2018-09-03 22:09:18.352  INFO 13272 --- [nio-8099-exec-1] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client hello-service initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=hello-service,current list of Servers=[PC201709201534:8083, PC201709201534:8082],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone;	Instance count:2;	Active connections count: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.0;]
},Server stats: [[Server:PC201709201534:8082;	Zone:defaultZone;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 08:00:00 CST 1970;	First connection made: Thu Jan 01 08:00:00 CST 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
, [Server:PC201709201534:8083;	Zone:defaultZone;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 08:00:00 CST 1970;	First connection made: Thu Jan 01 08:00:00 CST 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
]}ServerList:org.springf[email protected]760980fd

Ribbon輸出了當前客戶端維護的hello-service的服務列表情況。中專包含了各個例項的位置,Ribbon就是按照此資訊進行輪詢訪問,以實現對各個例項的請求總數量、第一次連線資訊、上一次連線資訊、總的請求失敗數量等。

2.基礎架構

上面我簡單的演示了Eureka服務治理系統的三個核心角色:服務註冊中心,服務提供者,服務消費者。

服務註冊中心:Eureka提供的服務端,提供服務註冊與服務發現的功能,也就是eureka-server。

服務提供者:提供服務的應用,可以是Spring Boot應用也可以是其他技術平臺且遵循Eureka通訊機機制的應用。他將自己提供的服務註冊到Eureka,以供其他應用發現,也就是我實現的hello-service應用。

服務消費者:消費者應用從服務註冊中心獲取服務列表,從而使消費者可以知道去何處呼叫其所需要的服務,除了已經使用過的Ribbon還有Feign的消費方式。

很多時候客戶端即是服務提供者也是服務消費者。

3.服務治理機制

1.服務提供者:

服務註冊
“服務提供者在啟動的時候會通過傳送REST請求的方式將自己註冊到EurekaServer上, 同時帶上了自身服務的 一 些元資料資訊。Eureka Server接收到這個REST請求之後,將元資料資訊儲存在 一 個雙層結構Map中, 其中第 一 層的key是服務名, 第二層的key是具體服務的例項名。(之前在實現Ribbon負載均衡的例子中, Eureka資訊面板中 一 個服務有多個例項的清況, 這些內容就是以這樣的雙層Map形式 儲存的。)

在服務註冊時需要確認:eureka.client.register-with-eureka=true引數是否正確,該值預設為true,若設定為false將不會啟動註冊操作。

服務同步
如架構圖中所示, 這裡的兩個服務提供者分別註冊到了兩個不同的服務註冊中心上,也就是說, 它們的資訊分別被兩個服務註冊中心所維護。 此時, 由於服務註冊中心之間因互相註冊為服務, 當服務提供者傳送註冊請求到 一 個服務註冊中心時, 它會將該請求轉發給叢集中相連的其他註冊中心,從而實現註冊中心之間的服務同步 。 通過服務同步,兩個服務提供者的服務資訊就可以通過這兩臺服務註冊中心中的任意 一 臺獲取到。

服務續約

在註冊完服務後,服務提供者會維護一個心跳用來持續高速Eureka Server,它還存活著,以防止Eureka Server的剔除任務將該服務例項從服務列表中排出,我們稱此操作為服務續約。

關於服務續約有兩個重要的屬性:

eureka.instance.leasse-renewal-interval-in-seconds=30

eureka.instance.leasse-expiration-duration-in-seconds=90

eureka.instance.leasse-renewal-interval-in-seconds引數用於定義服務續約任務的呼叫間隔時間,預設為30秒。

eureka.instance.leasse-expiration-duration-in-seconds引數用於定義服務失效時間,預設為90秒。

2.服務消費者:

獲取服務
在這裡,在服務註冊中心已經註冊了一個服務,並且該服務有兩個例項,當我們啟動服務消費者的時候,它會發送一個REST請求給服務註冊中心,來獲取上面註冊的服務清單,為了效能考慮,Eureka Server會維護一份只讀的服務清單來返回給客戶端,同時該快取清單會隔30秒更新一次。

獲取服務是服務消費者的基礎,所以必須確保eureka.client.fetch-registry=true引數沒有被修改成false,該值預設為ture。若希望修改快取清單的更新時間,可以通過eureka.client.registry-fetch-interval-seconds=30引數進行修改,該引數預設值為30秒。

服務呼叫
服務消費者在獲取服務清單後,通過服務名可以獲得具體提供服務的例項名和該例項的元資料,因為有這些服務實力的詳細資訊,所以客戶端可以根據自己的需要決定具體呼叫哪個例項,在ribbon中會預設採用輪詢的方式進行呼叫,從而實現客戶端的負載均衡。

對於訪問例項的選擇,Eureka中Region和Zone的概念,一個Region中可以包含多個Zone,每個服務客戶端需要被註冊到一個Zone中,所以每個客戶端對應一個Region和一個Zone,在服務呼叫的時候,先訪問同處一個Zone中的服務提供方,若訪問不到,就訪問其他的Zone。

服務下線
在系統執行過程中必然會面臨關閉和重啟服務的某個例項的情況,在服務關閉期間,我們自然不會希望客戶端繼續呼叫關閉了的例項,所以在客戶端程式中,當服務例項進行正常的關閉操作時,,會觸發一個服務下線的REST請求給Eureka Server,告訴服務註冊中心:我要下線了,服務端在接收到請求後。將該服務狀態置為下線(DOWN),並把該下線事件傳播出去

3.服務註冊中心:

失效剔除
有些時候, 我們的服務例項並不 一 定會正常下線, 可能由於記憶體溢位、 網路故障等原因使得服務不能正常工作, 而服務註冊中心並未收到“ 服務下線 ”的請求。 為了從服務列表中將這些無法提供服務的例項剔除, EurekaServer在啟動的時候會建立 一 個定時任務,預設每隔 一 段時間(預設為60秒) 將當前清單中超時(預設為90秒)沒有續約的服務剔除出去。

自我保護
當我們在本地除錯基於Eureka的程式時, 基本上都會碰到這樣 一 個問題, 在服務註冊
中心的資訊面板中出現類似下面的紅色警告資訊:

實際上, 該警告就是觸發了EurekaServer的自我保護機制。 之前我們介紹過, 服務註冊到EurekaServer之後,會維護 一 個心跳連線,告訴EurekaServer自己還活著。EurekaServer在執行期間,會統計心跳失敗的比例在15分鐘之內是否低於85%, 如果出現低於的情況 (在單機除錯的時候很容易滿足, 實際在生產環境上通常是由於網路不穩定導致), Eureka Server會將當前的例項註冊資訊保護起來, 讓這些例項不會過期, 儘可能保護這些註冊資訊。 但是, 在這段保護期間內例項若出現問題, 那麼客戶端很容易拿到實際已經不存在的服務例項, 會出現呼叫失敗的清況, 所以客戶端必須要有容錯機制, 比如可以使用請求重試、 斷路器等機制。

由於本地除錯很容易觸發註冊中心的保護機制,這會使得註冊中心維護的服務例項不準確。所以在本地開發時,可以使用eureka.server.enable-self-preservation=false引數來關閉保護機制,以確保註冊中心可以將不可用的例項正確剔除。