SpringCloud?Eureka服務注冊與發(fā)現實踐
一、Eureka基礎知識概述
1.Eureka兩個核心組件
- Eureka Server :服務注冊中心,主要用于提供服務注冊功能。
當微服務啟動時,會將自己的服務注冊 到 Eureka Server。Eureka Server 維護了一個可用服務列表,存儲了所有注冊到 Eureka Server 的可用服務的信息,這些可用服務可以在 Eureka Server 的管理界面中直觀看到
- Eureka Client :客戶端,通常指的是微服務系統中各個微服務,主要用于和 Eureka Server 進行交互。
在微服務應用啟動后,Eureka Client 會向 Eureka Server 發(fā)送心跳(默認周期為 30 秒)。若 Eureka Server 在多個心跳周期內沒有接收到某個 Eureka Client 的心跳,Eureka Server 將它從可用服務列表中移除(默認 90 秒)
Eureka的心跳機制主要用于確??蛻舳耍ǚ仗峁┱撸┡c服務器(服務注冊中心)之間的連接活性??蛻舳藛雍螅瑫ㄆ谙蚍掌靼l(fā)送心跳數據,以告知服務器自己仍然處于活動狀態(tài)
2.Eureka 服務注冊與發(fā)現

- 服務注冊中心(Register Service): 它是一個 Eureka Server,用于提供服務注冊和發(fā)現功能。
- 服務提供者(Provider Service): 它是一個 Eureka Client,用于提供服務。它將自己提供的服務注冊到服務注冊中心,以供服務消費者發(fā)現。
- 服務消費者(Consumer Service): 它是一個 Eureka Client,用于消費服務。它可以從服務注冊中心獲取服務列表,調用所需的服務。
Eureka 實現服務注冊與發(fā)現的流程
- 搭建一個Eureka Server作為服務注冊中心
- 服務提供者Eureka Client啟動時,會把當前服務器的信息以服務名(spring.application.name)的方式注冊到服務注冊中心
- 服務消費者Eureka Client啟動時,也會向服務注冊中心注冊
- 服務消費者還會獲取一份可用路由服務列表,該列表中包含了所有注冊到服務注冊中心的服務信息(包括服務提供者和自身的信息)
- 在獲得了可用服務列表后,服務消費者通過 HTTP 或消息中間件遠程調用服務提供者提供的服務。
- 服務注冊中心(Eureka Server)所扮演的角色十分重要,它是服務提供者和服務消費者之間的橋梁。服務提供者只有將自己的服務注冊到服務注冊中心才可能被服務消費者調用,而服務消費者也只有通過服務注冊中心獲取可用服務列表后,才能調用所需的服務。
二、Eureka單機搭建
1.構建父模塊和三個子模塊 分別為注冊中心 eureka-service 倉儲模塊stock-service 訂單模塊 order-service

