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

解釋如下所示:
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 定理。
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的差別。
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注冊中心了。
大概翻譯如下所示:
如果是大于門檻值(85%),Eureka注冊中心判定為網絡故障,如果小于門檻值(85%),Eureka注冊中心判定為單節點故障。因為我這裡是啟動4個服務,就是向Eureka注冊中心注冊了4個服務,挂了2個,就是50%,遠遠低于85%。是以Eureka注冊中心判定此種情況為單節點故障。Eureka注冊中心認為節點在以後可以修複的,是以對出現故障的節點進行了保留,這就是Eureka注冊中心的自我保護。
6 、Eureka為什麼要啟動自我保護。
答:因為同時保留"好資料"與"壞資料"總比丢掉任何資料要更好,當網絡故障恢複後,這個 Eureka 節點會退出"自我保護模式"。當你的節點修複好以後,Eureka注冊中心會退出自我保護模式。
為什麼Eureka要進行自我保護,因為Eureka 還有用戶端緩存功能(也就是微服務的緩存功能),Eureka注冊中心緩存了之前注冊的資訊。即便 Eureka 叢集中所有節點都當機失效,微服務的 Provider 和 Consumer都能正常通信。微服務的負載均衡政策會自動剔除死亡的微服務節點。
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工具類,進行優雅停服。
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停止的效果如下所示:
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是高可用的,是以叢集節點之間的互相通路,需要将賬号密碼寫入到請求位址裡面。新增的配置如下所示:
修改配置檔案,maven install,上傳到伺服器,運作,啟動以後,登入界面如下所示:
登入成功以後和之前的界面完全一緻。
注意,因為現在開啟了安全認證,如果服務端,Provider用戶端或者Consumer用戶端,在配置檔案裡面都需要加上你配置的賬号和密碼。如果不添加會導緻啟動失敗的哦。這塊尤為的坑,自己注意吧,感覺Springboot的更新太變态了。
1 # 用戶端需要向服務端進行注冊,必須配置服務端的位址。知道服務端在那裡。
2 #設定服務注冊中心位址,可以将叢集中的所有節點列處理。
3 eureka.client.serviceUrl.defaultZone=http://eureka:123456@eureka1:8761/eureka/,http://eureka:123456@eureka2:8761/eureka/