java框架之SpringCloud(3)-Eureka服務註冊與發現

上一章節完成了一個簡單的微服務案例,下面就經過在這個案例的基礎上集成 Eureka 來學習 Eureka。html

介紹

概述

Eureka 是 Netflix 的一個子模塊,也是核心模塊之一。Eureka 是一個基於 REST 的服務,用於定位服務,以實現雲端中間層服務發現和故障轉移。服務註冊與發現對於微服務架構來講是很是重要的,有了服務發現與註冊,只須要使用服務的標識符,就能夠訪問到服務。功能相似於 dubbo的註冊中心,好比 Zookeeper。java

SpringCloud 封裝了 Netflix 公司開發的 Eureka 模塊來實現服務註冊與發現,Eureka 採用了 C/S 的設計架構。web

Eureka Server 做爲服務註冊功能的服務器,它是服務註冊中心。而系統中的其它微服務,使用 Eureka 的客戶端鏈接到 Eureka Server 並維持心跳鏈接。這樣系統的維護人員就能夠經過 Eureka Server 來監控系統中的各個微服務是否正常運行。SpringCloud 的一些其它模塊(好比 Zuul)就能夠經過 Eureka Server 來發現系統中的其它微服務,並執行相關邏輯。算法

基本架構

Eureka Server 包含兩個組件:spring

  • Eureka Server :提供服務註冊的服務,各個節點啓動後,會在 Eureka Server 中進行註冊,這樣 EurekaServer 中的服務註冊表中將會存儲全部可用服務節點的信息,服務節點的信息能夠在界面中直觀的看到。
  • Eureka Client:是一個 Java 客戶端,用於簡化 Eureka Server 的交互,客戶端同時也具有一個內置的、使用輪詢(round-robin)負載算法的負載均衡器。在應用啓動後,將會向 Eureka Server 發送心跳(默認週期爲 30 秒)。若是 Eureka Server 在多個心跳週期內沒有接收到某個節點的心跳,Eureka Server 將會從服務註冊表中把這個服務節點一處(默認 90 秒)。

三大角色

  • Eureka Server:提供服務註冊與發現。
  • Service Provider:服務提供方將自身服務註冊到 Eureka,從而是服務消費方可以找到。
  • Service Consumer:服務消費方從 Eureka 獲取到註冊服務列表,從而可以獲取並消費服務。

架構圖

圖 1:Eureka 架構圖apache

圖 2:Dubbo 架構圖瀏覽器

使用

EurekaServer端

一、新建名爲 "microservicecloud-eureka-7001" 的子工程做爲 Eureka 服務端,依賴以下:安全

<?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">
    <parent>
        <artifactId>microservicecloud</artifactId>
        <groupId>zze.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>microservicecloud-eureka-7001</artifactId>

    <dependencies>
        <!--eureka-server服務端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
        <!-- 修改後當即生效,熱部署 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>springloaded</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>
</project>
pom.xml

二、配置 Eureka :服務器

server:
  port: 7001

eureka:
  instance:
    hostname: localhost # eureka 服務端實例名稱

  client:
    register-with-eureka: false # 表示不向註冊中心註冊本身
    fetch-registry: false # false 表示本身就是註冊中心,不須要檢索服務
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 設置與 Eureka Server 交互的地址,可用來查詢註冊的服務
application.yml

三、編寫主啓動類,並使用註解開啓 Eureka Server 功能:網絡

