天天看點

SpringCloud的入門學習之深入了解Eureka注冊中心

1、Eureka 注冊中心三種角色。

  答:a、Eureka Server,注冊中心,通過 Register、Get、Renew 等接口提供服務的注冊和發現。

    b、Application Service (Service Provider),服務提供方,把自身的服務執行個體注冊到 Eureka Server中。

    c、Application Client (Service Consumer),服務調用方,通過 Eureka Server擷取服務清單,消費服務。

2、Erueka的架構圖,如下所示。來源:​​https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance​​

SpringCloud的入門學習之深入了解Eureka注冊中心

解釋如下所示:

Eureka Server是Eureka的注冊中心。Application Service是Service Provider。Application Client是Service Consumer。Application Service和Application Client都是Eureka Client(即Eureka用戶端)。

a、Register(服務注冊):把自己的 IP 和端口注冊給 Eureka。

b、Renew(服務續約):發送心跳包,每 30 秒發送一次。告訴 Eureka 自己還活着。

c、Cancel(服務下線):當 provider 關閉時會向 Eureka 發送消息,把自己從服務清單中删除。防止 consumer 調用到不存在的服務。

d、Get Registry(擷取服務注冊清單):擷取其他服務清單。

e、Replicate(叢集中資料同步):eureka 叢集中的資料複制與同步。

f、Make Remote Call(遠端調用):完成服務的遠端調用。

3、什麼是CAP原則?

  答:CAP原則又稱CAP定理,指的是在一個分布式系統中,Consistency(強一緻性)、Availability(可用性)、Partition tolerance(分區容錯性),三者不可兼得。CAP 由 Eric Brewer 在 2000 年 PODC 會議上提出。該猜想在提出兩年後被證明成立,成為我們熟知的 CAP 定理。

SpringCloud的入門學習之深入了解Eureka注冊中心
CAP定律:任何分布式系統隻可同時滿足二點,沒法三者兼顧。 
SpringCloud的入門學習之深入了解Eureka注冊中心

3.1、ACID和CAP的對比如下所示:

  1)、傳統得ACID分别是什麼,A(Atomicity)原子性、C(Consistency)一緻性、I(Isolation)獨立性、D(Durability)持久性。

  2)、CAP原則,Consistency(強一緻性)、Availability(可用性)、Partition tolerance(分區容錯性)。CAP的三進二,任何分布式系統,沒要辦法同時滿足強一緻性,可用性,分區容錯性,三者不可兼得,隻能三個選擇其中兩個。

  3)、典型的CAP,CA(強一緻性、可用性)的有RABMS傳統的關系型資料庫,包含Mysql、Oracle、Sql server。CP(強一緻性、分區容錯性)的有MongoDB、HBase、Redis。AP(可用性、分區容錯性)的有CouchDB、Cassandra、DynamoDB、Riak。

3.2、CAP理論的核心是,一個分布式系統不可能同時很好的滿足一緻性、可用性和分區容錯性這三個需求,是以,根據cap原理将NoSQL資料庫分成了滿足CA原則、滿足CP原則,和滿足AP原則三大類。分布式叢集,分區容錯性必須要實作,是以隻能在一緻性和可用性之間進行選擇。

  1)、CA原則,單點叢集,滿足一緻性,可用性的系統,通常在可擴充性上不太強大。

  2)、CP原則,分布式叢集,滿足一緻性,分區容錯性的系統,通常性能不是特别高。

  3)、AP原則,分布式叢集,滿足可用性,分區容錯性的系統,通常可能對一緻性要求低一些。

4、Zookeeper與Eureka的差別。

