網關組件 Zuul 性能一般,未來將退出 Spring Cloud 生態圈,所以直接使用 GateWay,把 GateWay 劃分到第一代 Spring Cloud 核心組件中。
各組件整體結構如下:
Gateway:所有服務的入口;日志、黑白名單、鑒權。
Ribbon:負載均衡。
?
Hystrix:熔斷器,服務熔斷,服務降級。
?
Feign:遠程調用。
?
Eureka:服務注冊與發現;微服務名稱、IP、端口號。
?
Config:搭建配置中心微服務;實現對配置文件的統一管理,配置自動刷新 - bus。
?
Actor
---> Gateway 網關
--轉發-->
{
[搜索微服務,搜索微服務],
[商品微服務,商品微服務],
[支付微服務,支付微服務],
[秒殺微服務,秒殺微服務],
RestTemplate + Ribbon + Hystrix 或 Feign
}
---->
{
服務注冊中心 Eureka,
配置中心 Config
}
從形式上來說,Feign 一個頂三,Feign = RestTemplate + Ribbon + Hystrix。
Eureka 服務注冊中心
常用的服務注冊中心:Eureka、Nacos、Zookeeper、Consul。
關于服務注冊中心
注意:服務注冊中心本質上是為了解耦服務提供者和服務消費者。
服務消費者 -> 服務注冊中心 -> 服務提供者。
對于任何一個微服務,原則上都應存在或者支持多個提供者(比如商品微服務部署多個實例),這是由微服務的分布式屬性決定的。
更進一步,為了支持彈性擴、縮容特性,一個微服務的提供者的數量和分布往往是動態變化的,也是無法預先確定的。因此,原本在單體應用階段常用的靜態 LoadBalance 機制就不再適用了,需要引入額外的組件來管理微服務提供者的注冊與發現,而這個組件就是服務注冊中心。
注冊中心實現原理
1. 啟動:
容器 --> 服務注冊中心
容器 --> 服務提供者
容器 --> 服務消費者
?
2. 注冊:
服務消費者 --> 服務注冊中心
服務提供者 --> 服務注冊中心
?
3. 獲取服務信息:
服務消費者 <--獲取服務信息--> 服務注冊中心
?
4. Invoke:
服務消費者 --負載均衡-熔斷機制--> 服務提供者
分布式微服務架構中,服務注冊中心用于存儲服務提供者地址信息、服務發布相關的屬性信息,消費者通過主動查詢和被動通知的方式獲取服務提供者的地址信息,而不再需要通過硬編碼方式得到提供者的地址信息。消費者只需要知道當前系統發布了那些服務,而不需要知道服務具體存在于什么位置,這就是透明化路由。
1)服務提供者啟動。
2)服務提供者將相關服務信息主動注冊到注冊中心。
3)服務消費者獲取服務注冊信息:Pull 模式 - 服務消費者可以主動拉取可用的服務提供者清單;Push 模式 - 服務消費者訂閱服務,當服務提供者有變化時,注冊中心也會主動推送更新后的服務清單給消費者。
4)服務消費者直接調用服務提供者。
另外,注冊中心也需要完成服務提供者的健康監控,當發現服務提供者失效時需要及時剔除。
主流服務中心對比
Zookeeper:
Dubbo + Zookeeper
?
zookeeper = 存儲 + 監聽通知
?
Zookeeper 是一個分布式服務框架,是 Apache Hadoop 的一個子項目,它主要是用來解決分布式應用中經常遇到的一些數據管理問題,如:統一命名服務、狀態同步服務、集群管理、分布式應用配置項的管理等。
?
Zookeeper 用來做服務注冊中心,主要是因為它具有節點變更通知功能,只要客戶端監聽相關服務節點,服務節點的所有變更,都能及時的通知到監聽客戶端,這樣作為調用方只要使用 Zookeeper 的客戶端就能實現服務節點的訂閱和變更通知功能了,非常方便。另外,Zookeeper 可用性也可以,因為只要半數以上的選舉節點存活,整個集群就是可用的,最少節點數為 3。
Eureka:
Eureka 由 Netflix 開源,并被 Pivatal 集成到 Spring Cloud 體系中,它是基于 RestfulAPI 風格開發的服務注冊與發現組件。
Consul:
Consul 是由 HashiCorp 基于 Go 語言開發的支持多數據中心分布式高可用的服務發布和注冊服務軟件, 采用 Raft 算法保證服務的一致性,且支持健康檢查。
Nacos:
Nacos 是一個更易于構建云原生應用的動態服務發現、配置管理和服務管理平臺。簡單來說 Nacos 就是注冊中心 + 配置中心的組合,幫助我們解決微服務開發必會涉及到的服務注冊與發現,服務配置,服務管理等問題。Nacos 是 Spring Cloud Alibaba 核心組件之一,負責服務注冊與發現,還有配置。
CAP 定理:
CAP 定理又稱 CAP 原則,指的是在一個分布式系統中,Consistency 一致性、 Availability 可用性、Partition tolerance 分區容錯性,最多只能同時三個特性中的兩個,三者不可兼得。
?
P:分區容錯性 - 分布式系統在遇到某節點或網絡分區故障的時候,仍然能夠對外提供滿足一致性或可用性的服務(一定的要滿足的)。
?
C:數據一致性 - all nodes see the same data at the same time.
?
A:高可用 - Reads and writes always succeed.
?
CAP 不可能同時滿足三個,要么是 AP,要么是 CP。
對比:
- Eureka - JAVA - AP - HTTP
- Consul - Go - CP - HTTP / DNS
- Zookeeper - Java - CP - 客戶端
- Nacos - Java - 支持 AP / CP 切換 - HTTP
服務注冊中心組件 Eureka
服務注冊中心的一般原理、對比了主流的服務注冊中心方案,目光聚焦 Eureka。
Eureka 基礎架構:
Eureka Server 需要手動搭建一個工程,并引入相關依賴,進行對應的配置文件設置。
?
Renew 心跳 / 心跳檢測:服務注冊默認 30s 續約,默認 90s 沒有收到續約就會將 Client 實例從服務列表中剔除。
?
ApplicationService 服務提供者
----> Eureka Client
--注冊服務--> Eureka Server 注冊中心
?
Eureka Server 注冊中心
--服務列表-緩存--> Eureka Client
----> ApplicationClient 客戶端消費者
?
ApplicationClient 客戶端消費者 --調用服務--> ApplicationService 服務提供者
Eureka 交互流程及原理:
不同的 Eureka Server 會互相同步復制(Replicate)服務實例列表;
每個 Eureka Server 是一個集群;
Eureka Server 搭建集群來保持高可用服務注冊中心;
每個 Eureka Server 可能處于不同地域不同的機房。
?
Eureka 服務注冊中心:[Eureka Server 1, Eureka Server 2, Eureka Server 3]
?
Appllication Service 服務提供者 - 含有 Eureka Client
Application Client 服務消費者 - 含有 Eureka Client
?
服務提供者可以進行 Register, Renew, Cancel, Get Registry 于服務注冊中心。
服務消費者可以進行 Get Registry 于服務注冊中心。
?
服務消費者 Make Remote Call 于服務提供者。
Eureka 包含兩個組件 - Eureka Server 和 Eureka Client:
- Eureka Client 是一個 Java 客戶端,用于簡化與 Eureka Server 的交互;Eureka Server 提供服務發現的能力。
- 各個微服務啟動時,會通過 Eureka Client 向 Eureka Server 進行注冊自己的信息(如網絡信息),Eureka Server 會存儲該服務的信息。
- Application Service 作為服務提供者向 Eureka Server 中注冊服務,Eureka Server 接受到注冊事件會在集群和分區中進行數據同步,Application Client 作為消費端(服務消費者)可以從 Eureka Server 中獲取到服務注冊信息,進行服務調用。
- 微服務啟動后,會周期性地向 Eureka Server 發送心跳以續約自己的信息; Eureka Server 默認心跳續約周期為 30s,默認 90s 后會將還沒有續約的 Client 給剔除。
- 如果 Eureka Server 在一定時間內沒有接收到某個微服務節點的心跳,將會注銷該微服務節點(默認 90 秒)。
- 每個 Eureka Server 同時也是 Eureka Client,多個 Eureka Server 之間通過復制的方式完成服務注冊列表的同步。
- Eureka Client 會緩存 Eureka Server 中的信息。即使所有的 Eureka Server 節點都宕掉,服務消費者依然可以使用緩存中的信息找到服務提供者。
Eureka 通過心跳檢測、健康檢查和客戶端緩存等機制,提高系統的靈活性、可伸縮性和高可用性。
搭建單例 Eureka Server 服務注冊中心
實現過程:
- 單實例 Eureka Server -> 訪問管理界面。
- 服務提供者(商品微服務注冊到集群)。
- 服務消費者(頁面靜態化微服務注冊到 Eureka / 從 Eureka Server 獲取服務信息)。
- 完成調用 。
1)搭建 Eureka Server 服務 lagou-cloud-eureka
lagou-parent 中增加 Spring Cloud 版本號依賴管理;Spring Cloud 是一個綜合的項目,下面有很多子項目,比如 eureka 子項目;Greemwich 對應的 Spring Boot 是 2.0.x 版本。
<dependencyManagement>
<dependencies>
<!-- SCN -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2)lagou-cloud-eureka 工程 pom.xml 中引入依賴
<dependencies>
<!-- Eureka server 依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
注意:在父工程的 pom 文件中手動引入 jaxb 的 jar,因為 Jdk 9 之后默認沒有加載該模塊,但是 Eureka Server 使用到,所以需要手動導入,否則 Eureka Server 服務無法啟動。
父工程 lagou-parent 的 pom 中增加依賴:
<!-- 引入 Jaxb,開始 -->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.2.10-b140310.1920</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!-- 引入 Jaxb,結束 -->
3)在 application.yml 文件中配置 Eureka server 服務端口,服務名等信息:
server:
port: 9200
spring:
application:
name: lagou-cloud-eureka
eureka:
# Eureka server 本身也是 eureka 的一個客戶端,因為在集群下需要與其他 eureka server 進行數據的同步
client:
# 定義 eureka server url, 如果是集群情況下 defaultZone 設置為集群下的別的 Eureka Server 的地址,多個地址使用逗號隔開
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
# 自己就是服務不需要注冊自己
register-with-eureka: false
# 自己就是服務不需要從 Eureka Server 獲取服務信息, 默認為 true
fetch-registry: false
instance:
# 當前 eureka 實例的主機名
hostname: localhost
# 使用 ip 注冊,否則會使用主機名注冊了(此處考慮到對老版本的兼容,新版本經過實驗都是 ip)
prefer-ip-address: true
# 自定義實例顯示格式,加上版本號,便于多版本管理,注意是 ip-address,早期版本是 ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
4)編寫啟動類,聲明當前服務為 Eureka 注冊中心
package com.renda.eureka;
?
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
?
/**
* @author Renda Zhang
* @since 2020-11-01 16:36
*/
@SpringBootApplication
@EnableEurekaServer // 表示當前項目為 Eureka Server
public class EurekaApplication {
?
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
?
}
5)訪問 http://localhost:9200/,如果出現 Eureka 注冊中心后臺頁面,則表明 Eureka Server 發布成功。
6)商品微服務和頁面靜態化微服務注冊到 Eureka。
兩個微服務的 POM 文件中都添加 Eureka Client 依賴:
<!-- Eureka client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
兩個微服務的 application.yml 都配置 Eureka 服務端信息:
eureka:
client:
service-url:
defaultZone: http://localhost:9200/eureka/
instance:
# 使用 ip 注冊,否則會使用主機名注冊了(此處考慮到對老版本的兼容,新版本經過實驗都是 ip)
prefer-ip-address: true
# 自定義實例顯示格式,加上版本號,便于多版本管理,注意是 ip-address,早期版本是 ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
修改兩個微服務的啟動類,加上注解:
// 將當前項目作為 Eureka Client 注冊到 Eureka Server, 只能在 Eureka 環境中使用
@EnableEurekaClient
或者使用可以應用在所有服務注冊中心環境的注解:
// 將當前項目表示為注冊中心的客戶端,向注冊中心進行注冊,可以在所有的服務注冊中心環境下使用
@EnableDiscoveryClient
7)刷新 Eureka 注冊中心后臺頁面,發現新增了兩個微服務信息。
搭建 Eureka Server 高可用集群
在互聯網應用中,服務實例很少有單個的。
如果 EurekaServer 只有一個實例,該實例掛掉,正好微服務消費者本地緩存列表中的服務實例也不可用,那么這個時候整個系統都受影響。
在生產環境中,會配置 Eureka Server 集群實現高可用。
Eureka Server 集群之中的節點通過點對點(P2P)通信的方式共享服務注冊表。
開啟兩臺 Eureka Server 以搭建集群。
修改個人電腦中 host 地址:
127.0.0.1 LagouCloudEurekaServerA
127.0.0.1 LagouCloudEurekaServerB
將 lagou-cloud-eureka 復制一份為 lagou-cloud-eureka2
lagou-cloud-eureka 的 application.yml:
server:
port: 9200
spring:
application:
name: lagou-cloud-eureka
eureka:
# Eureka server 本身也是 eureka 的一個客戶端,因為在集群下需要與其他 eureka server 進行數據的同步
client:
# 定義 eureka server url, 如果是集群情況下 defaultZone 設置為集群下的別的 Eureka Server 的地址,多個地址使用逗號隔開
service-url:
defaultZone: http://LagouCloudEurekaServerB:9201/eureka
# 表示是否向 Eureka 中心注冊自己的信息,因為自己就是 Eureka Server 所以不進行注冊, 默認為 true
register-with-eureka: true
# 是否查詢 / 拉取 Eureka Server 服務注冊列表,默認為 true
fetch-registry: true
instance:
# 使用 ip 注冊,否則會使用主機名注冊了(此處考慮到對老版本的兼容,新版本經過實驗都是 ip)
prefer-ip-address: true
# 自定義實例顯示格式,加上版本號,便于多版本管理,注意是 ip-address,早期版本是 ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
lagou-cloud-eureka2 的 application.yml:
server:
port: 9201
spring:
application:
name: lagou-cloud-eureka
eureka:
# Eureka server 本身也是 eureka 的一個客戶端,因為在集群下需要與其他 eureka server 進行數據的同步
client:
# 定義 eureka server url, 如果是集群情況下 defaultZone 設置為集群下的別的 Eureka Server 的地址,多個地址使用逗號隔開
service-url:
defaultZone: http://LagouCloudEurekaServerA:9200/eureka
# 表示是否向 Eureka 中心注冊自己的信息,因為自己就是 Eureka Server 所以不進行注冊, 默認為 true
register-with-eureka: true
# 是否查詢 / 拉取 Eureka Server 服務注冊列表,默認為 true
fetch-registry: true
instance:
# 使用 ip 注冊,否則會使用主機名注冊了(此處考慮到對老版本的兼容,新版本經過實驗都是 ip)
prefer-ip-address: true
# 自定義實例顯示格式,加上版本號,便于多版本管理,注意是 ip-address,早期版本是 ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
商品微服務的 application.xml:
server:
# 微服務的集群環境中,通常會為每一個微服務疊加。
port: 9000
spring:
application:
name: lagou-service-product
datasource:
driver-class-name: com.MySQL.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/renda01?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: password
eureka:
client:
service-url:
# 把 eureka 集群中的所有 url 都填寫了進來,也可以只寫一臺,因為各個 eureka server 可以同步注冊表
defaultZone: http://LagouCloudEurekaServerA:9200/eureka, http://LagouCloudEurekaServerB:9201/eureka
instance:
# 使用 ip 注冊,否則會使用主機名注冊了(此處考慮到對老版本的兼容,新版本經過實驗都是 ip)
prefer-ip-address: true
# 自定義實例顯示格式,加上版本號,便于多版本管理,注意是 ip-address,早期版本是 ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
頁面靜態化微服務 application.xml:
server:
# 后期該微服務多實例,端口從 9100 遞增(10 個以內)
port: 9100
Spring:
application:
name: lagou-service-page
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/renda01?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: password
eureka:
client:
service-url:
# 把 eureka 集群中的所有 url 都填寫了進來,也可以只寫一臺,因為各個 eureka server 可以同步注冊表
defaultZone: http://LagouCloudEurekaServerA:9200/eureka, http://LagouCloudEurekaServerB:9201/eureka
instance:
# 使用 ip 注冊,否則會使用主機名注冊了(此處考慮到對老版本的兼容,新版本經過實驗都是 ip)
prefer-ip-address: true
# 自定義實例顯示格式,加上版本號,便于多版本管理,注意是 ip-address,早期版本是 ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
服務消費者調用服務提供者
改造頁面靜態化微服務:之前是直接通過 RestTemplate 寫死 URL 進行調用,現在通過 Eureka 方式進行調用。
@RestController
@RequestMapping("/page")
public class PageController {
@Autowired
RestTemplate restTemplate;
@Autowired
DiscoveryClient discoveryClient;
@GetMapping("/getProduct/{id}")
public Products getProduct(@PathVariable Integer id) {
// 1.獲得 Eureka 中注冊的 lagou-service-product 實例集合
List<ServiceInstance> instances = discoveryClient.getInstances("lagou-service-product");
// 2.獲得實例集合中的第一個
ServiceInstance instance = instances.get(0);
// 3.根據實例信息拼接 IP 地址
String host = instance.getHost();
int port = instance.getPort();
String url = "http://" + host + ":" + port + "/product/query/" + id;
// 調用并返回
return restTemplate.getForObject(url, Products.class);
}
}
啟動注冊中心集群和微服務并使用 Postman 進行測試:
GET http://localhost:9100/page/getProduct/1
Eureka 細節詳解
Eureka 元數據詳解
Eureka 的元數據有兩種:標準元數據和自定義元數據。
標準元數據:主機名、IP 地址、端口號等信息,這些信息都會被發布在服務注冊表中,用于服務之間的調用。
自定義元數據:可以使用 eureka.instance.metadata-map 配置,符合 KEY / VALUE 的存儲格式;這些元數據可以在遠程客戶端中訪問。
eureka:
instance:
# 使用 ip 注冊,否則會使用主機名注冊了(此處考慮到對老版本的兼容,新版本經過實驗都是 ip)
prefer-ip-address: true
# 自定義實例顯示格式,加上版本號,便于多版本管理,注意是 ip-address,早期版本是 ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
# 自定義元數據,會和標準元數據一起注冊到服務注冊中心
metadata-map:
ip: 192.168.186.128
port: 10000
user: RendaZhang
pwd: 123456
可以在程序中可以使用 DiscoveryClient 獲取指定微服務的所有元數據信息:
@RestController
@RequestMapping("/page")
public class PageController {
?
@Autowired
RestTemplate restTemplate;
?
@Autowired
DiscoveryClient discoveryClient;
?
@GetMapping("/getProduct/{id}")
public Products getProduct(@PathVariable Integer id) {
// 1.獲得 Eureka 中注冊的 lagou-service-product 實例集合
List<ServiceInstance> instances = discoveryClient.getInstances("lagou-service-product");
// 2.獲得實例集合中的第一個
ServiceInstance instance = instances.get(0);
// 3.根據實例信息拼接 IP 地址
String host = instance.getHost();
int port = instance.getPort();
// 獲取打印自定義元數據
Map<String, String> metadata = instance.getMetadata();
Set<Map.Entry<String, String>> entries = metadata.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
String url = "http://" + host + ":" + port + "/product/query/" + id;
// 調用并返回
return restTemplate.getForObject(url, Products.class);
}
?
}
Eureka 客戶端詳解
服務提供者(也是 Eureka 客戶端)要向 EurekaServer 注冊服務,并完成服務續約等工作。
服務注冊詳解(服務提供者):
1)當導入了 eureka-client 依賴坐標,配置 Eureka 服務注冊中心地址;
2)服務在啟動時會向注冊中心發起注冊請求,攜帶服務元數據信息;
3)Eureka 注冊中心會把服務的信息保存在 Map 中。
服務續約詳解(服務提供者):
服務每隔 30 秒會向注冊中心續約 (心跳) 一次(也稱為報活),如果沒有續約,租約在 90 秒后到期,然后服務會被失效。每隔 30 秒的續約操作稱之為心跳檢測。
- Eureka Client - 30s 續約一次,在 Eureka Server 更新自己的狀態(Client 端進行配置)。
- Eureka Server - 90s 還沒有進行續約,將該微服務實例從服務注冊表(Map)剔除(Client端進行配置)。
- Eureka Client - 30s 拉取服務最新的注冊表并緩存到本地(Client 端進行配置)。
- 往往不需要調整這兩個配置。
# 向 Eureka 服務中心集群注冊服務
eureka:
instance:
# 租約續約間隔時間,默認 30 秒
lease-renewal-interval-in-seconds: 30
# 租約到期,服務時效時間,默認值 90 秒, 服務超過 90 秒沒有發生心跳,EurekaServer 會將服務從列表移除
lease-expiration-duration-in-seconds: 90
獲取服務列表(服務注冊表)詳解(服務消費者):
每隔 30 秒服務會從注冊中心中拉取一份服務列表,這個時間可以通過配置修改;往往不需要調整。
# 向 Eureka 服務中心集群注冊服務
eureka:
client:
service-url:
# 每隔多久拉取一次服務列表
registry-fetch-interval-seconds: 30
1)服務消費者啟動時,從 Eureka Server 服務列表獲取只讀備份,緩存到本地。
2)每隔 30 秒,會重新獲取并更新數據。
3)每隔 30 秒的時間可以通過配置 eureka.client.registry-fetch-interval-seconds 修改。
Eureka 服務端詳解
服務下線:
1)當服務正常關閉操作時,會發送服務下線的 REST 請求給 Eureka Server。
2)服務中心接受到請求后,將該服務置為下線狀態
失效剔除:
Eureka Server會定時(間隔值是 eureka.server.eviction-interval-timer-in-ms,默認 60s)進行檢查,如果發現實例在在一定時間(此值由客戶端設置的 eureka.instance.lease-expiration-duration-in-seconds 定義,默認值為 90s)內沒有收到心跳,則會注銷此實例。
自我保護機制:
自我保護模式正是一種針對網絡異常波動的安全保護措施,使用自我保護模式能使 Eureka 集群更加的健壯、穩定的運行。
自我保護機制的工作機制是:如果在 15 分鐘內超過 85% 的客戶端節點都沒有正常的心跳,那么 Eureka 就認為客戶端與注冊中心出現了網絡故障,Eureka Server 自動進入自我保護機制,此時會出現以下幾種情況:
- Eureka Server 不再從注冊列表中移除因為長時間沒收到心跳而應該過期的服務。
- Eureka Server 仍然能夠接受新服務的注冊和查詢請求,但是不會被同步到其它節點上,保證當前節點依然可用。
- 當網絡穩定時,當前 Eureka Server 新的注冊信息會被同步到其它節點中。
默認情況下,如果 Eureka Server 在一定時間內(默認 90 秒)沒有接收到某個微服務實例的心跳,Eureka Server 將會移除該實例。但是當網絡分區故障發生時,微服務與 Eureka Server 之間無法正常通信,而微服務本身是正常運行的,此時不應該移除這個微服務,所以引入了自我保護機制。
- 因此 Eureka Server 可以很好的應對因網絡故障導致部分節點失聯的情況,而不會像 Zookeeper 那樣如果有一半不可用的情況會導致整個集群不可用而變成癱瘓。
- 經驗:建議生產環境打開自我保護機制
在單機測試的時候很容易滿足心跳失敗比例在 15 分鐘之內低于 85%,這個時候就會觸發 Eureka 的保護機制,一旦開啟了保護機制(默認開啟),則服務注冊中心維護的服務實例就不是那么準確了,此時通過修改 Eureka Server 的配置文件來關閉保護機制,這樣可以確保注冊中心中不可用的實例被及時的剔除(不推薦)。
eureka:
server:
# 關閉自我保護模式(默認為 true)
enable-self-preservation: false
Ribbon 負載均衡
關于負載均衡
負載均衡一般分為服務器端負載均衡和客戶端負載均衡。
所謂服務器端負載均衡,比如 Nginx、F5(硬件負載) 這些,請求到達服務器之后由這些負載均衡器根據一定的算法將請求路由到目標服務器處理。
所謂客戶端負載均衡,比如 Ribbon,服務消費者客戶端會有一個服務器地址列表,調用方在請求前通過一定的負載均衡算法選擇一個服務器進行訪問,負載均衡算法的執行是在請求客戶端進行。
Ribbon 是 Netflix 發布的負載均衡器。Eureka 一般配合 Ribbon 進行使用,Ribbon 利用從 Eureka 中讀取到服務信息,在調用服務提供者提供的服務時,會根據一定的算法進行負載。
Ribbon 高級應用
需求:
復制商品微服務 lagou-service-product,命名為 lagou-service-product2,端口號 9001;在商品微服務中定義接口,返回當前服務實例占用的端口號。
頁面靜態化微服務通過負載均衡策略調用商品微服務的 controller。
Eureka Server --服務實例列表--> 頁面靜態化微服務
頁面靜態化微服務
--Ribbon--> 負載均衡算法
----> [商品微服務 9000,商品微服務 9001,商品微服務 9002]
在微服務中使用 Ribbon 不需要額外導入依賴坐標,微服務中引入過 eureka-client 相關依賴,會自動引入 Ribbon 相關依賴坐標。
代碼中使用如下,在服務消費者的 RestTemplate 上添加對應注解即可:
@SpringBootApplication
@EnableDiscoveryClient
public class PageApplication {
public static void main(String[] args) {
SpringApplication.run(PageApplication.class,args);
}
// 向容器中注入一個 RestTemplate,封裝了 HttpClient
@Bean
@LoadBalanced // 啟動請求的 Ribbon 負載均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
在 lagou-serivce-product 中創建一個 Controller,定義方法返回當前微服務所使用的容器端口號:
com.renda.product.controller.ServiceInfoController
@RestController
@RequestMapping("/service")
public class ServiceInfoController {
@Value("${server.port}")
private String port;
@GetMapping("/port")
public String getPort(){
return port;
}
}
然后按照 lagou-serivce-product 復制創建 lagou-serivce-product2 微服務;端口改為 9001,除了 spring.applicaton.name 保持為 lagou-service-product,其它地方改為 lagou-service-product2;ProductApplication 改為 Product2Application;最后在父工程下手動加入新增的模塊。最后如果 IDEA 沒有顯示新增模塊,就刪掉父工程的新增模塊引入,同步一次后,再把新增模塊引入代碼加回去并同步。
在頁面靜態化微服務中調用 lagou-server-product 下的資源路徑:http://lagou-server-product/server/query。
@RestController
@RequestMapping("/page")
public class PageController {
@Autowired
RestTemplate restTemplate;
@GetMapping("/getProduct/{id}")
public Products getProduct(@PathVariable Integer id) {
String url = "http://lagou-service-product/product/query/" + id;
return restTemplate.getForObject(url, Products.class);
}
@GetMapping("/loadProductServicePort")
public String getProductServerPort() {
return restTemplate.getForObject("http://lagou-service-product/service/port", String.class);
}
}
Ribbon 負載均衡策略
Ribbon 內置了多種負載均衡策略,內部負責復雜均衡的頂級接口為 com.netflix.loadbalancer.IRule,接口簡介:
package com.netflix.loadbalancer;
?
/**
* Interface that defines a "Rule" for a LoadBalancer. A Rule can be thought of
* as a Strategy for loadbalacing. Well known loadbalancing strategies include
* Round Robin, Response Time based etc.
*
* @author stonse
*
*/
public interface IRule{
/*
* choose one alive server from lb.allServers or
* lb.upServers according to key
*
* @return choosen Server object. NULL is returned if none
* server is available
*/
?
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
IRule 的子實現類:
IRule <--實現-- AbstractLoadBalancerRule
?
AbstractLoadBalancerRule <--繼承-- [RandomRule, RoundRobinRule, ClientConfigEnabledRoundRobinRule, RetryRule]
?
RoundRobinRule <--繼承-- WeightedResponseTimeRule
?
ClientConfigEnabledRoundRobinRule
<--繼承-- [PredicateBasedRule, BestAvailableRule]
?
PredicateBasedRule
<--繼承-- [AvailabilityFilteringRule, ZoneAvoidanceRule]
- RoundRobinRule - 輪詢策略:
默認超過 10 次獲取到的 server 都不可用,會返回一個空的 server。
- RandomRule - 隨機策略:
如果隨機到的 server 為 null 或者不可用的話,會 while 不停的循環選取。
- RetryRule - 重試策略:
一定時限內循環重試。默認繼承 RoundRobinRule,也支持自定義注入,RetryRule 會在每次選取之后,對選舉的 server 進行判斷,是否為 null,是否 alive,并且在 500ms 內會不停的選取判斷。而 RoundRobinRule 失效的策略是超過 10 次,RandomRule 是沒有失效時間的概念,只要 serverList 沒都掛。
- BestAvailableRule - 最小連接數策略:
遍歷 serverList,選取出可用的且連接數最小的一個 server。該算法里面有一個 LoadBalancerStats 的成員變量,會存儲所有 server 的運行狀況和連接數。如果選取到的 server 為 null,那么會調用 RoundRobinRule 重新選取。
- AvailabilityFilteringRule - 可用過濾策略:
擴展了輪詢策略,會先通過默認的輪詢選取一個 server,再去判斷該 server 是否超時可用,當前連接數是否超限,都成功再返回。
- ZoneAvoidanceRule - 區域權衡策略(默認策略):
擴展了輪詢策略,繼承了 2 個過濾器:ZoneAvoidancePredicate 和 AvailabilityPredicate,除了過濾超時和鏈接數過多的 server,還會過濾掉不符合要求的 zone 區域里面的所有節點, 在一個區域/機房內的服務實例中輪詢;先過濾再輪詢。
修改負載均衡策略:
# 針對的被調用方微服務名稱,不加就是全局生效
lagou-service-product:
ribbon:
# 請求連接超時時間
ConnectTimeout: 2000
# 請求處理超時時間
ReadTimeout: 15000
# 對所有操作都進行重試
OkToRetryOnAllOperations: true
## 根據如上配置,當訪問到故障請求的時候,它會再嘗試訪問一次當前實例(次數由 MaxAutoRetries 配置),
## 如果不行,就換一個實例進行訪問,如果還不行,再換一次實例訪問(更換次數由 MaxAutoRetriesNextServer 配置),
## 如果依然不行,返回失敗信息。
# 對當前選中實例重試次數,不包括第一次調用
MaxAutoRetries: 2
# 切換實例的重試次數
MaxAutoRetriesNextServer: 2
# 負載策略調整
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.AvailabilityFilteringRule
Ribbon 核心源碼剖析
Ribbon 工作原理:
Actor ----> 攔截器 ----> 負載均衡算法(Ribbon)----> 目標微服務
Ribbon:按照一定算法選取服務實例
SpringCloud 充分利用了 SpringBoot 的自動裝配特點。
引入的 Maven 依賴 --> spring-cloud-commons-2.1.0.RELEASE.jar --> META-INF --> spring.factories 配置文件:
# AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.springframework.cloud.client.CommonsClientAutoConfiguration,
org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration,
org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration,
org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration,
org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration,
org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration,
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,
...
點擊配置文件中的 LoadBalancerAutoConfiguration 跳轉到其源碼文件:
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
...
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
...
}
- @Configuration 標識當前類為配置類
- @ConditionalOnClass(RestTemplate.class) 表示只有存在 RestTemplate 類時該配置才會裝配生效。
- @ConditionalOnBean(LoadBalancerClient.class) 表示只有存在 LoadBalancerClient 類時改配置才生效。
- restTemplates 集合會自動注入添加了 @LoadBalanced 注解的 RestTemplate 對象。
- LoadBalancerInterceptorConfig 的 restTemplateCustomizer 為 restTemplates 集合添加了攔截器;該攔截器就是后續攔截請求進行負載處理的。
Hystrix 熔斷器
Hystrix 熔斷器屬于一種容錯機制。
微服務中的雪崩效應
微服務中,一個請求可能需要多個微服務接口才能實現,會形成復雜的調用鏈路。
服務雪崩效應:是一種因“服務提供者的不可用”導致“服務調用者不可用”,并將不可用逐漸放大的現象。
[MQ 微服務,MQ 微服務,MQ 微服務] ----> 靜態化微服務 ----> 商品微服務
站在靜態化微服務角度來看:
扇入 - 上游服務對該服務的調用
扇出 - 該服務對下游服務的調用
- 扇入:代表著該微服務被調用的次數,扇入大,說明該模塊復用性好。
- 扇出:該微服務調用其他微服務的個數,扇出大,說明業務邏輯復雜。
扇入大是一個好事,扇出大不一定是好事。
在微服務架構中,一個應用可能會有多個微服務組成,微服務之間的數據交互通過遠程過程調用完成。這就帶來一個問題,假設微服務 A 調用微服務 B 和微服務 C,微服務 B 和微服務 C 又調用其它的微服務,這就是所謂的“扇出”。如果扇出的鏈路上某個微服務的調用響應時間過長或者不可用,對微服務 A 的調用就會占用越來越多的系統資源,進而引起系統崩潰,所謂的“雪崩效應”。
最下游商品微服務響應時間過長,大量請求阻塞,大量線程不會釋放,會導致服務器資源耗盡,最終導致上游服務甚至整個系統癱瘓。
形成原因 - 服務雪崩的過程可以分為三個階段:
- 服務提供者不可用。
- 重試加大請求流量。
- 服務調用者不可用。
服務雪崩的每個階段都可能由不同的原因造成:
服務提供者不可用:硬件故障、程序 Bug、緩存擊穿、用戶大量請求。
重試加大請求流量:用戶重試、代碼邏輯重試。
服務調用者不可用:同步等待造成的資源耗盡。
雪崩效應解決方案
從可用性可靠性著想,為防止系統的整體緩慢甚至崩潰,采用的技術手段。
介紹三種技術手段應對微服務中的雪崩效應,這三種手段都是從系統可用性、可靠性角度出發,盡量防止系統整體緩慢甚至癱瘓。
服務熔斷
熔斷機制是應對雪崩效應的一種微服務鏈路保護機制。在各種場景下都會接觸到熔斷。高壓電路中,如果某個地方的電壓過高,熔斷器就會熔斷,對電路進行保護。股票交易中,如果股票指數過高,也會采用熔斷機制,暫停股票的交易。同樣,在微服務架構中,熔斷機制也是起著類似的作用。當扇出鏈路的某個微服務不可用或者響應時間太長時,熔斷該節點微服務的調用,進行服務的降級,快速返回錯誤的響應信息。當檢測到該節點微服務調用響應正常后,恢復調用鏈路。
注意:
1)服務熔斷重點在“斷”,切斷對下游服務的調用。
2)服務熔斷和服務降級往往是一起使用的,Hystrix 就是這樣。
服務降級
通俗講就是整體資源不夠用了,先將一些不關緊的服務停掉(調用的時候,返回一個預留的值,也叫做兜底數據),待渡過難關高峰過去,再把那些服務打開。
服務降級一般是從整體考慮,就是當某個服務熔斷之后,服務器將不再被調用,此刻客戶端可以自己準備一個本地的 Fallback 回調,返回一個默認值,這樣做,雖然服務水平下降,但好歹可用,比直接掛掉要強。
服務限流
服務出問題或者影響到核心流程的性能時,暫時將服務屏蔽掉,待高峰或者問題解決后再打開;但是有些場景并不能用服務降級來解決,比如秒殺業務這樣的核心功能,這個時候可以結合服務限流來限制這些場景的并發 / 請求量。
限流措施:
- 限制總并發數(比如數據庫連接池、線程池)。
- 限制瞬時并發數(如 nginx 限制瞬時并發連接數)。
- 限制時間窗口內的平均速率(如 Guava 的 RateLimiter、nginx 的 limit_req 模塊,限制每秒的平均速率)。
- 限制遠程接口調用速率、限制 MQ 的消費速率等。
Hystrix 簡介
Hystrix - defend your application 是由 Net?ix 開源的一個延遲和容錯庫,用于隔離訪問遠程系統、服務或者第三方庫,防止級聯失敗,從而提升系統的可用性與容錯性。
Hystrix 主要通過以下幾點實現延遲和容錯:
- 包裹請求:使用 HystrixCommand 包裹對依賴的調用邏輯。如頁面靜態化微服務方法使用 @HystrixCommand 添加 Hystrix 控制。
- 跳閘機制:當某服務的錯誤率超過一定的閾值時,Hystrix 可以跳閘,停止請求該服務一段時間。
- 資源隔離:Hystrix 為每個依賴都維護了一個小型的線程池 - 艙壁模式。如果該線程池已滿, 發往該依賴的請求就被立即拒絕,而不是排隊等待,從而加速失敗判定。
- 監控:Hystrix 可以近乎實時地監控運行指標和配置的變化,例如成功、失敗、超時、以及被拒絕的請求等。
- 回退機制:當請求失敗、超時、被拒絕,或當斷路器打開時,執行回退邏輯?;赝诉壿嬘砷_發人員自行提供,例如返回一個默認值。
- 自我修復:斷路器打開一段時間后,會自動進入“半開”狀態,從而探測服務是否可用;如果還是不可用,再次退回打開狀態。
Hystrix 應用
熔斷處理
目的:商品微服務長時間沒有響應,服務消費者 --> 頁面靜態化微服務快速失敗給用戶提示。
引入依賴:服務消費者工程(靜態化微服務)中引入Hystrix依賴坐標(也可以添加在父工程中)
<!-- 熔斷器 Hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
開啟熔斷:服務消費者工程(靜態化微服務)的啟動類中添加熔斷器開啟注解 @EnableCircuitBreaker。
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker // 啟用熔斷服務
public class PageApplication {
public static void main(String[] args) {
SpringApplication.run(PageApplication.class,args);
}
// 向容器中注入一個 RestTemplate,封裝了 HttpClient
@Bean
@LoadBalanced // 啟動請求的 Ribbon 負載均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
定義服務降級處理方法:業務方法上使用 @HystrixCommand 的 fallbackMethod 屬性關聯到服務降級處理方法。
@RestController
@RequestMapping("/page")
public class PageController {
?
@Autowired
RestTemplate restTemplate;
?
...
?
/**
* 模擬服務超時,熔斷處理
* 針對熔斷處理,Hystrix 默認維護一個線程池,默認大小為 10。
*/
@HystrixCommand(
//只有是在 @HystrixCommand 中定義了 threadPoolKey,就意味著開啟了艙壁模式(線程隔離),該方法就會自己維護一個線程池。
// 默認所有的請求共同維護一個線程池,實際開發:每個方法維護一個線程池
threadPoolKey = "getProductServerPort2",
// 每一個屬性對應的都是一個 HystrixProperty
threadPoolProperties = {
// 并發線程數
@HystrixProperty(name = "coreSize", value = "1"),
// 默認線程隊列值是 -1,默認不開啟
@HystrixProperty(name = "maxQueueSize", value = "20")
},
// 超時時間的設置
commandProperties = {
// 設置請求的超時時間,一旦請求超過此時間那么都按照超時處理,默認超時時間是 1s
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
}
)
@GetMapping("/loadProductServicePort2")
public String getProductServerPort2() {
return restTemplate.getForObject("http://lagou-service-product/service/port", String.class);
}
?
}
商品微服務模擬超時操作:
@RestController
@RequestMapping("/service")
public class ServiceInfoController {
?
@Value("${server.port}")
private String port;
?
@GetMapping("/port")
public String getPort(){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return port;
}
?
}
使用 Postman 測試:
GET http://localhost:9100/page/loadProductServicePort2
測試返回超時錯誤信息:getProductServerPort2 timed-out and fallback failed.。
因為沒有降級處理,所以只得到 500 超時錯誤信息。
降級處理
配置 @HystrixCommand 注解,定義降級處理方法:
@RestController
@RequestMapping("/page")
public class PageController {
?
@Autowired
RestTemplate restTemplate;
...
?
/**
* 服務降級演示:是在服務熔斷之后的兜底操作
*/
@HystrixCommand(
// 超時時間的設置
commandProperties = {
// 設置請求的超時時間,一旦請求超過此時間那么都按照超時處理,默認超時時間是 1s
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000"),
// 統計窗口時間的設置
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
// 統計窗口內的最小請求數
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
// 統計窗口內錯誤請求閾值的設置 50%
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
// 自我修復的活動窗口時間
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
},
// 設置回退方法
fallbackMethod = "getProductServerPortFallBack"
)
@GetMapping("/loadProductServicePort3")
public String getProductServerPort3() {
return restTemplate.getForObject("http://lagou-service-product/service/port", String.class);
}
?
/**
* 定義回退方法,當請求出發熔斷后執行,補救措施
* 注意:
* 1.方法形參和原方法保持一致
* 2.方法的返回值與原方法保持一致
*/
public String getProductServerPortFallBack(){
// 兜底數據
return "-1";
}
?
}
使用 Postman 測試:
GET http://localhost:9100/page/loadProductServicePort3
Hystrix 艙壁模式
Hystrix 艙壁模式即線程池隔離策略。
如果不進行任何設置,所有熔斷方法使用一個 Hystrix 線程池(10 個線程),那么這樣的話會導致問題,這個問題并不是扇出鏈路微服務不可用導致的,而是線程機制導致的,如果方法 A 的請求把 10 個線程都用了,方法 2 請求處理的時候壓根都沒法去訪問 B,因為沒有線程可用,并不是 B 服務不可用:
----> 帶有 @HystrixCommand 注解方法1
--10個請求--> Hystrix 默認的線程池
----> A 服務
?
----> 帶有 @HystrixCommand 注解方法2
--10個請求--> Hystrix 默認的線程池
----> A 服務
?
所有加有 @HystrixCommand 的方法會共同維護一個線程池;
默認線程池有 10 個線程,默認隊列值為 -1;
如果不進行任何設置,
Hystrix 默認的線程池實現不適合在生產環境中使用。
為了避免問題服務請求過多導致正常服務無法訪問,Hystrix 不是采用增加線程數,而是單獨的為每一個控制方法創建一個線程池的方式,這種模式叫做“艙壁模式",也是線程隔離的手段;即在 @HystrixCommand 中設置 threadPoolProperties 屬性
Hystrix 工作流程與高級應用
出現調用錯誤 ----> 是否達到最小請求數
沒有達到最小請求數 ----> 沒有遇到問題
達到最小請求數 ----> 錯誤數量是否達到閾值
錯誤數量沒有達到閾值 ----> 沒有遇到問題
錯誤數量達到閾值 ----> 跳閘
跳閘 ----> 遠程服務是否已經正常
遠程服務不正常 ----> 跳閘
遠程服務正常 ----> 重置斷路器,調用可以通過
10 秒時間窗口:檢測是否達到最小請求數和錯誤數量是否達到閾值。
5 秒活動窗口:檢測遠程服務是否已經正常。
1)當調用出現問題時,開啟一個時間窗(10s)
2)在這個時間窗內,統計調用次數是否達到最小請求數?如果沒有達到,則重置統計信息,回到第 1 步;如果達到了,則統計失敗的請求數占所有請求數的百分比。然后統計是否達到閾值? 如果達到,則跳閘(不再請求對應服務);如果沒有達到,則重置統計信息,回到第 1 步。
3)如果跳閘,則會開啟一個活動窗口(默認 5s),每隔 5s,Hystrix 會讓一個請求通過,到達那個問題服務,看是否調用成功,如果成功,重置斷路器回到第 1 步,如果失敗,回到第 3 步。
可以自定義的斷路器行為:
- 出現錯誤時,時間窗口的長度。
- 最小請求數。
- 錯誤請求的百分比。
- 跳閘后,活動窗口的長度。
@HystrixCommand(
// 超時時間的設置
commandProperties = {
// 設置請求的超時時間,一旦請求超過此時間那么都按照超時處理,默認超時時間是 1s
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000"),
// 統計窗口時間的設置
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
// 統計窗口內的最小請求數
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
// 統計窗口內錯誤請求閾值的設置 50%
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
// 自我修復的活動窗口時間
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
},
)
上述通過注解進行的配置也可以配置在配置文件中:
# 配置熔斷策略:
hystrix:
command:
default:
circuitBreaker:
# 強制打開熔斷器,如果該屬性設置為 true,強制斷路器進入打開狀態,將會拒絕所有的請求。 默認 false 關閉的
forceOpen: false
# 觸發熔斷錯誤比例閾值,默認值 50%
errorThresholdPercentage: 50
# 熔斷后休眠時長,默認值 5 秒
sleepWindowInMilliseconds: 3000
# 熔斷觸發最小請求次數,默認值是 20
requestVolumeThreshold: 2
execution:
isolation:
thread:
# 熔斷超時設置,默認為 1 秒
timeoutInMilliseconds: 2000
基于 spring boot 的健康檢查觀察跳閘狀態(自動投遞微服務暴露健康檢查細節):
# springboot 中暴露健康檢查等斷點接口
management:
endpoints:
web:
exposure:
include: "*"
# 暴露健康接口的細節
endpoint:
health:
show-details: always
使用 Postman 訪問健康檢查接口:
GET http://localhost:9100/actuator/health
Hystrix 線程池隊列配置案例
有一次在生產環境,突然出現了很多筆還款單被掛起,后來排查原因,發現是內部系統調用時出現了 Hystrix 調用異常。在開發過程中,因為核心線程數設置的比較大,沒有出現這種異常。放到了測試環境,偶爾有出現這種情況。
后來調整 maxQueueSize 屬性,確實有所改善??蓻]想到在生產環境跑了一段時間后卻又出現這種了情況,此時去查看 maxQueueSize 屬性,可是 maxQueueSize 屬性是設置值了。
為什么 maxQueueSize 屬性不起作用,后來通過查看官方文檔發現 Hystrix 還有一個 queueSizeRejectionThreshold 屬性,這個屬性是控制隊列最大閾值的,而 Hystrix 默認只配置了 5 個,因此就算把 maxQueueSize 的值設置再大,也是不起作用的,兩個屬性必須同時配置。
hystrix:
threadpool:
default:
# 并發執行的最大線程數,默認 10;建議和服務器的核數一樣
coreSize: 10
# BlockingQueue 的最大隊列數,默認值 -1
maxQueueSize: 1000
# 即使 maxQueueSize 沒有達到,達到 queueSizeRejectionThreshold 該值后,請求也會被拒絕,默認值 5
queueSizeRejectionThreshold: 800
改進后的配置案例 - 將核心線程數調低,最大隊列數和隊列拒絕閾值的值都設置大一點:
hystrix:
threadpool:
default:
coreSize: 10
maxQueueSize: 1500
queueSizeRejectionThreshold: 1000
想了解更多,歡迎關注我的微信公眾號:Renda_Zhang






