Zookeeper 做注冊(cè)中心的缺陷
Peter Kelley(個(gè)性化教育初創(chuàng)公司 knewton 的一名軟件工程師)發(fā)表了一篇文章說(shuō)明為什么Zookeeper 用于服務(wù)發(fā)現(xiàn)是一個(gè)錯(cuò)誤的做法,他主要提出了三個(gè)缺點(diǎn):
ZooKeeper 無(wú)法很好的處理網(wǎng)絡(luò)分區(qū)問(wèn)題,當(dāng)網(wǎng)絡(luò)分區(qū)中的客戶端節(jié)點(diǎn)無(wú)法到達(dá) Quorum 時(shí),會(huì)與 ZooKeeper 失去聯(lián)系,從而也就無(wú)法使用其服務(wù)發(fā)現(xiàn)機(jī)制。
服務(wù)發(fā)現(xiàn)系統(tǒng)應(yīng)該是一個(gè) AP 系統(tǒng),設(shè)計(jì)上針對(duì)可用性;而 ZooKeeper 是一個(gè) CP 系統(tǒng)。
ZooKeeper 的設(shè)置和維護(hù)非常困難,實(shí)際操作的時(shí)候也容易出錯(cuò),比如在客戶端重建 Watcher,處理 Session 和異常的時(shí)候。
當(dāng)然, Peter Kelley 提出的這幾個(gè)問(wèn)題并不是不能克服的,并不能說(shuō)明基于 ZooKeeper 就不能做好一個(gè)服務(wù)發(fā)現(xiàn)系統(tǒng),但是我們可能有更簡(jiǎn)潔的方案來(lái)實(shí)現(xiàn)。
Eureka 介紹
什么是 Eureka
官方的介紹在這里Eureka Wiki。Eureka 是 Netflix 開(kāi)源的一個(gè) Restful 服務(wù),主要用于服務(wù)的注冊(cè)發(fā)現(xiàn)。Eureka 由兩個(gè)組件組成: Eureka 服務(wù)器和 Eureka 客戶端。 Eureka 服務(wù)器用作服務(wù)注冊(cè)服務(wù)器。 Eureka 客戶端是一個(gè) Java 客戶端,用來(lái)簡(jiǎn)化與服務(wù)器的交互、作為輪詢負(fù)載均衡器,并提供服務(wù)的故障切換。 Netflix 在其生產(chǎn)環(huán)境中使用的是另外的客戶端,它提供基于流量、資源利用率以及出錯(cuò)狀態(tài)的加權(quán)負(fù)載均衡。
- 開(kāi)源: 大家可以對(duì)實(shí)現(xiàn)一探究竟,甚至修改源代碼。
- 可靠: 經(jīng)過(guò) Netflix 多年的生產(chǎn)環(huán)境考驗(yàn),使用應(yīng)該比較靠譜處心。
- 功能齊全: 不但提供了完整的注冊(cè)發(fā)現(xiàn)服務(wù),還有 Ribbon 等可以配合使用服務(wù)。
- 基于 Java: 對(duì)于 Java 程序員來(lái)說(shuō),使用起來(lái),心里比較有底。
- Spring Cloud 可以使用 Spring Cloud,與 Eureka 進(jìn)行了很好的集成,使用起來(lái)非常方便。
Eureka 架構(gòu):
Netflix 主要是在 AWS 中使用 Eureka 的,雖然同時(shí)也支持本地環(huán)境,但是了解 AWS 的一些基礎(chǔ)概念對(duì)于理解 Eureka 的設(shè)計(jì)非常有幫助。
區(qū)域與可用區(qū)
首先,我們先熟悉兩個(gè)概念:
- 區(qū)域(Region): AWS 云服務(wù)在全球不同的地方都有數(shù)據(jù)中心,比如北美、南美、歐洲和亞洲等。與此對(duì)應(yīng),根據(jù)地理位置我們把某個(gè)地區(qū)的基礎(chǔ)設(shè)施服務(wù)集合稱為一個(gè)區(qū)域。通過(guò) AWS 的區(qū)域,一方面可以使得 AWS 云服務(wù)在地理位置上更加靠近我們的用戶,另一方面使得用戶可以選擇不同的區(qū)域存儲(chǔ)他們的數(shù)據(jù)以滿足法規(guī)遵循方面的要求。美東(北佛吉尼亞)、美西(俄勒岡)、美西(北加利佛尼亞)、歐洲(愛(ài)爾蘭)、亞太(新加坡)、亞太(東京)等。每個(gè)區(qū)域都有自己對(duì)應(yīng)的編碼,如:
| 區(qū)域 | 編碼 |
|---|---|
| 亞太(東京) | ap-northeast-1 |
| 亞太(新加坡) | ap-southeast-1 |
| 亞太(悉尼) | ap-southeast-2 |
| 歐洲(愛(ài)爾蘭) | eu-west-1 |
| 南美(圣保羅) | sa-east-1 |
| 美東(北佛杰尼亞) | us-east-1 |
| 美西(北加利佛尼亞) | us-west-1 |
| 美西(俄勒岡) | us-west-2 |
- 可用區(qū)(Zone): AWS 的每個(gè)區(qū)域一般由多個(gè)可用區(qū)(AZ)組成,而一個(gè)可用區(qū)一般是由多個(gè)數(shù)據(jù)中心組成。AWS引入可用區(qū)設(shè)計(jì)主要是為了提升用戶應(yīng)用程序的高可用性。因?yàn)榭捎脜^(qū)與可用區(qū)之間在設(shè)計(jì)上是相互獨(dú)立的,也就是說(shuō)它們會(huì)有獨(dú)立的供電、獨(dú)立的網(wǎng)絡(luò)等,這樣假如一個(gè)可用區(qū)出現(xiàn)問(wèn)題時(shí)也不會(huì)影響另外的可用區(qū)。在一個(gè)區(qū)域內(nèi),可用區(qū)與可用區(qū)之間是通過(guò)高速網(wǎng)絡(luò)連接,從而保證有很低的延時(shí)。
每次當(dāng)用戶需要使用 EC2 相關(guān)資源的時(shí)候,他需要首先選擇目標(biāo)區(qū)域,如美東(北佛杰尼亞)us-east-1。然后在創(chuàng)建 EC2 產(chǎn)例的時(shí)候,用戶可以選擇實(shí)例所在的可用區(qū),比如可以是 us-east-1a 或 us-east-1b 等??捎脜^(qū)的編碼就是區(qū)域后面添加不同的英文字母。
Eureka 架構(gòu)說(shuō)明
下圖是 Eureka Wiki 中提供的架構(gòu)圖:

從上面的架構(gòu)圖可以看出,主要有三種角色:
Eureka Server
*** 通過(guò) Register, Get,Renew 等 接口提供注冊(cè)和發(fā)現(xiàn)Application Service (Service Provider):
*** 服務(wù)提供方
*** 把自身服務(wù)實(shí)例注冊(cè)到 Eureka ServerApplication Client (Service Consumer):
*** 服務(wù)調(diào)用方
*** 通過(guò) Eureka Server 獲取服務(wù)實(shí)例,并調(diào)用 Application Service
他們主要進(jìn)行的活動(dòng)如下:
- 每個(gè) Region 有一個(gè) Eureka Cluster, Region 中的每個(gè) Zone 都至少有一個(gè) Eureka Server。
- Service 作為一個(gè) Eureka Client,通過(guò) register 注冊(cè)到 Eureka Server,并且通過(guò)發(fā)送心跳的方式更新租約(renew leases)。如果 Eureka Client 到期沒(méi)有更新租約,那么過(guò)一段時(shí)間后,Eureka Server 就會(huì)移除該 Service 實(shí)例。
- 當(dāng)一個(gè) Eureka Server 的數(shù)據(jù)改變以后,會(huì)把自己的數(shù)據(jù)同步到其他 Eureka Server。
- Application Client 也作為一個(gè) Eureka Client 通過(guò) Get 接口從 Eureka Server 中獲取 Service 實(shí)例信息,然后直接調(diào)用 Service 實(shí)例。
- Application Client 調(diào)用 Service 實(shí)例時(shí),可以跨可用區(qū)調(diào)用。
Eureka Demo
實(shí)際工作中,我們很少會(huì)直接使用 Eureka,因?yàn)?Spring Cloud 已經(jīng)把 Eureka 與 Spring Boot 進(jìn)行了集成,使用起來(lái)更為簡(jiǎn)單,所以我們使用 Spring Cloud 作為示例。
這里是官方提供的一個(gè)示例:spring-cloud-eureka-example
啟動(dòng) Eureka Server
Eureka Server 非常簡(jiǎn)單,只需要三個(gè)步驟:
- 在 pom.xml 中添加依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
- 實(shí)現(xiàn) Application,添加 annotation。 @EnableEurekaServer、@EnableDiscoveryClient 執(zhí)行 main 方法啟動(dòng) Eureka Server。
@SpringBootApplication
@EnableEurekaServer
@EnableDiscoveryClient
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
- 運(yùn)行 Application 即可啟動(dòng) Server,啟動(dòng) Server 后打開(kāi) http://localhost:8761/,可以看到信息頁(yè)面。
注冊(cè)服務(wù)
把一個(gè)服務(wù)注冊(cè)在 server 中需要以下幾個(gè)步驟:
- 添加 eureka 依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
- 添加 @EnableEurekaClient 注解
@EnableEurekaClient
public class Application
- 在 application.yml 或者 application.properties 中添加配置
eureka:
instance:
leaseRenewalIntervalInSeconds: 1
leaseExpirationDurationInSeconds: 2
client:
serviceUrl:
defaultZone: http://127.0.0.1:8761/eureka/
healthcheck:
enabled: true
lease:
duration: 5
spring:
application:
name: customer-service
配置中有兩項(xiàng)需要額外注意:
eureka.client.serviceUrl.defaultZone:指定 Eureka 服務(wù)端的地址,當(dāng)客戶端沒(méi)有專門進(jìn)行配置時(shí),就會(huì)使用這個(gè)默認(rèn)地址。
spring.application.name:服務(wù)注冊(cè)所使用的名稱,同時(shí)其他服務(wù)查找該服務(wù)時(shí)也使用該名稱。我們啟動(dòng)該服務(wù)后,可以在管理頁(yè)面中查看到該服務(wù)已經(jīng)在注冊(cè)中心中注冊(cè)成功了。
服務(wù)發(fā)現(xiàn)與負(fù)載均衡(Ribbon + RestTemplate)
直接使用 Eureka Client 還是比較麻煩的,幸運(yùn)的是,RestTemplate 整合了 Eureka Client,Ribbon 為我們提供了多樣的負(fù)載均衡的功能,為我們提供了很多便利,我們所需要做的就是在 Spring 中注冊(cè)一個(gè) RestTemplate,并且添加 @LoadBalanced 注解
@Configuration
public class Config {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
接下來(lái),可以直接使用 RestTemplate 調(diào)用服務(wù)。服務(wù)的 URL 中包含了服務(wù)名稱,例如:http://customer-service/customer,其中, customer-service 是服務(wù)名,而 customer 是該服務(wù)下的一個(gè)接口。
@Autowired
private RestTemplate restTemplate;
public MessageWrapper<Customer> getCustomer(int id) {
Customer customer = restTemplate.exchange( "http://customer-service/customer/{id}", HttpMethod.GET, null, new ParameterizedTypeReference<Customer>() { }, id).getBody();
return new MessageWrapper<>(customer, "server called using eureka with rest template");
}
Eureka Api
如果使用的是非 Java 的語(yǔ)言客戶端,可以通過(guò) API 的方式進(jìn)行集成。相關(guān)文檔請(qǐng)查看 https://github.com/Netflix/eureka/wiki/Eureka-REST-operations