服務發現和註冊和Eureka

Spring Cloud和雲端計算沒有關係,只是一個基於Spring Boot的快速構建分散式系統的工具集。

 

一 Spring Cloud特點

# 約定優於配置

# 開箱即用,快速啟動

# 適用於各種環境,可以部署在PC server或者 雲環境

# 輕量級的元件

# 元件的支援很豐富,功能齊全

# 選型中立

 

二 服務提供者和服務消費者

 

三 服務發現和註冊

為什麼需要服務註冊與發現

# 服務重啟或者升級後IP地址變化

# 水平伸縮後服務例項的變化

# 同一個節點執行多個服務

所以需要一種序號產生器制,幫助我們去獲取響應。

 

核心機制:

將例項的資訊註冊到註冊中心

呼叫者通過註冊中心查詢服務

呼叫者獲取服務例項列表

呼叫者通過負載均衡通訊

 

3.1 基本流程

首先:服務消費者和服務註冊者向服務發現元件註冊

其次:服務消費者要呼叫的時候會從服務發現元件中進行查詢

 

 

需求:

# 每一個服務例項都會在啟動的時候通過HTTP/REST或者Thrift等方式釋出遠端API

# 服務端例項的具體數量及位置會發生動態變化

# 虛擬機器與容器通常會被分配動態IP地址

 

 

3.2 服務發現元件的功能

# 服務登錄檔: 是一個記錄當前可用服務例項的網路資訊的資料庫,是服務發現機制的核心。服務登錄檔提供查詢API和管理API,使用API 獲得可用的服務例項,使用管理API實現註冊和登出。

# 服務註冊

# 健康檢查

 

3.3 服務發現的方式

3.3.1 客戶端發現

它的主要特點是客戶端決定服務例項的網路位置,並且對請求進行負載均衡。客戶端查詢服務登錄檔(可用服務例項資料庫),使用負載均衡演算法選擇一個例項,併發出請求。典型代表Eureka或者ZK

客戶端發現模式的優缺點

優點:

不需要很多的網路跳轉

缺點:

客戶端和服務登錄檔耦合

需要為應用程式每一種程式語言、框架等建立客戶端發現邏輯,比如 Netflix Prana就為非JVM客戶端提供一套基於HTTP代理服務發現方案

 

# 伺服器端發現

向某一服務傳送請求,客戶端會通過向執行位置已知的路由器或者負載均衡器傳送請求。他們會查詢服務登錄檔,並向可用的服務例項轉發該請求。典型代表Consul Nginx

伺服器端發現模式優缺點:

優點:

客戶端無需實現發現功能,只需要向路由器或者負載均衡器傳送請求即可

缺點:

除非成為雲環境的一部分,否則該路由機制必須作為另一系統元件進行安裝與配置。為實現可用性和一定的接入能力,還需要為其配置一定數量的副本。

相較於客戶端發現,伺服器端發現機制需要更多的網路跳轉。

 

3.4 服務發現元件Eureka

3.4.1 什麼是Eureka

 

Eureka是Netflix開發的服務發現框架,本身是一個基於REST的服務,主要用於定位執行在AWS域中的中間層服務,以達到負載均衡和中間層服務故障轉移的目的。Spring Cloud將它整合在其他子專案spring-cloud-netflix中,以實現spring cloud服務發現功能。

 

3.4.2 Eureka原理

先需要明白AWS幾個概念:

Region: AWS雲服務在全球不同的地方都有資料中心,比如北美、南美和歐洲亞洲等。與此對應,根據地理位置我們把某個地區的基礎設施服務集合稱為一個區域。不同區域之間是相互獨立的。說白了就類似於不同地方的機房。

Available Zone: 基於容災背景提出,簡單而言,就是相同region區域不同的機房

 

# 首先是服務註冊到Eureka

# 每30s傳送心跳檢測重新進行租約,如果客戶端不能多次更新租約,它將在90s內從伺服器註冊中心移除。

# 註冊資訊和更新會被複制到其他Eureka 節點,來自任何區域的客戶端科可以查詢到註冊中心資訊,每30s發生一次複製來定位他們的服務,並進行遠端呼叫

