Dive into Eureka

Dive into Eureka

2016年 06月 25日node

因爲項目中使用了Eureka作服務註冊和發現,因此最近花了一些時間比較深刻的研究了一下Eureka,今天就來介紹一下它的實現細節。git

1. What is Eureka?

其官方文檔中對本身的定義是:github

Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers. We call this service, the Eureka Server. Eureka also comes with a Java-based client component,the Eureka Client, which makes interactions with the service much easier. The client also has a built-in load balancer that does basic round-robin load balancing.spring

簡單來講Eureka就是Netflix開源的一款提供服務註冊和發現的產品,而且提供了相應的Java客戶端。數據庫

2. Why Eureka?

那麼爲何咱們在項目中使用了Eureka呢?我大體總結了一下,有如下幾方面的緣由:緩存

  • 它提供了完整的Service Registry和Service Discovery實現
    • 首先是提供了完整的實現,而且也經受住了Netflix本身的生產環境考驗,相對使用起來會比較省心。
  • 和Spring Cloud無縫集成
    • 咱們的項目自己就使用了Spring Cloud和Spring Boot,同時Spring Cloud還有一套很是完善的開源代碼來整合Eureka,因此使用起來很是方便。
    • 另外,Eureka還支持在咱們應用自身的容器中啓動,也就是說咱們的應用啓動完以後,既充當了Eureka的角色,同時也是服務的提供者。這樣就極大的提升了服務的可用性。
  • Open Source
    • 最後一點是開源,因爲代碼是開源的,因此很是便於咱們瞭解它的實現原理和排查問題。

3. Dive into Eureka

相信你們看到這裏,已經對Eureka有了一個初步的認識,接下來咱們就來深刻了解下它吧~架構

3.1 Overview

3.1.1 Basic Architecture

architecture-overview

上圖簡要描述了Eureka的基本架構,由3個角色組成:app

  1. Eureka Server
    • 提供服務註冊和發現
  2. Service Provider
    • 服務提供方
    • 將自身服務註冊到Eureka,從而使服務消費方可以找到
  3. Service Consumer
    • 服務消費方
    • 從Eureka獲取註冊服務列表,從而可以消費服務

須要注意的是,上圖中的3個角色都是邏輯角色。在實際運行中,這幾個角色甚至能夠是同一個實例,好比在咱們項目中,Eureka Server和Service Provider就是同一個JVM進程。ide

3.1.2 More in depth

architecture-detail

上圖更進一步的展現了3個角色之間的交互。性能

  1. Service Provider會向Eureka Server作Register(服務註冊)、Renew(服務續約)、Cancel(服務下線)等操做。
  2. Eureka Server之間會作註冊服務的同步,從而保證狀態一致
  3. Service Consumer會向Eureka Server獲取註冊服務列表,並消費服務

3.2 Demo

爲了給你們一個更直觀的印象,咱們能夠經過一個簡單的demo來實際運行一下,從而對Eureka有更好的瞭解。

3.2.1 Git Repository

Git倉庫:git@github.com:nobodyiam/spring-cloud-in-action.git

這個項目使用了Spring Cloud相關類庫,包括:

3.2.2 準備工做

Demo項目使用了Spring Cloud Config作配置,因此第一步先在本地啓動Config Server。

因爲項目基於Spring Boot開發,因此直接運行com.nobodyiam.spring.cloud.in.action.config.ConfigServerApplication便可。

3.2.3 Eureka Server Demo

Eureka Server的Demo模塊名是:eureka-server

3.2.3.1 Maven依賴

eureka-server是一個基於Spring Boot的Web應用,咱們首先須要作的就是在pom中引入Spring Cloud Eureka Server的依賴。

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

3.2.3.2 啓用Eureka Server

啓用Eureka Server很是簡單,只須要加上@EnableEurekaServer便可。

@EnableEurekaServer
@SpringBootApplication
public class EurekaServiceApplication {
  public static void main(String[] args) {
    SpringApplication.run(EurekaServiceApplication.class, args);
  }
}

作完以上配置後,啓動應用,Eureka Server就開始工做了!

啓動完,打開http://localhost:8761就能看到啓動成功的畫面了。

eureka-server-init-page

3.2.4 Service Provider and Service Consumer Demo

Service Provider的Demo模塊名是:reservation-service

Service Consumer的Demo模塊名是:reservation-client

3.2.4.1 Maven依賴

reservation-servicereservation-client都是基於Spring Boot的Web應用,咱們首先須要作的就是在pom中引入Spring Cloud Eureka的依賴。

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

3.2.4.2 啓動Service Provider

