SpringCloud系列(二)Eureka詳解

Eureka簡介

Eureka是Netflix的一個子模塊,也是核心模塊之一。Eureka是一個基於REST服務,用於定位服務,以實現雲端中間層服務發現和故障轉移。服務註冊與發現對於微服務架構來說是非常重要的,有了服務發現與註冊,只需要使用服務的標識符,就可以訪問到服務,而不需要修改服務調用的配置文件。

SpringCloud封裝了Netflix公司開發的Eureka模塊來實現服務註冊和發現。Eureka採用了C-S的設計架構。EurekaServer作爲服務註冊功能的服務器,它是服務註冊中心。而系統中的其他微服務,使用Eureka的客戶端連接到EurekaServer並維持心路連接。這樣系統的維護人員就可以通過EurekaServer來監控系統中各個微服務是否正常運行。SpringCloud的一些其他模塊(比如Zuul)就可以通過EurekaServer來發現系統中的其他微服務,並執行相關的邏輯。

Eureka的基本架構

在這裏插入圖片描述

  • Eureka:註冊中心,相當於管理者
  • ApplicationService:提供服務,相當於生產者
  • ApplicationClient:用戶客戶端,相當於消費者

Eureka簡單實現

這裏使用的均爲 properties文件,如果你喜歡使用 yml 風格,推薦一個神器 在線yml和properties文件互轉

新建一個 SpringCloud 項目,此項目基於 SpringBoot 2.2.2.RELEASE 版本

再創建3個子模塊,一個生產者,一個消費者,一個註冊中心

在這裏插入圖片描述

父工程

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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.fu.springcloud</groupId>
    <artifactId>springcloud</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
    </properties>

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

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

生產者

新建一個 porducer,端口設置爲 8081

在這裏插入圖片描述

項目架構如圖所示,簡單 crud 代碼不再展示,只展示核心配置代碼

pom.xml

<dependencies>
    <!-- Eureka客戶端 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </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>

application.properties

server.port=8081
# 服務的名字,註冊到註冊中心的名字,消費者來根據名字調用服務 可以重複
spring.application.name=springcloud-producer
# EurekaServer地址
eureka.client.service-url.defaultZone=http://127.0.0.1:8888/eureka
# 當調用getHostname獲取實例的hostname時,返回ip而不是host名稱
eureka.instance.prefer-ip-address=true
# 指定自己的ip信息,不指定的話會自己尋找
eureka.instance.ip-address=127.0.0.1
# 執行當前服務的應用ID  不可以重複  標識的是每一個具體的的服務
eureka.instance.instance-id=springcloud-producer-8181

啓動類

@SpringBootApplication
@EnableEurekaClient		// 需要此註解,啓用Eureka客戶端
public class SpringCloud01Producer {
    public static void main(String[] args) {
        System.out.println("生產者服務啓動...8081");
        SpringApplication.run(SpringCloud01Producer.class, args);
    }
}

Controller

@RestController
@Scope("prototype")
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("list")
    public List<User> queryUserList() {
        return userService.queryUserList();
    }
}

消費者

在這裏插入圖片描述

pom.xml

<dependencies>
    <!-- Eureka客戶端 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <!-- http遠程調用的協議 -->
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>3.9.0</version>
    </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>

application.properties

server.port=8080
spring.application.name=springcloud-consumer
# EurekaServer地址
eureka.client.service-url.defaultZone=http://127.0.0.1:8888/eureka
# 當調用getHostname獲取實例的hostname時,返回ip而不是host名稱
eureka.instance.prefer-ip-address=true
# 指定自己的ip信息,不指定的話會自己尋找
eureka.instance.ip-address=127.0.0.1
# 執行當前服務的應用ID  不可以重複  標識的是每一個具體的的服務
eureka.instance.instance-id=springcloud-consumer-8080

RestTemplateConfig.java

@SpringBootConfiguration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }
}

啓動類

@SpringBootApplication
@EnableEurekaClient
public class SpringCloud01Consumer {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloud01Consumer.class, args);
    }
}

Controller