2.父模塊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>org.example</groupId>
<artifactId>cloud-02-Eureka-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>cloud-eureka-service</module>
<module>cloud-stock-service</module>
<module>cloud-order-service</module>
</modules>
<!-- 統一管理jar包版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.18.22</lombok.version>
<mysql.version>8.0.24</mysql.version>
<druid.version>1.2.8</druid.version>
<mybatis-plus.version>3.0.7.1</mybatis-plus.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jdbc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<!-- 子模塊繼承之后,提供作用:鎖定版本+子modlue不用寫groupId和version -->
<dependencyManagement>
<dependencies>
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.對cloud-eureka-service注冊中心操作
3.1 pom.xml文件
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
3.2 主啟動類
// 添加數據庫和 druid 卻未配置
// 則添加 exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class}
// 則啟動報錯
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class})
@EnableEurekaServer//告訴服務器我是一個注冊中心
public class EurekaServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServiceApplication.class,args);
}
}
3.3 application.yml
server:
port: 8001
eureka:
instance:
hostname: 127.0.0.1 #eureka服務端的實例名字127.0.0.1 或 localhost
client:
#表識不向注冊中心注冊自己
register-with-eureka: false
#表示自己就是注冊中心,職責是維護服務實例,并不需要去檢索服務
fetch-registry: false
service-url:
#設置與eureka server交互的地址查詢服務和注冊服務都需要依賴這個地址
#服務注冊位置 http://127.0.0.1:8001/eureka/
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
4.對倉儲模塊cloud-stock-service操作
4.1 pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
4.2 主啟動類
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class})
@EnableEurekaClient//需要使用Eureka注冊中心添加此注解
public class StockServiceApplication {
public static void main(String[] args) {
SpringApplication.run(StockServiceApplication.class,args);
}
}
4.3 application.yml
server:
port: 9001
spring:
application:
name: cloud-stock-service
eureka:
client:
# 向服務端注冊
register-with-eureka: true
# 需要檢索
fetchRegistry: true
service-url:
defaultZone: http://localhost:8001/eureka
4.4 StockController
@RestController
@RequestMapping("/stock")
public class StockController {
@GetMapping("/subStock")
public String subStock(){
System.out.println("庫存減1");
return "庫存減1";
}
}
5.對訂單模塊cloud-order-service操作
5.1 pom.xml文件
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
5.2 主啟動類
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class})
@EnableEurekaClient
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class,args);
}
}
5.3 application.yml
server:
port: 7001
spring:
application:
name: cloud-order-servce
eureka:
client:
# 向服務端注冊
register-with-eureka: true
# 需要檢索
fetchRegistry: true
service-url:
defaultZone: http://localhost:8001/eureka/
5.4 OrderController
@RestController
@RequestMapping("/order")
public class OrderController {
//這里特別提醒 `http://` 千萬別忘加 本人因為這個錯誤找了很長時間 。。。。
//CLOUD-STOCK-SERVICE 這里是eureka注冊中心的名稱
private static final String HOST = "http://CLOUD-STOCK-SERVICE";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/addOrder")
public String addOrder(){
System.out.println("訂單已完成");
return restTemplate.getForObject(HOST + "/stock/subStock",String.class);
}
}
5.5 RestTemplate配置類
@Configuration
public class ApplicationConfig {
@Bean
@LoadBalanced//負載均衡器
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
6.啟動設置
我用的是idea 2021 ,多應用啟動設置在如下位置

7.啟動
7.1 先啟動服務注冊中心cloud-eureka-service 訪問localhost:8001

7.2 在以端口號為9001和 9002 分別啟動 cloud-stock-service
在yml文件 server.port更改 啟動完成9001 更改端口號為9002 在啟動一次 再次訪問localhost:8001

7.3 在以端口號為7001和 7002 分別啟動 cloud-order-service ,和 7.2更改方式一樣 訪問 localhost:8001

8. **測試 **
8.1 訪問 localhost:7001/order/addOrder

8.2 觀察控制臺,當我們多少刷新訪問 localhost:7001/order/addOrder 則會出現9001和9002交替處理請求(輪循),緩解服務器壓力,選擇服務規(guī)則和 @LoadBalanced 負載均衡器有關

試著訪問localhost:7002,結果相同
三、Eureka集群搭建
在微服務架構中,一個系統往往由十幾甚至幾十個服務組成,若將這些服務全部注冊到同一個 Eureka Server 中,就極有可能導致 Eureka Server 因不堪重負而崩潰,最終導致整個系統癱瘓。解決這個問題最直接的辦法就是部署 Eureka Server 集群。
1.新建cloud-eureka-server-2和cloud-eureka-server-3

2.修改映射配置
打開 C:\Windows\System32\drivers\etc 目錄下的hosts文件 修改映射配置添加進hosts文件

3.更改yml文件
3.1 cloud-eureka-service-2的yml文件
server:
port: 8002
eureka:
instance:
hostname: eureka8002.com #eureka服務端的實例名字
client:
#表識不向注冊中心注冊自己
register-with-eureka: false
#表示自己就是注冊中心,職責是維護服務實例,并不需要去檢索服務
fetch-registry: false
service-url:
#設置與eureka server交互的地址查詢服務和注冊服務都需要依賴這個地址
defaultZone: http://eureka8003.com:8003/eureka/
3.2 cloud-eureka-service-2的yml文件
server:
port: 8003
eureka:
instance:
hostname: eureka8003.com #eureka服務端的實例名字
client:
#表識不向注冊中心注冊自己
register-with-eureka: false
#表示自己就是注冊中心,職責是維護服務實例,并不需要去檢索服務
fetch-registry: false
service-url:
#設置與eureka server交互的地址查詢服務和注冊服務都需要依賴這個地址
defaultZone: http://eureka8002.com:8002/eureka/
3.3 cloud-order-service的yml文件
server:
port: 7001
spring:
application:
name: cloud-order-servce
eureka:
client:
# 向服務端注冊
register-with-eureka: true
# 需要檢索
fetchRegistry: true
service-url:
defaultZone: http://localhost:8002/eureka,http://localhost:8003/eureka
3.3 cloud-stock-service的yml文件
server:
port: 9001
spring:
application:
name: cloud-stock-service
eureka:
client:
# 向服務端注冊
register-with-eureka: true
# 需要檢索
fetchRegistry: true
service-url:
defaultZone: http://localhost:8002/eureka,http://localhost:8003/eureka
4.分別為cloud-eureka-service-2和cloud-eureka-service-3添加啟動類
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class})
@EnableEurekaServer//告訴服務器我是一個注冊中心
public class EurekaServiceApplication2 {
public static void main(String[] args) {
SpringApplication.run(EurekaServiceApplication2.class,args);
}
}
-----------------------------------------------------------------------------------------------------------------
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class})
@EnableEurekaServer//告訴服務器我是一個注冊中心
public class EurekaServiceApplication3 {
public static void main(String[] args) {
SpringApplication.run(EurekaServiceApplication3.class,args);
}
}
5.啟動
5.1 啟動cloud-eureka-service-2和cloud-eureka-service-3
5.1.1 訪問localhost:8002