SpringCloud的入門學習之深入了解Eureka注冊中心

  4.1)、Zookeeper保證CP原則,當向注冊中心查詢服務清單的時候,我們可以忍受注冊中心傳回的是幾分鐘以前的注冊資訊,但不能接受服務直接down掉不可用。也就是說服務注冊功能對可用性的要求要高于一緻性。但是zk會出現這樣一種情況,當master節點因為網絡故障與其他節點失去聯系的時候,剩餘節點會重新進行leader選舉。問題在于,選舉leader的時間太長,30~120秒,且選舉期間整個zk叢集都是不可用的,這樣就導緻選舉期間服務癱瘓。在雲部署的環境下,因為網絡問題使得zk叢集失去master節點是較大機率會發生的事情,雖然服務能夠最終恢複,但是漫長的選舉時間導緻的注冊長期不可用是不能容忍的。

  4.2)、Eureka設計的時候優先保證可用性。Eureka各個節點都是平等的,幾個節點挂點不會影響節點的工作,剩餘的節點依然可以提供注冊和查詢服務。而Eureka得用戶端在向某個Eureka注冊得時候如果發現了連接配接失敗,則會自動切換至其他節點,隻要有一台Eureka還存活,就能保證注冊服務得可用性(即保證可用性),隻不過查詢的資訊部署最新的(不能保證強一緻性)。除此之外,Eureka還有一種自我保護機制,如果在15分鐘内超過85%的節點都沒要正常的心跳,那麼Eureka就認為用戶端與注冊中心出現了網絡故障,此時會出現以下幾種情況。

  第一種情況,Eureka不再從注冊清單中移除因為長時間沒有收到心跳而應該過期的服務。

  第二種情況,Eureka仍然能夠接受新服務的注冊和查詢請求,但是不會被同步到其他節點上(即保證了目前節點依然可用)。

  第三種情況,當網絡穩定的的時候,目前執行個體新的注冊資訊會被同步到其他節點中。

  是以,Eureka可以很好的應對因為網絡故障導緻部分節點失去聯系的情況,而不會像zookeeper那樣使整個注冊服務癱瘓。

5、Eureka的優雅停服。在什麼條件下,Eureka會啟動自我保護?什麼是自我保護模式?

  1)、自我保護的條件,一般情況下,微服務在 Eureka上注冊後,會每30秒發送心跳包,Eureka通過心跳來判斷服務時候健康。同時會定期删除超過 90秒沒有發送心跳服務。

  2)、但是有兩種情況會導緻 Eureka Server 收不到微服務的心跳。第一種情況是微服務自身的原因。第二種情況是是微服務與 Eureka 之間的網絡故障。如果未設定優雅停服的情況下,此兩種情況是不會将服務進行删除的。

    通常(微服務的自身的故障關閉,也被稱為單節點故障)隻會導緻個别服務出現故障,一般不會出現大面積故障。而(網絡故障)通常會導緻 Eureka Server 在短時間内無法收到大批心跳。考慮到這個差別,Eureka 設定了一個閥值,當判斷挂掉的服務的數量超過閥值時, Eureka Server 認為很大程度上出現了網絡故障,将不再删除心跳過期的服務。

  3)、那麼這個閥值是多少呢?

    如果15 分鐘之内是否低于 85%,Eureka Server 在運作期間,會統計心跳失敗的比例在 15 分鐘内是否低于 85% 這種算法叫做 Eureka Server 的自我保護模式。

  4)、案例如下:啟動之前搭建的Eureka Server叢集版,啟動Provider用戶端,啟動Consumer用戶端,可以在頁面檢視到都已經注冊到Eureka Server注冊中心了。

SpringCloud的入門學習之深入了解Eureka注冊中心
現在将Provider用戶端、Consumer用戶端關閉(在eclipse停止啟動的Provider服務、Consumer服務即可)。
SpringCloud的入門學習之深入了解Eureka注冊中心

大概翻譯如下所示:

如果是大于門檻值(85%),Eureka注冊中心判定為網絡故障,如果小于門檻值(85%),Eureka注冊中心判定為單節點故障。因為我這裡是啟動4個服務,就是向Eureka注冊中心注冊了4個服務,挂了2個,就是50%,遠遠低于85%。是以Eureka注冊中心判定此種情況為單節點故障。Eureka注冊中心認為節點在以後可以修複的,是以對出現故障的節點進行了保留,這就是Eureka注冊中心的自我保護。

SpringCloud的入門學習之深入了解Eureka注冊中心

