Sermant熱插拔能力在故障注入場景的實踐

华为云开发者联盟發表於2024-04-07

本文分享自華為雲社群《Sermant熱插拔能力在故障注入場景的實踐》,作者:張豪鵬 華為雲高階軟體工程師

一、 前言

Sermant是基於Java位元組碼增強技術的無代理服務網格,採用Java位元組碼增強技術為宿主應用程式提供服務治理功能。從1.2.0版本開始,Sermant已經實現了在服務不停機狀態下進行安裝和解除安裝的熱插拔功能,在上一篇文章《服務執行時動態掛載JavaAgent和外掛——Sermant熱插拔能力解析》中已經介紹了Sermant熱插拔功能的實現原理。本篇文章將透過故障注入場景,來展示Sermant熱插拔能力的應用價值。

二、 故障注入

1) 什麼是故障注入?

故障注入是一種測試方法,它透過在系統中故意引入錯誤或故障,來測試系統對這些錯誤或故障的響應和恢復能力,並驗證系統是否能夠正常處理這些異常情況。下圖是故障注入測試中的一些常見故障:

Sermant熱插拔能力在故障注入場景的實踐

透過故障注入技術,測試人員可以在Java應用中模擬各種故障場景,以便評估應用的響應能力和恢復能力,還可以幫助提前攔截和發現Java應用潛在的可靠性問題,提升應用穩定性,避免現網出現重大質量事故。例如:

  • 透過在指定方法中丟擲自定義異常可以測試系統在異常情況下的穩定性
  • 透過修改指定方法的返回值可以測試系統在異常資料情況下的處理能力
  • 透過資料庫故障可以測試系統在異常情況下是否可以保持資料一致性等。

2) Java應用實現故障注入方案的難點是什麼?

傳統的故障注入方案是在應用中透過手動或者指令碼的方式來引入故障,例如:修改程式碼、改變輸入值、隨機錯誤注入等。傳統方式在進行故障注入測試時會存在以下問題:

  • 透過修改程式碼來注入故障時,每次注入新的故障都需要進行程式碼修改並重啟服務,影響故障注入的效率
  • 透過開關配置來進行故障注入時,需要增加大量的開關邏輯判斷

因此如何在不重啟應用的情況下實現故障注入,並且可以重複的進行故障注入對提高故障注入測試的效率和全面性變得至關重要。

三、 Sermant熱插拔功能在故障注入場景下的應用

Sermant熱插拔功能可以在服務不停機狀態下進行故障注入外掛的安裝和解除安裝,故障注入外掛在外掛安裝的時候可以指定任意方法進行故障注入,在故障注入測試完成後可以解除安裝該外掛來避免影響系統執行,故障注入外掛解除安裝完成後可以重新安裝故障注入插入進行其他方法的故障注入。

1) Sermant熱插拔功能是什麼?

Sermant熱插拔功能是基於JavaAgent動態載入機制實現的,可以在服務不停機狀態下進行Java Agent和外掛的安裝、解除安裝,而且安裝、解除安裝外掛時不會影響其他外掛的正常執行。

下圖為Sermant熱插拔能力的示意圖,Sermant可以在服務執行過程中進行Agent動態安裝,Agent安裝完成後可以透過動態安裝、解除安裝外掛來調整所需的微服務治理能力,也可以解除安裝整個Agent。

Sermant熱插拔能力在故障注入場景的實踐

2) 基於Sermant實現故障注入外掛

Sermant外掛主要透過實現以下介面來實現位元組碼增強的功能:

  1. 透過實現PluginConfig來定義外掛需要的配置。
  2. 透過實現AbstractPluginDeclarer來宣告進行位元組碼增強的方法,類似於面向切面程式設計中的Joint point。
  3. 透過實現AbstractInterceptor來定義攔截器,類似於面向切面程式設計中的Advice。

Sermant外掛的詳細介紹請參考文章《開發者能力機制解析,玩轉Sermant開發》

故障注入外掛可以在PluginConfig實現類中接受Sermant動態安裝時傳遞的引數資訊。(基於Sermant熱插拔功能進行動態安裝時,可以透過Java Attach API傳輸的引數來設定PluginConfig實現類的屬性值)。下面為配置類FaultInjectConfig的程式碼實現:

@ConfigTypeKey("fault")  
public class FaultInjectConfig implements PluginConfig {  
    private String className;  
  
    private String methodName;  
  
    private String exceptionName;  
  
    public String getClassName() {  
        return className;  
    }  
  
