前言
上一章節,講解了在單機模式下的服務注冊與發現的相關知識點及簡單示例。而在實際生產或者在這種微服務架構的分布式環境中,需要考慮發生故障時,各組件的高可用。而其實高可用,我的簡單粗俗理解就是,通過系統的冗余進行高可用,或者是進行集群部署,保證一台服務不可用時,會進行自動轉移至可用的服務中。今天的章節,就來說說關於
Eureka的高可用吧。
一點知識
講解前,我們先來聊聊在使用
Dubbo時耳聞能詳的Zookeeper和Eureka之間的區別吧。
CAP原則
根據百度百科的定義,CAP定理又稱CAP原則,指的是在一個分布式系統中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分區容錯性),最多只能同時三個特性中的兩個,三者不可兼得。
在分布式領域,大家應該對CAP理論不陌生了吧(了解但也是沒有深入了解過⊙﹏⊙‖∣)。
以下摘至百度百科:
● 一致性(C):在分布式系統中的所有數據備份,在同一時刻是否同樣的值。(等同於所有節點訪問同一份最新的數據副本)
● 可用性(A):在集群中一部分節點故障后,集群整體是否還能響應客戶端的讀寫請求。(對數據更新具備高可用性)
● 分區容錯性(P):以實際效果而言,分區相當於對通信的時限要求。系統如果不能在時限內達成數據一致性,就意味着發生了分區的情況,必須就當前操作在C和A之間做出選擇。
至於為何不能同時滿足,以上在分區容錯性也有簡單的說明了,一般來說,分區容錯無法避免,因此可以認為CAP的P總是成立,而對於一致性和可用性為何不能同時滿足,簡單來說就是:存在可能通信失敗情況,即:出現分區容錯,至於更詳細具體的,大家可以看下大佬阮一峰的這篇文章:CAP 定理的含義。這里就不闡述了,不是很了解~
Eureka與Zookeeper區別
對於Eureka而言,其是滿足
AP的,而Zookeeper而言,是滿足CP的。
Eureka是滿足AP的:
- 優先保證可用性
- 各個節點都是平等的,幾個節點掛掉不會影響正常節點的工作,剩余的節點依然可以提供注冊和查詢服務
- 在向某個Eureka注冊時如果發現連接失敗,則會自動切換至其它節點,只要有一台Eureka還在,就能保證注冊服務可用(保證可用性),只不過查到的信息可能不是最新的(不保證強一致性)
Zookeeper是滿足AP的:
- 任何時刻對ZooKeeper的訪問請求能得到一致的數據結果,同時系統對網絡分割具備容錯性
- 不能保證每次服務請求的可用性
- 當master節點因為網絡故障與其他節點失去聯系時,剩余節點會重新進行leader選舉
- 選舉leader的時間太長,30 ~ 120s, 且選舉期間整個zk集群都是不可用的,這就導致在選舉期間注冊服務癱瘓
我的簡單理解:由於Zookeeper有loader選舉策略,使其可以保證數據的一致性。而Eureka,本事沒有選舉策略,各服務是獨立運行的,至少在某一時刻,各服務之間的數據是不一致的,而在可用性方面,Zookeeper也是由於選舉策略原因,在選舉期間是,整個zookeeper是不可用的,會造成短暫(看選舉時長)的服務不可用。而對於Eureka而言,服務是獨立運行的,所以不會因為某台服務不可用導致了其他服務不可用情況。感覺上面有點繞,簡單來說,就是Zookeeper通過選舉策略保證數據的一致性,但缺失了可用性,Eureka由於服務獨立運行,通過心跳等通信策略進行數據同步,存在數據不一致性,但保證了服務的可用性。
所以,綜上所述,作為服務注冊中心而言,可用性原則是比數據一致性更重要的,同時上一章節也有說過,由於Eureka有自我保護模式,可保護服務注冊表中的信息不被剔除,所以Eureka可以很好的應對因網絡故障導致節點失去聯系的情況。
Eurkea高可用介紹及示例
Eureka的高可用
官網中,關於Eureka的高可用部分是這么描述的:


所以可以獲悉,Eureka Server可以運行多個實例來構建集群,解決單點問題,Eureka Server采用的是Peer to Peer對等通信。這是一種去中心化的架構,無master/slave區分,每一個Peer都是對等的。在這種架構中,節點通過彼此互相注冊來提高可用性,每個節點需要添加一個或多個有效的serviceUrl指向其他節點。每個節點都可被視為其他節點的副本。
如果某台Eureka Server宕機,Eureka Client的請求會自動切換到新的Eureka Server節點,當宕機的服務器重新恢復后,Eureka會再次將其納入到服務器集群管理之中。當節點開始接受客戶端請求時,所有的操作都會進行replicateToPeer(節點間復制)操作,將請求復制到其他Eureka Server當前所知的所有節點中。
所以,簡單來說,Eureka Server的高可用,實際上就是將自己也作為服務向其他服務注冊中心進行注冊,這樣就可以形成一組相互注冊的服務注冊中心,以實現服務清單的互相同步,達到高可用的效果。
另外,從官網文檔中有提到Zones、Regions,Region和Zone(或者Availability Zone)均是AWS的概念。在非AWS環境下,我們可以先簡單地將region理解為Eureka集群,zone理解成機房。下圖就可以理解為一個Eureka集群被部署在了zone1機房和zone2機房中。

對這些概念的其他相關知識,也深入了解,大家感興趣,可自行搜索下吧。
示例前,先看看集群模式下,Eureka的架構圖。

- Service Provider會向Eureka Server做Register(服務注冊)、Renew(服務續約)、Cancel(服務下線)等操作。
- Eureka Server之間會做注冊服務的同步,從而保證狀態一致
- Service Consumer會向Eureka Server獲取注冊服務列表,並消費服務
具體的原理分析,可以看看這篇比較早的文章:https://nobodyiam.com/2016/06/25/dive-into-eureka/,雖然是比較早的文章,但寫的比較詳細,可以看看。
接下來,以官網文檔demo,示例下。
Eureka服務端高可用
通過點對點配置,注冊中心通過相互注冊來實現高可用配置。以下構建一個雙節點的集群模式。
修改spring-cloud-eureka-server項目。
0.創建一個application-ha.properties配置文件,同時修改application.properties文件。
application-ha.properties
spring.application.name=eureka-service-ha
# 修改端口
server.port=1001
# 實例的主機名稱
eureka.instance.hostname=myPeer2
## 不要向注冊中心注冊自己
#eureka.client.register-with-eureka=false
## 表示不去檢索其他的服務,因為服務注冊中心本身的職責就是維護服務實例,它也不需要去檢索其他服務
#eureka.client.fetch-registry=false
# 指定服務注冊中心地址
eureka.client.service-url.defaultZone=http://myPeer1:1000/eureka
application.properties
spring.application.name=eureka-service-ha
# 修改端口
server.port=1000
# 實例的主機名稱
eureka.instance.hostname=myPeer1
## 不要向注冊中心注冊自己
#eureka.client.register-with-eureka=false
## 表示不去檢索其他的服務,因為服務注冊中心本身的職責就是維護服務實例,它也不需要去檢索其他服務
#eureka.client.fetch-registry=false
# 指定服務注冊中心地址
eureka.client.service-url.defaultZone=http://myPeer2:1000/eureka
#spring.profiles.active=ha
1.由於是在同一台進行模擬,首先修改hosts文件,當瀏覽器請求一個地址時,首先會從此文件選擇對應對應的IP地址,找不到時才請求CDS域名解析服務器進行解析
C:\Windows\System32\drivers\etc\hosts文件:
127.0.0.1 myPeer1
127.0.0.1 myPeer2
友情提示:若提示無修改權限,可根據以下網址進行相應修改:編輯hosts文件無法保存怎么辦
2.啟動應用,我們這里直接啟動兩個,可通過修改spring.profiles.active值來啟動不同環境的應用,或者使用-jar xx.jar --spring.profiles.active=xx來啟動。


