Spring Cloud Eureka 架構原理及集群搭建,實戰講解!
一、背景介紹
在之前的文章中,我們介紹了 Spring Cloud 相關的技術體系,相信大家對微服務已經有了初步的認識。如果做過微服務架構相關的項目,會發現服務注冊中心、服務負載均衡和服務遠程調用這三個組件,可以說是核心中的核心,整個微服務之間的互調工作都必須通過這幾個組件來完成。
其中服務注冊中心,主要專注于對整個微服務系統的服務注冊、發現、負載、降級、以及對服務健康狀態的監控和管理功能等。
今天通過這篇文章,我們一起來了解一下 Spring Cloud 技術體系中最核心的組件之一 Eureka。
Eureka 是 Netflix 開源的一款提供服務注冊和發現的框架,它允許服務實例進行自我注冊和發現,從而實現服務的負載均衡和故障轉移。
在 Spring Cloud 的服務治理下,開發者只需通過簡單的注解配置,即可快速完成具有高可用的服務注冊中心。
盡管 Netflix 之后對 Eureka 不再維護了,但是它基本思想和原理對于后來的服務注冊中心發展有著深遠的影響,例如阿里開源的 Nacos,能隱約看到其身影。
二、架構演變介紹
沒有服務注冊中心之前,當一個項目調用另一個項目接口時,通常調用流程類似于如下圖。
圖片
當有了服務注冊中心之后,任何一個項目都不能直接去調用,都需要通過服務中心來完成,調用流程會變成如下圖。
圖片
可以看到,中間插入“服務中心”之后,雖然多了一個模塊,流程變得有些復雜,但是整個調用流程變得更加靈活了。尤其是在集群環境下,“服務中心”的優勢非常明顯。
在單體架構環境下,項目 A 遠程調用項目 B 的接口,通常的做法是將接口地址 (例如http://192.168.1.1:8080/a) 寫在配置文件中,當項目 B 換了服務器或者端口發生了變化,就需要手動同步更新配置文件,十分麻煩。更重要的是,對于要求高可用的項目來說,通常都是集群部署,項目 B 會部署在多臺服務器上,需要人工進行配置的工作量巨大。
在微服務架構環境下,項目 A 和項目 B 在服務啟動后,會主動將服務實例里面的接口地址、主機 IP 和端口等信息推送到“服務中心”,這個過程我們稱之為服務注冊;當項目 A 準備向項目 B 發起遠程調用時,會先從“服務中心”拉取相關注冊表信息,這個過程可以稱之為服務發現;如果找到有效的目標地址,最后再發起遠程調用。
整個過程,項目 A 無需關心項目 B 所在的主機 IP 和端口信息,也不需要寫死在配置文件中,當項目 B 部署在多臺服務器上,項目 A 在發起調用之前,只需要抽取其中一個有效的服務發起遠程調用即可,無需人工干預,極大的減輕了運維的工作量。
可以看得出,“服務中心”的引入,相比單體架構而已,雖然交互變得復雜了一點,但是帶來的好處也是明顯的,開發者只需通過較少的運維工作,就可以實現服務高可用的效果。
下面我們一起來看看,如何利用 Eureka 搭建一套高可用的服務注冊中心。
三、方案實踐
在 Spring Cloud 生態中,Eureka 采用了 C-S 的架構設計,由 Eureka server 和 Eureka client 兩個組件組成。Eureka server,通常用來做服務中心,提供服務的注冊與發現功能;Eureka client,用于與服務中心進行交互,同時作為輪詢負載均衡器,并提供服務的故障切換支持。
它們之間的關系嗎,可以用如下圖來描述。
圖片
在應用啟動后,Eureka client 每隔一個時間段會向 Eureka server 發送心跳(默認 30s),如果 Eureka server 在多個心跳周期沒有接受到某個節點的心跳,Eureka server 認為這個節點已經掛掉,會將其從服務注冊表中將這個服務節點移除掉。
從上圖可以看到,在 Eureka 架構中,有 3 個重要的角色,分別是:
- Eureka Server:服務注冊中心,負責提供服務注冊和發現功能
- Service Provider:服務提供方,會將自身服務注冊到 Eureka,以便服務消費方能夠找到
- Service Consumer:服務消費方,會從 Eureka 服務中心獲取服務注冊列表,以便能夠消費服務
下面我們通過一個簡單的項目案例,來體驗一下這三個角色的關系。
3.1、創建服務注冊中心
首先,創建一個 Spring Boot 工程,命名為eureka-server,并在pom.xml中引入相關的依賴內容,示例如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
接著,創建一個服務啟動類并添加@EnableEurekaServer注解,表示當前是一個 Eureka 服務注冊中心。
@EnableEurekaServer
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
默認情況下,服務注冊中心也會將自己作為客戶端來注冊它自己,因此我們需要禁用它的客戶端注冊行為。
只需要在application.properties配置文件中,添加如下信息即可。
spring.application.name=eureka-server
server.port=8001
# 表示當前 eureka 實例主機名稱,不配置的話默認為當前電腦名稱
eureka.instance.hostname=localhost
# 表示是否將自己注冊到Eureka Server,默認為true
eureka.client.register-with-eureka=false
# 表示是否從Eureka Server獲取注冊信息,默認為true
eureka.client.fetch-registry=false
啟動服務之后,訪問http://localhost:8001/,可以看到下面的頁面,沒有發現任何服務,這是因為還沒有客戶端注冊。
圖片
3.2、創建服務提供方
與上文類似,首先創建一個 Spring Boot 工程,命名為eureka-provider,并在pom.xml中引入相關的依賴內容,示例如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
接著,創建一個服務啟動類并添加@EnableDiscoveryClient注解,表示當前是一個 Eureka 客戶端服務。
@EnableEurekaServer
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
然后,創建一個 web 接口,以便等會發起 RPC 調用測試,可以通過DiscoveryClient接口從服務中心查詢所有注冊的服務實例。
@RestController
publicclass HelloController {
@Autowired
private DiscoveryClient discoveryClient;
/**
* 從服務中心查詢注冊的服務
* @return
*/
@GetMapping("/dc")
public String dc() {
String services = "Services: " + discoveryClient.getServices();
System.out.println(services);
return services;
}
@GetMapping("/hello")
public String index() {
System.out.println("收到客戶端發起的rpc請求!");
return"hello,我是服務提供方";
}
}
最后,還需要在application.properties配置文件中,添加服務注冊中心地址,示例如下:
spring.application.name=eureka-provider
server.port=9001
# 設置與Eureka Server交互的地址,多個地址可使用【,】分隔
eureka.client.serviceUrl.defaultZnotallow=http://localhost:8001/eureka/
啟動服務之后,再次訪問http://localhost:8001/,可以看到下面的頁面,eureka-provider成功注冊到服務中心。
圖片
其次,也可以直接訪問http://localhost:9001/dc,查詢當前服務中心注冊的服務,可以得到類似于如下內容。
Services: [eureka-provider]
3.3、創建服務消費方
同理,創建一個 Spring Boot 工程,命名為eureka-consumer。由于服務提供方和服務消費方,都屬于 Eureka 客戶端服務,其pom.xml所需要的依賴內容和服務啟動配置完全一致,在此就不重復粘貼了。
接著,編寫一個配置類,因為需要發起遠程調用,我們可以利用RestTemplate工具發起 HTTP 請求。
@Configuration
public class WebConfig {
/**
* 初始化一個 RestTemplate 工具
* @return
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
在 Spring Cloud Commons 中提供了大量的與服務治理相關的抽象接口,例如上文介紹的DiscoveryClient,還有下文要使用的LoadBalancerClient。
LoadBalancerClient是一個負載均衡客戶端接口,我們可以利用它從服務注冊中心獲取有效的服務實例,然后發起遠程調用,示例如下:
@RestController
publicclass HelloController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
/**
* 從服務中心查詢注冊的服務
* @return
*/
@GetMapping("/dc")
public String dc() {
String services = "Services: " + discoveryClient.getServices();
System.out.println(services);
return services;
}
/**
* 發起遠程調用測試
* @return
*/
@GetMapping("/rpc")
public String rpc() {
// 從服務提供方中選擇一個有效的服務實例
ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-provider");
String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/hello";
System.out.println("遠程調用地址:" + url);
// 通過 http 方式發起遠程調用
String result = restTemplate.getForObject(url, String.class);
return"發起遠程調用,收到返回的信息:" + result;
}
}
最后,在application.properties配置文件中添加相關的配置信息。
spring.application.name=eureka-consumer
server.port=9002
eureka.client.serviceUrl.defaultZnotallow=http://localhost:8001/eureka/
將服務注冊中心、服務提供方、服務消費方依次啟動起來,然后訪問http://localhost:9002/rpc,可以得到類似于如下內容。
發起遠程調用,收到返回的信息:hello,我是服務提供方
可以清晰的看到,服務消費方成功的遠程調用了服務提供方的接口,并收到返回結果。
四、集群配置
服務注冊中心是一個特別關鍵的服務,如果是單節點,一旦掛了,整個微服務就無法使用了。對于生產環境,通常都是通過集群方式來完成部署。
實際上,Eureka 可以通過互相注冊的方式來實現高可用的部署,因此我們只需要將 Eureke Server 配置其他可用的 serviceUrl 就能實現高可用部署。
對于雙節點服務注冊中心,可實現的思路如下!
4.1、雙節點服務注冊中心配置
1)創建application-eureka1.properties配置文件,作為eureka1服務中心的配置,并將serviceUrl指向eureka2,內容如下:
spring.application.name=eureka-server
server.port=8001
eureka.instance.hostname=eureka1
eureka.client.serviceUrl.defaultZnotallow=http://eureka2:8002/eureka/
2)創建application-eureka2.properties配置文件,作為eureka2服務中心的配置,并將serviceUrl指向eureka1,內容如下:
spring.application.name=eureka-server
server.port=8002
eureka.instance.hostname=eureka2
eureka.client.serviceUrl.defaultZnotallow=http://eureka1:8001/eureka/
3)在本地 hosts 文件做了一個假域名映射,用來配置區分不同的 Eureka Server 地址。
127.0.0.1 eureka1
127.0.0.1 eureka2
4)將服務注冊到 eureka 集群
spring.application.name=eureka-consumer
server.port=9002
eureka.client.serviceUrl.defaultZnotallow=http://eureka1:8001/eureka/,http://eureka2:8002/eureka/
5)將服務進行打包,然后啟動
#打包
mvn clean package
# 分別以 eureka1 和 eureka2 配置信息啟動 eureka
java -jar eureka-server.jar --spring.profiles.active=eureka1
java -jar eureka-server.jar --spring.profiles.active=eureka2
依次啟動服務后,訪問http://localhost:8001/,可以得到類似于如下內容。
圖片
其中eureka2表示備用節點,將其中一個節點停掉,服務依然可以正常運行。
4.2、eureka 集群配置
在生產環境,通常會配置三臺及以上的服務注冊中心,以此來保證服務的穩定性,配置原理也類似,將當前服務注冊中心分別指向其它的服務注冊中心。
配置文件案例如下:
# 第一個配置文件
spring.application.name=eureka-server
server.port=8001
eureka.instance.hostname=eureka1
eureka.client.serviceUrl.defaultZnotallow=http://eureka2:8002/eureka/,http://eureka3:8003/eureka/
# 第二個配置文件
spring.application.name=eureka-server
server.port=8002
eureka.instance.hostname=eureka2
eureka.client.serviceUrl.defaultZnotallow=http://eureka1:8001/eureka/,http://eureka3:8003/eureka/
# 第三個配置文件
spring.application.name=eureka-server
server.port=8003
eureka.instance.hostname=eureka3
eureka.client.serviceUrl.defaultZnotallow=http://eureka1:8001/eureka/,http://eureka2:8002/eureka/
五、小結
最后總結一下,Eureka 是 Spring Cloud 體系中最重要的組件之一,主要用于服務的注冊和發現管理。
雖然之后 Netflix 對其停止維護了,以至于 Spring Cloud 官方不建議大家在新的項目中優先使用,但是 Eureka 作為初代的服務注冊中心,但是其基本思想和原理對于后來的服務注冊中心發展有著深遠的影響。
對于了解和學習 Spring Cloud 技術體系,Eureka 依然是必不可少的一站。
六、參考
1.http://www.ityouknow.com/springcloud/2017/05/10/springcloud-eureka.html