    public void setClassName(String className) {  
        this.className = className;  
    }  
  
    public String getMethodName() {  
        return methodName;  
    }  
  
    public void setMethodName(String methodName) {  
        this.methodName = methodName;  
    }  
  
    public String getExceptionName() {  
        return exceptionName;  
    }  
  
    public void setExceptionName(String exceptionName) {  
        this.exceptionName = exceptionName;  
    }  
} 

故障注入外掛可以在AbstractPluginDeclarer實現類中宣告進行位元組碼增強的類和方法,即進行故障注入的類和方法。結合配置類FaultInjectConfig,故障注入外掛可以在Sermant動態安裝時調整故障注入的類和方法。如下面程式碼塊所示(下面為故障注入宣告器FaultInjectDeclarer的程式碼,基於配置類FaultInjectConfig宣告故障注入的類和方法):

public class FaultInjectDeclarer extends AbstractPluginDeclarer {  

    private FaultInjectConfig faultInjectConfig = PluginConfigManager.getPluginConfig(FaultInjectConfig.class);  
  
    // 透過faultInjectConfig的className匹配需要進行位元組碼增強的類  
    @Override  
    public ClassMatcher getClassMatcher() {  
        return ClassMatcher.nameEquals(faultInjectConfig.getClassName());  
    }  
  
    // 攔截宣告,透過faultInjectConfig的methodName匹配需要進行增強的方法,並宣告攔截器為CustomExceptionInterceptor  
    @Override  
    public InterceptDeclarer[] getInterceptDeclarers(ClassLoader classLoader) {  
        return new InterceptDeclarer[]{  
                InterceptDeclarer.build(MethodMatcher.nameEquals(faultInjectConfig.getMethodName()),  
                        new CustomExceptionInterceptor())};  
    }  
} 

故障注入外掛需要在AbstractInterceptor的實現類中定義位元組碼增強的邏輯,即進行故障注入,下面程式碼塊為實現在方法執行前丟擲自定義異常的故障注入邏輯:

public class CustomExceptionInterceptor extends AbstractInterceptor {  
    private FaultInjectConfig faultInjectConfig = PluginConfigManager.getPluginConfig(FaultInjectConfig.class);  
  
    @Override  
    public ExecuteContext before(ExecuteContext context) throws Exception {  
        // 例項化需要丟擲的異常,並設定  
        Exception exception = (Exception) Class.forName(faultInjectConfig.getExceptionName())  
                .getConstructor(null).newInstance(null);  
        context.setThrowableOut(exception);  
  
        // 設定跳過方法原有執行邏輯  
        context.skip(new Object());  
        return context;  
    }  
  
    @Override  
    public ExecuteContext after(ExecuteContext context) throws Exception {  
        return context;  
    }  
} 

透過實現AbstractPluginDeclarer和AbstractInterceptor,故障注入外掛可以在任何方法中注入想要的故障型別。

接下來我們以在ClassB的sayHello方法注入自定義異常這個故障為例來看Sermant是如何實現故障注入的。

3) 基於Sermant熱插拔功能實現故障注入外掛的安裝

首先需要利用Java Attach API將Sermant載入到已執行的服務中,然後Sermant會解析Java Attach API傳輸的引數來執行命令解析,根據解析出來的命令型別來執行對應的命令。當命令型別為INSTALL-PLUGINS時,Sermant會執行安裝命令。

Sermant熱插拔功能會先獲取故障注入外掛包的路徑並進行載入,Sermant採用自定義的類載入器PluginClassLoader和ServiceClassLoader對外掛包中的類進行載入(Sermant類隔離架構解析可訪問文章《Sermant類隔離架構解析——解決JavaAgent場景類衝突的實踐》)。然後從Java Attach API傳輸的引數中解析需要增強的類名ClassB和方法資訊sayHello,設定配置類FaultInjectConfig的屬性className為ClassB、methodName為sayHello。

Sermant熱插拔能力在故障注入場景的實踐

Sermant載入完成故障注入外掛之後,會透過類檔案轉換器(ClassFileTransformer)對ClassB的sayHello方法進行位元組碼增強處理。為了避免對同一個方法進行多次位元組碼增強帶來效能和資源損耗,Sermant只會對目標方法增強一次,第一次增強時會針對目標方法建立攔截器列表,並將攔截器放入其中,後續增強只需要將攔截器放入該增強方法對應的攔截器列表中即可。如下圖所示,圖中InterceptorA為其他外掛對ClassA的print方法進行增強的攔截器,圖中InterceptorB為其他外掛對ClassB的sayHello方法進行增強的攔截器。