啓用Service Provider很是簡單,只須要加上@EnableDiscoveryClient便可。

@EnableDiscoveryClient
@SpringBootApplication
public class ReservationServiceApplication {
  public static void main(String[] args) {
    new SpringApplicationBuilder(ReservationServiceApplication.class)
            .run(args);
  }
}

作完以上配置後,啓動應用,Server Provider就開始工做了!

啓動完,打開http://localhost:8761就能看到服務已經註冊到Eureka Server了。

eureka-server-with-service-provider

3.2.4.3 啓動Service Consumer

啓動Service Consumer其實和Service Provider同樣,由於本質上Eureka提供的客戶端是不區分Provider和Consumer的,通常狀況下,Provider同時也會是Consumer。

@EnableDiscoveryClient
@SpringBootApplication
public class ReservationClientApplication {
  @Bean
  CommandLineRunner runner(DiscoveryClient dc) {
    return args -> {
      dc.getInstances("reservation-service")
              .forEach(si -> System.out.println(String.format(
                      "Found %s %s:%s", si.getServiceId(), si.getHost(), si.getPort())));
    };
  }
  public static void main(String[] args) {
    SpringApplication.run(ReservationClientApplication.class, args);
  }
}

上述代碼中經過dc.getInstances("reservation-service")就能獲取到當前全部註冊的reservation-service服務。

3.3 Eureka Server實現細節

看了前面的demo,咱們已經初步領略到了Spring Cloud和Eureka的強大之處,經過短短几行配置就實現了服務註冊和發現!

相信你們必定想了解Eureka是如何實現的吧,因此接下來咱們繼續Dive!首先來看下Eureka Server的幾個對外接口實現。

3.3.1 Register

首先來看Register(服務註冊),這個接口會在Service Provider啓動時被調用來實現服務註冊。同時,當Service Provider的服務狀態發生變化時(如自身檢測認爲Down的時候),也會調用來更新服務狀態。

接口實現比較簡單,以下圖所示。

  1. ApplicationResource類接收Http服務請求,調用PeerAwareInstanceRegistryImplregister方法
  2. PeerAwareInstanceRegistryImpl完成服務註冊後,調用replicateToPeers向其它Eureka Server節點(Peer)作狀態同步

eureka-server-register

註冊的服務列表保存在一個嵌套的hash map中:

  • 第一層hash map的key是app name,也就是應用名字
  • 第二層hash map的key是instance name,也就是實例名字

3.2.4.2中的截圖爲例,RESERVATION-SERVICE就是app name,jason-mbp.lan:reservation-service:8000就是instance name。

Hash map定義以下:

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry =
new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();

3.3.2 Renew

Renew(服務續約)操做由Service Provider按期調用,相似於heartbeat。主要是用來告訴Eureka Server Service Provider還活着,避免服務被剔除掉。接口實現以下圖所示。

能夠看到,接口實現方式和register基本一致:首先更新自身狀態,再同步到其它Peer。

eureka-server-renew

3.3.3 Cancel

Cancel(服務下線)通常在Service Provider shut down的時候調用,用來把自身的服務從Eureka Server中刪除,以防客戶端調用不存在的服務。接口實現以下圖所示。

eureka-server-cancel

3.3.4 Fetch Registries

Fetch Registries由Service Consumer調用,用來獲取Eureka Server上註冊的服務。

爲了提升性能,服務列表在Eureka Server會緩存一份,同時每30秒更新一次。

eureka-server-fetch

3.3.5 Eviction

Eviction(失效服務剔除)用來按期在Eureka Server檢測失效的服務,檢測標準就是超過必定時間沒有Renew的服務。

默認失效時間爲90秒,也就是若是有服務超過90秒沒有向Eureka Server發起Renew請求的話,就會被當作失效服務剔除掉。

失效時間能夠經過eureka.instance.leaseExpirationDurationInSeconds進行配置,按期掃描時間能夠經過eureka.server.evictionIntervalTimerInMs進行配置。

接口實現邏輯見下圖:

eureka-server-evict

3.3.6 How Peer Replicates

在前面的Register、Renew、Cancel接口實現中,咱們看到了都會有replicateToPeers操做,這個就是用來作Peer之間的狀態同步。

經過這種方式,Service Provider只須要通知到任意一個Eureka Server後就能保證狀態會在全部的Eureka Server中獲得更新。

具體實現方式其實很簡單,就是接收到Service Provider請求的Eureka Server,把請求再次轉發到其它的Eureka Server,調用一樣的接口,傳入一樣的參數,除了會在header中標記isReplication=true,從而避免重複的replicate。

3.3.7 How Peer Nodes are Discovered