5.1.2 訪問localhost:8003

5.2 啟動cloud-order-service和 端口號為9001和9002的cloud-stock-service集群服
5.2.1 訪問localhost:8002

5.2.2 訪問localhost:8003

6.測試 多次訪問localhost:7001 觀察控制臺

以上方式可以形成一組互相注冊的 Eureka Server 集群,當服務提供者發(fā)送注冊請求到 Eureka Server 時,Eureka Server 會將請求轉發(fā)給集群中所有與之相連的 Eureka Server 上,以實現 Eureka Server 之間的服務同步。

通過服務同步,服務消費者可以在集群中的任意一臺 Eureka Server 上獲取服務提供者提供的服務。這樣,即使集群中的某個服務注冊中心發(fā)生故障,服務消費者仍然可以從集群中的其他 Eureka Server 中獲取服務信息并調用,而不會導致系統的整體癱瘓,這就是 Eureka Server 集群的高可用性。
四、心跳續(xù)約
“心跳”指的是一段定時發(fā)送的自定義信息,讓對方知道自己“存活”,以確保連接的有效性。大部分 CS 架構的應用程序都采用了心跳機制,服務端和客戶端都可以發(fā)心跳。通常情況下是客戶端向服務器端發(fā)送心跳包,服務端用于判斷客戶端是否在線
心跳續(xù)約是指服務實例(Eureka客戶端)定期向Eureka服務器發(fā)送心跳包,以證明其仍然在線并愿意繼續(xù)提供服務。Eureka服務器會根據這些心跳包來更新服務實例的活躍狀態(tài),并維護一個可用的服務實例列表。

