服務治理: Spring Cloud Eureka

c旋兒發表於2019-05-28

服務治理: Spring Cloud Eureka

一、簡介

Spring cloud eurekaSpring cloud netfilx中的一部分,它基於Netflix Eureka做了二次封裝,主要職責完成Eureka 中的服務治理功能

本篇主要探討如下:

  • 服務治理和Eureka簡介
  • 構建服務註冊中心
  • 服務註冊與服務發現
  • Eureka 基礎架構
  • Eureka 的服務治理機制
  • Eureka 的配置

二、 功能概述

服務治理

​ 服務治理可以是說微服務架構中最為核心的基礎模組,它主要用來實現各個微服務實現的自動化註冊與發現。在開始的時候微服務系統的服務可能並不多,我們需要一些配置來完成服務的呼叫。

  • 服務註冊: 在服務治理框架中,通常會構建一個註冊中心,由各個服務提供者來向註冊中心登記並提供服務,將主機與埠號、版本號、通訊協議等一些附加資訊告知註冊中心,註冊中心按照服務名分類組織服務清單。

    服務名 位置
    服務A 192.168.1.101:8000, 192.168.1.102:8000
    服務B 192.168.1.103:9000,192.168.1.104:9000,192.168.1.105:9000

    比如我們有兩個提供服務A 的程式分別位於192.168.1.101:8000, 192.168.1.102:8000 上,另外還有三個提供服務B 的程式分別位於192.168.1.103:9000,192.168.1.104:9000,192.168.1.105:9000 程式上,那麼你向服務中心註冊過後,服務中心就會有一個這樣的服務列表,服務中心向各個註冊的服務傳送心跳機制,來檢驗服務是否可用,若不可用就會把服務剔除,來達到故障排除的效果。

  • 服務發現: 由於在服務治理框架下運作,服務間的呼叫不再通過指定的Ip:埠號這種方式來實現 ,而是向服務名發起請求實現。所以,在服務呼叫方在呼叫服務提供方介面的時候,並不知道具體服務的位置。因此,服務呼叫方需要向服務中心獲取服務列表,以實現對具體服務的訪問。

    比如一個服務呼叫者C想要獲取服務A的ip來完成介面的呼叫,那麼他首先應該去服務中心發起諮詢你服務的請求,由註冊中心的服務列表將A的位置傳送給呼叫者C,如果按照上面服務A地址的話,那麼呼叫者C會由兩個服務A的地址來提供服務,當服務C需要呼叫的時候,便從服務A中的清單中採用輪詢的方式取出一個位置來服務呼叫,這個過程也被稱為負載均衡。

Netflix Eureka

  • Eureka是Netflix開發的服務發現框架,本身是一個基於REST的服務,主要用於定位執行在AWS域中的中間層服務,以達到負載均衡和中間層服務故障轉移的目的。SpringCloud將它整合在其子專案spring-cloud-netflix中,以實現SpringCloud的服務發現功能。
  • Eureka包含兩個元件: Eureka ServerEureka Client
  • Eureka Server 簡稱Eureka 服務端, 主要提供服務註冊功能,其實也就相當於是註冊中心,和其他服務註冊中心一樣,提供高可用的配置,同時也支援叢集部署,當叢集中某一個節點發生故障時,那麼Eureka就會進入自我保護模式,它允許故障的節點繼續提供服務的發現與註冊,當故障分片恢復執行時,叢集中的其他分片會把他們的狀態再同步回來。
  • Eureka Client:簡稱Eureka 客戶端,主要處理服務的註冊與發現。客戶端通過註解和引數配置的方式,Eureka 客戶端向註冊中心註冊自身的服務並週期性的傳送心跳機制來更新服務租約。同時,它也能從服務端查詢當前註冊的服務資訊並把它們快取到本地並週期性地重新整理服務狀態。

搭建服務註冊中心

