上面內容介紹了Eureka並寫了demo案例,這篇繼續深入研究一下它。
一:高可用的Eureka Server
Eureka Server 即服務的註冊中心,在上篇的案例中,我們只有一個Eureka Server ,事實上EurekaServer也可以是一個集羣,形成高可用的Eureka中心。
服務同步:
多個Eureka Server之間也會互相註冊成服務,當服務提供者註冊到Eureka Server集羣中的某個節點時,該節點會把服務的信息同步到集羣中的每個節點,從而實現數據同步。因此,無論客戶端訪問到EurekaServer集羣中的任一個節點,都可以獲取到完整的服務列表信息。
我們接着上篇博客的內容(博文地址)來演示註冊中心集羣相互註冊的場景,當然我們這邊並不是真正的集羣,只是模擬,效果是一樣的。
1:啓動一個Eureka Server服務,服務的配置如下:
spring:
application:
name: eureka-server
server:
port: 8010
eureka:
client:
service-url:
defaultZone: http://localhost:8009/eureka/
instance:
prefer-ip-address: true
instance-id: 127.0.0.1:8010
注意!這裏和之前比做了一些改動,就是註冊的地址變了(端口號變了),defaultZone的屬性變了,因爲我們這一次不是自己註冊自己而是向另外一個註冊中心註冊自己。改好之後我們啓動,會一直報如下錯,這是正常的,因爲我們8009端口 的註冊中心還沒啓動,當前註冊中心一直在嘗試註冊不成功。
com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
************************************
com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused: connect
******************************
2:我們在idea中再配置一個啓動服務。注意我們只是再配置一個啓動服務,但是代碼我們還是使用的同一個eurekaserver模塊的代碼。
只是把圖中的Eureka Server註冊中心配置兩個啓動服務。
1)在idea中右上角找到如下內容,點擊Edit Configurations.....。
2)點進去之後我們發現這裏顯示了我本地配置了四個啓動服務,EurekaApplication-1是我剛纔啓動的服務。
複製之後命名爲EurekaApplication-2,看下,啓動類配置都是同一個:
我們剛纔用EurekaApplication-1啓動了一個服務,我們把配置文件改一下,再用EurekaApplication-2啓動一下:
spring:
application:
name: eureka-server
server:
port: 8009
eureka:
client:
service-url:
defaultZone: http://localhost:8010/eureka/
instance:
prefer-ip-address: true
instance-id: 127.0.0.1:8009
我們訪問一下服務:
3:啓動了兩個Eureka註冊中心,我們就需要把Eureka客戶端註冊到這裏,註冊多個EurekaServer需要用逗號隔開。
生產者producer模塊配置如下:
eureka:
client:
service-url:
defaultZone: http://localhost:8010/eureka/,http://localhost:8009/eureka/
instance:
prefer-ip-address: true
instance-id: 127.0.0.1:8011
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/db_1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
application:
name: eureka-service.producer
server:
port: 8011
mybatis:
type-aliases-package: com.eureka.entity
消費者consumer模塊的配置如下:
server:
port: 8012
eureka:
client:
service-url:
defaultZone: http://localhost:8010/eureka/,http://localhost:8009/eureka/
instance:
prefer-ip-address: true
instance-id: 127.0.0.1:8012
spring:
application:
name: eureka-service.consumer
我們兩個註冊中心之間會同步數據,原則上來說我們客戶端也可以只向一個註冊中心(EurekaServer)註冊即可,但是我們並不能保證我們只註冊的那一個EurekaServer還活着,所以最好是每個EurekaServer都配置上去。這樣即時某個服務掛了也沒事。
同樣的,如果我們的註冊中心(EurekaServer)超過了兩個,這三個註冊中心也需要都互相註冊。
上面配置完,我請求我們consumer是能夠正常訪問的,即時我們停掉註冊中心(EurekaServer)中的其中一個服務,也是可以正常訪問的。假如我們停掉EurekaApplication-2這個啓動服務。這個EurekaApplication-1就會報錯,因爲註冊找不到服務了。
但是這個時候我們訪問consumer,還是能夠得到結果的。
二:關於時長配置屬性
服務註冊:
從上篇demo實踐中,可以看出,無論註冊中心還是服務的消費者或生產者都會在本身服務啓動的時候去註冊中心註冊自己,包括註冊中心自己。
這是因爲服務在啓動時候都會檢查一個屬性配置:
eureka:
client:
register-with-eureka: true
如果這個屬性設置爲true,服務註冊中心也會將自己作爲客戶端來嘗試註冊自己,爲true(默認)時自動生效。我們從
所以一般情況下,當我們設置服務爲註冊中心時,需要關閉它,如果要做集羣的話,需要在做註冊中心集羣的時候,register-with-eureka
必須打開,因爲需要進行相互註冊,不然副本無法可用。就像我們上面的啓動兩個註冊中心一樣,默認都是會註冊自己,頁面上顯示了兩個EurekaServer服務。
服務續約:
在註冊完成之後,服務提供者會維持一個心跳(定時向EurekaServer發起Rest請求),告訴EurekaServer自己還活着,我們稱這爲服務的續約(renew):
改變續約行爲可以通過下面兩個屬性:
eureka:
instance:
#最小的過期時長,多久沒有發送消息,就認爲這臺服務掛了
lease-expiration-duration-in-seconds: 90
#心跳週期,多久向服務發起請求告知是否存活的狀態,不易過高,不然影響性能
lease-renewal-interval-in-seconds: 30
服務消費者模塊屬性:
在服務消費者啓動後,會向服務註冊中心請求一份服務清單,該清單記錄了已經註冊到服務中心的服務實例。該請求動作不會僅限於啓動的時候,因爲消費者需要訪問正確的、健康的服務實例,因此會定時發送請求。間隔時間通過配置參數:
eureka:
client:
#要不要拉取服務列表,默認爲true
fetch-registry: true
# 多久拉取一次
registry-fetch-interval-seconds: 3
三:失效剔除和自我保護
服務下線:
當服務進行正常關閉操作時,它會觸發一個服務下線的REST請求給EurekaServer,告訴服務註冊中心:"我準備下線了"
服務中心接受到請求之後,將該服務置爲下線狀態。
失效剔除:
有時我們的服務可能由於某種原因不能正常工作比如:代碼內部異常,網絡故障等。而服務註冊中心並沒有收到服務下線的通知。相對於服務提供者的“服務續約”操作,服務註冊中心在啓動的時候會創建一個定時任務,默認每隔一段時間(60s)將清單中超時(默認90s)沒有續約的服務剔除,這個操作稱爲失效剔除。可以在服務註冊中心配置如下屬性對剔除週期進行修改,單位是毫秒:
eureka:
server:
eviction-interval-timer-in-ms: 60000
自我保護:
我們關停一個服務,就會在Eureka面板看到一條警告:
這就觸發了Eureka的自我保護機制,當服務未按時進行心跳續約時,Eureka會統計服務實例最近15分鐘心跳續約的比例是否低於85%,在生產環境下,因爲網絡延遲等原因,心跳失敗實例比例很有可能超標,但是此時就把服務剔除並不妥當,因爲服務可能沒有宕機。Eureka在這段時間內不會剔除任何服務實例。直到網絡回覆正常。生產環境下這很有效,保證了大多數服務依然可用,不過也有可能獲取到失敗的服務實例,因此服務調用者必須做好失敗容錯。
可以通過如下屬性來關閉自我保護:默認爲true開啓。一般我們也讓它開啓。
eureka:
server:
enable-self-preservation: false
四:負載均衡Ribbon使用
在上一篇實踐中,我們啓動一個eureka-service.produce,然後使用DiscoveryClient來獲取服務實例信息,然後獲取ip和端口號來訪問。
但是在實際環境中,我們往往啓動很多個eureka-service.produce集羣。此時我們獲取的服務列表中就會有多個,到底該訪問哪一個呢?這個時候就要用到負載均衡的算法了,在Eureka中已經爲我集成了負載均衡組件:Ribbon,簡單修改代碼即可。
1: Ribbon介紹:
是Netflix發佈的負載均衡器,它有助於控制HTTP和TCP客戶端的行爲。爲Ribbon配置服務提供者地址列表後,Ribbon就可以基於某種負載均衡算法,自動地幫助服務消費者去請求。Ribbon默認爲我們提供了很多負載均衡算法,例如輪詢,隨機等。當然,也可以自定義。
Ribbon和Nginx區別:
Nginx服務器負載均衡: 客戶端所有請求都會交給nginx,然後由nginx實現轉發請求,即負載均衡是由服務端實現。nginx服務負載均衡適合於針對服務器端,比如:tomcat、jetty等
Ribbon本地負載均衡 :在調用接口的時候、會在eureka註冊中心上獲取註冊信息服務列表,獲取到之後,緩存在jvm本地,使用本地實現rpc遠程技術進行調用,即是客戶端實現負載均衡。ribbon本地負載均衡適合微服務rpc遠程調用,比如:dubbo,springcloud等
2:Ribbon的使用
在使用之前,我把服務的提供方改一下端口再啓動一個服務,這樣就有兩個可以提供服務的ip地址:
http://localhost:8015/getProducts,http://localhost:8011/getProducts
我們也可以使用命令行改變端口號啓動:-Dserver.port=8015 它可以覆蓋配置文件裏面的配置。
1)我們在上次實例中使用Ribbon,首先我們在我們消費者服務模塊引入依賴:但是Ribbon是和Eureka配合使用的,所以Eureka客戶端的依賴包裏已經有Rbbon的依賴了。
2)在RestTemplate配置類上加上註解: @LoadBalanced
加上這個註解之後,Ribbon就會攔截http請求,對它進行處理起到負載均衡的效果。
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
3)把我們的請求路徑改爲服務id。
@GetMapping("/getAllProduct")
public String getAllProduct(){
//使用Ribbon請求第一種方式: 我們把地址換成服務id即可
String uri="http://EUREKA-SERVICE.PRODUCER/getProducts";
String vos = restTemplate.getForObject(uri, String.class);
return vos;
}
訪問一下還是有結果的:
這種方式,Ribbon底層會用一個攔截器LoadBalancerInterceptor對http請求進行攔截:處理過程如下:
這一次我們看到根據服務id獲取的ip地址爲:169.254.58.139:8011,並不是127.0.0.1,這是因爲它直接取的是我本機在局域網中的地址:
我第二次請求的時候地址就會變化:
默認的算法是輪詢的。
我們可以通過配置來改變獲取服務的算法:
EUREKA-SERVICE.PRODUCER: #服務的id
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #算法的實現類
Ribbon的算法都是由IRule接口而來的,它的實現類有下面幾種,配置不同的算法實現類實現不同的算法。默認的算法是RandRobinRule:輪詢算法。
我們按如下配置啓動之後,debug可以看到選擇服務的方法已經使用了RandomRule,隨機算法。