@RestController
@Scope("prototype")
public class UserController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    @RequestMapping("queryUserList")
    public List queryUserList() {
        // 使用如下代碼可以:不使用 Eureka,也能直接遠程調用生產者
//        String url = "http://127.0.0.1:8081/list";
//        return restTemplate.getForObject(url, List.class);

        // 獲取所有遠程服務的調用地址
        List<String> services = discoveryClient.getServices();
        System.out.println("註冊中心上服務列表有: " + services);
        // 通過服務名獲取調用的服務信息
        List<ServiceInstance> instances = discoveryClient.getInstances("springcloud-producer");
        for(ServiceInstance element: instances){
            System.out.println(element.getServiceId()+"\t"+element.getHost()+"\t"+element.getPort()+"\t" +element.getUri());
        }
        if (instances.size() == 0) {
            return null;
        }
        // 因爲現在是單體服務,並不是集羣模式,所以只可能有一個服務,直接調用第一個即可
        ServiceInstance instance = instances.get(0);
        String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/list";
        return this.restTemplate.getForObject(url, List.class);
    }
}

註冊中心

properties文件用於單個 註冊中心,yml文件用於Eureka集羣

這裏使用的是properties文件

在這裏插入圖片描述

pom.xml

<dependencies>
    <!-- Eureka服務端 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </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>

application.properties

server.port=8888
spring.application.name=eureka-server

# 註冊中心主機名
eureka.instance.hostname=127.0.0.1
# 是否將自己註冊到EurekaServer,默認是true
eureka.client.fetch-registry=false
# 是否進行檢測,false: 因爲自己是註冊中心,不需要去檢索服務信息
eureka.client.register-with-eureka=false
# 註冊地址 多節點用 , 分隔
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

啓動類

@SpringBootApplication
@EnableEurekaServer		// 表示這是一個 Eureka的服務端,即註冊中心
public class SpringCloud01EurekaServer {
    public static void main(String[] args) {
        System.out.println("Eureka服務啓動...8888");
        SpringApplication.run(SpringCloud01EurekaServer.class, args);
    }
}

測試

先啓動Eureka註冊中心,再啓動生產者和消費者。

在這裏插入圖片描述

查看eureka的控制面板,這兩個服務都已經註冊進去了

在這裏插入圖片描述

訪問消費者的控制器,可以正常獲取到數據

在這裏插入圖片描述

Eureka自我保護機制

什麼是Eureka自我保護機制

官方解釋: 自我保護模式正是一種針對網絡異常波動的安全保護措施,使用自我保護模式能使Eureka集羣更加的健壯、穩定的運行。

默認情況下,Eureka Client會定時的向 Eureka Server端發送心跳包,默認是30s發送一次,目的是告訴 Eureka Server當前客戶端實例還處於存活狀態,如果Eureka server在一定時間內沒有收到實例的心跳,便會把該實例從註冊表中刪除(默認是90秒),但是,如果短時間內丟失大量的實例心跳,便會觸發Eureka server的自我保護機制。

官方解釋總是這麼晦澀難懂,接下來使用一個實際場景,助你理解Eureka的自我保護機制。

啓動項目完成,打開eureka的控制面板,可以看到如下內容

在這裏插入圖片描述

現在我們停止了 Consumer 服務,刷新頁面

在這裏插入圖片描述

可以看到,出現了一個警告:緊急情況!Eureka可能不正確地聲稱實例在不在的情況下出現。續訂小於閾值,因此不會爲了安全而過期實例。

但是並沒有把consumer服務剔除掉,而是使用了緩存,將consumer讀取出來。

有可能這是因爲網絡波動的緣故,導致Eureka沒有及時的收到 consumer 服務的心跳,這時候就需要Eureka的自我保護機制。如果沒有自我保護,直接把consumer服務給剔除了,那網絡連接穩定的時候,又需要重新將服務註冊到Eureka上,這顯然是非常消耗性能的。

關閉Eureka自我保護機制

爲什麼要關閉

這麼好的功能爲什麼要關閉呢?

在生產環境下,肯定是要開啓的。

但是在開發環境下,我們需要不停的測試各種代碼,難免會一直重啓服務,但是 Eureka 服務一般不會更改,也就不必要重啓;我們在進行測試的時候,明明服務已經關閉了,但是Eureka上仍然顯示有此服務,會給我們開發人員帶來影響,不利於我們的開發。所以,在開發環境下,我們需要關閉Eureka的自我保護機制。

關閉方式

註冊中心關閉自我保護機制,修改檢查失效服務的時間,以確保註冊中心將不可用的實例及時正確剔除