# 客戶端還可以快取一些服務例項資訊,所以即使Eureka全掛掉,客戶端也是可以定位到服務地址的

 

3.4.3 搭建Euraka Server

首先:搭建parent專案

<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.0http://maven.apache.org/xsd/maven-4.0.0.xsd”>

     
<modelVersion>4.0.0</modelVersion>

 

     
<modules>

          
<module>microservice-consumer</module>

          
<module>microservice-provider</module>

          
<module>microservice-discovery-eureka</module>

     
</modules>

     
<groupId>com.microservice</groupId>

     
<artifactId>microservice</artifactId>

     
<version>1.0-SNAPSHOT</version>

     
<packaging>pom</packaging>

 

     
<parent>

          
<groupId>org.springframework.boot</groupId>

          
<artifactId>spring-boot-starter-parent</artifactId>

          
<version>1.5.6.RELEASE</version>

     
</parent>

 

     
<dependencyManagement>

          
<dependencies>

                
<dependency>

                     
<groupId>org.springframework.cloud</groupId>

                     
<artifactId>spring-cloud-dependencies</artifactId>

                     
<version>Dalston.SR3</version>

                     
<type>pom</type>

                     
<scope>import</scope>

                
</dependency>

          
</dependencies>

     
</dependencyManagement>

     
<dependencies>

          
<dependency>

                
<groupId>org.springframework.cloud</groupId>

                
<artifactId>spring-cloud-starter-config</artifactId>

          
</dependency>

          
<dependency>

                
<groupId>org.springframework.cloud</groupId>

                
<artifactId>spring-cloud-starter-eureka</artifactId>

          
</dependency>

 

          
<dependency>

                
<groupId>mysql</groupId>

                
<artifactId>mysql-connector-java</artifactId>

                
<version>5.1.44</version>

          
</dependency>

     
</dependencies>

     
<build>

          
<finalName>${project.artifactId}</finalName>

          
<plugins>

                
<!–資原始檔拷貝外掛 –>

                
<plugin>

                     
<groupId>org.apache.maven.plugins</groupId>

                     
<artifactId>maven-resources-plugin</artifactId>

                     
<configuration>

                           
<encoding>UTF-8</encoding>

                     
</configuration>

                
</plugin>

                
<!–java編譯外掛 –>

                
<plugin>

                     
<groupId>org.apache.maven.plugins</groupId>

                     
<artifactId>maven-compiler-plugin</artifactId>

                     
<configuration>

                           
<source>1.8</source>

                           
<target>1.8</target>

                           
<encoding>UTF-8</encoding>

                     
</configuration>

                
</plugin>

                
<plugin>

                     
<groupId>org.springframework.boot</groupId>

                     
<artifactId>spring-boot-mavenplugin</artifactId>

                
</plugin>

          
</plugins>

          
<pluginManagement>

                
<plugins>

                     
<!–配置tomcat外掛 –>

                     
<plugin>

                           
<groupId>org.apache.tomcat.maven</groupId>

                           
<artifactId>tomcat7-mavenplugin</artifactId>

                           
<version>2.2</version>

                     
</plugin>

                
</plugins>

          
</pluginManagement>

     
</build>

</project>

 

其次:搭建Euraka Server

<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.0http://maven.apache.org/xsd/maven-4.0.0.xsd”>

     
<modelVersion>4.0.0</modelVersion>

     
<packaging>war</packaging>

 

     
<parent>

          
<artifactId>microservice</artifactId>

          
<groupId>com.microservice</groupId>

          
<version>1.0-SNAPSHOT</version>

     
</parent>

 

     
<artifactId>microservice-discovery-eureka</artifactId>

 

     
<dependencies>

          
<dependency>

                
<groupId>org.springframework.cloud</groupId>

                
<artifactId>spring-cloud-starter-eureka-server</artifactId>

          
</dependency>

          
<dependency>

                
<groupId>org.springframework.boot</groupId>

                
<artifactId>spring-boot-starter-security</artifactId>

          
</dependency>

     
</dependencies>

