招行二面:為什么有了服務降級,還需要服務熔斷?
在分布式系統中,我們經常聽到服務熔斷這個詞,那么,什么是服務熔斷?為什么需要服務熔斷?如何實現服務熔斷?這篇文章,我們來聊一聊。
一、什么是服務熔斷?
簡單來說,服務熔斷(Circuit Breaker)是一種用于提高分布式系統健壯性的設計模式。它的靈感來源于電路中的熔斷器,當電路中出現問題時,熔斷器會自動斷開,防止故障擴大,保護整個系統。應用在微服務架構中,服務熔斷機制可以在某個服務出現故障或響應緩慢時,快速失敗或采取備用方案,從而避免級聯失敗,提升系統的整體穩定性。
二、原理分析
接下來,我們講解服務熔斷的原理,整體總結成下面五個步驟。
1. 正常狀態
在正常情況下,服務之間的調用是通暢的,熔斷器處于關閉狀態。所有請求都會正常發送到目標服務,沒有任何干預。
2. 監控與檢測
熔斷器會監控目標服務的調用情況,包括請求成功率、失敗率、響應時間等。當某個閾值被超過(比如連續失敗次數超過預設值),熔斷器會認為目標服務可能出現了問題。
3. 打開熔斷
一旦檢測到目標服務可能故障,熔斷器會打開(Open),此時所有對該服務的請求都會被立即失敗,不再發送實際請求。這就像是電路中的熔斷器斷開一樣,防止故障蔓延。
4. 半開啟狀態
過一段時間后,熔斷器會進入半開啟狀態(Half-Open),允許少量請求嘗試調用目標服務。如果這些請求成功,熔斷器會重新關閉,恢復正常狀態;如果失敗,熔斷器繼續保持打開狀態。
5. 備用機制
當熔斷器打開時,可以采取備用方案,比如返回默認值、跳過某些操作,甚至切換到其他服務實例,以保證系統的部分功能仍然可用。
通過這樣的機制,服務熔斷能夠有效地防止單個服務故障導致的系統級別的連鎖反應。
三、示例演示
為了更好地理解服務熔斷,接下來,我們將使用 Resilience4j 這個輕量級的容錯庫來實現服務熔斷機制。Resilience4j是一個專為 Java 8及以上版本設計的庫,具有易用性和高性能的特點。
1. 環境準備
首先,確保你的項目中已經引入了Resilience4j的依賴。以Maven項目為例,添加以下依賴到pom.xml中:
<dependencies>
<!-- Resilience4j核心依賴 -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-all</artifactId>
<version>2.0.2</version>
</dependency>
<!-- 其他依賴項 -->
</dependencies>
2. 編寫服務熔斷代碼
下面是一個簡單的示例,展示如何使用Resilience4j實現服務熔斷。當目標服務響應慢或失敗時,熔斷器會起作用,快速返回備用結果。
import io.github.resilience4j.circuitbreaker.*;
import io.github.resilience4j.decorators.Decorators;
import java.time.Duration;
import java.util.concurrent.*;
import java.util.function.Supplier;
publicclass CircuitBreakerDemo {
public static void main(String[] args) {
// 創建CircuitBreaker配置
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 失敗率閾值
.waitDurationInOpenState(Duration.ofSeconds(5)) // 打開狀態持續時間
.slidingWindowSize(4) // 滑動窗口大小
.build();
// 創建CircuitBreaker實例
CircuitBreaker circuitBreaker = CircuitBreaker.of("myCircuitBreaker", config);
// 模擬目標服務調用
Supplier<String> decoratedSupplier = Decorators.ofSupplier(() -> callExternalService())
.withCircuitBreaker(circuitBreaker)
.withFallback(Collections.singletonList(CircuitBreaker.class),
throwable -> "默認響應")
.decorate();
// 模擬多次調用
for (int i = 0; i < 10; i++) {
try {
String response = decoratedSupplier.get();
System.out.println("響應: " + response);
} catch (Exception e) {
System.out.println("調用失敗: " + e.getMessage());
}
// 等待1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
// 模擬外部服務調用,隨機失敗或延時
private static String callExternalService() {
double random = Math.random();
if (random < 0.5) {
// 模擬失敗
thrownew RuntimeException("服務調用失敗");
} else {
// 模擬延時
try {
Thread.sleep(2000); // 2秒延時
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return"成功響應";
}
}
}
3. 代碼解析
- 配置CircuitBreaker:我們創建了一個自定義的熔斷器配置,設置了失敗率閾值為50%,滑動窗口大小為4次調用,打開狀態持續5秒。
- 裝飾目標服務調用:使用Decorators將目標服務調用裝飾為一個有熔斷器保護的供應者(Supplier)。同時,我們設置了一個備用響應,當熔斷器打開或目標服務調用失敗時,返回“默認響應”。
- 模擬調用:在for循環中,我們模擬了多次服務調用。目標服務callExternalService隨機成功或失敗,并可能產生延時。通過這種方式,我們可以觀察熔斷器是如何根據調用結果自動切換狀態的。
運行這段代碼,當失敗率超過 50%時,熔斷器會打開,后續的請求會立即返回“默認響應”。經過 5秒后,熔斷器會進入半開啟狀態,嘗試恢復調用。如果目標服務恢復正常,熔斷器會重新關閉,系統恢復正常運行。
四、問題解答
回到文章的標題:為什么有了服務降級還需要服務熔斷?
這里我們總結了四個核心理由:
- 避免資源浪費:當一個服務出現故障時,如果沒有熔斷機制,系統可能會持續不斷地嘗試調用這個失敗的服務,導致請求積壓和資源耗盡。服務熔斷通過快速失敗,避免了不必要的調用,節省了寶貴的系統資源。
- 防止級聯故障:在微服務架構中,服務之間通常相互依賴。如果一個服務出現問題,持續的失敗調用可能會影響到依賴它的其他服務,導致級聯故障。服務熔斷器可以在問題初期及時切斷受影響的服務調用,防止故障擴散到整個系統。
- 加速系統恢復:通過熔斷機制,系統能夠更快地檢測到服務的故障狀態,并在熔斷器打開后,等待一段時間再嘗試恢復調用。這有助于目標服務有足夠的時間進行自我修復,從而加速整個系統的恢復過程。
- 提供更好的用戶體驗: 服務降級雖然能夠保證核心功能的可用性,但在高負載或持續失敗的情況下,用戶可能會頻繁遇到降級后的功能或默認響應,影響使用體驗。服務熔斷器通過控制調用頻率和恢復策略,能夠在保證必要降級的同時,減少對用戶的負面影響。