在eureka項目的 application.properties 文件中,添加以下配置

# 是否開啓保護模式,開發環境下關閉自我保護機制,保證不可用服務及時踢出,默認是 true,開啓保護機制
eureka.server.enable-self-preservation=false
# 清理不可用服務的間隔時間,默認是 60* 1000 (60秒)
eureka.server.eviction-interval-timer-in-ms=2000

減短客戶端服務發送服務心跳給服務端的時間, 在開發測試時,將值設置設置小些,保證服務關閉後註冊中心能及時踢出服務。

在 consumer 項目的 application.properties 文件中,添加如下配置

# 租賃到期持續時間,即 如果6秒還沒有收到信息,就在eureka中刪除此服務
eureka.instance.lease-expiration-duration-in-seconds=6
# 租賃更新時間間隔,即 每隔2秒向註冊中心發送一個確認信息
eureka.instance.lease-renewal-interval-in-seconds=2

測試關閉是否成功

重新啓動3個項目

在這裏插入圖片描述

我們看到一個警告信息,是提示你Eureka的自我保護已經關閉了,忽略即可

​ 自動保存模式關閉。這可能無法在出現網絡或其他問題時保護實例。

然後關閉 consumer 服務,再次刷新頁面(6秒後,會刪除consumer服務)

在這裏插入圖片描述

因爲我們只配置了 consumer 服務,所以當關閉 producer服務時,eureka並不能及時將 producer 服務剔除

Eureka如何優雅停服

我們手動關閉了 eureka 的自我保護機制,有點太暴力了,而且關閉自我保護機制之後,控制面板還會有一個紅色警告,這讓強迫症的開發者看起來很不爽。並且每個服務都需要配置那些信息,項目上線的時候,每個配置都需要修改,比較繁瑣。

那麼如何優雅的將 eureka中不可用的服務及時剔除呢?

配置

需要在 client 客戶端上配置:

pom.xml

<!-- 監控服務 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

application.properties

# 暴露已經停止的服務
management.endpoints.web.exposure.include=shutdown
# 關閉已經停止的服務
management.endpoint.shutdown.enabled=true

測試

啓動3個項目,然後使用 post請求 訪問如下地址,使用form,或者ajax都行,這裏使用 PostMan 進行測試

http://127.0.0.1:8080/actuator/shutdown
在這裏插入圖片描述

然後刷新頁面

在這裏插入圖片描述

可以看到,producer服務已經從eureka註冊中心移除。而且也沒有煩人的警告信息,項目上線的時候也不需要更改配置信息

Eureka常用配置總結

# 註冊中心主機名
eureka.instance.hostname=127.0.0.1

# 是否將自己註冊到EurekaServer,默認是true
eureka.client.fetch-registry=false

# 是否進行檢測,false: 因爲自己是註冊中心,不需要去檢索服務信息
eureka.client.register-with-eureka=false

# 註冊地址 多節點用 , 分隔
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

# 是否開啓保護模式,開發環境下關閉自我保護機制,保證不可用服務及時踢出,默認是 true,開啓保護機制
eureka.server.enable-self-preservation=false

# 清理不可用服務的間隔時間,默認是 60* 1000 (60秒)
eureka.server.eviction-interval-timer-in-ms=2000

# 當調用getHostname獲取實例的hostname時,返回ip而不是host名稱
eureka.instance.prefer-ip-address=true

# 指定自己的ip信息,不指定的話會自己尋找
eureka.instance.ip-address=127.0.0.1

# 執行當前服務的應用ID  不可以重複  標識的是每一個具體的的服務
eureka.instance.instance-id=springcloud-producer-8181

# 是否開啓保護模式,開發環境下關閉自我保護機制,保證不可用服務及時踢出,默認是 true,開啓保護機制
eureka.server.enable-self-preservation=false

# 清理不可用服務的間隔時間,默認是 60* 1000 (60秒)
eureka.server.eviction-interval-timer-in-ms=2000

# 表示eureka client間隔多久去拉取服務器註冊信息,默認爲30秒
eureka.client.registry-fetch-interval-seconds=5

# 不啓動此服務
eureka.client.enabled=false

Eureka搭建集羣實現高可用

詳情參見:SpringCloud系列(三)Eureka搭建集羣實現高可用(三種方式)

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