Sermant熱插拔能力在故障注入場景的實踐

透過Sermant熱插拔功能給ClassB的sayHello方法注入自定義異常的故障之後,在執行ClassB的sayHello方法時,攔截器就會攔截sayHello方法並執行故障注入邏輯,丟擲自定義異常。如下圖所示:

Sermant熱插拔能力在故障注入場景的實踐

4) 基於Sermant熱插拔功能實現故障注入的解除安裝

當故障注入測試結束後,Sermant熱插拔功能的解除安裝能力可以將故障注入外掛解除安裝,取消故障注入外掛所有的位元組碼增強,將服務還原到增強前的狀態。解除安裝之後還可以繼續其他型別故障的注入測試。

當需要關閉故障注入外掛時,可以透過Java Attach API來執行JavaAgent的動態機制,Sermant會解析Java Attach API傳遞的命令資訊來執行對應的操作,當命令型別為UNINSTALL_PLUGINS時Sermant會執行解除安裝流程。

Sermant熱插拔功能會先取消故障注入外掛的位元組碼增強,並清除故障注入外掛的外掛資訊:例如:外掛載入時使用的自定義類載入器、載入時建立的Interceptor、故障注入外掛的配置等。

Sermant熱插拔能力在故障注入場景的實踐

最後Sermant會關閉類載入器、清除快取的外掛資訊,將故障注入外掛完全解除安裝。

Sermant熱插拔能力在故障注入場景的實踐

解除安裝完成之後不會影響原服務的功能,而且故障注入外掛可以再此安裝,進行其他的故障測試。

5) 基於Sermant熱插拔功能實現故障注入外掛的第二次安裝

故障注入外掛解除安裝完成以後,還可以透過Sermant熱插拔功能重新安裝故障注入外掛。故障注入外掛支援透過調整FaultInjectConfig的屬性配置來為其他方法注入故障。重新安裝時,可以透過調整Java Attach API傳遞的引數來修改FaultInjectConfig的配置,透過配置不同的類名和方法名可以對其他的方法進行故障注入,例如:設定FaultInjectConfig中的類名和方法名為ClassC和printResult,就可以在ClassC的printResult方法中注入故障,重新安裝故障注入外掛流程和第一次安裝沒有任何區別,這裡就不再贅述。

依賴於Sermant熱插拔功能的外掛解除安裝能力可以完全解除安裝故障注入外掛,重新安裝故障注入外掛不需要進行任何特殊處理。重新安裝故障注入外掛之後,就可以針對新的方法進行故障注入測試。

四、 Sermant熱插拔功能應用探索

透過Sermant熱插拔功能在故障場景的應用,已經可以看出Sermant熱插拔功能在故障注入場景下可以發揮巨大的作用,但Sermant熱插拔功能除了在故障注入場景還可以在故障診斷、以及微服務應用的升級下發揮巨大的作用。例如:

故障診斷:當服務出現故障時,可以透過Sermant熱插拔功能動態安裝外掛去獲取服務的關鍵資訊,如執行緒堆疊跟蹤、記憶體使用情況、方法執行時間等。這些資訊可以幫助開發人員快速診斷應用程式的故障,並且可以在應用程式執行時進行修改和最佳化。

微服務治理能力升級:當需要進行微服務治理能力升級時,也可以透過Sermant熱插拔功能將升級的程式碼透過外掛的形式動態的安裝到微服務應用中,而不需要重啟服務。

五、 總結

本篇文章介紹了Sermant熱插拔功能在故障注入場景的應用,透過故障注入場景我們可以發現,Sermant熱插拔功能在故障注入場景下可以發揮重大的作用。利用Sermant熱插拔功能開發者和使用者可以在微服務執行過程中動態的進行故障注入,還可以多次注入不同的故障,幫助測試微服務的可靠性、穩定性。

Sermant熱插拔功能不僅可用於故障注入,還可用故障診斷、以及微服務應用的升級等場景。Sermant熱插拔功能不在微服務治理方面可以為開發者和使用者提供了更多的便利,幫助他們更有效地管理和維護微服務應用。

Sermant 作為專注於服務治理領域的位元組碼增強框架,致力於提供高效能、可擴充套件、易接入、功能豐富的服務治理體驗,並會在每個版本中做好效能、功能、體驗的看護,廣泛歡迎大家的加入。

  • Sermant官網:https://sermant.io
  • GitHub倉庫地址:https://github.com/huaweicloud/Sermant

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章