- Demo 源碼下載
- 本案例為源碼分支的 eureka 分支
服務發現概述
- 服務發現機制是為了解決硬網絡編碼問題,服務消費者使用這種機制獲取服務提供者網絡信息,當微服務網絡地址發生變更(例如IP或端口),會重新注冊到服務發現組件,而服務消費者就無須人工修改網絡地址了。
- 服務提供者、服務消費者、服務發現之間的關系如下:
- 微服務在啟動時,將自己的網絡地址等信息注入到服務發現組件中,服務發現組件會存儲這些信息
- 服務消費者可以從服務發現組件查詢服務提供者的網絡地址,并使用該地址調用服務提供者的接口
- 各個微服務與服務發現組件使用一定的機制(心跳)通信,服務組件如長時間無法與某微服務實例通信,就會注銷該實例
- 服務發現組件功能:
- 服務注冊表:是服務發現組件的核心,用來記錄微服務信息,如名稱、IP、端口等,提供查詢和管理API,注冊和注銷
- 服務注冊與服務發現:服務注冊是指服務在啟動時將自己信息發送到服務注冊組件上,服務發現是指查詢可用微服務列表及網絡地址的機制
- 服務檢查:服務發現組件檢測已注冊的服務,若某一個服務長時間無法訪問則移除該實例
Eureka
- Eureka 是開源的服務發現組件,本身是一個基于 REST 的服務,包含 Server 和 Client 兩部分
Eureka 架構
- Region:表示服務系統中的地理位置,Spring Cloud默認使用的是 us-east-1
- Availbility Zone:可理解為機房,Region理解為跨機房的 Eureka 集群
- Application Service:服務提供者
- Application Client:服務消費者
- Make Remote Call:如調用 RESTful API
- us-east-1c、us-east-1d、us-east-1e都是zone,都屬于 us-east-1 這個region
- Eureka Server:提供服務發現能力,各個微服務啟動時,會向 Eureka Server 注冊自己的信息,Eureka Server 會保存這些信息
- Eureka Client:是一個 Java 客戶端,用于簡化與Eureka Server的交互
- 微服務啟動后,會周期性(默認30秒)的與 Eureka Server 發送心跳信息
- 如果 Eureka Server 在一定時間內沒接收到心跳信息則注銷該實例(默認90秒)
- 多個 Eureka Server 之間通過復制方式,來實現服務注冊表中數據的同步
- Eureka Client 會緩存注冊表中信息,微服務無需每次請求都查詢 Eureka Server,降低了壓力
快速入門
編寫 Eureka Server (Demo源碼 eureka)
- 引入 Eureka Server 庫
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- 添加 @EnableEurekaServer注解聲明這是一個 Eureka Server
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
- 添加配置
server:
port: 8761 #配置端口
eureka:
client:
register-with-eureka: false #是否向服務端注冊自己,它本身就是Eureka Server,所以為false
fetch-registry: false #表示是否從Eureka Server獲取信息,因為這是一個單節點,不需要同步其它Eureka Server的數據,所以為 false
service-url:
defaultZone: http://localhost:8761/eureka/ #設置 Eureka Client 與 Eureka Server 同步的地址,注冊、查詢服務都要使用該地址,多個地址可用逗號分隔
- 啟動 Eureka Server,訪問 http://localhost:8761/ 則可看到 Eureka 后臺
將微服務注冊到 Eureka Server
- 引入 Eureka Client 庫
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
- 添加 @EnableDiscoverClient 注解聲明這是一個微服務
@SpringBootApplication
@EnableEurekaClient
public class FlimUserApplication {
public static void main(String[] args) {
SpringApplication.run(FlimUserApplication.class, args);
}
}
- 添加配置
spring:
application:
name: flim-user #注冊到Eureka的名字
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/ #設置EurekaServer的服務地址
instance:
prefer-ip-address: true #true代表將自己的IP注冊到EurekaServer上,false代表注冊主機名到EurekaServer
- 訪問 http://localhost:8761/ 則可看到微服務已注冊到 Eureka Server
image
Eureka高可用
- 單節點 Eureka Server 并不適合生產環境,Eureka Client 會定時連接 Eureka Server,獲取服務注冊表中的信息并緩存在本地,微服務消費遠程 API 時總是使用本地緩存中的數據。因此即使 Eureka Server 發生故障也不會影響到服務之間的調用,但如果 Eureka Server 故障時,某些微服務也出現了不可用的情況,Eureka Client 中的緩存若不被更新,則可能會影響到微服務之間的調用
- 因此在生產環境中,通常會部署一個高可用的 Eureka Server 集群,Eureka Server 可以通過運行多個實例并相互注冊的方式實現高可用部署,Eureka Server 之間也會彼此增量的同步信息,從而確保所有節點數據一致,節點之間相互注冊也是 Eureka Server 的默認行為
構建雙結點的 Eureka Server 集群
- 修改 Eureka Server 配置文件,添加兩個 Eureka 環境
spring:
application:
name: eureka
---
spring:
profiles: eureka1
eureka:
instance:
hostname: eureka1
client:
service-url:
default-zone: http://localhost:8762/eureka
---
spring:
profiles: eureka2
eureka:
instance:
hostname: eureka2
client:
service-url:
default-zone: http://localhost:8761/eureka
- 執行以下命令啟動兩個 Eureka Server 實例,可看到兩個實例之間已同步(注意:先后運行實例后,要等待一會才會同步成功)
java -jar eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active = eureka1 --server.port=8761
java -jar eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active = eureka2 --server.port=8762
Eureka 高可用
將應用注冊到 Eureka Server 集群上
- 只需配置多個 Eureka Server 地址,就可以將其注冊到Eureka Server集群了
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
- 即使只配置一個 Eureka Server 集群中的某個節點,也能注冊到集群上,因為多個Eureka Server之間數據會同步
Eureka Server 安全
為 Eureka Server 添加用戶認證
- 前面例子中 Eureka Server 允許匿名訪問,可以構建一個需要登錄才能訪問的 Eureka Server,這需要添加 Spring Security 庫
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 添加以下配置
spring:
security:
user:
name: user
password: 123456
- 訪問 http:localhost:8761/ 時會提示身份驗證對話框
將微服務注冊到需要認證的 Eureka Server
- 修改配置文件,屬性上加上用戶名與密碼即可
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka/
- 對于更復雜的需求可以創建一個類型為 DiscoveryClientOptionalArgs 的 @Bean,并向其中注入 ClientFilter
Eureka 元數據
- Eureka 元數據有兩種,標準元數據和自定義元數據
- 標準元數據:指主機名、IP地址、端口號和健康檢查信息等,這些信息都會被發現服務注冊表中,用于服務之間調用
- 自定義元數據:可以用 eureka.instance.metadata-map 配置,這些元數據可以在遠程客戶端中訪問,但一般不會改變遠程客戶端行為,除非客戶端知道該元數據的含義
自定義元數據-服務提供者
- 修改配置文件,使用 eureka.instance.metadata-map 添加自定義元數據
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka/
instance:
prefer-ip-address: true
metadata-map:
my-metadata: 自定義的元數據
自定義元數據-服務消費者
- 修改電影微服務 MovieController 獲取自定義元數據
@RestController
public class MovieController {
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/user-instance")
public List<ServiceInstance> showInfo(){
return this.discoveryClient.getInstances("flim-user"); //獲取Flim-User微服務實例的信息
}
}
- 訪問 http://localhost:8010/user-instance 會返回用戶微服務的各種信息
[
{
"host": "192.168.43.43",
"port": 8080,
"metadata": {
"management.port": "8080",
"my-metadata": "自定義的元數據"
},
"secure": false,
"serviceId": "FLIM-USER",
"uri": "http://192.168.43.43:8080",
"instanceInfo": {
"instanceId": "linyuandembp:flim-user",
"app": "FLIM-USER",
"appGroupName": null,
"ipAddr": "192.168.43.43",
"sid": "na",
"homePageUrl": "http://192.168.43.43:8080/",
"statusPageUrl": "http://192.168.43.43:8080/info",
"healthCheckUrl": "http://192.168.43.43:8080/health",
"secureHealthCheckUrl": null,
"vipAddress": "flim-user",
"secureVipAddress": "flim-user",
"countryId": 1,
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
},
"hostName": "192.168.43.43",
"status": "UP",
"leaseInfo": {
"renewalIntervalInSecs": 30,
"durationInSecs": 90,
"registrationTimestamp": 1513418181530,
"lastRenewalTimestamp": 1513418181530,
"evictionTimestamp": 0,
"serviceUpTimestamp": 1513418181530
},
"isCoordinatingDiscoveryServer": false,
"metadata": {
"management.port": "8080",
"my-metadata": "自定義的元數據"
},
"lastUpdatedTimestamp": 1513418181530,
"lastDirtyTimestamp": 1513418181485,
"actionType": "ADDED",
"asgName": null,
"overriddenStatus": "UNKNOWN"
}
}
]
Eureka Server 的 REST 端點
- Eureka Server 提供了一些 REST 端點,非 JVM 的微服務可以使用這些 REST 端點操作 Eureka,從而實現服務注冊與發現,其中:
- appID:應用程序的名稱
- instanceID:與實例相關聯的唯一ID
REST 端點集合
- 使用 REST 請求向 Eureka Server 注冊微服務時,需要 POST 一個請求體( JSON 或 XML 格式,請求題略長,此處不展示)
Eureka 自我保護模式
- Eureka 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.
- 默認情況下,如果 EurekaServer 在一定時間內沒接收到某個微服務的心跳,則會注銷該實例,但當網絡發生故障,微服務無法與 EurekaServer 正常通信時,該行為就非常危險了,Eureka 通過“自我保護”來解決這個問題,當 Eureka Server 節點短時間內丟失過多客戶端時,就會進入自我保護模式,一旦進入該模式,Eureka Server 就會保護服務注冊表中的信息,不再刪除服務注冊表中的信息(不注銷任何微服務),當網絡恢復后,Eureka Server 退出自我保護模式
多網環境下的IP選擇
- 對于多網卡用戶,如某臺服務器有三塊網卡,但只有 eth1 可以被訪問,可以通過配置文件來實現
- 使用 spring.cloud.inetutils.ignored-interfaces 屬性忽略指定名稱的網卡
spring:
cloud:
inetutils:
ignored-interfaces:
- docker0 #忽略docker0網卡
- veth.* #忽略所有veth開頭的網卡
- 使用 spring.cloud.inetutils.preferredNetworks 屬性指定使用的網絡地址
spring:
cloud:
inetutils:
preferred-networks:
- 192.168
- 10.0
Eureka 健康檢查
- 在 Status 欄有個 UP 表示應用程序狀態正常,還有其它值如:DOWN、OUT_OF_SERVICE、UNKNOWN等,但只有 UP 狀態的才會被服務請求,Eureka Server 與 Eureka Client 之間使用心跳機制確定 Eureka Client 狀態,正常情況下為 UP
- 該機制不能反應應用程序狀態,如微服務與 Eureka Server 心跳正常,可該服務數據源發生問題,無法正常工作
- Spring Boot Actuator 提供了 /health 端點,該端點可展示應用程序健康信息
Eureka 常見問題
Eureka 注冊服務慢
- 默認情況下,服務注冊到 Eureka Server 的過程較慢,在開發測試時往往希望能快速一點,從而提升工作效率
- 服務的注冊涉及到周期性心跳,默認 30 秒一次,只有當實例、服務器端和客戶端的本地緩存中的元數據都相同時,服務才能被發現(所以可能需要 3 次心跳)
- 可以使用參數 eureka.instance.leaseRenewalintervalInSeconds 修改時間間隔,單位為秒,從而加快客戶端連接到服務的過程
-生產環境最好使用默認值,因為服務器內部有一些計算
已停止的微服務節點注銷慢或不注銷
- 開發環境下,我們希望能迅速有效的注銷已停止的微服務實例,但 Eureka Server 清理無效節點周期(默認 90 秒),以及自我保護等原因,可能回遇到微服務注銷慢甚至不注銷
- 解決方法如下:
- Eureka Server 端:
- 關閉自我保護,并配置清理無效節點的間隔
eureka.server.enable-self-preservation = false #關閉自我保護 eureka.server.eviction-interval-timer-in-ms = 60000 #清理間隔,單位毫秒
- Eureka Client 端:
- 開啟健康檢查,并設置更新時間和到期時間
eureka.client.healthcheck.enabled = true #開啟健康檢查 eureka.instance.lease-renewal-interval-in-seconds = 30000 #更新時間,單位毫秒,默認30秒 eureka.instance.lease-expiration-duration-in-seconds = 90000 #到期時間,單位毫秒,默認90秒
- Eureka Server 端:
如何自定義微服務的 InstanceID
- InstanceID 用于唯一標識到 Eureka Server 上的微服務實例,Eureka Server 首頁可以直觀的看到各個微服務的 InstanceID
- 在 Spring Cloud 中,服務的 InstanceID 默認值是
${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}
- 也可以通過下面配置修改 InstanceID 格式
eureka:
instance:
instance-id: ${spring.cloud.client.ipAddress}:${server.port}
Eureka 的UNKNOWN 問題
- 注冊信息 UNKNOWN 有兩種情況,一種是應用名稱 UNKNOWN,一種是應用狀態 UNKNOWN
- 應用名稱 UNKNOWN 可能是因為未配置 spring.application.name 或 eureka.instance.appname 屬性
- 應用狀態 UNKNOWN 是微服務不正常情況,該問題一般由健康檢查導致,eureka.client.healthcheck.enabled=true 必須設置在 application.yml 中,而不是 bootstrap.yml 中