Spring Cloud Eureka 是採用SpringBoot 進行專案的快速搭建的,如果不太瞭解SpringBoot的話,可以瞭解一下SpringBoot 入門例項。

  • 首先建立SpringBoot工程,命名為Eureka-server,也就是Eureka服務端,建立完成後在pom.xml檔案中增加如下maven依賴,完整的檔案如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.eureka.server</groupId>
    <artifactId>eureka-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka-server</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Brixton.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>
  • 在SpringBoot啟動類,也就是@SpringBootApplication修飾的主方法中加入如下註解@EnableEurekaServer
    
        @EnableEurekaServer
    @SpringBootApplication
    public class EurekaServerApplication {

        public static void main(String[] args) {
            SpringApplication.run(EurekaServerApplication.class, args);
        }

    }

加入這個註解也就標識著這是一個Eureka的服務端,可以啟動服務了,但是啟動服務會報錯,因為你沒有新增註冊中心的相關配置。

  • application.properties檔案中加入如下內容
server.port=8000

eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

server.port 就代表著註冊中心的埠號

eureka.client.service-url.defaultZone :eureka客戶端預設服務url

eureka.client.register-with-eureka : 表示註冊中心是否向其他註冊中心註冊自己,單節點註冊中心不需要,設定為false

eureka.client.fetch-registry: 表示註冊中心是否主動去檢索服務,並不需要檢索服務,設定為false

其他配置:

# 專案contextPath,一般在正式釋出版本中,我們不配置
# 避免加上更目錄:Cannot execute request on any known server
# 加上根目錄也需要在註冊地址上加入根
 server.context-path=/eureka81
# 錯誤頁,指定發生錯誤時,跳轉的URL。請檢視BasicErrorController原始碼便知
 server.error.path=/error
# 通過spring.application.name屬性,我們可以指定微服務的名稱後續在呼叫的時候只需要使用該名稱就可以進行服務的訪問。
 spring.application.name=eureka-server
# eureka是預設使用hostname進行註冊,可通過一下項自動獲取註冊服務IP或者直接通過eureka.instance.ip-address指定IP
# eureka.instance.prefer-ip-address=true
# SpringBoot 在啟動的時候會讀配置檔案,會把prefer-ip-address 預設轉換為preferIpAddress駝峰命名
eureka.instance.preferIpAddress=true

# 設為false,關閉自我保護
eureka.server.enable-self-preservation=false
# 清理間隔(單位毫秒,預設是60*1000
eureka.server.eviction-interval-timer-in-ms=6000

# 開啟健康檢查(需要spring-boot-starter-actuator依賴)
eureka.client.healthcheck.enabled=false
# 續約更新時間間隔(預設30秒)
eureka.instance.lease-renewal-interval-in-seconds=10    
# 續約到期時間(預設90秒)
eureka.instance.lease-expiration-duration-in-seconds=30

沒有加入 eureka.instance.preferIpAddress=true 之前,預設本地為註冊中心

服務治理: Spring Cloud Eureka

加入 eureka.instance.preferIpAddress=true 之後,圈出來的ip即為eureka.client.service-url.defaultZone指定的 ip。

服務治理: Spring Cloud Eureka

在完成了上述配置之後,應用程式啟動並訪問http://localhost:1111/ 可以看到如下圖所示的資訊版,其中Instances curently registered with Eureka 是空的,表明還沒有任何服務提供者提供服務。

註冊服務提供者

在完成了上述搭建之後,接下來我們嘗試將一個既有的SpringBoot應用加入Eureka服務治理體系去。

使用上一小節的快速入門工程進行改造,將其作為一個微服務應用向服務註冊中心釋出註冊自己

pom.xml配置如下:

        <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Brixton.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

        <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
  • 配置完pom.xml,我們需要在啟動類上加入@EnableDiscoverClient註解,用於開啟eureka-client客戶端
  • application.properties中加入如下內容
    # 這個名字就是Eureka註冊中新的例項名稱
    spring.application.name=server-provider
    # 向註冊中心註冊自己
    eureka.client.service-url.defaultZone=http://localhost:1111/eureka/
  • 配置完上面兩個之後,在package資料夾下新建HelloController類,具體程式碼如下
  @RestController
  public class HelloController {

      private final Logger log = LoggerFactory.getLogger(HelloController.class);

      @Resource
      private DiscoveryClient discoveryClient;

      @RequestMapping(value = "hello", method = RequestMethod.GET)
      public String hello(){
          ServiceInstance instance = discoveryClient.getLocalServiceInstance();
          log.info("instance.host = " + instance.getHost() + " instance.service = " +  instance.getServiceId()
                  + " instance.port = " + instance.getPort());
          return "Hello World";
      }
  }
  • 啟動服務提供者,啟動完成後,會出現如下表示啟動成功。