</project>

 

然後:在application.xml中配置application.yml檔案

security:

 
basic:

   
enabled: true

 
user:

   
name: user

   
password: password

server:

 
port: 8761

eureka:

 
client:

   
register-with-eureka: false

   
fetch-registry: false

   
service-url:

     
defaultZone:http://user:[email protected]:8761/eureka

 

   

最後:建立EurekaApplication,並新增@SpringBootApplication

@EnableEurekaServer

public
class EurakaApplication{

     
public static
void main(String[] args)
throws Exception {

           SpringApplication.run(EurakaApplication.class,
args);

      }

 

}

 

就可以啟動Eureka Server了

3.4 將微服務註冊到Eureka上

首先:確認當前maven環境下是否引入了Eureka相關的配置,並且新增如下依賴

          
<dependency>

                
<groupId>org.springframework.boot</groupId>

                
<artifactId>spring-boot-starter-actuator</artifactId>

          
</dependency>

其次:在微服務啟動類上新增@EnableEurekaClient註解,使得他成為一個Eureka Client,在啟動的 時候就向Eureka Server註冊。

然後:在微服務的應用程式中application.yml配置檔案中,我們不要新增如下配置,因為這表示是伺服器,客戶端我們是需要向Eureka註冊的

eureka:

 
client:

   
register-with-eureka: false

fetch-registry:
false

應該可以的配置有健康檢查和路徑配置

eureka:

 
client:

   
healthcheck
:

     
enabled: true

   
service-url:

     
defaultZone:http://nicky:[email protected]:8761/eureka

最後:在啟動類新增@EnableEurekaClient註解,表示這個類可以作為Eureka 客戶端,啟動之後可以向Eureka註冊

@SpringBootApplication

@EnableEurekaClient

public
class UserServiceRunner{

     
public static
void main(String[] args)
throws Exception {

           SpringApplication.run(UserServiceRunner.class,
args);

      }

}

 

3.5 Eureka配置項

eureka.client.allow-redirects:是否允許重定向Eureka客戶端請求到其他或者備份伺服器,預設為fasle

eureka.client.eureka-connection-idle-timeout-seconds:HTTP連線到eureka伺服器可以在關閉之前保持空閒的時間(幾秒鐘)。

eureka.client.eureka-server-connect-timeout-seconds:表示連線Eureka伺服器,等待多長時間算超時

eureka.client.eureka-server-port: Eureka Server埠

eureka.client.eureka-server-d-n-s-name:獲取要查詢的DNS名稱以獲得eureka伺服器的列表。

eureka.client.eureka-server-read-timeout-seconds:示在從eureka伺服器讀取資料之前需要等待多長時間(以秒為單位)

eureka.client.eureka-server-total-connections:從eureka客戶端到所有eureka伺服器的所允許連線總數。

eureka.client.eureka-server-total-connections-per-host:設定每一個主機所允許的到Eureka Server連線的數量

eureka.client.fetch-registry: 是否允許客戶端向Eureka 登錄檔獲取資訊,一般伺服器為設定為false,客戶端設定為true

eureka.client.register-with-eureka:是否允許向Eureka Server註冊資訊,預設true,如果是伺服器端,應該設定為false

eureka.client.fetch-remote-regions-registry:逗號分隔的區域列表,用於獲取eureka註冊資訊

eureka.client.g-zip-content:從伺服器端獲取資料是否需要壓縮

eureka.client.prefer-same-zone-eureka: 是否優先使選擇相同Zone的例項,預設為true

eureka.client.registry-fetch-interval-seconds:多長時間從Eureka Server登錄檔獲取一次資料,預設30s

eureka.client.service-url:可用區域對映,列出完全合格的url與eureka伺服器通訊。每個值可以是一個URL,也可以是一個逗號分隔的替代位置列表。

eureka.dashboard.enabled: 是否啟用Eureka首頁,預設為true

eureka.dashboard.path: 預設為/

eureka.instance.appname:在eureka註冊的應用程式的名稱。

eureka.instance.app-group-name:在eureka註冊的應用程式的組名稱

