Eureka簡單學(xué)習(xí)
這是個人學(xué)習(xí)筆記,如有錯誤,還望海涵、斧正。
一、簡介
Eureka 是一個專門用于服務(wù)發(fā)現(xiàn)的服務(wù)器,一些服務(wù)注冊到該服務(wù)器,而另一些服務(wù)通過該服務(wù)器查找其所要調(diào)用執(zhí)行的服務(wù)。
其本身是一個基于 REST 的服務(wù),主要用于定位運(yùn)行在 AWS 域中的中間層服務(wù),以達(dá)到負(fù)載均衡和中間層服務(wù)故障轉(zhuǎn)移的目的。
可以充當(dāng)服務(wù)發(fā)現(xiàn)服務(wù)器的組件很多,例如 Zookeeper、Consul、Eureka 等。
下圖是一張Eureka的體系架構(gòu):

(圖片引自:https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance)
二、使用方法
通過idea的Spring Initializr創(chuàng)建一個springcloud項(xiàng)目

1.創(chuàng)建server
(1)導(dǎo)入Eureka Server依賴

(2)修改application配置文件為yml格式
? 直接改后綴名就行(個人習(xí)慣,yml更舒服)
(3)修改配置文件
eureka:
instance:
hostname: localhost #指定Eureka主機(jī)
client:
register-with-eureka: false #是否向Eureka注冊自己(主機(jī)不注冊自己)
fetch-registry: false #指定此客戶端是否可以獲取eureka的注冊信息
service-url: #暴露服務(wù)中心地址
defaultZone: http://localhost:8081/eureka
server:
port: 8081
(4)啟動
給啟動類加上 開啟Eureka的注解 @EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer
public class TigerApplication {
public static void main(String[] args) {
SpringApplication.run(TigerApplication.class, args);
}
}
啟動項(xiàng)目,訪問項(xiàng)目地址http://localhost:8081/,成功啟動后如下圖

2.創(chuàng)建provider
有兩種方式供選擇:
第一種方式
引用依賴
嫌麻煩就直接復(fù)制剛剛創(chuàng)建的server項(xiàng)目,重命名為eureka-provider-8082。然后手動修改pom文件(復(fù)制粘貼下面的的依賴就行)
*刪掉刪掉刪掉刪掉原來的server依賴,就是下面這個:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
然后引入一個client的依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
provider引入上面兩個依賴就夠了,但是為了和數(shù)據(jù)庫做連接,我們還要引入下面的依賴:
<!--數(shù)據(jù)庫連接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--修改MySQL驅(qū)動版本-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope>
</dependency>
<!-- jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
除了數(shù)據(jù)庫,作為一個web項(xiàng)目還要引入以下依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
下面兩個是輔助用的依賴,可引可不引。lombok用來生成實(shí)體類的get/set方法,actuator用來配置監(jiān)控終端的顯示信息
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--actuator 依賴-->
<dependency> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置
配置文件(info為actuator配置)
eureka:
instance:
hostname: localhost #指定Eureka主機(jī)
instance-id: eureka-provider-8082 #指定當(dāng)前客戶端在服務(wù)中心注冊的名稱
# 指定服務(wù)中心
client:
# register-with-eureka: true #是否向Eureka注冊自己(主機(jī)不注冊自己)
# fetch-registry: true #指定此客戶端是否可以獲取eureka的注冊信息
service-url: #暴露服務(wù)中心地址
defaultZone: http://localhost:8081/eureka
server:
port: 8082
spring:
# 指定當(dāng)前微服務(wù)對外暴露的名稱
application:
name: eureka-provider-1
# 配置spring-data-jap
jpa:
# 是否在spring容器啟時創(chuàng)建表
generate-ddl: true
show-sql: true
hibernate:
ddl-auto: none
# 配置數(shù)據(jù)源
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///test_my?useUnicode=true&characterEncoding=utf8
username: root
password: root
logging:
# 設(shè)置日志輸出格式
pattern:
console: level-%level %msg%n
level:
root: info
org.hibernate: info
org.hibernate.type.descriptor.sql.BasicBinder: trace
org.hibernate.type.descriptor.sql.BasicExtractor: trace
com.abc.provider: debug
# Eureka工作界面中instance-id處鏈接的信息 參數(shù)都是自定義的
info:
author: tiger
app.name: eureka-provider-8082
app.desc: a desc
第二種方式
重新創(chuàng)建一個項(xiàng)目,在依賴處選擇照下圖選擇就行

