Eureka和Ribbon,中心架構圖原理
一、Eureka 註冊中心架構原理
Eureka 架構圖
Register(服務註冊):把自己的 IP 和埠註冊給 Eureka。
Renew(服務續約):傳送心跳包,每 30 秒傳送一次。告訴 Eureka 自己還活著。
Cancel(服務下線):當 provider 關閉時會向 Eureka 傳送訊息,把自己從服務列表中刪除。防止 consumer 呼叫到不存在的服務。
Get Registry(獲取服務註冊列表):獲取其他服務列表。
Replicate(叢集中資料同步):eureka 叢集中的資料複製與同步。
Make Remote Call(遠端呼叫):完成服務的遠端呼叫。
二、基於分散式 CAP 定理,分析註冊中心兩大主流框架:Eureka與 Zookeeper 的區別
1 什麼是 CAP 原則
CAP 原則又稱 CAP 定理,指的是在一個分散式系統中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分割槽容錯性),三者不可兼得。
CAP 由 Eric Brewer 在 2000 年 PODC 會議上提出。該猜想在提出兩年後被證明成立,成為我們熟知的 CAP 定理。
分散式系統 CAP 定理
資料一致性 (Consistency):也叫做資料原子性
系統在執行某項操作後仍然處於一致的狀態。在分散式系統 中,更新操作執行成功後所有的使用者都應該讀到最新的值,這樣的系統被認為是具有強一致性的。等同於所有節點訪問同一份最新的資料副本。
服務可用性 (Availablity):
每一個操作總是能夠在一定的時間內返回結果,這裡需 要注意的是"一定時間內"和"返回結果"。一定時間內指的是,在可以容忍的範圍內返回結果,結果可以是成功或者是失敗。
分割槽容錯性 (Partition-torlerance):
在網路分割槽的情況下,被分隔的節點仍能正常對外提供服務(分散式叢集,資料被分佈儲存在不同的伺服器上,無論什麼情況,伺服器都能正常被訪問)
2 Zookeeper 與 Eureka 的區別
三、Eureka 優雅停服
1 在什麼條件下,Eureka 會啟動自我保護?
什麼是自我保護模式
1)自我保護的條件
一般情況下,微服務在 Eureka 上註冊後,會每 30 秒傳送心跳包,Eureka 通過心跳來判斷服務時候健康,同時會定期刪除超過 90 秒沒有傳送心跳服務。
2)有兩種情況會導致 Eureka Server 收不到微服務的心跳
a.是微服務自身的原因
b.是微服務與 Eureka 之間的網路故障
通常(微服務的自身的故障關閉)只會導致個別服務出現故障,一般不會出現大面積故障,而(網路故障)通常會導致 Eureka Server 在短時間內無法收到大批心跳。
考慮到這個區別,Eureka 設定了一個閥值,當判斷掛掉的服務的數量超過閥值時, Eureka Server 認為很大程度上出現了網路故障,將不再刪除心跳過期的服務。
3)那麼這個閥值是多少呢?
15 分鐘之內是否低於 85%;
Eureka Server 在執行期間,會統計心跳失敗的比例在 15 分鐘內是否低於 85% 這種演算法叫做 Eureka Server 的自我保護模式。
2.為什麼要自我保護
1)因為同時保留"好資料"與"壞資料"總比丟掉任何資料要更好,當網路故障恢復後,這個 Eureka 節點會退出"自我保護模式"。
2)Eureka 還有客戶端快取功能(也就是微服務的快取功能)。即便 Eureka 叢集中所有節點都宕機失效,微服務的 Provider 和 Consumer 都能正常通訊。
3)微服務的負載均衡策略會自動剔除死亡的微服務節點。
3 如何關閉自我保護
修改 Eureka Server 配置檔案
#關閉自我保護:true 為開啟自我保護,false 為關閉自我保護
eureka.server.enableSelfPreservation=false
#清理間隔(單位:毫秒,預設是 60*1000)
eureka.server.eviction.interval-timer-in-ms=60000
4.如何優雅停服
不需要再 Eureka Server 中配置關閉自我保護
需要再服務中新增 actuator.jar 包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.13.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sxt</groupId>
<artifactId>springcloud-eureka-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springcloud-eureka-provider</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</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-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
修改配置檔案
spring.application.name=eureka-provider
server.port=9090
eureka.client.serviceUrl.defaultZone=http://eureka1:8761/eureka/,http://eureka2:8761/eureka/
#啟用 shutdown
endpoints.shutdown.enabled=true
#禁用密碼驗證
endpoints.shutdown.sensitive=false
傳送一個關閉服務的 URL 請求
public class HttpClientUtil {
public static String doGet(String url, Map<String, String> param) {
// 建立Httpclient物件
CloseableHttpClient httpclient = HttpClients.createDefault();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 建立uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key));
}
}
URI uri = builder.build();
// 建立http GET請求
HttpGet httpGet = new HttpGet(uri);
// 執行請求
response = httpclient.execute(httpGet);
// 判斷返回狀態是否為200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doGet(String url) {
return doGet(url, null);
}
public static String doPost(String url, Map<String, String> param) {
// 建立Httpclient物件
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 建立Http Post請求
HttpPost httpPost