eureka.instance.health-check-url: 健康檢查絕對路徑

eureka.instance.health-check-url-path:健康檢查相對路徑

eureka.instance.hostname:設定主機名

eureka.instance.instance-id:設定註冊例項的id

eureka.instance.lease-expiration-duration-in-seconds:設定多長時間意味著租約到期,預設90

eureka.instance.lease-renewal-interval-in-seconds:表示Eureka客戶端需要傳送心跳到eureka伺服器的頻率(以秒為單位),以表明它仍然存在。指定的期間內如果沒有收到心跳leaseExpirationDurationInSeconds

eureka.instance.metadata-map:可以設定後設資料

eureka.instance.prefer-ip-address: 例項名以IP,但是建議hostname,預設為false

 

四 負載均衡

4.1 Ribbon的介紹和架構

實現負載均衡,我們可以通過伺服器端和客戶端做負載均衡。伺服器端做負載均衡,比如可以使用Nginx。而客戶端做負載均衡,就是客戶端有一個元件,知道有哪些可用的微服務,實現一個負載均衡的演算法。

 

Ribbon工作流程主要分為兩步:

第一:先選擇Eureka Server,優先選擇在同一個Zone且負載較少的Server;

第二:再根據使用者指定的策略,再從server取到的服務註冊列表中選擇一個地址。其中Ribbon提供了很多種策略,例如輪詢round bin,隨機Random,根據響應時間加權。

4.2 使用Ribbon進行負載均衡

4.2.1 基本用法

要使用Ribbon進行負載均衡,那麼就需要引入對應的依賴,如果已經因如果Eureka的依賴,那麼就不需要再次引入Ribbon的依賴了。

          
<dependency>

                
<groupId>org.springframework.cloud</groupId>

                
<artifactId>spring-cloud-starter-ribbon</artifactId>

          
</dependency>

然後在啟動類上加上註解@LoadBalanced,則負載均衡生效。

@SpringBootApplication

@EnableEurekaClient

public
classMovieServiceRibbonRunner {

     
@Bean

     
@LoadBalanced

     
public RestTemplaterestTemplate(){

          
return new RestTemplate();

      }

 

     
public static
void main(String[] args) {

           SpringApplication.run(MovieServiceRibbonRunner.class,
args);

      }

}

 

我們可以修改application.yml檔案的instance_id屬性:

eureka:

  client:

    healthcheck:

      enabled: true

    service-url:

      defaultZone:http://nicky:[email protected]:8761/eureka

  instance:

instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}

 

在消費者Controller中,訪問虛擬的ip,即我們微服務的名稱

@RestController

public
class MovieController {

     

     
@Autowired

     
private RestTemplate restTemplate;

     

     
@GetMapping(“/movie/{id}”)

     
public User findById(@PathVariable Long
id) {

 

          
returnthis.restTemplate.getForObject(“http://microservice-provider-user/user/” id,
User.class);

      }

}

 

最後啟動兩個服務提供者例項,可以修改埠實現。

4.2.2 通過程式碼自定義配置Ribbon

首先,該類需要加上@Configuration註解,但是注意,如果加上這個註解,他就不能包含在註解@ComponentScan或者@SpringBootApplication所指定包掃描路徑。

@Configuration

public
classRibbonTestConfiguration {

     
@Autowired

      IClientConfig
config;

     

     
@Bean

     
public IRuleribbonRule(IClientConfig config){

          
return new RandomRule();

      }

}

其次:在啟動類上加上@RibbonClient註解,指定名稱和configuration檔案類

@SpringBootApplication

@EnableEurekaClient

@RibbonClient(name=”microservice-provider-user”,configuration=RibbonTestConfiguration.class)

public
classMovieServiceRunner {

     
@Bean

     
@LoadBalanced

     
public RestTemplaterestTemplate(){

          
return new RestTemplate();

      }

 

     
public static
void main(String[] args) {

           SpringApplication.run(MovieServiceRunner.class,
args);

      }

}

 

4.2.3 通過配置檔案自定義Ribbon

即我們可以通過yml或者properties配置檔案,進行配置,然後使用我們配置的選項。