配置
配置文件:
由于數(shù)據(jù)庫連接驅(qū)動不同,所以datasource配置和第一種方法的不太一樣。其他配置則一致
eureka:
instance:
hostname: localhost #指定Eureka主機(jī)
instance-id: eureka-provider-8082 #指定當(dāng)前客戶端在服務(wù)中心注冊的名稱
# 指定服務(wù)中心
client:
# register-with-eureka: true #是否向Eureka注冊自己(主機(jī)不注冊自己)
# fetch-registry: true #指定此客戶端是否可以獲取eureka的注冊信息
service-url: #暴露服務(wù)中心地址
defaultZone: http://localhost:8081/eureka
server:
port: 8083
spring:
# 指定當(dāng)前微服務(wù)對外暴露的名稱
application:
name: eureka-provider-1
# 配置spring-data-jap
jpa:
# 是否在spring容器啟時創(chuàng)建表
generate-ddl: true
show-sql: true
hibernate:
ddl-auto: none
# 配置數(shù)據(jù)源
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///test_my?useUnicode=true&&characterEncoding=utf8&serverTimezone=GMT
username: root
password: root
logging:
# 設(shè)置日志輸出格式
pattern:
console: level-%level %msg%n
level:
root: info
org.hibernate: info
org.hibernate.type.descriptor.sql.BasicBinder: trace
org.hibernate.type.descriptor.sql.BasicExtractor: trace
com.abc.provider: debug
# Eureka工作界面中instance-id處鏈接的信息 參數(shù)都是自定義的
info:
author: tiger
app.name: eureka-provider-8082
app.desc: a desc
啟動
在啟動類上加上啟動注解@EnableEurekaClient,項(xiàng)目跑起來后在 http://localhost:8081/ 上可以看到已經(jīng)注冊進(jìn)去了

至于controller service dao entity 這些,相信大家都會的,這里就不寫出來了。
3.創(chuàng)建consumer
依賴

配置
eureka:
instance:
hostname: localhost #指定Eureka主機(jī)
instance-id: eureka-consumer-8083 #指定當(dāng)前客戶端在服務(wù)中心注冊的名稱
# 指定服務(wù)中心
client:
# register-with-eureka: true #是否向Eureka注冊自己(主機(jī)不注冊自己)
# fetch-registry: true #指定此客戶端是否可以獲取eureka的注冊信息
service-url: #暴露服務(wù)中心地址
defaultZone: http://localhost:8081/eureka
server:
port: 8083
spring:
# 指定當(dāng)前微服務(wù)對外暴露的名稱
application:
name: eureka-consumer-1
# Eureka工作界面中instance-id處鏈接的信息 參數(shù)都是自定義的
info:
author: tiger
app.name: eureka-provider-8082
app.desc: a desc
啟動
啟動類加上注解@EnableEurekaClient,啟動完成后在 http://localhost:8081/ 上可以看到已經(jīng)注冊進(jìn)去了

