1. 概述
在本教程中,我們將介紹通過“Spring Cloud Netflix Eureka”實現客户端服務發現。
客户端服務發現 允許服務在不硬編碼主機名和端口的情況下找到並與其他服務進行通信。 在這種架構中,唯一的“固定點”是 服務註冊表,每個服務都必須將其註冊到該註冊表中。
一個缺點是,所有客户端都必須實現某種邏輯來與此固定點交互。這假設了在實際請求之前需要額外的網絡往返。
通過 Netflix Eureka,每個客户端都可以同時作為服務器,向與其連接的同伴複製其狀態。換句話説,客户端檢索所有連接的同伴的列表,並通過負載均衡算法向其他服務發送所有後續請求。
為了瞭解客户端的存在,它們必須向註冊表發送心跳信號。
為了實現本教程的目標,我們將實現三個 微服務:
- 一個 服務註冊表 (Eureka Server)
- 一個 REST 服務,它將其自身註冊到註冊表中 (Eureka Client)
- 一個 Web 應用程序,它作為註冊表感知型客户端消費 REST 服務 (Spring Cloud Netflix Feign Client)
2. Eureka 服務器
部署一個 Eureka 服務器 用於服務註冊,就像這樣:
- 將 spring-cloud-starter-netflix-eureka-server 添加到依賴項中
- 通過在 @SpringBootApplication 註解中添加 @EnableEurekaServer 來啓用 Eureka 服務器
- 配置一些屬性
讓我們一步一步來。
首先,我們將創建一個新的 Maven 項目並將依賴項放入其中。 請注意,我們正在導入 spring-cloud-starter-parent 到本教程中描述的所有項目:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>2023.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>我們可以查看最新的 Spring Cloud 發佈版本,請參考 Spring 項目文檔。
然後我們將會創建主應用程序類:
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}最後,我們將配置屬性,採用 YAML 格式,一個 application.yml 文件將作為我們的配置文件:
server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false我們正在配置應用程序端口;對於 Eureka 服務器的默認端口是 8761。 我們告訴內置的 Eureka Client 不要與其自身註冊,因為我們的應用程序應該作為服務器運行。
現在,我們將瀏覽器指向 http://localhost:8761 以查看 Eureka 儀表板,並在其中檢查已註冊的實例。
目前,我們可以看到基本指示器,例如狀態和健康指示器:
3. Eureka 客户端
為了使一個 @SpringBootApplication 具有發現能力,我們需要將 Spring Discovery 客户端(例如,spring-cloud-starter-netflix-eureka-client)包含到我們的 classpath 中。
然後,我們需要使用 @Configuration 註解,並添加 @EnableDiscoveryClient 或 @EnableEurekaClient 註解。請注意,如果我們在 classpath 中包含了 spring-cloud-starter-netflix-eureka-client 依賴,則此註解是可選的。
後者告訴 Spring Boot 顯式地使用 Spring Netflix Eureka 進行服務發現。為了為我們的客户端應用程序填充一些示例生命週期,我們還將包含 spring-boot-starter-web 包到 pom.xml 中,並實現一個 REST 控制器。
但是首先,我們添加依賴。 再次,我們可以讓 spring-cloud-starter-parent 依賴來為我們確定工件版本:
<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-web</artifactId>
</dependency>我們將實現主應用程序類:
@SpringBootApplication
@RestController
public class EurekaClientApplication implements GreetingController {
@Autowired
@Lazy
private EurekaClient eurekaClient;
@Value("${spring.application.name}")
private String appName;
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
@Override
public String greeting() {
return String.format(
"Hello from '%s'!", eurekaClient.getApplication(appName).getName());
}
}以及 GreetingController 接口:
public interface GreetingController {
@RequestMapping("/greeting")
String greeting();
}與其使用接口,我們也可以直接在 EurekaClientApplication 類中聲明映射關係。如果我們需要在服務器和客户端之間共享該接口,那麼使用接口是有幫助的。
接下來,我們需要配置一個 application.yml 文件,其中包含一個配置好的 Spring 應用名稱,以便在已註冊的應用列表中唯一標識我們的客户端。
我們可以讓 Spring Boot 為我們選擇一個隨機端口,因為稍後我們會通過其名稱訪問該服務。
最後,我們需要告訴客户端它必須在哪裏查找註冊表:
spring:
application:
name: spring-cloud-eureka-client
server:
port: 0
eureka:
client:
serviceUrl:
defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}
instance:
preferIpAddress: true我們決定以這種方式設置 Eureka 客户端,因為這種服務應該具有良好的可擴展性。
現在我們將運行客户端,並再次將瀏覽器指向 http://localhost:8761,以查看其在 Eureka 儀表盤上的註冊狀態。通過使用儀表盤,我們可以進行進一步的配置,例如將已註冊客户端的主頁與儀表盤鏈接,用於管理目的。然而,配置選項超出了本文的範圍:
4. Feign 客户端
為了完成我們包含三個依賴微服務的項目,我們將使用 Spring Netflix Feign 客户端,構建一個基於 REST 消費的 Web 應用程序。
可以將 Feign 視為一個具有服務發現能力的 Spring RestTemplate,它使用接口與端點進行通信。這些接口將在運行時自動實現,而不是使用 service-urls,而是使用 service-names。
如果沒有 Feign, 我們需要將一個 EurekaClient 實例自動注入到我們的控制器中,並通過 service-name 獲取服務信息,作為 Application 對象。
我們會使用這個 Application 對象來獲取所有實例的列表,選擇一個合適的實例,然後使用這個 InstanceInfo 對象來獲取主機名和端口。這樣,我們就可以使用任何 http 客户端 執行標準的請求。
@Autowired
private EurekaClient eurekaClient;
@RequestMapping("/get-greeting-no-feign")
public String greeting(Model model) {
InstanceInfo service = eurekaClient
.getApplication(spring-cloud-eureka-client)
.getInstances()
.get(0);
String hostName = service.getHostName();
int port = service.getPort();
// ...
}一個 RestTemplate 也可用於通過名稱訪問 Eureka 客户端服務,但此主題超出了本文範圍。
為了設置我們的 Feign Client 項目,我們將以下四個依賴項添加到其 pom.xml 中:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>Feign 客户端位於 spring-cloud-starter-feign 包中。為了啓用它,我們必須使用 @Configuration 註解,並使用 @EnableFeignClients 啓用它。要使用它,我們只需在一個接口上使用 @FeignClient(“service-name”) 註解,並將其自動注入到控制器中。
創建此類Feign 客户端的良好方法是創建具有 @RequestMapping 註解方法的接口,並將它們放入單獨的模塊中。這樣它們就可以在服務器端和客户端之間共享。在服務器端,我們可以將其實現為 @Controller,而在客户端,它們可以擴展並標記為 @FeignClient。
此外,spring-cloud-starter-eureka 包 需要包含在項目中的,並通過在主應用程序類上使用 @EnableEurekaClient 啓用它。
spring-boot-starter-web 和 spring-boot-starter-thymeleaf 依賴項用於呈現包含來自我們 REST 服務的提取數據的視圖。
這將是我們的Feign Client 接口:
@FeignClient("spring-cloud-eureka-client")
public interface GreetingClient {
@RequestMapping("/greeting")
String greeting();
}在這裏,我們將實現主應用程序類,該類同時作為控制器起作用。
@SpringBootApplication
@EnableFeignClients
@Controller
public class FeignClientApplication {
@Autowired
private GreetingClient greetingClient;
public static void main(String[] args) {
SpringApplication.run(FeignClientApplication.class, args);
}
@RequestMapping("/get-greeting")
public String greeting(Model model) {
model.addAttribute("greeting", greetingClient.greeting());
return "greeting-view";
}
}
這將是我們的視圖的 HTML 模板:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Greeting Page</title>
</head>
<body>
<h2 th:text="${greeting}"/>
</body>
</html>application.yml配置文件的內容與前一步驟基本相同:
spring:
application:
name: spring-cloud-eureka-feign-client
server:
port: 8080
eureka:
client:
serviceUrl:
defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}現在我們可以構建並運行這個服務。最後,我們將瀏覽器指向 http://localhost:8080/get-greeting,它應該顯示類似於以下內容:
Hello from SPRING-CLOUD-EUREKA-CLIENT!5. ‘TransportException: 無法在任何已知服務器上執行請求’
在運行 Eureka 服務器時,我們經常會遇到如下異常:
com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server基本上,這通常是由於 application.properties 或 application.yml 中的配置錯誤造成的。 Eureka 提供了兩個客户端可配置的屬性:
- registerWithEureka: 如果將此屬性設置為 true, 則服務器啓動時,內置客户端將嘗試將其註冊到 Eureka 服務器。
- fetchRegistry: 如果將此屬性配置為 true, 則內置客户端將嘗試獲取 Eureka 註冊表。
現在,當我們啓動 Eureka 服務器時,我們不希望內置客户端將其配置為與服務器一起註冊。
如果我們將上述屬性標記為 true(或者不配置它們,因為它們默認設置為 true),則在啓動服務器時,內置客户端將嘗試將其註冊到 Eureka 服務器,並嘗試獲取註冊表,但此時註冊表尚未可用。 結果,我們收到 TransportException。
因此,我們絕不應該在 Eureka 服務器應用程序中將這些屬性配置為 true。 在 application.yml 中應使用的正確設置如下:
eureka:
client:
registerWithEureka: false
fetchRegistry: false6. 結論
在本文中,我們學習瞭如何使用 Spring Netflix Eureka Server 構建服務註冊表,並將其註冊為 Eureka Clients。
由於我們的 Eureka Client 在步驟 3 中監聽隨機端口,因此在沒有註冊表的信息支持的情況下,它無法知道其位置。藉助 Feign Client 和我們的註冊表,我們可以在位置發生變化時定位並消費 REST 服務。
最後,我們看到了微服務架構中服務發現的整體圖景。