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