注意:這裡有些優先順序的順序問題:

配置檔案定義的優先順序大於通過使用Java程式碼@RibbonClient的優先順序大於使用spring預設的優先順序

支援以下屬性:

NFLoadBalancerClassName:應該實現 ILoadBalancer

NFLoadBalancerRuleClassName:應該實現 IRule

NFLoadBalancerPingClassName:應該實現IPing

NIWSServerListClassName:應該實現 ServerList

NIWSServerListFilterClassName:應該實現 ServerListFilter

 

application.yml中配置如下:

microservice-provider-user:

 
ribbon:

   
NFLoadBalancerRuleClassName:com.netflix.loadbalancer.RandomRule

 

五 Feign (宣告式的REST Client)

5.1 簡介及基礎使用

Feign是一個宣告式的web服務客戶端,它使得寫web服務客戶端更加容易。使用Feign建立一個介面並對其進行註解。它具有可插拔的註解支援,包括Feign自己的註解以及jax-rs註釋。Feign還支援可插拔的的編碼器和解碼器。Spring Cloud增加了對Spring MVC註解的支援,並使用了在Spring Web中預設使用的相同的HttpMessageConverters。Spring
Cloud整合了Ribbon和靈感,在使用時提供了負載均衡的http客戶端。

 

加入Feign的依賴到Maven

<dependency>

      <groupId>org.springframework.cloud</groupId>

      <artifactId>spring-cloud-starter-feign</artifactId>

</dependency>

然後建立介面,並且新增註解@EnableFeignClients

@SpringBootApplication

@EnableEurekaClient

@EnableFeignClients

public
classMovieServiceRunner {

     
public static
void main(String[] args) {

           SpringApplication.run(MovieServiceRunner.class,
args);

      }

}

 

寫真正的介面去呼叫其他微服務,以供本應用呼叫

建立介面:

首先在介面加上註解@FeignClient,指定需要呼叫微服務名字

其次:如果啟動的時候,提示Http Method不正確我們需要使用以前的老的註解,即:

@RequestMapping(method= RequestMethod.GET, value = “/user/{id}”),而不能使用:

@GetMapping(“/user/{id}”)

 

然後:如果提示PathVariableannotation was empty on param 0,那麼我們就需要:

 

還有就是:微服務提供方和消費者兩邊的HTTP 方法必須最好一致,如果一邊是GET 請求,另外一邊是POST請求,就會報錯。而且如果傳遞的是一個負載物件,即使指定的是GET請求,也會作為POST請求,一般做法就是不傳遞物件,而是傳遞單個引數

比如@ReqquestParam(“name”) String name,@ReqquestParam(“age”) int age之類的

 

public UserfindById(@PathVariable(“id”) Long id); 而不是:

public UserfindById(@PathVariable Long id); 就可以

這相當於是Feign的坑吧

@FeignClient(“microservice-provider-user”)

public
interface UserFeignClient {

     
@GetMapping(“/user/{id}”)

     
public User findById(@PathVariable Long
id);

}

 

然後在其他類中就可以呼叫了:

@RestController

public
class MovieController {

 

     
@Autowired

     
private UserFeignClient userFeignClient;

 

     
@GetMapping(“/movie/{id}”)

     
public User findById(@PathVariable Long
id) {

 

          
return userFeignClient.findById(id);

      }

}

 

5.2 覆寫Feign的配置

Spring Cloud允許你完全控制Feign 客戶端通過宣告一些額外的註解,即@FeignClient(name = “stores”,configuration = FooConfiguration.class)

 

一般來說,我們不需要使用在自定義的FooConfiguration上使用@Configuration元件,如果要使用我們需要把它從包含@ComponentScan或者@SpringBootApplication註解所掃描的包中排除掉。和Ribbon類似。

 

假設我們現在定義了一個Configuration1這個類,使用feign預設的契約,而不是使用SpringMvcContract,即:

@Configuration

public
class Configuration1 {

     
@Bean

     
public ContractfeignContract() {

 

          
return newfeign.Contract.Default();

      }

}