4.創(chuàng)建集群
由于都使用同一個機(jī)器進(jìn)行測試,先配一下host文件,加入以下配置
#eureka server
127.0.0.1 eureka-server-8081
127.0.0.1 eureka-server-8088
127.0.0.1 eureka-server-8089
然后將之前端口為8081的eureka-server項(xiàng)目復(fù)制兩份,共三個項(xiàng)目組成集群
三個項(xiàng)目的yml文件配置如下:
eureka-server-8081:
eureka:
instance:
hostname: eureka-server-8081 #指定Eureka主機(jī)
client:
register-with-eureka: false #是否向Eureka注冊自己(主機(jī)不注冊自己)
fetch-registry: false #指定此客戶端是否可以獲取eureka的注冊信息
service-url: #暴露服務(wù)中心地址
defaultZone: http://eureka-server-8081:8081/eureka,http://eureka-server-8088:8088/eureka,http://eureka-server-8089:8089/eureka
server:
port: 8081
eureka-server-8088:
eureka:
instance:
hostname: eureka-server-8088 #指定Eureka主機(jī)
client:
register-with-eureka: false #是否向Eureka注冊自己(主機(jī)不注冊自己)
fetch-registry: false #指定此客戶端是否可以獲取eureka的注冊信息
service-url: #暴露服務(wù)中心地址
defaultZone: http://eureka-server-8081:8081/eureka,http://eureka-server-8088:8088/eureka,http://eureka-server-8089:8089/eureka
server:
port: 8088
eureka-server-8089:
eureka:
instance:
hostname: eureka-server-8089 #指定Eureka主機(jī)
client:
register-with-eureka: false #是否向Eureka注冊自己(主機(jī)不注冊自己)
fetch-registry: false #指定此客戶端是否可以獲取eureka的注冊信息
service-url: #暴露服務(wù)中心地址
defaultZone: http://eureka-server-8081:8081/eureka,http://eureka-server-8088:8088/eureka,http://eureka-server-8089:8089/eureka
server:
port: 8089
可以看到,要組成集群,則將所有的server的地址都配置到defaultZone上即可,每個地址通過逗號分隔(注意逗號不能有空格,因?yàn)閥ml里空格是有效字符)
然后分別啟動三個項(xiàng)目,得出以下界面,即為集群搭建成功:
http://eureka-server-8081:8081/、

http://eureka-server-8088:8088/、

http://eureka-server-8089:8089/

這時候啟動 provider 和 consumer 的微服務(wù),只要 provider 和 consumer 的 defaultZone 中配了上面任意一個server 的地址,就可以在三個 server 中都注冊。
當(dāng)然,provider 和 consumer 最好將三個地址都配上,這樣某個 server 掛掉后, 還能繼續(xù)使用其他 server
啟動 provider 和 consumer 后,我們查看三個 server 的界面,他們都會存在同樣的服務(wù)列表如下:

三、底層原理
Eureka 注冊信息管理機(jī)制
描述
Eureka 的注冊信息則是通過“緩存 + 存儲”的雙 Map 機(jī)制完成的。
Eureka 的數(shù)據(jù)存儲分了兩層:數(shù)據(jù)存儲層和數(shù)據(jù)緩存層。
Eureka Client 在拉取服務(wù)信息時,先從緩存層獲取,若獲取不到,則先把數(shù)據(jù)存儲層的數(shù)據(jù)加載到緩存中,再從緩存中獲取。(也就是說,Eureka Client永遠(yuǎn)都是從緩存層拉取服務(wù)信息)
數(shù)據(jù)存儲層
數(shù)據(jù)存放位置
數(shù)據(jù)存儲層中的信息是存放在內(nèi)存中的,不做持久化。
原因:
1.若不是所有 Eureka Server 都宕機(jī),則宕機(jī)的 Server 重啟后,會從其它 Server 中同步注冊信息
2.若全部 Eureka Server 宕機(jī),則重新接受客戶端的注冊
數(shù)據(jù)存放方式
用于存儲注冊信息的是一個雙層 ConcurrentHashMap。如:Map<String, Map<String, Lease>>
第一層的 key 是 spring.application.name
第二層的 key 是微服務(wù)主機(jī)的 InstanceId(即配置文件里的 instance-id),value 是 Lease 對象(續(xù)租對象)。Lease 對象包含了服務(wù)詳情和服務(wù)治理相關(guān)的信息。
當(dāng)出現(xiàn)客戶端向 Eureka 服務(wù)器提交 register、renew 和 cancel 請求時,都會修改這 個雙層 Map 中的數(shù)據(jù)。
數(shù)據(jù)緩存層
一級緩存 readOnlyCacheMap
一級緩存中的數(shù)據(jù)只要不被替換就會一直存在,客戶端拉取注冊信息時就是從這個緩存中拉取
二級緩存 readWriteCacheMap
二級緩存中的數(shù)據(jù)會隨著客戶端的 register、renew 和 cancel 請求而更新,因?yàn)楫?dāng)客戶端發(fā)起這些請求,數(shù)據(jù)存儲層的數(shù)據(jù)會被更新,為了保證緩存層和存儲層數(shù)據(jù)的一致性,二級緩存會從存儲層從新加載數(shù)據(jù)。
新數(shù)據(jù)的加載不會立即引發(fā)一級緩存的更新或清空,一級緩存會定期從二級緩存中同步數(shù)據(jù)。
Eureka 的自我保護(hù)機(jī)制
現(xiàn)象
在上面啟動 provider 和 consumer 項(xiàng)目的時候,我們都能在 server 的界面上看到一句紅色的警告
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
這是因?yàn)镋ureka開啟了自我保護(hù)機(jī)制,翻譯過來就是:
緊急情況!當(dāng)微服務(wù)主機(jī)聯(lián)系不上時,Eureka 不能夠正確判斷它們是否處于 up 狀態(tài)。當(dāng)更新(指收到的微服務(wù)主機(jī)的心跳)小于閾值時,為了安全,微服務(wù)主機(jī)將不再失效。
原因
默認(rèn)情況下,Eureka Server 在 90 秒內(nèi)(微服務(wù)默認(rèn)30秒發(fā)一次心跳)沒有收到某微服務(wù)的心跳,則判斷改為服務(wù)的主機(jī)宕機(jī),將該微服務(wù)從服務(wù)注冊信息表中刪除。
但很多時候,因?yàn)?strong>網(wǎng)絡(luò)抖動等原因,導(dǎo)致微服務(wù)主機(jī)發(fā)送的心跳沒有被 Eureka Server 收到,從而被 Eureka Server 刪除。
若短時間內(nèi)網(wǎng)絡(luò)恢復(fù)正常,但由于服務(wù)列表中刪除了該微服務(wù),所以該微服務(wù)不能再提供服務(wù)。
為了防止上述情況發(fā)生,Eureka 便有了自我保護(hù)機(jī)制。
即在短時間內(nèi),如果 Eureka Server 丟失了較多的服務(wù),即收到的心跳數(shù)量小于閾值,為了保證系統(tǒng)的可用性,Eureka會進(jìn)入自我保護(hù)模式,此時服務(wù)列表只可以讀取、注冊,不可以刪除。這樣便給了那些因?yàn)榫W(wǎng)絡(luò)抖動而被Eureka Server 誤認(rèn)為宕機(jī)的微服務(wù)主機(jī)重新復(fù)活的機(jī)會。當(dāng)網(wǎng)絡(luò)恢復(fù)正常,微服務(wù)主機(jī)得以繼續(xù)發(fā)送心跳,當(dāng)心跳數(shù)達(dá)到閾值以上,則退出自我保護(hù)模式。
關(guān)于閾值:自我保護(hù)機(jī)制的默認(rèn)閾值是85%。當(dāng)一定時間內(nèi)收到的心跳數(shù)小于總微服務(wù)數(shù)量的85%時,開啟自我保護(hù)模式。
從頁面上的兩個參數(shù),我們可以判斷出來什么時候會開啟自我保護(hù)

Renews threshold 是 15分鐘內(nèi)每分鐘應(yīng)該收到的數(shù)量。
Renews (last min) 是 最后一分鐘實(shí)際收到的數(shù)量。
注意,Eureka默認(rèn)你的是微服務(wù)應(yīng)用是100個,所以(100*85%)/15 取整后為 5,即每分鐘應(yīng)該收到5個以上的心跳。
所以,很容易判斷,當(dāng)Renews (last min) < Renews threshold時,自我保護(hù)就會開啟。
但由上我們又知道,如果注冊到 Eureka 的 微服務(wù)不達(dá)到一定的數(shù)量,這個自我保護(hù)機(jī)制并不能由明顯效果。
下面給出自定義自我保護(hù)閾值和關(guān)閉自我保護(hù)的參數(shù):
eureka:
server:
renewal-percent-threshold: 0.75 #默認(rèn)0.85
enable-self-preservation: true # 關(guān)閉自我保護(hù)機(jī)制