文章目錄
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