6 、Eureka為什麼要啟動自我保護。

  答:因為同時保留"好資料"與"壞資料"總比丢掉任何資料要更好,當網絡故障恢複後,這個 Eureka 節點會退出"自我保護模式"。當你的節點修複好以後,Eureka注冊中心會退出自我保護模式。

    為什麼Eureka要進行自我保護,因為Eureka 還有用戶端緩存功能(也就是微服務的緩存功能),Eureka注冊中心緩存了之前注冊的資訊。即便 Eureka 叢集中所有節點都當機失效,微服務的 Provider 和 Consumer都能正常通信。微服務的負載均衡政策會自動剔除死亡的微服務節點。

SpringCloud的入門學習之深入了解Eureka注冊中心

7、如何關閉Eureka的自我保護。可以修改Eureka Server配置檔案,在Eureka Server的注冊中心中進行操作,如下所示:

在你的application.properties配置檔案中新增如下配置即可以,關閉自我保護,并在注冊中心清理你的無效服務。如果是高可用的,在兩個或者多個配置檔案application-eureka1.properties、application-eureka2.properties都新增如下配置即可。重新開機,打包,部署,運作可以檢視效果。

1 # 關閉自我保護,值設定為true為開啟自我保護。值設定為false為關閉自我保護。
2 eureka.server.enable-self-preservation=false
3 
4 # 清理間隔,機關毫秒,預設是60*1000
5 eureka.server.eviction-interval-timer-in-ms=60000      

8、如何進行Eureka的優雅停服,不需要在Eureka Server中配置關閉自我保護,需要再服務中添加actuator.jar包。在服務端進行配置,進行Eureka的優雅停服,上面的在Eureka Server注冊中心的關閉自我保護配置就可以注釋掉,或者删除掉了。

我這裡修改的是服務端provider的pom.xml配置檔案,如下所示。将spring-cloud-starter-netflix-eureka-client修改成了spring-cloud-starter-netflix-eureka-client,

因為springcloud eureka 服務端包含了actuator.jar包。

1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
 5     https://maven.apache.org/xsd/maven-4.0.0.xsd">
 6     <modelVersion>4.0.0</modelVersion>
 7     <parent>
 8         <groupId>org.springframework.boot</groupId>
 9         <artifactId>spring-boot-starter-parent</artifactId>
10         <version>2.2.0.RELEASE</version>
11         <relativePath />
12         <!-- lookup parent from repository -->
13     </parent>
14     <groupId>com.bie</groupId>
15     <artifactId>springcloud-eureka-provider</artifactId>
16     <version>0.0.1-SNAPSHOT</version>
17     <name>springcloud-eureka-provider</name>
18     <description>Demo project for Spring Boot</description>
19 
20     <properties>
21         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
22         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
23         <java.version>1.8</java.version>
24         <spring-cloud.version>Hoxton.RC1</spring-cloud.version>
25         <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
26     </properties>
27 
28     <dependencies>
29         <dependency>
30             <groupId>org.springframework.boot</groupId>
31             <artifactId>spring-boot-starter-web</artifactId>
32         </dependency>
33         <!-- springcloud eureka 服務端也包含了actuator.jar包 -->
34         <!-- pom中引用actuator -->
35         <dependency>
36             <groupId>org.springframework.boot</groupId>
37             <artifactId>spring-boot-starter-actuator</artifactId>
38         </dependency>
39         <dependency>
40             <groupId>org.springframework.cloud</groupId>
41             <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
42         </dependency>
43         <dependency>
44             <groupId>org.springframework.boot</groupId>
45             <artifactId>spring-boot-starter-test</artifactId>
46             <scope>test</scope>
47             <exclusions>
48                 <exclusion>
49                     <groupId>org.junit.vintage</groupId>
50                     <artifactId>junit-vintage-engine</artifactId>
51                 </exclusion>
52             </exclusions>
53         </dependency>
54     </dependencies>
55 
56     <dependencyManagement>
57         <dependencies>
58             <dependency>
59                 <groupId>org.springframework.cloud</groupId>
60                 <artifactId>spring-cloud-dependencies</artifactId>
61                 <version>${spring-cloud.version}</version>
62                 <type>pom</type>
63                 <scope>import</scope>
64             </dependency>
65         </dependencies>
66     </dependencyManagement>
67 
68     <build>
69         <plugins>
70             <plugin>
71                 <groupId>org.springframework.boot</groupId>
72                 <artifactId>spring-boot-maven-plugin</artifactId>
73             </plugin>
74         </plugins>
75     </build>
76 
77     <repositories>
78         <repository>
79             <id>spring-milestones</id>
80             <name>Spring Milestones</name>
81             <url>https://repo.spring.io/milestone</url>
82         </repository>
83     </repositories>
84 
85 </project>      