題外話:第一個啟動的應用,后台會報錯Connection refused: connect,等第二個應用啟動后,就正常了~
Eureka客戶端注冊至集群上
客戶端只需要通過修改配置文件的
eureka.client.service-url.defaultZone值即可。
修改spring-cloud-eureka-client項目
0.修改配置文件application.properties
spring.application.name=eureka-client
server.port=2000
# 注冊中心地址
eureka.client.service-url.defaultZone=http://myPeer1:1000/eureka,http://myPeer2:1001/eureka
# 啟用ip配置 這樣在注冊中心列表中看見的是以ip+端口呈現的
eureka.instance.prefer-ip-address=true
# 實例名稱 最后呈現地址:ip:2000
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
當然,也可只注冊到某個節點上,其他的節點也會有此服務列表的,一般建議以集群方式進行配置,即多注冊中心配置。避免單點故障,Eureka在搜索注冊中心時,根據defaultZone列表,找到一個可用的,之后就不會繼續去下一個注冊中心地址拉取服務列表了,此時若其中一個注冊中心掛了,這個時候客戶端會繼續去第二個注冊中心拉取服務列表的。
啟動后,可以看見eureka-client注冊上去了。

現在我們停止一個應用,可以看出不可用的服務列表中已經有相關信息了

高可用測試
為了驗證高可用性是否成功,創建一個
spring-cloud-eureka-server-ha-test項目,作為服務消費者使用RestTemplate+ribbon進行調用spring-cloud-eureka-client的服務。
創建spring-cloud-eureka-server-ha-test項目
0.引入pom依賴
<!-- 客戶端依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- 引入web,提供一個簡單的api接口 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
1.配置文件,配置注冊中心地址
spring.application.name=eureka-ha-test
server.port=8888
#指定注冊中心地址
eureka.client.serviceUrl.defaultZone=http://myPeer1:1000/eureka/,http://myPeer2:1001/eureka/
# 啟用ip配置 這樣在注冊中心列表中看見的是以ip+端口呈現的
eureka.instance.prefer-ip-address=true
# 實例名稱 最后呈現地址:ip:2000
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
2.啟動類,配置RestTemplateBean類,同時加入@LoadBalanced注解實現服務調用。
@SpringCloudApplication
@EnableDiscoveryClient
@Slf4j
public class EurekaServiceHaApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(EurekaServiceHaApplication.class, args);
log.info("spring-cloud-eureka-server-ha-test啟動!");
}
//加入負載均衡能力
//同時可根據applicationName 來訪問服務
//如http://EUREKA-CLIENT/add
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
3.編寫一個控制類,簡單調用EUREKA-CLIENT服務方法。
/**
* 調用簡單示例
* @author oKong
*
*/
@RestController
public class DemoController {
@Autowired
private RestTemplate testTemplate;
@GetMapping("/")
public String index() {
//訪問服務提供者
return testTemplate.getForObject("http://EUREKA-CLIENT/", String.class);
}
}
4.啟動應用類,訪問注冊中心和http://127.0.0.1:8888 。


可看見輸出spring-cloud-eureka-client!表明調用成功。在控制台也可以看見,從注冊中心拉取了EUREKA-CLIENT的服務地址:

這個時候,可以試着停止其中一個甚至全部的Eureka Server,再繼續訪問,可以看見服務還是可以調用的,但要知道此時調用方是可能是根據本地的緩存列表中直接獲取地址的,而不是從注冊服務中心,等待下次心跳機制時間到時,才會去進行拉取最新的服務列表的。
關於RestTemplate和Ribbon相關知識點,會在下一章節進行闡述的,這里就不敞開了。
一點疑問
第一次看官網文檔進行demo時,還在想通過設置端口號不就可以了嗎,為何還需要指定eureka.instance.hostname呢,多麻煩?嘗試下使用ip或者不配置試試看。
- 修改
eureka.instance.hostname為localhost或者127.0.0.1時,

可以看見,在DS Replicas中是空的,說明沒有可復制的服務,registered-replicas和available-replicas都是空的。
客戶端配置eureka.client.service-url.defaultZone。
eureka.client.service-url.defaultZone=http://127.0.0.1:1001/eureka,http://127.0.0.1:1000/eureka
會發現,在1001服務上有注冊上去,1000服務沒有服務信息。(客戶端注冊是按順序進行優先注冊和獲取服務列表的)
1001服務:

1000服務:

這說明集群模式是沒有生效的,注冊中心之間沒有相互復制服務列表。
- 修改配置文件,一個修改成
127.0.0.1,另一個修改成localhost或者實際內網ip地址
說明下:
1001對應hostname為:127.0.0.1
1000對應hostname為:192.168.81.1
1001服務:

1000服務:

會發現,集群模式成功了。
接着我們啟動客戶端。
1000服務:

1001服務:

可以看見,和上面設置myPeer1和myPeer2效果是一樣的,都有被復制了。
集群配置的一點淺談
從上面可以大致獲悉,Eureka互相注冊要求各個Eureka server實例的eureka.instance.hostname不同,如果相同,則會被Eureka標記為unavailable-replicas(像本地設置為127.0.0.1,干脆就不顯示了,具體不明。。⊙﹏⊙‖∣),之前的同步就失效了。而對於客戶端的defaultZone配置而言,是優先從第一個開始注冊和拉取服務的,成功聯通后就不會再繼續找下一個注冊服務了。
綜上所述:
- 若是一台服務器,部署多個
Eureka server服務時,設置每個服務
的hostname不一致,同時要設置eureka.instance.prefer-ip-address為false,不使用IP地址進行注冊。 - 若是多台服務器部署時,設置
hostname為本機的ip地址,可使用spring.cloud.client.ip-address變量進行賦值。同時設置eureka.instance.prefer-ip-address為true。這樣的話,客戶端使用ip進行連接就方便了,不然還要去配置host有點坑了。 - 對於客戶端的
defaultZone建議還是配置多個注冊中心地址。就算本身集群模式無效,好歹其中一個不可用了,還能連其他的注冊中心呀。
在最后收尾時,搜索到一篇文章,也是大致的意思,大家可以點擊看一看:構建高可用Eureka注冊中心

其源碼地址:https://github.com/wangfei0904306/eureka-HA
以上可能理解有偏差,還希望知道的同學能不吝賜教下,謝謝了!
Eureka注冊中心訪問認證
默認情況下,訪問注冊中心頁面是匿名訪問的,不需要一些認證。在生產中,為了安全性,可加入身份認證功能。
添加認證很簡單,官網文檔也有說明:

0.加入pom依賴:
<!-- 開啟認證 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
1.修改配置文件,設置用戶名和密碼及集群狀態下,注冊至其他服務中心時,加入用戶名和密碼:
# 設置帳號密碼
# 若不設置 默認帳號是user,密碼隨機,啟動時會打印在控制台上
spring.security.user.name=oKong
spring.security.user.password=123456
# 加入用戶名和密碼
eureka.client.service-url.defaultZone=http://oKong:123456@127.0.0.1:1001/eureka
友情提示:不設置name和password時,默認用戶是user,密碼是隨機的,啟動時會打印在控制台上的:

2.修改啟動類,根據官網的提示,關閉/eureka/**的CSRF的令牌。
/**
* Eureka服務端
* @author oKong
*
*/
@SpringBootApplication
@EnableEurekaServer
@Slf4j
public class EureakServiceApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(EureakServiceApplication.class, args);
log.info("spring-cloud-eureka-service啟動!");
}
/**
* 忽略此路徑下的CSRF令牌
* @author oKong
*
*/
@EnableWebSecurity
static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}
}
3.啟動應用。再次訪問:http:127.0.0.1:1000 時,就需要輸入用戶名和密碼了。

4.客戶端也是類似的,修改defaultZone加入用戶名和密碼即可:
eureka.client.service-url.defaultZone=http://oKong:123456@127.0.0.1:1001/eureka,http://oKong:123456@127.0.0.1:1000/eureka
參考資料
總結
本章節主要是
Eureka服務的高可用進行了簡單的介紹了下。對於集群模式下的一些最佳實踐還是有待商討的,還希望大家能說說自個的方案!至於一些其他的特性,如元數據配置等,這里就不闡述了,用的不多。同時,本章節利用RestTemplate+ribbon進行了簡單的服務調用,沒有敞開說,下一章節就是開始講解服務消費者相關知識點,應該也會分成兩章節來描述,因為涉及到了Ribbon和Feign相關知識點。我一直覺得使用都是很簡單的,只有理解里面的原理之類的,這樣才是一通百通吧,加深印象!使用起來也能比較順利。
最后
目前互聯網上大佬都有分享
SpringCloud系列教程,內容可能會類似,望多多包涵了。原創不易,碼字不易,還希望大家多多支持。若文中有錯誤之處,還望提出,謝謝。
老生常談
- 個人QQ:
499452441 - 微信公眾號:
lqdevOps

個人博客:http://blog.lqdev.cn
源碼示例:https://github.com/xie19900123/spring-cloud-learning
原文地址:http://blog.lqdev.cn/2018/09/09/SpringCloud/chapter-three/
