微服務之Eureka(二)服務中心互相註冊-Ribbon的結合使用

姑蘇冷發表於2020-03-02

上面內容介紹了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,隨機演算法。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

相關文章