新增配置檔案,啟動shutdown、禁用密碼驗證,如下所示:

由于springboot1.x版本和springboot2.x版本的差異性,這些配置都進行改變。

可以參考官網:​​https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-endpoints​​

1 # 啟用 shutdown
2 management.endpoint.shutdown.enabled=true
3 
4 # 暴露shutdown。include後面可以添加你想用到的端點
5 management.endpoints.web.exposure.include=shutdown      

如果是springboot2.x版本配置,springboot1.x版本的配置會,出現如下所示報錯:

1                 http-outgoing-0 << "{"timestamp":"2019-11-16T05:07:19.423+0000","status":404,"error":"Not Found","message":"No message available","path":"/shutdown"}[\r][\n]"
2 2019-11-16 13:07:19.450 [main] DEBUG org.apache.http.headers -
3                 http-outgoing-0 << HTTP/1.1 404      

如何進行優雅停服的,這個時候不能停止服務。需要發送一個http請求,但是不可以使用浏覽器發送請求,因為浏覽器發送的都是get請求,該請求必須使用post發送請求的,是以可以使用postman工具或者httpClient工具類,進行優雅停服。

SpringCloud的入門學習之深入了解Eureka注冊中心

httpclient工具類,代碼如下所示:

我這裡使用的是"http://localhost:8080/actuator/shutdown,如果隻是"http://localhost:8080/shutdown可能會發現并不能将服務進行停止的。

