1、Eureka(服務注冊中心)的基本介紹
1.1、服務注冊中心的基本介紹
服務注冊中心是用於管理微服務提供者的注冊與發現的組件。在分布式微服務架構中,服務注冊中⼼用於存儲服務提供者的地址信息、服務發布相關的屬性信息,消費者通過主動查詢和被動通知的方式獲取服務提供者的地址信息,不再需要通過硬編碼⽅式得到提供者的地址信息。
對於任何⼀個微服務,原則上都應存在或者⽀持多個提供者(provider),這是由微服務的分布式屬性決定的。所以為了⽀持彈性擴縮容特性,微服務的同一個提供者的數量和分布往往是動態變化的,也是無法預先確定的。這個時候就需要服務注冊中心來管理微服務提供者的注冊與發現了。服務注冊中心本質上就是為了解耦服務提供者和服務消費者。
1.2、Eureka注冊中心
Eureka是SpringCloud的核心模塊之一,Eureka是一個基於RestFul的服務,用於定位服務,以實現雲端中間層服務注冊、服務發現和故障轉移。服務注冊與發現對與微服務來說是非常重要的,有了服務發現與注冊,只需要使用服務的標識符,就可以訪問到服務,而不需要修改服務調用的配置文件了,功能類似於 Dubbo的注冊中心,比如Zookeeper。
Eureka架構中的三個核心角色:
- 服務注冊中心(Eureka 服務端,即Eureka Server):Eureka的服務端應用,提供服務注冊和發現功能
- 服務提供者(Eureka 客戶端,即Eureka client):提供服務的應用,可以是SpringBoot應用,也可以是其它任意技術實現,只要對外提供的是Rest風格服務即可
- 服務消費者(Eureka 客戶端,即Eureka client):消費應用從注冊中心獲取服務列表,從而得知每個服務方的信息,知道去哪里調用服務方。
Eureka包含兩個組件:Eureka Server和Eureka Client。Eureka客戶端就是指我們的開發的服務,包括服務提供者和消費者。
Eureka Server提供服務注冊服務,各個節點啟動后,會在Eureka Server中進行注冊,這樣EurekaServer中的服務注冊表中將會存儲所有可用服務節點的信息,服務節點的信息可以在界面中直觀的看到。
Eureka Client是一個java客戶端,用於簡化與Eureka Server的交互,客戶端同時也就是一個內置的、使用輪詢(round-robin)負載算法的負載均衡器。
1.2.1、Eureka的運行機制
Eureka 的整個運行機制大致如下:
- 注冊:Eureka client 一次次反復連接eureka,直到注冊成功為止。
- 拉取或訂閱:消費者會把注冊中心的整個注冊表都全部拉取過來緩存到本地,會每隔30秒拉取一次注冊表,更新注冊信息,最終服務消費者會基於服務列表做負載均衡,選中一個微服務后發起遠程調用服務。
- 心跳:消費提供者每30秒發送一次心跳,Eureka 在每次收不到心跳后就會記一個數,如果3次沒有收到心跳 eureka 會刪除這個服務(將地址從注冊表中刪除)
- 自我保護模式:特殊情況,由於網絡不穩定15秒內85%服務器出現心跳異常(一次收不到就算心跳一次),會保護所有的注冊信息不刪除,就算3次沒有收到心跳的情況也不會刪除,網絡恢復后,可以自動退出保護模式,在開發測試期間,可以關閉保護模式。
服務提供方與Eureka之間通過“心跳”
機制進行監控,當某個服務提供方出現問題,Eureka自然會把它從服務列表中剔除,這就實現了服務的自動注冊、發現、狀態監控。
原理圖如下:
2、搭建一個Eureka Server
我們新建一個 springboot 項目,比如 springclouddemo 作為父工程,在該項目里建立 eureka-server 模塊作為 Eureka 的服務端,建立 order-service 作為 Eureka 的客戶端,在 springclouddemo 父工程里管理着子模塊的依賴。
項目目錄結構如下:
父工程 springclouddemo 實際只作為一個依賴管理,不需要一些代碼模塊,比如src、resource等目錄可以刪除。父工程的 pom.xml 文件內容如下:
<?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> <packaging>pom</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>springclouddemo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springclouddemo</name> <description>Demo project for Spring Boot</description> <modules> <module>eureka-server</module> </modules> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR9</spring-cloud.version> </properties> <dependencyManagement> <dependencies> <!-- springCloud --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.5.RELEASE</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> </project>
搭建 Eureka server 實際只需引入 Eureka 的服務依賴,添加一些配置,然后給啟動類加上 @EnableEurekaServer 注解即可,該注解標記了該服務是一個Eureka服務。
eureka-server 模塊的 pom.xml 文件內容如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.example</groupId> <artifactId>springclouddemo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>eureka-server</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!--eureka服務端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <!--<version>2.2.5.RELEASE</version>--> </dependency> </dependencies> </project>
application.properties 配置如下:
spring.application.name=myeurekaserver server.port=10086 # 表示是否將自己注冊到Eureka Server,默認為true,即也會將本身自己的服務注冊到Eureka Server中,可以設置為false eureka.client.register-with-eureka=true # 表示是否從Eureka Server獲取注冊信息,默認為true,可以設置為false eureka.client.fetch-registry=true # 設置與Eureka Server交互的地址 eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/
啟動類代碼如下:
package com.example.eurekaserver; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer @SpringBootApplication public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
啟動Eureka Server的入口,訪問 http://localhost:10086/,即可看到類似以下頁面:
紅色提示信息:自我保護模式被關閉,在網絡或其他問題的情況下可能不會保護實例失效。項目在開發階段可關閉。
Eureka 服務端照上述步驟已搭建好,實際上上面的 eureka server 自己本身也作為一個 eureka client 注冊在了服務端,由此在頁面上的已注冊實例才可以看到 eureka server 本身。
2.1、搭建 Eureka client客戶端並注冊服務
下面在 springclouddemo 父工程下建立 order-service 模塊作為 Eureka 的客戶端。搭建 Eureka client 只需引入客戶端依賴,添加相應配置,給啟動類添加 @EnableEurekaClient 注解即可。
創建一個 springboot 項目 order-service,引入 eureka 客戶端依賴,當然如果該項目需要操作數據庫等還需要引入其他依賴,比如 mysql、mybatis。
pom.xml 文件內容如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.example</groupId> <artifactId>springclouddemo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <groupId>com.example</groupId> <artifactId>order-service</artifactId> <version>0.0.1-SNAPSHOT</version> <name>order-service</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!--eureka服務端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>com.example</groupId> <artifactId>feign-api</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
配置文件 application.yaml 如下:
server: port: 8081 spring: datasource: username: root password: 123456 url: jdbc:mysql://localhost:3306/cloud_order?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC driver-class-name: com.mysql.jdbc.Driver application: name: eureka-provider #設置當前應用名稱,在eureka中Aoolication顯示,通過該名稱可以獲取路徑 mybatis: mapper-locations: classpath:mapping/*Mapper.xml eureka: client: service-url: # eureka的地址信息 defaultZone: http://127.0.0.1:10086/eureka
啟動類代碼如下:
package com.example.orderservice; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient //啟用eurekaclient,新版本可以不用加該注解 public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }
啟動該項目,在 eureka server的頁面下就可以看到實例列表下新增了該項目,也就是該項目作為 eureka client 注冊到了 eureka server 端。
2.2、Eureka client 調用另一服務
啟動一個 web 服務后,我們可以通過 http://ip:port/xxx 的方式來調用該服務的接口,但是這樣需要通過硬編碼的方式寫死需調用的服務的 ip 和端口號。當服務的提供者和消費者注冊到同一個注冊中心時,服務的消費者就可以拉取注冊中心的服務列表,由此來給指定的服務發起請求。
假設有模塊 order-service 和 user-service,兩個模塊都在注冊中心注冊了服務,即都是作為 eureka client,下面演示在 order-service 中調用 user-service的服務。
在 order-service 的啟動類中創建 RestTemplate,並添加負載均衡注解,如下:
import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient;import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableEurekaClient //啟用eurekaclient,新版本可以省略 public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } /** * 創建RestTemplate並注入Spring容器 */ @Bean @LoadBalanced //負載均衡注解 public RestTemplate restTemplate() { return new RestTemplate(); } }
上面通過添加 @LoadBalanced 注解即可實現負載均衡,即如果目標服務有多個服務器時,會負載均衡到目標服務器的其中一台中。
(實際上 RestTemplate 是用於發起http請求的,就算不是通過服務調用的方式發起,我們也可以通過該依賴來發起http請求)
通過注冊中心服務調用跟普通的 http 請求調用方式差不多,只不過在調用時,可以將目標服務在注冊中心注冊的服務名來替換目標服務具體的 ip 和端口。如下:
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.client.RestTemplate; @Service public class OrderService { @Autowired private RestTemplate restTemplate; public Order queryOrderById(Long orderId) { ... // 利用RestTemplate發起http請求 // 目標服務的ip和端口無需寫死,只需寫上目標服務在注冊中心注冊的服務名即可。下面假設user-service模塊在注冊中心注冊的服務名為userservice String url = "http://userservice/user/" + orderId; // 發送http get請求,實現遠程調用服務 User user = restTemplate.getForObject(url, User.class); ... } }
上面通過寫服務名而不是具體的 ip 來發出請求,實際上 spring 會自動幫助我們從 eureka-server 端,根據 userservice 這個服務名稱來獲取服務的實例列表,而后完成負載均衡。springcloud 默認的負載均衡策略是輪詢。
3、Eureka調用服務的負載均衡原理(Ribbon 組件)
springcloud底層利用了一個名為Ribbon的組件來實現的負載均衡的功能。
Ribbon的底層采用了一個攔截器,攔截了RestTemplate發出的請求,對地址做了修改。用一幅圖來總結一下:
基本流程如下:( 這里模擬訂單服務 order-service 需要請求用戶服務 user-service )
-
攔截我們的 RestTemplate 請求 http://userservice/user/1
-
RibbonLoadBalancerClient 會從請求url中獲取服務名稱,也就是 userservice
-
DynamicServerListLoadBalancer 根據 userservice 到 eureka 拉取服務列表
-
eureka 返回列表,localhost:8081、localhost:8082
-
IRule 利用內置負載均衡規則,從列表中選擇一個,例如 localhost:8081
-
RibbonLoadBalancerClient 修改請求地址,用 localhost:8081 替代userservice,得到http://localhost:8081/user/1,發起真實請求
具體原理可參考:https://www.cnblogs.com/chencan/p/16057356.html
3.1、負載均衡策略
springcloud 默認的負載均衡策略是輪詢,但我們可以指定使用哪種策略。
負載均衡的規則都定義在IRule接口中,而IRule有很多不同的實現類:
不同規則的含義如下所示:
內置負載均衡規則類 | 規則描述 |
---|---|
RoundRobinRule | 簡單輪詢服務列表來選擇服務器。它是Ribbon默認的負載均衡規則。 |
AvailabilityFilteringRule | 對以下兩種服務器進行忽略: (1)在默認情況下,這台服務器如果3次連接失敗,這台服務器就會被設置為“短路”狀態。短路狀態將持續30秒,如果再次連接失敗,短路的持續時間就會幾何級地增加。 (2)並發數過高的服務器。如果一個服務器的並發連接數過高,配置了AvailabilityFilteringRule規則的客戶端也會將其忽略。並發連接數的上限,可以由客戶端的<clientName>.<clientConfigNameSpace>.ActiveConnectionsLimit屬性進行配置。 |
WeightedResponseTimeRule | 為每一個服務器賦予一個權重值。服務器響應時間越長,這個服務器的權重就越小。這個規則會隨機選擇服務器,這個權重值會影響服務器的選擇。 |
ZoneAvoidanceRule (默認的) |
以區域可用的服務器為基礎進行服務器的選擇。使用Zone對服務器進行分類,這個Zone可以理解為一個機房、一個機架等。而后再對Zone內的多個服務做輪詢。 |
BestAvailableRule | 忽略那些短路的服務器,並選擇並發數較低的服務器。 |
RandomRule | 隨機選擇一個可用的服務器。 |
RetryRule | 重試機制的選擇邏輯 |
3.2、指定使用的負載均衡策略
通過定義IRule實現可以修改負載均衡的規則,有兩種方式:
方式一:在配置類中注冊一個IRule組件,注冊需要的規則。
比如在啟動類(啟動類實際也是一個配置類)中注冊 IRule 組件,代碼如下:
@SpringBootApplication @EnableEurekaClient //啟用eurekaclient,新版本可以省略 public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } /** * 創建RestTemplate並注入Spring容器 */ @Bean @LoadBalanced //負載均衡注解 public RestTemplate restTemplate() { return new RestTemplate(); } @Bean public IRule randomRule() { return new RandomRule(); //指定負載均衡策略為隨機 } }
使用這種方式修改負載均衡策略是全局生效的,也就是該消費者即 order-service 往所有的服務發起請求時,使用的都是指定的隨機策略。
方式二: 在服務消費者的配置文件如 application.yml 中設置負載均衡的策略,如下:
userservice: #指定給某個目標服務發起請求時的負載均衡規則,這里是目標服務的服務名稱 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #負載均衡規則,這里指定的是隨機訪問
使用這種方式可針對具體某一個服務進行配置。上面的配置指定了只有往 userservice 服務發起服務調用時就使用隨機策略。
可參考:https://www.cnblogs.com/chencan/p/16057356.html
3.3、Ribbon 的飢餓加載
Ribbon 默認是采用懶加載,即第一次訪問時才會去創建LoadBalanceClient,這樣的話在第一次調用另一服務時請求時間會很長。此時我們可以配置為飢餓加載,飢餓加載會在項目啟動時就創建,這樣可以降低第一次另一服務的耗時。
通過在服務消費者配置文件中添加下面配置開啟飢餓加載:
ribbon: eager-load: enabled: true clients: userservice #指定該服務需要進行飢餓加載的目標服務的服務名稱
可參考:https://www.cnblogs.com/chencan/p/16057356.html