SpringCloud基於Nacos和Eureka 實現雙註冊雙訂閱模式,可用於將註冊中心Eureka平滑過渡到Nacos的解決方案
前言
大概在去年的時候發現生產環境使用eureka經常會發現服務假死eureka沒有給踢掉的情況,然後就衍生了要不就換個註冊中心試試,然後就瞭解到了nacos,正好他還融合了配置中心,但是後來礙於切換時怕生產環境不穩定,丟資料等問題就一直沒有換,但後續的專案的註冊中心都換成了nacos,這篇文章我就來模擬一下如何將eureka平滑切換成nacos
父工程構建
這裡我在父工程裡邊又單獨建立了一層父工程,我分別在alibaba-cloud 、netflix-cloud 中模擬新舊微服務
父工程pom
```xml
<groupId>top.fate</groupId>
<artifactId>nacoAndEureka</artifactId>
<packaging>pom</packaging>
<version>1.0.0</version>
<modules>
<module>netflix-cloud</module>
<module>alibaba-cloud</module>
</modules>
```
模擬舊版微服務
netflix-cloud pom如下 ,因為這裡是模擬舊服務,所以都採用舊版本
```xml
<artifactId>netflix-cloud</artifactId>
<packaging>pom</packaging>
<modules>
<module>eureka</module>
<module>eureka-provider</module>
<module>eureka-consumer</module>
</modules>
<properties>
<spring.boot.version>2.1.2.RELEASE</spring.boot.version>
<spring.cloud.version>Greenwich.SR5</spring.cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- springBoot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- springCloud -->
<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>
```
搭建eureka
- pom依賴如下 ```xml
<artifactId>eureka</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
- EurekaApplication 啟動類
java
package top.fate.eureka;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer @SpringBootApplication public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
} ``` - application.yml
yml
server:
port: 8761
spring:
application:
name: eureka-service
eureka:
instance:
# 設定該服務註冊中心的hostname
hostname: 127.0.0.1
client:
# 我們建立的是服務註冊中心,而不是普通的應用,這個應用會向註冊中心註冊它自己
#,設定為false就是禁止自己向自己註冊的這個種行為
register-with-eureka: false
# 不去檢索其他的服務,因為註冊中心本身的職責就是維護服務例項
fetch-registry: false
# 制定服務註冊中心的位置
service-url.defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
eureka-provider
- pom依賴如下
```xml
<artifactId>eureka-provider</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
``` - EurekaProviderApplication 啟動類
```java package top.fate.eurekaprovider;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
/* * @auther:Wangxl * @Emile:[email protected] * @Time:2022/6/16 14:23 / @SpringBootApplication @EnableEurekaClient @RestController public class EurekaProviderApplication { public static void main(String[] args) { SpringApplication.run(EurekaProviderApplication.class, args); }
@GetMapping("/info")
public String info(){
return "this is eureka-service";
}
}
``` - application.yml
yml
server:
port: 8081
spring:
application:
name: provider
eureka:
client:
service-url:
defaultZone: "http://localhost:8761/eureka"
eureka-consumer
- pom依賴如下
```xml
<artifactId>eureka-consumer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
``` - EurekaConsumerApplication 啟動類
```java package top.fate.eurekaconsumer;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; import top.fate.eurekaconsumer.client.EurekaProviderClient;
/* * @auther:Wangxl * @Emile:[email protected] * @Time:2022/6/16 14:43 / @SpringBootApplication @EnableEurekaClient @EnableFeignClients(clients = EurekaProviderClient.class) public class EurekaConsumerApplication { public static void main(String[] args) { SpringApplication.run(EurekaConsumerApplication.class, args); } } ``` - EurekaProviderClient
```java package top.fate.eurekaconsumer.client;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping;
/* * @auther:Wangxl * @Emile:[email protected] * @Time:2022/6/16 14:48 / @FeignClient(value = "provider") public interface EurekaProviderClient {
@GetMapping("info")
String info();
}
- ConsumerController
java
package top.fate.eurekaconsumer.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import top.fate.eurekaconsumer.client.EurekaProviderClient;
import javax.annotation.Resource;
/* * @auther:Wangxl * @Emile:[email protected] * @Time:2022/6/16 14:48 / @RestController public class ConsumerController {
@Resource
private EurekaProviderClient eurekaProviderClient;
@GetMapping("getProvider")
public String getProvider(){
return eurekaProviderClient.info();
}
} ```
測試服務是否可以調通
這裡我三個服務都啟動正常,直接訪問8091consumer測試 ,如下圖所示consumer 可以訪問provider
第一階段流程圖
模擬新版微服務
alibaba-cloud pom如下,採用最新版技術棧
```xml
<artifactId>alibaba-cloud</artifactId>
<packaging>pom</packaging>
<modules>
<module>nacos-consumer</module>
<module>nacos-provider</module>
</modules>
<properties>
<spring.boot.version>2.6.3</spring.boot.version>
<spring.cloud.version>2021.0.1</spring.cloud.version>
<spring.cloud.alibaba.version>2021.0.1.0</spring.cloud.alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- springBoot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- springCloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring-cloud-alibaba -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
```
啟動安裝nacos
可以參考SpringCloudAlibaba篇(二)整合Nacos註冊配置中心 這篇文章我就不重複操作了
nacos-provider
- pom 依賴 ```xml
<artifactId>nacos-provider</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
``` - NacosProviderApplication 啟動類
```java package top.fate.nacosprovider;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
/* * @auther:Wangxl * @Emile:[email protected] * @Time:2022/6/16 16:55 / @SpringBootApplication @RestController @EnableConfigurationProperties(AutoServiceRegistrationProperties.class) public class NacosProviderApplication {
public static void main(String[] args) {
SpringApplication.run(NacosProviderApplication.class, args);
}
@GetMapping("/info")
public String info() {
return "this is nacos-service";
}
} ``` - application.properties
spring.autoconfigure.exclude=org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration
- application.yml
yml
url:
nacos: localhost:8848
server:
port: 8082
spring:
application:
name: provider
profiles:
active: dev
cloud:
nacos:
discovery:
#叢集環境隔離
cluster-name: shanghai
#名稱空間
namespace: ${spring.profiles.active}
#持久化例項 ture為臨時例項 false為持久化例項 臨時例項發生異常直接剔除, 而持久化例項等待恢復
ephemeral: true
#註冊中心地址
server-addr: ${url.nacos}
eureka:
client:
service-url:
defaultZone: "http://localhost:8761/eureka"
nacos-consumer
- pom依賴
```xml
<artifactId>nacos-consumer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
``` - NacosConsumerApplication 啟動類
```java package top.fate.nacosconsumer;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties; import org.springframework.cloud.openfeign.EnableFeignClients; import top.fate.nacosconsumer.client.EurekaProviderClient; import top.fate.nacosconsumer.client.NacosProviderClient; import top.fate.nacosconsumer.client.ProviderClient;
/* * @auther:Wangxl * @Emile:[email protected] * @Time:2022/6/16 16:39 / @SpringBootApplication @EnableFeignClients(clients = {EurekaProviderClient.class, NacosProviderClient.class, ProviderClient.class}) @EnableConfigurationProperties(AutoServiceRegistrationProperties.class) public class NacosConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(NacosConsumerApplication.class, args);
}
} ``` - ProviderClient
```java package top.fate.nacosconsumer.client;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping;
/* * @auther:Wangxl * @Emile:[email protected] * @Time:2022/6/16 18:24 / @FeignClient(value = "provider") public interface ProviderClient {
@GetMapping("info")
String info();
} ``` - ConsumerController
```java package top.fate.nacosconsumer.controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import top.fate.nacosconsumer.client.ProviderClient;
import javax.annotation.Resource;
/* * @auther:Wangxl * @Emile:[email protected] * @Time:2022/6/16 14:48 / @RestController public class ConsumerController {
@Resource
private ProviderClient providerClient;
@GetMapping("getProvider")
public String getProvider(){
return providerClient.info();
}
} ``` - application.properties
spring.autoconfigure.exclude=org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration
- application.yml
yml
url:
nacos: localhost:8848
server:
port: 8092
spring:
application:
name: nacos-consumer
profiles:
active: dev
cloud:
nacos:
discovery:
#叢集環境隔離
cluster-name: shanghai
#名稱空間
namespace: ${spring.profiles.active}
#持久化例項 ture為臨時例項 false為持久化例項 臨時例項發生異常直接剔除, 而持久化例項等待恢復
ephemeral: true
#註冊中心地址
server-addr: ${url.nacos}
eureka:
client:
service-url:
defaultZone: "http://localhost:8761/eureka"
上線雙註冊雙訂閱新provider服務
先啟動nacosProviderApplication
如下圖所示,我們已經實現了雙註冊,nacos和eureka中都註冊了服務
nacos
eureka
平滑切換註冊中心
驗證舊consumer
這裡我訪問8091的舊版Netflix客戶端也就是
eureka-consumer
,看一下呼叫的是8081 eureka 還是8082 nacos , 這裡我反覆呼叫了十幾次,返回結果為 - this is nacos-service - this is eureka-service 因為此時我們的8091
客戶端只有eurekaClient
,然後我們的provider在eureka註冊中心有兩個例項
,所以就觸發了負載均衡
,這裡我們用的預設輪詢模式
,當前流程如下圖 -
下線舊provider
現在我們就可以陸續開始平滑
切換註冊中心
了,舊provider可以關掉了,關掉舊provider之後此時的流程就如下圖所示了此時我們再訪問
舊consumer
只會返回 this is nacos-service,因為舊的provider已經下線了 ,新provider當前已經切換完成!
上線雙註冊雙訂閱新consumer服務,下線舊consumer
啟動nacoConsumerApplication 訪問8092驗證是否能正常訪問,繼續訪問getProvider介面,如下圖所示訪問正常,然後我們就可以下線舊consumer服務了
疑惑 (該步驟可以直接略過)
現在我們有個疑惑,現在有兩個註冊中心,服務發現是走的eureka還是nacos呢 為此,我做了個實驗,我分別啟動了
舊provider
、新provider
、新consumer
此時雙註冊中心的服務 - eureka - - provider8081、provider8082 - - consumer8092 - nacos - - provider8082 - - consumer8092現在我通過
consumer8092
客戶端去請求,得到的結果只有 this is nacos-service ,因此判斷註冊中心預設走的是nacos. 因為走nacos
只會返回this is nacos-service
, nacos只有一個例項。 如果走eureka的話會輪詢返回this is nacos-service、this is eureka-service
,eureka有兩個例項。 -此時的流程圖
虛線代表該線路空閒這裡我找了下原始碼CompositeDiscoveryClient,呼叫的時候打了斷點,發現系統建立了
三個discoveryClient
,nacos
排在第一個,如果可用的話直接就返回了 ,所以可以理解為預設走的是nacos 這裡我想到了nacos有個服務下線功能,如果我將nacos中的服務下線之後應該就會去走eureka了吧 等待幾秒過後,通過consumer8092
客戶端去請求,得到了我想要的結果 分別輪詢返回了this is nacos-service
、this is eureka-service
,證明已經走eureka了
此時流程圖
虛線代表該線路空閒
最後
此時我們生產上邊是 新consumer、新provider、eureka、nacos,既然我們要切換到nacos,那eureka就也要停掉了,我們可以在
下一版的服務中去掉 eureka的依賴和配置
,只留下nacos,將這一個新版本部署上去之後就可以停掉eureka了 -如下圖所示
注意
如果直接引入eureka-client和nacos-client 會報錯,如下
Field autoServiceRegistration in org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration required a single bean, but 2 were found: - nacosAutoServiceRegistration: defined by method 'nacosAutoServiceRegistration' in class path resource [com/alibaba/cloud/nacos/registry/NacosServiceRegistryAutoConfiguration.class] - eurekaAutoServiceRegistration: defined by method 'eurekaAutoServiceRegistration' in class path resource [org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.class]
1. 需要在配置檔案新增如下內容spring.autoconfigure.exclude=org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration
2. 啟動類添加註解 ```java @EnableConfigurationProperties(AutoServiceRegistrationProperties.class) ````原創不易,請點個贊再走吧!感謝