1 package com.bie.utils;
  2 
  3 import java.io.IOException;
  4 import java.net.URI;
  5 import java.util.ArrayList;
  6 import java.util.List;
  7 import java.util.Map;
  8 
  9 import org.apache.http.NameValuePair;
 10 import org.apache.http.client.entity.UrlEncodedFormEntity;
 11 import org.apache.http.client.methods.CloseableHttpResponse;
 12 import org.apache.http.client.methods.HttpGet;
 13 import org.apache.http.client.methods.HttpPost;
 14 import org.apache.http.client.utils.URIBuilder;
 15 import org.apache.http.entity.ContentType;
 16 import org.apache.http.entity.StringEntity;
 17 import org.apache.http.impl.client.CloseableHttpClient;
 18 import org.apache.http.impl.client.HttpClients;
 19 import org.apache.http.message.BasicNameValuePair;
 20 import org.apache.http.util.EntityUtils;
 21 
 22 public class HttpClientUtil {
 23 
 24     public static String doGet(String url, Map<String, String> param) {
 25 
 26         // 建立Httpclient對象
 27         CloseableHttpClient httpclient = HttpClients.createDefault();
 28 
 29         String resultString = "";
 30         CloseableHttpResponse response = null;
 31         try {
 32             // 建立uri
 33             URIBuilder builder = new URIBuilder(url);
 34             if (param != null) {
 35                 for (String key : param.keySet()) {
 36                     builder.addParameter(key, param.get(key));
 37                 }
 38             }
 39             URI uri = builder.build();
 40 
 41             // 建立http GET請求
 42             HttpGet httpGet = new HttpGet(uri);
 43 
 44             // 執行請求
 45             response = httpclient.execute(httpGet);
 46             // 判斷傳回狀态是否為200
 47             if (response.getStatusLine().getStatusCode() == 200) {
 48                 resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
 49             }
 50         } catch (Exception e) {
 51             e.printStackTrace();
 52         } finally {
 53             try {
 54                 if (response != null) {
 55                     response.close();
 56                 }
 57                 httpclient.close();
 58             } catch (IOException e) {
 59                 e.printStackTrace();
 60             }
 61         }
 62         return resultString;
 63     }
 64 
 65     public static String doGet(String url) {
 66         return doGet(url, null);
 67     }
 68 
 69     public static String doPost(String url, Map<String, String> param) {
 70         // 建立Httpclient對象
 71         CloseableHttpClient httpClient = HttpClients.createDefault();
 72         CloseableHttpResponse response = null;
 73         String resultString = "";
 74         try {
 75             // 建立Http Post請求
 76             HttpPost httpPost = new HttpPost(url);
 77             // 建立參數清單
 78             if (param != null) {
 79                 List<NameValuePair> paramList = new ArrayList<>();
 80                 for (String key : param.keySet()) {
 81                     paramList.add(new BasicNameValuePair(key, param.get(key)));
 82                 }
 83                 // 模拟表單
 84                 UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList, "utf-8");
 85                 httpPost.setEntity(entity);
 86             }
 87             // 執行http請求
 88             response = httpClient.execute(httpPost);
 89             resultString = EntityUtils.toString(response.getEntity(), "utf-8");
 90         } catch (Exception e) {
 91             e.printStackTrace();
 92         } finally {
 93             try {
 94                 response.close();
 95             } catch (IOException e) {
 96                 // TODO Auto-generated catch block
 97                 e.printStackTrace();
 98             }
 99         }
100 
101         return resultString;
102     }
103 
104     public static String doPost(String url) {
105         return doPost(url, null);
106     }
107 
108     public static String doPostJson(String url, String json) {
109         // 建立Httpclient對象
110         CloseableHttpClient httpClient = HttpClients.createDefault();
111         CloseableHttpResponse response = null;
112         String resultString = "";
113         try {
114             // 建立Http Post請求
115             HttpPost httpPost = new HttpPost(url);
116             // 建立請求内容
117             StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
118             httpPost.setEntity(entity);
119             // 執行http請求
120             response = httpClient.execute(httpPost);
121             resultString = EntityUtils.toString(response.getEntity(), "utf-8");
122         } catch (Exception e) {
123             e.printStackTrace();
124         } finally {
125             try {
126                 response.close();
127             } catch (IOException e) {
128                 // TODO Auto-generated catch block
129                 e.printStackTrace();
130             }
131         }
132 
133         return resultString;
134     }
135 
136     public static void main(String[] args) {
137         // 請求位址是你的用戶端服務的位址。
138         String url = "http://localhost:8080/actuator/shutdown";
139         // 該url必須要使用dopost方式來發送
140         HttpClientUtil.doPost(url);
141     }
142 
143 }      

使用postman停止的效果如下所示:

SpringCloud的入門學習之深入了解Eureka注冊中心

9、如何加強Eureka注冊中心的安全認證。如果不進行安全認證的話,無論誰根據url打開Eureka管理界面,這樣很不安全,如下操作進行安全認證操作。在Eureka Server中添加security 包,新增jar包,配置如下所示:

1 <!-- security安全認證 -->
2 <dependency>
3     <groupId>org.springframework.boot</groupId>
4     <artifactId>spring-boot-starter-security</artifactId>
5 </dependency>      

由于我使用的Eureka是高可用的,是以叢集節點之間的互相通路,需要将賬号密碼寫入到請求位址裡面。新增的配置如下所示:

SpringCloud的入門學習之深入了解Eureka注冊中心
SpringCloud的入門學習之深入了解Eureka注冊中心

修改配置檔案,maven install,上傳到伺服器,運作,啟動以後,登入界面如下所示:

登入成功以後和之前的界面完全一緻。

SpringCloud的入門學習之深入了解Eureka注冊中心

注意,因為現在開啟了安全認證,如果服務端,Provider用戶端或者Consumer用戶端,在配置檔案裡面都需要加上你配置的賬号和密碼。如果不添加會導緻啟動失敗的哦。這塊尤為的坑,自己注意吧,感覺Springboot的更新太變态了。

1 # 用戶端需要向服務端進行注冊,必須配置服務端的位址。知道服務端在那裡。
2 #設定服務注冊中心位址,可以将叢集中的所有節點列處理。 
3 eureka.client.serviceUrl.defaultZone=http://eureka:123456@eureka1:8761/eureka/,http://eureka:123456@eureka2:8761/eureka/