package zze.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer // 開啓 Eureka Server 功能,標識當前程序就是一個 Eureka Server
public class Application_7001 {

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

四、測試:

運行主啓動類,瀏覽器訪問 http://localhost:7001/ 進入 Eureka Server 圖形化頁面:

test
@EnableDiscoveryClient 和 @EnableEurekaClient:
  • 共同點:都是可以讓註冊中心可以發現,掃描到該服務。
  • 不一樣點:@EnableEurekaClient 只適用於 Eureka 做爲註冊中心,@EnableDiscoveryClient 能夠是其餘註冊中心。 從Spring Cloud Edgware開始,@EnableDiscoveryClient 或@EnableEurekaClient 可省略。只需加上相關依賴,並進行相應配置,便可將微服務註冊到服務發現組件上。

客戶端Provider註冊

一、修更名爲 "microservicecloud-provider-dept-8001" 的工程的 pom 文件,添加以下 Eureka 客戶端依賴:

<!--Eureka 客戶端依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
pom.xml

二、將當前工程做爲 Eureka 客戶端註冊到 Eureka 服務,在配置文件添加以下配置:

eureka:
  client: # 將當前工程做爲 Eureka 客戶端
    service-url:
      defaultZone: http://localhost:7001/eureka # Eureka 服務端地址
application.yml

三、在主啓動類添加註解標識當前工程爲 Eureka 客戶端:

package zze.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient // 標註當前工程爲 Eureka 客戶端
public class Application_8001 {
    public static void main(String[] args) {
        SpringApplication.run(Application_8001.class, args);
    }
}
zze.springcloud.Application_8001

四、測試:

先啓動 7001 Eureka 服務端工程,再啓動 8001 Eureka 客戶端工程,瀏覽器訪問 http://localhost:7001/,能夠看到 8001 客戶端實例已經被註冊到 Eureka 服務端:

test

服務發現

服務發現實際上就是讓 EurekaServer 端可以掃描到咱們註冊的服務,默認咱們能夠經過 Web UI 的方式查看哪些服務註冊到了 EurekaServer,還能夠經過 Eureka 客戶端依賴提供的服務發現客戶端獲取註冊到 EurekaServer 的服務信息。

一、修更名爲 "microservicecloud-consumer-dept-80" 的服務工程添加以下 Eureka 客戶端依賴:

<!--Eureka 客戶端依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

二、在其主啓動類上添加註解啓用 Eureka 客戶端功能:

package zze.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class Application_80 {

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

三、修改 Controller:

package zze.springcloud.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import zze.springcloud.entities.Dept;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/consumer/dept")
public class DeptController {
    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/add")
    public boolean add(@RequestBody Dept dept) {
        return restTemplate.postForObject(getRestUrlPrefix("MICROSERVICECLOUD-PROVIDER-DEPT") + "/dept/add", dept,Boolean.class);
    }

    @GetMapping("/get/{id}")
    public Dept get(@PathVariable Long id){
        return restTemplate.getForObject(getRestUrlPrefix("MICROSERVICECLOUD-PROVIDER-DEPT") + "/dept/get/" + id, Dept.class);
    }

    @GetMapping("/list")
    public List<Dept> list(){
        return restTemplate.getForObject(getRestUrlPrefix("MICROSERVICECLOUD-PROVIDER-DEPT") + "/dept/list", List.class);
    }

    // 服務發現客戶端
    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     * 經過服務名稱獲取到服務實例對應的 url
     */
    private String getRestUrlPrefix(String serviceName){
        List<String> services = discoveryClient.getServices();
        System.out.println("---------------------------"+services);
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
        System.out.println(instances);
        // 好比只註冊了一個 MICROSERVICECLOUD-PROVIDER-DEPT 微服務實例
        ServiceInstance serviceInstance = instances.get(0);
        return String.format("http://%s:%s", serviceInstance.getHost(), serviceInstance.getPort());
    }

    /**
     * 獲取全部註冊到 EurekaServer 的服務信息
     * @return
     */
    @GetMapping("/discovery")
    public Object discovery(){
        Map<String, Object> map = new HashMap<>();
        // 獲取全部註冊到 EurekaServer 的微服務名稱,對應 spring.application.name
        List<String> services = discoveryClient.getServices();
        for (String service : services) {
            // 獲取對應服務全部實例
            List<ServiceInstance> instances = discoveryClient.getInstances(service);
            map.put(service, instances);
        }
        return map;
    }

}
zze.springcloud.controller.DeptController

四、測試:

正常訪問 http://localhost/consumer/dept/list:

訪問 http://localhost/consumer/dept/discovery 查看全部服務信息:

test

即經過服務發現咱們只須要使用約定的服務名稱就能夠經過註冊中心訪問到具體服務信息。

EurekaServer集羣搭建

一、新建兩個子工程做爲兩個 EurekaServer 端,分別名爲 "microservicecloud-eureka-7002"、"microservicecloud-eureka-7003",主啓動類分別名爲 "Application_7002"、"Application_7003",依賴同 "microservicecloud-eureka-7001"。

二、因爲是單機測試,需修改本機 host 映射:

127.0.0.1 www.eurekaserver1.com
127.0.0.1 www.eurekaserver2.com
127.0.0.1 www.eurekaserver3.com

三、分別修改 700一、700二、7003 的配置文件:

server:
  port: 7001

eureka:
  instance:
    hostname: www.eurekaserver1.com # eureka 服務端實例名稱

  client:
    register-with-eureka: false # 表示不向註冊中心註冊本身
    fetch-registry: false # false 表示本身就是註冊中心,不須要檢索服務
    service-url:
      defaultZone: http://www.eurekaserver2.com:7002/eureka/,http://www.eurekaserver3.com:7003/eureka/
application.yml#7001
server:
  port: 7002

eureka:
  instance:
    hostname: www.eurekaserver2.com # eureka 服務端實例名稱

  client:
    register-with-eureka: false # 表示不向註冊中心註冊本身
    fetch-registry: false # false 表示本身就是註冊中心,不須要檢索服務
    service-url:
      defaultZone: http://www.eurekaserver1.com:7001/eureka/,http://www.eurekaserver3.com:7003/eureka/
application.yml#7002
server:
  port: 7003

eureka:
  instance:
    hostname: www.eurekaserver3.com # eureka 服務端實例名稱

  client:
    register-with-eureka: false # 表示不向註冊中心註冊本身
    fetch-registry: false # false 表示本身就是註冊中心,不須要檢索服務
    service-url:
      # 單機版
      # defaultZone: http://${e ureka.instance.hostname}:${server.port}/eureka/ # 設置與 Eureka Server 交互的地址,可用來查詢註冊的服務
      defaultZone: http://www.eurekaserver1.com:7001/eureka/,http://www.eurekaserver2.com:7002/eureka/
application.yml#7003

四、修改 8001 客戶端 Eureka 配置,讓客戶端註冊到多個 Eureka 服務端:

eureka:
  client: # 將當前工程做爲 Eureka 客戶端
    service-url:
      # 單機版
      # defaultZone: http://localhost:7001/eureka # Eureka 服務端地址
      defaultZone: http://www.eurekaserver1.com:7001/eureka,http://www.eurekaserver2.com:7002/eureka,http://www.eurekaserver3.com:7003/eureka
  instance:
    instance-id: microservicecloud-provider-dept
    prefer-ip-address: true # 訪問路徑顯示 IP
application.yml

五、測試:

依次啓動 700一、700二、7003 EurekaServer 服務,接着再啓動 8001 客戶端服務,客戶端服務能夠正常訪問。
訪問 http://www.eurekaserver1.com:7001/


訪問 http://www.eurekaserver2.com:7002/


訪問 http://www.eurekaserver3.com:7003/:

test

由測試結果可知,客戶端服務已經註冊到了多個 Eureka 服務端,每一個 Eureka 服務端上又掛載了其它 Eureka 服務端的副本,Eureka 集羣搭建成功。

補充

actuator與信息完善

上面咱們經過訪問 Eureka 的 Web 頁看到以下界面:

服務名稱修改

該界面是描述的是有哪些 Eureka 客戶端實例註冊到了當前 Eureka 服務端,說明以下:

  • Application 欄對應工程配置中的 spring.application.name 屬性,即說明這個客戶端是屬於哪一個工程。
  • 而 Status 欄則表示相應 Eureka 客戶端實例的標識,能夠經過 eureka.instance.instance-id 屬性進行修改。

IP信息提示

Status 欄所顯示的實例名稱是能夠點擊的,它所跳轉的頁面爲 隨機域名:端口/info ,以下:

若是咱們但願將這個地址的隨機域名改成 IP 地址,則能夠在配置文件中修改 eureka.instance.prefer-ip-address 屬性值爲 true 實現:

Info內容構建

爲方便咱們使用,Eureka 容許咱們經過點擊客戶端實例訪問實例的詳細信息,對應路徑爲 /info ,但咱們訪問時會發現會返回 404 以下:

其實 SpringBoot 自己提供的監控功能就能夠幫咱們解決這個問題,須要在客戶端工程中引入監控相關依賴:

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

使用 maven 資源插件,讓項目屬性加載到項目環境變量中:

<build>
    <finalName>microservicecloud</finalName>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <configuration>
                <delimiters>
                    <delimit>$</delimit>
                </delimiters>
            </configuration>
        </plugin>
    </plugins>
</build>

接着咱們就能夠在客戶端配置文件中添加 info 相關配置,例如:

info:
  host: ${java.rmi.server.hostname}
  port: ${server.port}
  app.name: microservicecloud-provider-dept-8001
  build.artifactId: ${project.artifactId}
  build.version: ${project.version}

重啓客戶端程序,從新訪問 /info :

自我保護機制

當咱們在作上述測試的時候,咱們可能會發現 Eureka 頁可能會顯示以下紅字:

這個其實就是由於 Eureka 的自我保護機制引發的。默認狀況下,若是 EurekaServer 在必定時間內沒有接收到某個微服務實例的心跳,EurekaServer 將會註銷該實例(默認爲 90 秒)。可是當網絡分區故障時,微服務與 EurekaServer之間沒法正常通訊,以上行爲可能就變得很是危險了——由於微服務自己實際上是健康的,此時本不該該註銷這個微服務。Eureka 經過「自我保護模式」來解決這個問題:當 EurekaServer 節點在短期內丟失過多的客戶端時(可能發生了網絡分區故障),那麼這個節點就會進入自我保護模式。一旦進入該模式,EurekaServer 就會保護服務註冊表中的信息,再也不刪除服務註冊表中的數據(也就是不會註銷任何微服務)。當網絡故障回覆後,EurekaServer 節點會自動退出自我保護模式。

在自我保護模式中,EurekaServer 會保護服務註冊表中的信息,不註銷任何服務實例。當它收到的心跳數從新恢復到閾值以上時,該 EurekaServer 就會自動退出自我保護模式。它的設計哲學就是寧肯保留錯誤的服務註冊信息,也不盲目註銷任何可能健康的服務實例,其目的是遵循 AP 原則。

關於 CAP 原則可參考【CAP 定理的含義

綜上,自我保護模式是一種應對網絡異常的安全保護措施,它的架構哲學是寧肯同時保留全部微服務(不管微服務是健康仍是不健康),也不盲目註銷任何健康的微服務。使用自我保護模式,可讓 Eureka 集羣更加的健壯、穩定。

在 SpringCloud Eureka 服務端工程中,能夠經過 eureka.server.enable-self-preservation = false 來禁用自我保護模式。

Eureka VS Zookeeper

做爲服務註冊中心,Eureka 比 Zookeeper 好在哪裏?

著名的 CAP 理論指出,一個分佈式系統不可能同時知足 C(一致性)、A(可用性)和 P(分區容錯性)。因爲分區容錯性 P 是在分佈式系統中必須保證的,所以咱們只能在 A 和 C 之間權衡。

所以 Zookeeper 保證的是 CP,而 Eureka 則是保證 AP。

Zookeeper 保證 CP:
當向註冊中心查詢服務列表時,咱們能夠容忍註冊中心返回的是幾分鐘之前的註冊信息,但不能接受服務直接 down 掉不可用。也就是說,服務註冊功能對可用性的要求要高於一致性。可是 Zookeeper 會出現這樣一種狀況,當 master 節點由於網絡故障與其它節點失去聯繫時,剩餘節點會從新進行 leader 選舉。問題在於,選舉 leader 的時間太長(30 - 120s),且選舉期間整個 Zookeeper 集羣都是不可用的,這就致使在選舉期間註冊服務癱瘓。在雲部署的環境下,因網絡問題使得 Zookeeper 失去 master 節點是較大機率會發生的事,雖然服務在最終可以恢復,可是漫長的選舉時間致使的註冊長期不可用是不能容忍的。
Eureka 保證 AP:
Eureka 看明白了這一點,所以在設計時就優先保證可用性。Eureka 各個節點都是平等的,幾個節點掛掉不會影響正常節點的工做,剩餘節點依然能夠提供註冊和查詢服務。而 Eureka 的客戶端在向某個 Eureka 註冊師若是發現鏈接失敗,則會自動切換至其它節點,只要有一臺 Eureka 服務還在,就能保證註冊服務可用(保證可用性),只不過查到的信息可能不是最新的(不保證強一致性)。除此以外,Eureka 還有一種自我保護機制,若是在 15 分鐘內超過 85% 的節點都沒有正常的心跳,那麼 Eureka 就認爲客戶端與註冊中心出現了網絡故障,此時就會出現以下幾種狀況:
  1. Eureka 再也不從註冊列表中移除由於長時間沒收到心跳而應該過時的服務。
  2. Eureka 仍然可以接受新服務的註冊和查詢請求,可是不會被同步到其它節點上(即保證當前節點依然可用)。
  3. 當網絡穩定時,當前實例新的註冊信息會被同步到其它節點中。
所以, Eureka 能夠很好的應對因網絡故障致使部分節點失去聯繫的狀況,而不會向 Zookeeper 那樣使整個註冊服務癱瘓。
相關文章
相關標籤/搜索