1. 程式人生 > >01. Spring Cloud--服務發現

01. Spring Cloud--服務發現

目錄

1.1 簡介

什麼是服務發現?
在任何分散式架構中,都需要找到機器所在的實體地址。這個概念自分散式計算開始出現就已經存在,並且被正式稱為服務發現。

服務發現對於微服務和基於雲的應用程式至關重要,主要原因有兩個。

  • 它為應用團隊提供了一種能力,可以快速地對在環境中執行的服務例項數量進行水平伸縮。通過服務發現,服務消費者能夠將服務的物理位置抽象出來。由於服務消費者不知道實際服務例項的物理位置,因此可以從可用服務池中新增或移除服務例項。
  • 它有助於提高應用程式的彈性,當微服務例項變得不健康或者不可用的時候,大多數服務發現引擎將從內部可用服務列表中一處該例項。由於服務發現引擎會在路由服務時繞過不可用服務,因此能夠使不可用服務造成的損害降至最小。

1.2 服務發現的特點

基於雲的微服務環境的解決方案是使用服務發現機制,這一機制具有以下特點。

  • 高可用 – 服務發現需要能夠支援“熱”叢集環境,在服務發現叢集中可以跨多個節點共享服務查詢。如果一個節點變得不可用,叢集中的其他節點應該能夠接管工作。
  • 點對點 – 服務發現叢集中的每一個節點共享服務例項的狀態。
  • 負載均衡 – 服務發現需要在所有服務例項之間動態地對請求進行負載均衡,以確保服務呼叫分佈在由它管理的所有服務例項上。在許多方面,服務發現取代了許多早期Web應用程式實現中使用的更靜態的、手動管理的負載均衡器。
  • 有彈性 – 服務發現的客戶端應該在本地“快取”服務資訊。本地快取允許服務發現功能逐步降級,這樣,如果服務發現本身變得不可用的時候,應用程式仍然可以基於本地快取中維護的資訊來執行和定位服務。
  • 容錯 – 服務發現需要檢測出服務例項什麼時候是不健康的,並從可以接收客戶端請求的可用服務列表中移除該例項。服務發現應該在沒有人為干預的情況下,對這些故障進行檢測,並採取行動。

1.3 服務發現架構

架構圖:
服務發現架構圖

1.3.1 服務註冊

當服務例項啟動時,它們將通過一個或多個服務發現例項來註冊他們可以訪問的物理位置、路徑和埠。雖然每個服務例項都具有唯一的IP地址和埠,但是每個服務例項都將以相同的服務ID進行註冊。服務ID是唯一標識一組相同服務例項的鍵。

服務通常只在一個服務發現例項中進行註冊。大多數服務發現的實現使用資料傳播的點對點模型,每個服務例項的資料都被傳遞到服務發現叢集中的所有其他節點。

最後,每個服務例項將通過服務發現服務去推送服務例項的狀態,或者服務發現服務從服務例項拉取狀態。任何未能返回良好的健康檢查資訊的服務都將從可用服務例項池中刪除。

1.3.2 負載均衡

服務在向服務發現服務進行註冊之後,這個服務就可以被需要使用這項服務功能的應用程式或者其他服務使用。客戶端可以使用不同的方式來發現服務。

在每次呼叫服務時,客戶端可以只依賴於服務發現引擎來解析服務位置。這種方式被稱為服務端負載均衡。使用這種模式,每次呼叫註冊的微服務例項時,服務發現引擎就會被呼叫。但是,這種方法很脆弱,因為客戶端完全依賴於服務發現引擎來查詢和呼叫服務。

還有一種更加健壯的方法就是使用客戶端負載均衡

在這個模式下,當服務消費者需要呼叫一個服務時:

  1. 它將聯絡服務發現服務,獲取它請求的所有服務例項,然後在服務消費者的機器上本地快取資料。
  2. 每當客戶端需要呼叫該服務時,服務消費者將從快取中查詢該服務的位置資訊。通常,客戶端快取將使用簡單的負載均衡演算法,如“輪詢”,以確保服務呼叫分佈在多個服務例項之間。
  3. 然後,客戶端將定期與服務發現服務進行聯絡,並重新整理服務例項的快取。如果在呼叫服務的過程中,服務呼叫失敗,那麼本地的服務發現快取失效,服務發現客戶端將嘗試再次訪問服務發現服務並重新整理本地資料。

1.4 構建Spring Eureka服務端

程式碼清單:新增依賴到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.spring</groupId>
  <artifactId>eureka-server</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

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

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.6.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.SR2</spring-cloud.version>
  </properties>

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

  <repositories>
    <repository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>
</project>

程式碼清單:在application.properties檔案中建立Eureka配置

spring.application.name=eureka-server
server.port=9000 #Eureka伺服器將要監聽的埠
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false #不要將自己註冊到Eureka服務列表中
eureka.client.fetch-registry=false #不要在本地快取登錄檔資訊
eureka.server.enable-self-preservation=false #關閉自我保護機制
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

自我保護機制:
詳見以下網址 – Spring Cloud Eureka 自我保護機制

程式碼清單:標註引導類以啟用Eureka伺服器

@SpringBootApplication
@EnableEurekaServer //在Spring服務中啟用Eureka伺服器
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

至此,我們就完成了Spring Eureka服務端的構建。我們可以通過mvn spring-boot:run命令來啟動服務。

1.5 構建Spring Eureka客戶端

程式碼清單:新增依賴到pom.xml檔案中

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

程式碼清單:在application.properties檔案中建立Eureka配置

server.port=埠號
spring.application.name=服務名 #將使用Eureka註冊的服務的邏輯名稱
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/
eureka.instance.prefer-ip-address=true #註冊服務的IP,而不是伺服器名稱

每一個通過Eureka註冊的服務都會有連個與之相關的元件:應用程式ID例項ID。應用程式ID用於表示一組服務例項。在基於Spring Boot的微服務中,應用程式ID使用是由spring.application.name屬性設定的值。而例項ID是一個隨機數,用於代表單個服務例項。

eureka.instance.prefer-ip-address屬性告訴Eureka,要將服務的IP地址而不是服務的主機名註冊到Eureka。
為什麼偏向於IP地址?
在預設情況,Eureka在嘗試註冊服務的時候,將會使用主機名讓外界與它們進行聯絡。這種方式在基於伺服器的環境中執行良好,在這樣的環境中,服務會被分配到一個DNS支援的主機名。但是,在基於容器的部署(如Docker)中,容器將以隨機生成的主機名啟動,並且該容器沒有DNS記錄。

eureka.client.service-url.defaultZone屬性包含客戶端用於解析服務位置的Eureka服務的列表,該列表以逗號進行分割。

程式碼清單:標註引導類以啟用Eureka客戶端

@SpringBootApplication
@EnableEurekaClient #啟用Eureka客戶端
@EnableFeignClients #啟用Fegin客戶端
public class UserEurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserEurekaApplication.class, args);
    }
}

程式碼清單:Eureka客戶端呼叫其他服務

@FeginClient("product-eureka")
public interface DataClient {
    @GetMapping("/product/{userId}")
    List<Product> getProductByUserId(@PathVariable("userId") String userId);
}

以上程式碼表示:呼叫product-eureka服務提供的/product/{userId}介面。