然後在Feign的介面處,指定@FeignClient的configuration,重寫配置,使用剛才定義的Configuration1,即:

@FeignClient(name=”microservice-provider-user”,configuration=Configuration1.class)

public
interface UserFeignClient {

 

     
@GetMapping(“/user/{id}”)

     
public User findById(@PathVariable(“id”) Long
id);

}

但是在這兒我們依然用的是SpringMVC的註解,GetMapping,這裡雖然沒有報錯,但是啟動的時候就會報錯了。比如java.lang.IllegalStateException: MethodfindById not annotated with HTTP method type (ex. GET, POST)

所以,介面處我們應該替換為Feign自己的註解。

@FeignClient(name=”microservice-provider-user”,configuration=Configuration1.class)

public
interface UserFeignClient {

 

     
@RequestLine(“GET /user/{id}”)

     
public User findById(@Param(“id”) Long
id);

}

 

 

Feign支援請求響應GZIP壓縮:

feign.compression.request.enabled=true

feign.compression.response.enabled=true

 

5.3 feign的日誌

Feign的預設日誌級別是DEBUG 級別。

@Bean

Logger.Level feignLoggerLevel(){

      return Logger.Level.FULL;

}

 

六 常見問題解決方案

6.1 Eureka環境以及cloud配置

Eureka可以執行AWS環境和非AWS環境上,Eureka也可以以測試環境和生產環境執行。

如果需要執行在AWS環境上,則需要通過-Deureka.datacenter=cloud指定執行在AWS上,在yml中我們可以通過eureka.datacenter:cloud指定。

eureka.datacenter:cloud

如果需要執行在測試或者生產環境,我們需要通過-Deureka.environment來指定。如果指定測試環境eureka.environment: test, 如果執行在生產環境,則指定eureka.environment:product

 

6.2 自我保護提示

 

6.3 Eureka註冊服務慢的問題如何解決

作為例項還涉及到與註冊中心的週期性心跳,預設持續時間為30秒(通過serviceUrl)。在例項、伺服器、客戶端都在本地快取中具有相同的後設資料之前,服務不可用於客戶端發現(所以可能需要3次心跳)。你可以使用eureka.instance.leaseRenewalIntervalInSeconds配置,這將加快客戶端連線到其他服務的過程。

在生產中,最好堅持使用預設值,因為在伺服器內部有一些計算,他們對續約做出假設。

 

6.4如何解決Eureka Server不踢出已關停的節點的問題

伺服器端:

# 關閉自我保護

eureka.server.enable-self-preservation:false

# 縮小清理間隔(單位毫秒,預設是60*1000)

eureka.server.eviction-interval-timer-in-ms:  5000

 

客戶端:

開啟健康檢查(需要spring-boot-starter-actuator依賴)

eureka.client.healthcheck.enabled= true    

租期更新時間間隔(預設30秒)

eureka.instance.lease-renewal-interval-in-seconds=10

租期到期時間(預設90秒)

eureka.instance.lease-expiration-duration-in-seconds=30

 

6.5 Eureka HA配置(假設三個節點)

6.5.1 在hosts檔案中加入如下配置

127.0.0.1  peer1

127.0.0.1  peer2

127.0.0.1  peer3

 

6.5.2 在application.yml中加入以下配置

spring:

 
profiles: peer1

 
application:

   
name: EUREKA-HA

server:

 
port: 8761

eureka:

 
instance:

   
hostname: peer1

 
client:

   
serviceUrl:

     
defaultZone:http://peer2:8762/eureka/,http://peer3:8763/eureka/

spring:

 
profiles: peer2

 
application:

   
name: EUREKA-HA

server:

 
port: 8762

eureka:

 
instance:

   
hostname: peer2

 
client:

   
serviceUrl:

     
defaultZone:http://peer1:8761/eureka/,http://peer3:8763/eureka/

spring:

 
profiles: peer3

 
application:

   
name: EUREKA-HA

server:

 
port: 8763

eureka:

 
instance:

   
hostname: peer3

 
client:

    serviceUrl:

     
defaultZone:http://peer1:8761/eureka/,http://peer2:8762/eureka/