上一篇筆記寫到的兩個問題,Netfilx-Eureka和Ribbo會給我們一一解決。如果有看過dubbo 應該知道zookeeper,Eureka的角色就是zookeeper。
Eureka和Zookeeper主要都是作爲微服務的註冊和發現。
Eureka和Zookeeper不一樣的是,Zookeeper是作爲一個單獨的應用程序,而Eureka是直接嵌套在SpringCloud當中的。所以 Eureka Server本身就是一個SpringBoot的項目。
一、服務註冊和發現概念
上面的圖是我網上找的。由於微服務的地址變革可能會影響很大,所以就引入了服務註冊中心的角色。所有服務提供者必須將IP地址或者主機名註冊到服務器註冊中心當中,我們使用註冊中心就是Eurika Server。服務的消費者在調用服務器之前,先要到服務註冊中心查詢服務提供者的網絡地址信息,然後進行調用,這個原理非常相似於我們的DNS。獲得服務提供者的網絡地址的操作,我們稱爲服務發現。服務註冊中心一般提供一下功能組件:
1、服務註冊表:記錄各個微服務的信息,例如名稱、網絡地址、端口號等。服務器註冊表提供查詢和管理的API,用於管理和查詢可用的微服務實例。
2、服務註冊和發現:剛剛已經說過了,微服務在啓動時,將自己的信息註冊到服務器註冊中心當中。在消費者可以通過服務註冊中心發現服務實例。
3、服務檢查:檢查微服務的健康情況,如果微服務實例在一段時間沒有心跳信息,就會從服務實例註冊表中移除該實例。
二、Eureka
Eureka是Netfilx開源的服務發現組件,本身是基於REST的服務。包含Server和Client兩個部分。SpingCloud 將其集成到SpringCloud Netflix子項目中。
Eureka有兩個概念分別 Region和Availability Zone。他們都是Amazon AWS的概率。其中,Region表示AWS中的地理位置,每個Region都有多個Availability Zone,各個Region之間完全隔離。
Spring Cloud 默認使用的Region是us-east-1,在非AWS環境下,可以將Availability Zone理解成機房,將Region理解爲誇機房的Eureka集羣。
三、Eureka Server 和 Client
Eureka Server 提供服務器的發現的能力,各個微服務啓動時,都會在Eureka Server中註冊自己的信息,Eureka Server 會將這些信息存儲到註冊表當中。
Eureka Client 這個就是一個JAVA客戶端,用於簡化於Eureka Server的交互。當然如果你的微服務是跨平臺的,各個服務存在不同的開發語言,那就只能通過REST的方式去與Eureka Server進行交互了。REST不是我們的重點,因爲我們作爲一個JAVA狗,Eureka Client足夠簡化我們的操作。
以下是筆者的從參考書中摘錄的重點概念:
1、微服務啓動後,會週期性向Eureka Server 發送心跳信息默認是30秒。如果Eureka在一定的時間內(默認90秒)無法獲得實例的心跳信息,Eureka Server 將會註冊該實例。
2、默認情況下,Eureka Server 同時也是Eureka Client。多個Eureka Server實例,互相之間通過複製的方式,來實現服務註冊表的同步。
3、Eureka Client 會緩存服務註冊表中的信息。這樣有非常多的好處,微服務無需每次都請求查詢Eureka Server,從而降低Eureka Server的服務壓力和網絡壓力;即使所有Eureka Server都掛了,Eureka Client還是可以根據自己的緩存註冊表自給自足。
四、動手編寫Eureka Server
筆者此事已經開了第三個IDE了,我的MacBook的內存開始吃不消了。建議這些還是在內存比較大的計算機上進行測試,而且同時去編譯和加載會導致非常耗時間。唉··· 一會還要再加多一個Eureka Server 做高可用,呵呵!搞事情!
在Maven上我們的依賴配置如下:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency>然後在配置類當中打上@EnableEurekaServer
@SpringBootApplication @EnableEurekaServer public class EurekaserveraApplication { public static void main(String[] args) { SpringApplication.run(EurekaserveraApplication.class, args); } }最後配置一下我們的application.properties
server.port=8761 eureka.client.register-with-eureka=false eureka.client.fetch-registry=false eureka.client.service-url.defaultZone=http://lcoalhost:8761/eureka/
端口就不說了看看其他配置是什麼回事:
1、register-with-eureka 這個配置是否將自己的註冊到Eureka Server當中,默認true。由於當前應用就是Eureka Server 所以爲false
2、fetch-registry 表示是否從Eureka Server 中獲得註冊信息,默認爲true。目前是單點Eureka Server 所以不需要和其他Eureka Server 節點做同步,所以爲false
3、service-url.default-zone 設置與Eureka Server交互的地址,查詢服務和註冊服務都依賴這個地址。如果多個地址即多個Eureka Server節點需要用“,”分隔。默認是http://localhost:8761/eureka
配置完成,跑起!
需要注意的是,筆者之前是使用最新的SpringCloud版本1.5.4.RELEASE 但是實際當我打開頁面的過程當中發現無法打開上面的UI界面,只是有一個XML。筆者在查找了很多網上的資料後,也無法解決。但是在stackoverflow中我找到了一個帖子,說將其降級到1.5.2.RELEASE就正常出現頁面了。至於1.5.4爲什麼不可以顯示,可能後續我會有所補充。但是1.5.4其他的功能還是正常可以使用的。
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>改成:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
五、Eureka Server 高可用
Eureka Server作爲一個服務註冊中心當然是不允許出現單點異常的,所以Eureka Server 做高可用是一個必須的操作。上面已經介紹過Eureka Server高可用的理論了,現在就操作一下。環境上筆者就不搞兩臺設備來做了,筆者會在hosts文件當中添加兩條記錄,表示當前的兩臺計算機的主機名:
127.0.0.1 eurekaservera eurekaserverb
然後在同一個項目當中配置兩個profile,然後通過不同的profile分別代表eureka-servera、eureka-serverb的配置,然後分別啓動。不清楚profile的可以看看我之前的SpringBoot的筆記SpringBoot系列(5)---SpringBoot-Web和SpringBoot基礎 搜索profile即可。
application-servera.properties文件配置如下:
server.port=8761 eureka.instance.hostname=eurekaservera eureka.client.service-url.defaultZone=http://eurekaserverb:8762/eureka/端口號8761 實例名稱的主機名稱爲 eurekaservera。 register-with-eureka 和 fetch-registry 默認爲true 所以直接不用配置 default-zone設置爲其他的eureka server 這裏設置是eureka-serverb 由於是同一臺計算機 所以我用了不同的端口號。
application-serverb.properties文件配置如下:
server.port=8762 eureka.instance.hostname=eurekaserverb eureka.client.service-url.defaultZone=http://eurekaservera:8761/eureka/
就算端口號成8762 然後實例主機名稱改成 eureka-serverb 然後設置default-zone爲剛剛設置的eureka-servera
然後application.properties 修改profile.active 分別啓動
spring.application.name=tony-mall-discovery-eureka-ha spring.profiles.active=servera
可以見到UI界面上出現如下的信息:
高可用已經配置生效了!
六、Eureka Cilent
說了這麼久Eureka Server 連高可用都配置了,總得要用起來吧··· Eureka Client 真的非常簡單。你機會除了簡單的配置,你就感覺不到它的存在了。
我們分別在 用戶服務系統 和 商品服務系統 添加相應的Eureka Client 配置 主要爲了將自身信息隨SpringBoot項目開啓 註冊到 Eureka Server當中。
用戶服務系統的application.properties 文件如下:
server.port=8801 server.context-path=/user spring.datasource.url=jdbc:mysql://localhost:3306/shop_mall?charset=utf8mb4 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.username=root spring.datasource.password=tonyyan spring.application.name=tony-mall-provider-user eureka.client.service-url.defaultZone=http://eurekaservera:8761/eureka/,http://eurekaserverb:8762/eureka/ eureka.instance.prefer-ip-address=trueservice-url.defaultZone 我們將兩個Eureka Server 都註冊了。當然你可以註冊一個,然後通過指定的Eureka Server 自動獲得其他Eureka Server,同樣也可以實現高可用。但是最好也是配上,如果正好你指定的那臺Eureka Server 掛了,就呵呵了。
prefer-ip-address 爲true 這裏是代表註冊自身的ip地址 而不是主機名稱。
商品服務系統的application.properties 文件如下,跟用戶差不多:
server.port=8802 server.context-path=/product spring.datasource.url=jdbc:mysql://localhost:3306/shop_mall?charset=utf8mb4 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.username=root spring.datasource.password=tonyyan spring.application.name=tony-mall-provider-product eureka.client.service-url.defaultZone=http://eurekaservera:8761/eureka/,http://eurekaserverb:8762/eureka/ eureka.instance.prefer-ip-address=true
最後在兩個項目的配置類當中分別打上@EnableDiscoveryClient的 annotation
@SpringBootApplication @EnableDiscoveryClient public class ProductsystemApplication {
@SpringBootApplication @EnableDiscoveryClient public class UsersystemApplication {
將兩個微服務啓動,並查看Eureka Server 上的實例信息:
如果你配置了HA,你會發現兩臺註冊中心都會出現同樣的實例列表。OK!現在都配置好了,可以調用測試~
@GetMapping("/getProduct/{productId}/userId/{userId}") public Map<String, Object> getProduct(@PathVariable int productId, @PathVariable int userId) { Map<String, Object> map = new HashMap<>(); String url = "http://tony-mall-provider-user/user/getUserById/" + userId; User user = this.restTemplate.getForEntity(url, User.class).getBody(); Product product = this.productRepository.getByProductId(productId); map.put("user",user); map.put("product",product); return map; }可以發現調用其實都一樣,只不過URL 從IP地址改成了實例名稱了。其實這裏就是一個虛擬主機名稱。最後測試通過~ 我這裏就不貼出返回的JSON了。
六、Eureka Cilent 元數據和實例信息
Eureka Client 還可以獲得其他實例的信息:
@GetMapping("/showProvider") public List<ServiceInstance> showInfo(){ return this.discoveryClient.getInstances("tony-mall-provider-user"); }
返回如下的JSON:
[
{
"host": "192.168.28.100",
"port": 8801,
"uri": "http://192.168.28.100:8801",
"serviceId": "TONY-MALL-PROVIDER-USER",
"metadata": {},
"secure": false,
"instanceInfo": {
"instanceId": "192.168.28.100:tony-mall-provider-user:8801",
"app": "TONY-MALL-PROVIDER-USER",
"appGroupName": null,
"ipAddr": "192.168.28.100",
"sid": "na",
"homePageUrl": "http://192.168.28.100:8801/",
"statusPageUrl": "http://192.168.28.100:8801/info",
"healthCheckUrl": "http://192.168.28.100:8801/health",
"secureHealthCheckUrl": null,
"vipAddress": "tony-mall-provider-user",
"secureVipAddress": "tony-mall-provider-user",
"countryId": 1,
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
},
"hostName": "192.168.28.100",
"status": "UP",
"leaseInfo": {
"renewalIntervalInSecs": 30,
"durationInSecs": 90,
"registrationTimestamp": 1500002506247,
"lastRenewalTimestamp": 1500002506247,
"evictionTimestamp": 0,
"serviceUpTimestamp": 1500002506248
},
"isCoordinatingDiscoveryServer": false,
"metadata": {},
"lastUpdatedTimestamp": 1500002506248,
"lastDirtyTimestamp": 1500002506142,
"actionType": "ADDED",
"asgName": null,
"overriddenStatus": "UNKNOWN"
}
}
]
除了一些服務信息之外,還看到了這個屬性metadata 這個是元數據,又服務提供者提供。以下我們嘗試在用戶服務系統的application.properties中添加元數據:
eureka.instance.metadata-map.developer=TONY eureka.instance.metadata-map.provider_city=GZ重新調用我們寫的showProvider接口,發現metadata已經添加相應的元數據信息:
。。。省略。。。
"isCoordinatingDiscoveryServer": false,
"metadata": {
"provider_city": "GZ",
"developer": "TONY"
},
"lastUpdatedTimestamp": 1500003756538,
。。。省略。。。
七、Eureka Server 的自我保護模式
以下摘錄於《SpringCloud和Docker微服務架構實戰》
默認情況下,如果Eureka Sever在一定時間內沒有接收到某個微服務實例的心跳信息,Eureka Server 會直接註銷該實例。但是當網絡分區發生故障時,微服務與Eureka Server之間無法通信,直接註銷微服務實例就會非常危險。服務本身而言是健康的,不應該直接註銷微服務。
Eureka Server 通過 自我保護模式 來解決這個問題。當Eureka Server 節點在短時間內丟失多個客戶端時,那麼這個節點就會進入自我保護模式。一旦進入該模式,Eureka Server 就會保護服務註冊表的信息,不會再刪除註冊表信息(不會註銷實例信息)。當網絡故障恢復後,該Eureka Server 節點會自動退出自我保護模式。
自我保護模式是一種應對網絡異常的安全保護措施。
在Spring Cloud中,可以使用以下啓用或禁用安全保護措施
eureka.server.enable-self-preservation=false
默認爲true
八、多網卡環境選擇IP
對於多個網卡的服務器,指定IP的需求非常常見。例如某臺服務器有多張網卡,但是隻有eth1可以被其他服務器訪問;如果Eureka Client將其他網卡的IP註冊到Eureka Server上,其他服務消費者將無法方位服務提供實例的服務。
各個微服務註冊到Eureka Server 上的IP可以有以下操作進行指定:
忽略指定網卡:
eureka.instance.prefer-ip-address=true spring.cloud.inetutils.ignored-interfaces=docker0
使用正則表達式指定使用的網絡地址:
spring.cloud.inetutils.preferred-networks=192.168
我還是最喜歡這個方式,直接指定IP地址,簡單粗暴:
eureka.instance.ip-address=127.0.0.1
只使用站點的本地地址
spring.cloud.inetutils.use-only-site-local-interfaces=true
九、服務健康檢查
SpringBoot Actuator 提供了很多監控REST端點。方位這些斷點就可以獲得相應的服務運行狀況。
以下是SpringBoot Actuator 提供的端點 摘錄於SpringBoot官方文檔
ID | Description | Sensitive Default |
---|---|---|
|
Provides a hypermedia-based “discovery page” for the other endpoints. Requires Spring HATEOAS to be on the classpath. |
true |
|
Exposes audit events information for the current application. |
true |
|
Displays an auto-configuration report showing all auto-configuration candidates and the reason why they ‘were’ or ‘were not’ applied. (顯示自動配置信息) |
true |
|
Displays a complete list of all the Spring beans in your application. (顯示所有Bean信息) |
true |
|
Displays a collated list of all |
true |
|
Performs a thread dump. (顯示線程活動快照) |
true |
|
Exposes properties from Spring’s |
true |
|
Shows any Flyway database migrations that have been applied. |
true |
|
Shows application health information (when the application is secure, a simple ‘status’ when accessed over an unauthenticated connection or full message details when authenticated).(顯示應用程序的健康指標,這些值由HealthIndicator的實現類提供) |
false |
|
Displays arbitrary application info.(顯示應用程序的新,可以用info.*屬性自定義info端點公開的數據) |
false |
|
Shows and modifies the configuration of loggers in the application. (顯示和修改Logger配置) |
true |
|
Shows any Liquibase database migrations that have been applied. |
true |
|
Shows ‘metrics’ information for the current application.(顯示應用程序的度量標準信息) |
true |
|
Displays a collated list of all |
true |
|
Allows the application to be gracefully shutdown (not enabled by default). (關閉應用 默認不啓用,如果啓用需要配置endpoints.shutdown.enabled=true) |
true |
|
Displays trace information (by default the last 100 HTTP requests).(顯示跟蹤信息,默認情況下爲最近100個HTTP請求) |
true |
之後的筆記也會有相關Actuator的介紹,先整合到SpringCloud當中:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>然後就沒有然後了,非常簡單~ 添加個依賴就將actuator整合到Spring當中。當我們啓用了Eureka之後 health接口會出現如下信息:
http://localhost:8802/product/health
{
"description": "Remote status from Eureka server",
"status": "DOWN"
}
註銷所有Eureka配置之後重新訪問http://localhost:8802/product/health獲得如下JSON
{"status":"UP"}
info接口會出現如下信息:
http://localhost:8802/product/info
{}
info默認沒有返回任何數據,需要我們配置info屬性來定義info公開的端點數據。
info.app.name=@project.artifactId@ info.app.encoding=@project.build.sourceEncoding@ info.app.java=@java.version@
再次調用接口:http://localhost:8802/product/info
{
"app": {
"name": "productsystem",
"java": "1.8.0_121",
"encoding": "UTF-8"
}
}
事實上Eureka 已經集成了對實例的健康檢查
Status 還有其他取值:DOWN、OUT_OF_SERVICE 、UNKNOWN、UP
當Eureka Server 和 Eureka Client 之間使用心跳機制來確定Eureka Client 的狀態。默認情況下,如果Server 和Client 的心跳保持正常,應用程序就會始終保持UP狀態。
如果微服務器的數據源發現了問題,根本無法工作。我們希望Eureka Server 可以獲得 Client 的SpringBoot Actuator 提供的health端點,可以在Client客戶端當中的application.properties配置如下參數:
eureka.client.healthcheck.enable=true