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

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

一、 前言

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

二、 故障注入

1) 什麼是故障注入?

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

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

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

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

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

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

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

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

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

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

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

下圖爲Sermant熱插拔能力的示意圖,Sermant可以在服務運行過程中進行Agent動態安裝,Agent安裝完成後可以通過動態安裝、卸載插件來調整所需的微服務治理能力,也可以卸載整個Agent。

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加載完成故障注入插件之後,會通過類文件轉換器(ClassFileTransformer)對ClassB的sayHello方法進行字節碼增強處理。爲了避免對同一個方法進行多次字節碼增強帶來性能和資源損耗,Sermant只會對目標方法增強一次,第一次增強時會針對目標方法創建攔截器列表,並將攔截器放入其中,後續增強只需要將攔截器放入該增強方法對應的攔截器列表中即可。如下圖所示,圖中InterceptorA爲其他插件對ClassA的print方法進行增強的攔截器,圖中InterceptorB爲其他插件對ClassB的sayHello方法進行增強的攔截器。

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

4) 基於Sermant熱插拔功能實現故障注入的卸載

當故障注入測試結束後,Sermant熱插拔功能的卸載能力可以將故障注入插件卸載,取消故障注入插件所有的字節碼增強,將服務還原到增強前的狀態。卸載之後還可以繼續其他類型故障的注入測試。

當需要關閉故障注入插件時,可以通過Java Attach API來執行JavaAgent的動態機制,Sermant會解析Java Attach API傳遞的命令信息來執行對應的操作,當命令類型爲UNINSTALL_PLUGINS時Sermant會執行卸載流程。

Sermant熱插拔功能會先取消故障注入插件的字節碼增強,並清除故障注入插件的插件信息:例如:插件加載時使用的自定義類加載器、加載時創建的Interceptor、故障注入插件的配置等。

最後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 作爲專注於服務治理領域的字節碼增強框架,致力於提供高性能、可擴展、易接入、功能豐富的服務治理體驗,並會在每個版本中做好性能、功能、體驗的看護,廣泛歡迎大家的加入。

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章