目錄
雪崩效應 概述
雪崩效應
1、假設因爲某些原因導致服務提供者的響應非常緩慢,消費者對提供者的請求被強制等待,直到服務返回,在高負載場景下,如果不做任何處理,這種問題很可能造成所有處理用戶請求的線程都被耗竭,而不能響應用戶的進一步請求。
2、在徽服務架構中通常會有多個服務層調用,大量的微服務通過網絡進行通信,從而支撐起整個系統。各個微服務之間也難免存在大量的依賴關係,然而任何服務都不是 100% 可用的,網絡往往也是脆弱的,所以難免有些請求會失敗。 基礎服務的故障導致級聯故障,進而造成了整個系統的不可用,這種現象被稱爲服務雪崩效應。服務雪崩效應描述的是一種因服務提供者的不可用導致服務消費者的不可用,並將不可用逐漸放大的過程。
A 作爲服務提供者,B 爲 A 的服務消費者,c 和 D 是 B 的服務消費者,A 不可用引起了 B 的不可用,並將不可用像滾雪球一樣放大到 C 和 D 時,雪崩效應就形成了。
3、解決雪崩效應的方式一是設置 "超時時間",二是使用斷路器模式,而 Hystrix 庫提供了超時機制與斷路器模式。
超時機制
1、通過網絡請求其他服務時,都必須設置超時。正常情況下,一個遠程調用一般在幾十毫秒內就返回了,當依賴的服務不可用,或者因爲網絡問題時,響應時間將會變得很長(幾十秒 )。而通常情況下,一次遠程調用對應了一個線程/進程,如果響應太慢,那這個線程/進程就會得不到釋放,而線程/進程都對應了系統資源,如果大量的線程/進程得不到釋放, 並且越積越多,服務資源就會被耗盡,從而導致資深服務不可用,所以必須爲每個請求設置超時。
斷路器模式
1、家裏如果沒有斷路器,電流過載了(例如功率過大、短路等),電路不斷開,電路就會升溫,甚至是燒斷電路而起火。有了斷路器之後,當電流過載時,會自動切斷電路(跳閘),從而保護了整條電路與家庭的安全。當電流過載的問題被解決後,只要關閉斷路器,電路就又可以工作了。
2、同樣的道理,當依賴的服務有大量超時時,再讓新的請求去訪問已經沒有太大意義,只會無謂的消耗現有資源,假如我們設置了超時時間爲 1 秒,如果短時間內有大量的請求(譬如50個)在1秒內都 得不到響應,就往往意味着異常。此時就沒有必要讓更多的請求去訪問這個依賴了,我們應該使用斷路器避免資源浪費。
3、斷路器可以實現快速失敗,如果它在一段時間內偵測到許多類似的錯誤(譬如超時),就會強迫其以後的多個調用快速失敗,不再請求所依賴的服務,從而防止應用程序不斷地嘗試執行可能會失敗 的操作,這樣應用程序可以繼續執行而不用等待修正錯誤,或者浪費 CPU 時間去等待長時間的超時。
4、斷路器也可以使應用程序能夠診斷錯誤是否已經修正,如果已經修正,應用程序會再次嘗試調用操作。斷路器模式就像是那些容易導致錯誤的操作的一種代理。這種代理能夠記錄最近調用發生錯誤的次 數,然後決定使用允許操作繼續,或者立即返回錯誤。
Hystrix 斷路器概述
1、Hystrix github 官網:https://github.com/Netflix/Hystrix
2、spring-cloud-netflix-Hystrix 官方文檔:https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.1.0.RELEASE/single/spring-cloud-netflix.html#_circuit_breaker_hystrix_clients
3、Netflix 創建了一個名爲 Hystrix 的庫,該庫實現了斷路器模式。
4、較低級別的服務中的服務故障可能會導致級聯故障,直至用戶。對特定服務的調用超過 circuitBreaker.requestVolumeThreshold(默認值:20個請求),並且故障百分比在 metrics.rollingStats.timeInMilliseconds (默認值:10秒)內大於 circuitBreaker.errorThresholdPercentage(默認值:> 50%),則斷路器開啓,無法繼續請求,在錯誤和斷路的情況下,開發人員可以提供後備功能。
開路可以停止級聯故障,並讓不堪重負的服務故障時間得以恢復。回退可以是另一個受 Hystrix 保護的調用,靜態數據或合理的空值。可以將回退鏈接在一起,以便第一個回退進行其他業務調用,而後者又回退到靜態數據。
Hystrix 斷路器使用
1、使用非常簡單,官網 How to Include Hystrix 介紹的很詳細,分爲3步:
1)pom.xml 文件導入依賴:
spring-cloud-starter-netflix-hystrix
2)啓動類上添加註解表示啓用斷路器模式:
@EnableCircuitBreaker3)控制層方法上添加註解:@HystrixCommand(fallbackMethod = "defaultStores"),表示此方法往外拋出異常時,調用 defaultStores 方法
2、本文環境 java jdk 1.8 + Spring Boot 2.1.3 + Spring Cloud Greenwich.SR1 版本,演示的思路如下:
1)新建一個 Eureka Server 並啓動,應用名叫 eureka_server2020
2)新建一個 Eureka Client 服務提供者客戶端 ec-zebra 註冊到 Eureka Server ,並對外提供服務
3)新建一個 Eureka Client 服務消費者客戶端 snow-leopard 註冊到 Eureka Server,並調用 ec-zebra 微服務。同時使用 hystrix 斷路器。
4)先讓 snow-leopard 正常調通 ec-zebra,然後手動關閉 ec-zebra 微服務,此時 snow-leopard 自動調用 fallbackMethod
5)最後再手動開啓 ec-zebra,snow-leopard 便自動又開始正常調用 ec-zebra 了。
Eureka Server 註冊中心
1、註冊中心 pom.xml 依賴核心內容如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.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-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
2、全局配置文件如下,使用單機模式進行演示:
server:
port: 8761
eureka:
server:
enable-self-preservation: false #關閉自我保護機制
eviction-interval-timer-in-ms: 60000 #驅逐計時器掃描失效服務間隔時間。(單位毫秒,默認 60*1000)
instance:
hostname: localhost
client:
register-with-eureka: false #禁用自己向自己註冊
fetch-registry: false #不同步其他的 Eureka Server節點的數據
service-url: #Eureka Client 與 Eureka Server 交互的地址
default-zone: http://${eureka.instance.hostname}:${server.port}/eureka/
3、啓動類上必須開啓 Eureka Server:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @EnableEurekaServer :開啓 eureka server ,否則 eureka 服務不會生效.
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer2020Application {
public static void main(String[] args) {
SpringApplication.run(EurekaServer2020Application.class, args);
}
}
服務提供者
1、pom.xml 依賴核心如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.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-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
2、全局配置文件內容如下:
server:
port: 9394 #服務器端口
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/ #eureka 服務器地址
instance:
prefer-ip-address: true # IP 地址代替主機名註冊
instance-id: ${eureka.instance.appname}:${spring.cloud.client.ip-address}:${server.port} # 微服務節點實例名稱
appname: ec-zebra #微服務名稱
3、創建一個控制層,新建一個接口對外提供訪問:
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PersonController {
/**
* 獲取斑馬信息
* http://localhost:9394/zebra/person/info?id=33980
*
* @param id
* @return
*/
@GetMapping("zebra/person/info")
public String info(String id) {
JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance;
ObjectNode objectNode = jsonNodeFactory.objectNode();
objectNode.put("code", 200);
objectNode.put("id", id);
objectNode.put("info", "斑馬錦衣衛都指揮使");
return objectNode.toString();
}
}
服務消費者
1、pom.xml 在上面 "服務提供者" 的基礎上加上斷路器 spring-cloud-starter-netflix-hystrix 即可:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2、全局配置文件內容如下:
server:
port: 9395 #服務器端口
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/ #eureka 服務器地址
instance:
prefer-ip-address: true # IP 地址代替主機名註冊
instance-id: ${eureka.instance.appname}:${spring.cloud.client.ip-address}:${server.port} # 微服務節點實例名稱
appname: snow-leopard #微服務名稱
3、啓動類上開啓 hystrix 斷路器(微服務之間的調用,爲了簡單,這裏不使用 feign,直接使用 RestTemplate,加上 ribbon 組件時也是同理):
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* @EnableCircuitBreaker :表示開啓斷路器模式,不寫時只要導入了 hystrix 依賴默認也是開啓的。可以不寫。
* @EnableEurekaClient :只要導入了 eureka-client 依賴,則默認也是開啓 eureka 客戶端的。可以不寫。
*/
@SpringBootApplication
@EnableCircuitBreaker
public class EcHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(EcHystrixApplication.class, args);
}
/**
* 微服務之間的調用,這裏直接使用 org.springframework.web.client.RestTemplate 進行調用
* RestTemplate 它默認是沒有交由 spring 容器管理的,需要自己添加到容器,然後才能使用。
*
* @return
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
4、新建一個控制層,並提供對外訪問的接口,同時方法內部調用上面的服務提供者:
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
public class FoodController {
@Resource
private RestTemplate restTemplate;
private static JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance;
/**
* http://localhost:9395/leopard/food/foods?id=998760
* 1、當用戶調用本接口時,如果本方法內部出現異常,且沒有 try-catch 處理,則會默認自動倒退到 fallbackMethod 指定的方法
* 2、fallbackMethod 屬性指定的回調方法的參數和返回值必須與本接口完全一致,否則拋異常.
* 3、比如 restTemplate.getForObject 超時或者連接拒絕,此時便會自動倒退到 foods_fallbackMethod 方法並返回給用戶,且控制檯也不會看到異常
* 4、再比如本接口內部故意寫一個 int a = 1 / 0; 因爲沒有 try-catch 處理,默認也會自動倒退調用 foods_fallbackMethod 方法,且控制檯不會打印異常信息
* 5、如果本接口內部使用 try{xxx} catch(Xxx e){xxx} 捕獲了異常,則默認不再倒退到 foods_fallbackMethod 方法,而是接着繼續往後走.
*
* @param id
* @return
*/
@GetMapping("leopard/food/foods")
@HystrixCommand(fallbackMethod = "foods_fallbackMethod")
public String foods(String id) {
ObjectNode objectNode = jsonNodeFactory.objectNode();
objectNode.put("code", 200);
objectNode.put("id", id);
objectNode.put("food", "紅燒獅子頭");
String url = "http://localhost:9394/zebra/person/info?id=33980";
ObjectNode responseEntity = restTemplate.getForObject(url, ObjectNode.class);
objectNode.set("info", responseEntity);
return objectNode.toString();
}
public String foods_fallbackMethod(String id) {
ObjectNode objectNode = jsonNodeFactory.objectNode();
objectNode.put("code", 200);
objectNode.put("id", id);
objectNode.put("food", "四喜丸子");
objectNode.set("info", null);
return objectNode.toString();
}
}
斷路器測試
1)先啓動 註冊中心,以及微服務:
2、瀏覽器訪問 snow-leopard 服務消費者,它內部調用 EC-ZEBRA服務提供者,正常級聯調用之後,手動關閉 EC-ZEBRA,接着再訪問 snow-leopard 時,可以看到他已經倒退調用 fallback 方法了,最後重新啓動 EC-ZEBRA,服務級聯調用便又成功了。
3、下面是拓展,表明 @HystrixCommand 標識的接口如果對外拋出異常,則它默認調用 fallback,而如果接口內部的異常手動捕獲處理了,則默認不再倒退,而是繼續執行本接口。
hystrix Health Indicator 健康指標
1、官網 Health Indicator 中介紹 "連接的斷路器的狀態也顯示在調用應用程序的 /health 端點中"。
"hystrix": {
"status": "CIRCUIT_OPEN",
"details": {
"openCircuitBreakers": [
"FoodController::foods"
]
}
}
2、意思很簡單就是 Spring Boot Actuator 監控和管理應用程序 中的 /health 健康信息端點中含有 hystrix 斷路器信息,可以看到當前斷路器的開關狀態,具體到哪些接口已經開啓斷路器。
3、首先在需要監控的微服務中添加 actuator 依賴 :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
4、/health 端點默認是不展示細節給未經驗證授權的用戶的,所以可以手動配置它開啓:
#配置 actuator
management:
endpoint:
health:
show-details: always #將健康信息的細節展示給所有用戶查看,默認爲 never(不展示細節給所有用戶)
5、最後便可以進行驗證了,訪問地址:http://ip:port/content-path/actuator/health,可以看到 hystrix 的信息。注意上面已經說過:當超過 20 個請求,統計故障百分比 10 秒內 > 50% 時,打開斷路器。
CIRCUIT_OPEN:表示斷路器打開。