功能介紹
- Spring Cloud A 和 Spring Cloud B可以看作兩個服務的提供者
- Spring Cloud C 相當于 服務的消費者 定時(默認30秒)獲取服務列表
- registry服務注冊列表 里面存儲的是 各個注冊服務的 名稱 ip 端口號
- readWriteCacheMap 會實時同步registry注冊列表中的數據
- readOnlyCacheMap 默認每30秒去同步一次readWriterCacheMap對象中的數據
詳細流程
- A和B 服務 向Eureka Service 服務注冊列表中注冊服務 將自己的服務名稱 ip + 端口號注冊到 registry服務注冊列表。
- A和B每隔30秒 (默認30秒) 發(fā)送 一次心跳任務告訴Eureka 我還活著, registry服務注冊列表 同步數據到readWriteCacheMap
- C 服務 通過readOnlyCacheMap 每30秒拉去一次注冊表數據,這個數據不是同步的而是30秒(默認30秒)更新一次,這也是有時我們會在UI界面上看到服務注冊成功,調用時卻出現錯誤的原因。同理,服務下線也存在同樣的問題,服務已經下線了,但是還是有客戶端在調用已經下線的服務,這時就會出現連接拒絕的錯誤。
- 我們在Eureka UI頁面上看到的注冊信息,實際上并沒有走readOnlyCacheMap,而是直接通過registry服務注冊列表獲取,所以我們能夠在Eureka UI頁面實時的看到注冊的新服務。
- 服務續(xù)約默認是30秒 ,定時請理60秒清理一次超過90秒未續(xù)約的服務,也就是說在連續(xù)3次丟失心跳后會被Eureka Server的evict線程清理,最極端的情況,服務下線后,可能需要延遲180s之后,Eureka Server中的registry對象才會被更新。
綜上分析,在非手動清除的情況下,緩存需要180秒才能感知下線的服務,這種情況在生產環(huán)境中非常嚴重。
上述問題中,我們可以更改默認值,來解決感知下線服務時間過長問題
Eureka注冊中心
eureka:
server:
#清理無效服務間隔 (默認60秒)
eviction-interval-timer-in-ms: 1000
#同步readWrite到readOnly間隔(默認30秒)
response-cache-update-interval-ms: 10000
#Client直接從readWriteCacheMap更新服
#use-read-only-response-cache: false
服務提供者
eureka:
instance:
# Eureka注冊中心(服務端)在收到客戶端心跳之后,
#等待下一次心跳的超時時間,如果在這個時間內沒有收到下次心跳,則移除該客戶端。(默認90秒)
lease-expiration-duration-in-seconds: 5
# 客戶端向注冊中心發(fā)送心跳的時間間隔(默認30秒)
lease-renewal-interval-in-seconds: 2
五、Eureka自我保護機制
Eureka的自我保護機制是一種應對網絡異常的安全保護措施,寧可同時保留所有微服務(健康的服務和不健康的服務都會保留)也不盲目移除任何健康的服務。
當我們在本地調試基于 Eureka 的程序時,Eureka 服務注冊中心很有可能會出現紅色警告。

- 實際上,這個警告是觸發(fā)了 Eureka 的自我保護機制而出現的。默認情況下,如果 Eureka Server 在一段時間內沒有接收到某個服務提供者的心跳,就會將這個服務提供者提供的服務從服務注冊表中移除。 這樣服務消費者就再也無法從服務注冊中心中獲取到這個服務了,更無法調用該服務。
- 但在實際的分布式微服務系統中,健康的服務也有可能會由于網絡故障(例如網絡延遲、卡頓等原因)而無法與 Eureka Server正常通訊。若此時 Eureka Server因為沒有接收心跳而誤將健康的服務從服務列表中移除,這顯然是不合理的。而 Eureka 的自我保護機制就是來解決此問題的。
- 所謂 “Eureka 的自我保護機制”,其中心思想就是“好死不如賴活著”。如果 Eureka Server 在一段時間內沒有接收到 Eureka Client 的心跳,那么 Eureka Server 就會開啟自我保護模式,將所有的 Eureka Client 的注冊信息保護起來,而不是直接從服務注冊表中移除。一旦網絡恢復,這些 Eureka Client 提供的服務還可以繼續(xù)被服務消費者消費。
默認情況下,Eureka 的自我保護機制是開啟的,如果想要關閉,則需要在配置文件中添加以下配置
eureka:
server:
# false 關閉 Eureka 的自我保護機制,默認是開啟
enable-self-preservation: false
需要注意的是:
Eureka 的自我保護機制也存在弊端。如果在 Eureka 自我保護機制觸發(fā)期間,服務提供者提供的服務出現問題,那么服務消費者就很容易獲取到已經不存在的服務進而出現調用失敗的情況。此時,我們可以通過客戶端的容錯機制來解決此問題。
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Cursor IDE中SpringBoot項目啟動內存不足問題的解決方案
在CursorIDE中運行SpringBoot項目時,可能出現啟動失敗、報錯等問題,這些問題通常與JVM內存設置不當有關,本文總結了多種解決方案,需要的朋友可以參考下2026-04-04
IDEA2020.3.2版本自動注釋類和方法注釋模板配置步驟詳解
這篇文章主要介紹了IDEA2020.3.2版本自動注釋類和方法注釋模板配置步驟,本文給大家分享了我自己創(chuàng)建過程通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
SpringBoot Shiro 權限注解不起作用的解決方法
本文主要介紹了SpringBoot Shiro 權限注解不起作用的解決方法,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-07-07
Java Swing中的表格(JTable)和樹(JTree)組件使用實例
這篇文章主要介紹了Java Swing中的表格(JTable)和樹(JTree)組件使用實例,本文同時講解了表格和樹的基本概念、常用方法、代碼實例,需要的朋友可以參考下2014-10-10