那你們可能會有疑問,Eureka Server是怎麼知道有多少Peer的呢?

Eureka Server在啓動後會調用EurekaClientConfig.getEurekaServerServiceUrls來獲取全部的Peer節點,而且會按期更新。按期更新頻率能夠經過eureka.server.peerEurekaNodesUpdateIntervalMs配置。

這個方法的默認實現是從配置文件讀取,因此若是Eureka Server節點相對固定的話,能夠經過在配置文件中配置來實現。

若是但願能更靈活的控制Eureka Server節點,好比動態擴容/縮容,那麼能夠override getEurekaServerServiceUrls方法,提供本身的實現,好比咱們的項目中會經過數據庫讀取Eureka Server列表。

具體實現以下圖所示:

eureka-server-peer-discovery

3.3.8 How New Peer Initializes

最後再來看一下一個新的Eureka Server節點加進來,或者Eureka Server重啓後,如何來作初始化,從而可以正常提供服務。

具體實現以下圖所示,簡而言之就是啓動時把本身當作是Service Consumer從其它Peer Eureka獲取全部服務的註冊信息。而後對每一個服務,在本身這裏執行Register,isReplication=true,從而完成初始化。

eureka-server-peer-init

3.4 Service Provider實現細節

如今來看下Service Provider的實現細節,主要就是Register、Renew、Cancel這3個操做。

3.4.1 Register

Service Provider要對外提供服務,一個很重要的步驟就是把本身註冊到Eureka Server上。

這部分的實現比較簡單,只須要在啓動時和實例狀態變化時調用Eureka Server的接口註冊便可。須要注意的是,須要確保配置eureka.client.registerWithEureka=true。

service-provider-register

3.4.2 Renew

Renew操做會在Service Provider端按期發起,用來通知Eureka Server本身還活着。 這裏有兩個比較重要的配置須要注意一下:

  1. eureka.instance.leaseRenewalIntervalInSeconds

    Renew頻率。默認是30秒,也就是每30秒會向Eureka Server發起Renew操做。

  2. eureka.instance.leaseExpirationDurationInSeconds

    服務失效時間。默認是90秒,也就是若是Eureka Server在90秒內沒有接收到來自Service Provider的Renew操做,就會把Service Provider剔除。

具體實現以下:

service-provider-renew

3.4.3 Cancel

在Service Provider服務shut down的時候,須要及時通知Eureka Server把本身剔除,從而避免客戶端調用已經下線的服務。

邏輯自己比較簡單,經過對方法標記@PreDestroy,從而在服務shut down的時候會被觸發。

service-provider-cancel

3.4.4 How Eureka Servers are Discovered

這裏你們疑問又來了,Service Provider是怎麼知道Eureka Server的地址呢?

其實這部分的主體邏輯和3.3.7 How Peer Nodes are Discovered幾乎是同樣的。

也是默認從配置文件讀取,若是須要更靈活的控制,能夠經過override getEurekaServerServiceUrls方法來提供本身的實現。按期更新頻率能夠經過eureka.client.eurekaServiceUrlPollIntervalSeconds配置。

client-discover-eureka-server

3.5 Service Consumer實現細節

Service Consumer這塊的實現相對就簡單一些,由於它只涉及到從Eureka Server獲取服務列表和更新服務列表。

3.5.1 Fetch Service Registries

Service Consumer在啓動時會從Eureka Server獲取全部服務列表,並在本地緩存。須要注意的是,須要確保配置eureka.client.shouldFetchRegistry=true。

service-consumer-fetch-registries

3.5.2 Update Service Registries

因爲在本地有一份緩存,因此須要按期更新,按期更新頻率能夠經過eureka.client.registryFetchIntervalSeconds配置。

service-consumer-update-registries

3.5.3 How Eureka Servers are Discovered

Service Consumer和Service Provider同樣,也有一個如何知道Eureka Server地址的問題。

其實因爲Service Consumer和Service Provider本質上使用的是同一個Eureka客戶端,因此這部分邏輯是同樣的,這裏就再也不贅述了。詳細信息見3.4.4節

4. Summary

本文主要介紹了Eureka的實現思路,經過深刻了解Eureka Server、Service Provider、Service Consumer的實現,咱們清晰地看到了服務註冊、發現的一系列過程和實現方式。

相信對正在使用Eureka的同窗會有一些幫助,同時但願對暫不使用Eureka的同窗也能有必定的啓發,畢竟服務註冊、發現仍是比較基礎和通用的,瞭解了實現方式後,在使用上應該能更駕輕就熟一些吧~

相關文章
相關標籤/搜索