服務治理: Spring Cloud Eureka

  • 訪問http://localhost:1111/ ,主頁上顯示eureka-provider註冊到了註冊中心

服務治理: Spring Cloud Eureka

此處的Status 中的內容也就包括上面配置的spring.application.name=server-provider

  • 在主頁訪問 http://localhost:8080/hello ,發現頁面上 輸出了Hello World,控制檯列印出來了

    c.s.provider.controller.HelloController  : instance.host = macliu instance.service = server-provider instance.port = 8080

    注意事項

  1. 上面註冊到註冊中心的圖,你會發現這樣一行紅色的文字

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.

這是Eureka的一種自我保護機制,Eureka Server在執行期間,會統計心跳失敗的比例在15分鐘之內是否低於85%,如果出現低於的情況(在單機除錯的時候很容易滿足,實際在生產環境上通常是由於網路不穩定導致),Eureka Server會將當前的例項註冊資訊保護起來,同時提示這個警告。

Eureka server和client之間每隔30秒會進行一次心跳通訊,告訴server,client還活著

  1. 把上面的server-provider服務停止之後,會出現如下狀態

服務治理: Spring Cloud Eureka

這個表示server-provider 已經標記為下線,也就是 DOWN 狀態,再次重新上線後,發現Status又變為了UP狀態。

  1. 把上面的配置檔案中自我保護功能關閉後,出現如下狀態

服務治理: Spring Cloud Eureka

高可用配置中心

在微服務架構這樣的分散式環境中,需要充分考慮到發生故障的情況,所以在生產環境中必須對各個元件進行高可用部署,對於微服務是如此,對於註冊中心也一樣。

Eureka Server的設計就充分考慮了高可用問題,在Eureka的服務治理體系中,所有的節點既是服務提供方,也是服務的消費者,服務註冊中心也不例外,不同的註冊中心在向其他註冊中心提供節點列表的時候,也在向其他註冊中心獲取節點列表。

高可用的配置中心就是向其他註冊中心註冊自己,同時把服務列表提供給其他註冊中心,從而達到註冊中心列表同步,達到高可用的效果。通過下面兩個配置來實現

    eureka.client.register-with-eureka=true
    eureka.client.fetch-registry=true

下面就在單節點的基礎之上建立一下高可用的配置中心(雙節點註冊中心)

  • 首先,建立兩個配置檔案,分別是application-peer1.propertiesapplication-peer2.properties,內容分別如下

    • application-peer1.properties

      spring.application.name=eureka-server
      server.port=1111
      
      eureka.instance.hostname=peer1
      eureka.client.register-with-eureka=true
      eureka.client.fetch-registry=true
      
      eureka.client.service-url.defaultZone=http://peer2:1112/eureka/
    • application-peer2.properties

      spring.application.name=eureka-server
      server.port=1112
      
      eureka.instance.hostname=peer2
      eureka.client.register-with-eureka=true
      eureka.client.fetch-registry=true
      
      eureka.client.service-url.defaultZone=http://peer1:1111/eureka/
      
  • 在本地修改配置檔案/etc/hosts ,Windows下面是C:\Winows\System32\drivers\etc\hosts。

    新增如下內容

    127.0.0.1 peerl 
    127.0.0.1 peer2

    如下:

服務治理: Spring Cloud Eureka

  • 首先在idea 或者eclipse 使用mvn clean 和 mvn install命令,會直接打包,這裡注意,一定要在pom.xml中配置如下,否則使用java -jar會報沒有主清單屬性的錯誤。

    <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
    </build>
  • 打包完成後,切換到eureka-server專案,再切換到target目錄下,此時有mvn install 的jar包,使用java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer2 兩個命令,啟動兩個例程。起來過後分別訪問 http://localhost:peer1/eureka/ 和 http://localhost:peer2/eureka/ 主頁,發現對應的註冊中心分別註冊進去了,而且分片也處於可用分片狀態。

服務治理: Spring Cloud Eureka

服務治理: Spring Cloud Eureka

到現在為止,我們已經讓兩個註冊中心分別註冊各自的服務了,還記得上面還有一個server-provider服務嗎?我們也讓server-provider分別註冊到這兩個註冊中心。

server-provider中修改對應的配置檔案

eureka.client.service-url.defaultZone=http://peer1:1111/eureka, http://peer2:1112/eureka/

啟動程式,發現http://localhost:1111/ 和 http://localhost:1112/ 中都註冊了server-provider服務

服務治理: Spring Cloud Eureka

訪問http://localhost:8080/hello,你會發現頁面上顯示出來hello world,斷開其中的任意一個註冊中心,hello world也能夠顯示出來。也就是說,server-provider 分別對兩個註冊中心分別註冊了各自的服務,由兩個註冊中心以輪詢的方式提供服務。斷開其中一個註冊中心,還有另外一個註冊中心可以提供服務,這也是Eureka 高可用的體現。

注意事項

  1. 如果application-peer1.propertiesapplication-peer2.properties中的eureka.instance.hostname與 本地hosts檔案中的名稱不一致的話,那麼註冊中心啟動後,會使分片處於不可用的狀態, spring.application.name 表示的是例項的名稱,也就是如下的地方

服務治理: Spring Cloud Eureka

  1. server-provider註冊進來的時候,高可用配置的註冊中心會以輪詢的方式提供服務,每次提供服務是哪個註冊中心是不可預知的。

  2. 如我們不想使用主機名來定義註冊中心的地址,也可以使用IP地址的形式, 但是需要在配置檔案中增加配置引數eureka.instance.prefer-ip-address=true, 該值預設為false。

服務發現與消費

通過上述的內容介紹與實踐,我們已經搭建起來微服務架構中的核心元件— 服務註冊中心(包括單節點模式和高可用模式)。並用server-provider註冊雙節點,在頁面上發起一個url請求時,註冊中心找到server-provider,並有兩個節點以輪詢的方式提供服務。

下面就來構建一個消費者,它主要完成兩個目標:發現服務消費服務。其中,服務發現的任務由Eureka客戶端完成,消費服務的任務由Ribbon來完成。

先來認識一下什麼是Ribbon:Ribbon是客戶端負載均衡器,可以讓您對HTTP和TCP客戶端的行為進行控制。 Feign已經使用了Ribbon,如果你使用了@FeignClient,那麼Ribbon也適用。

Ribbon可以在通過客戶端中配置的ribbonServerList服務端列表去輪詢訪問以達到負載均衡的效果。當ribbonEureka聯合使用時,Ribbon的服務例項清單RibbonServerList會被DiscoveryEnabledNIWSServerList重寫,擴充套件成從Eureka註冊中心中獲取服務端列表。同時它也會用NIWSDiscoveryPing來取代Ping,它將職責委託給Eureka來確定服務端是否啟動,我們目前不細緻探討Ribbon的細節問題。

下面通過一個簡單的例項,看看Eureka的服務治理體系下如何實現服務的發現與消費。

  • 首先,先做一些準備工作,啟動之前實現的服務註冊中心eureka-server以及server-provider服務,為了實現ribbon的負載均衡功能,我們通過java -jar命令列的方式來啟動兩個不同埠的server-provider
    • 啟動一個eureka-server即可
    • 使用java -jar service-provider-0.0.1-SNAPSHOT.jar --server.port=8081java -jar service-provider-0.0.1-SNAPSHOT.jar --server.port=8082 來啟動兩個server-provider 程式
  • 啟動完成後,可見註冊中心註冊了兩個server-provider 例項

服務治理: Spring Cloud Eureka

  • 新建立一個SpringBoot 工程,命名為ribbon-consumer,相較於之前pom.xml,我們新增了spring-cloud-starter-ribbon
        
        <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Brixton.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
  • 建立完pom.xml,在ribbon-consumer 啟動類加上@EnableDiscoveryClient註解,讓該註解註冊為Eureka客戶端,以獲得服務發現的能力,同時,建立RestTemplate物件,加上@LoadBalance註解開啟負載均衡。
    @EnableDiscoveryClient
  @SpringBootApplication
  public class RibbonConsumerApplication {

      @Bean
      @LoadBalanced
      RestTemplate restTemplate(){
          return new RestTemplate();
      }

      public static void main(String[] args) {
          SpringApplication.run(RibbonConsumerApplication.class, args);
      }

  }
  • 在src目錄下新建一個RibbonController類,注入@RestTemplate,構造一個方法來呼叫server-provider中的/hello 方法。程式碼如下
        @RestController
    public class RibbonController {

        @Autowired
        RestTemplate restTemplate;

        @RequestMapping(value = "/ribbon-consumer",method = RequestMethod.GET)
        public String helloConsumer(){
            return restTemplate.getForEntity("http://server-provider/hello",String.class).getBody();
        }
    }

在helloConsumer方法上面採用Restful 風格的編碼方式,這個方法遠端呼叫了server-provider中的hello方法,在這裡不像是http://ip:埠號這種書寫方式,而是直接採用 服務名/方法名來直接呼叫方法,因為你不知道具體的方法在哪個ip的機器上,需要由Eureka進行呼叫,這樣消費者就不用關心由誰提供了服務,只要提供了服務即可,這也是物件導向方法的一種體現,同時也能很好的解耦。

  • application.properties 配置如下
spring.application.name=ribbon-consumer
server.port=9000

eureka.client.service-url.defaultZone=http://localhost:1111/eureka/

因為ribbon-consumer需要由客戶端來主動呼叫方法,所以需要提供例項名稱,埠號,並在註冊中心註冊ribbon-consumer服務

  • 啟動服務,訪問Eureka主頁發現Ribbon-consumer的服務也註冊進來了。

服務治理: Spring Cloud Eureka

INFO 29397 --- [nio-9000-exec-1] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client server-provider initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=server-provider,current list of Servers=[macliu:8082, macliu:8081],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:2;   Active connections count: 0;    Circuit breaker tripped count: 0;   Active connections per server: 0.0;]
},Server stats: [[Server:macliu:8082;   Zone:defaultZone;   Total Requests:0;   Successive connection failure:0;    Total blackout seconds:0;   Last connection made:Thu Jan 01 08:00:00 CST 1970;  First connection made: Thu Jan 01 08:00:00 CST 1970;    Active Connections:0;   total failure count in last (1000) msecs:0; average resp time:0.0;  90 percentile resp time:0.0;    95 percentile resp time:0.0;    min resp time:0.0;  max resp time:0.0;  stddev resp time:0.0]
, [Server:macliu:8081;  Zone:defaultZone;   Total Requests:0;   Successive connection failure:0;    Total blackout seconds:0;   Last connection made:Thu Jan 01 08:00:00 CST 1970;  First connection made: Thu Jan 01 08:00:00 CST 1970;    Active Connections:0;   total failure count in last (1000) msecs:0; average resp time:0.0;  90 percentile resp time:0.0;    95 percentile resp time:0.0;    min resp time:0.0;  max resp time:0.0;  stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@3dc2c2eb

再嘗試重新整理幾次url,因為我們實在後臺啟動的程式,在終端會看到如下的資訊

 INFO 28929 --- [nio-8082-exec-3] c.s.provider.controller.HelloController  : instance.host = macliuinstance.service = server-providerinstance.port = 8082

因為開了兩個終端,一個是8081埠,一個是8082埠,多重新整理幾次頁面後,會發現終端在迴圈輸出上面的資訊,來判斷使用ribbon 實現了負載均衡。

歡迎關注Java建設者:一起學習交流
服務治理: Spring Cloud Eureka

相關文章