天天看點

JavaAgent-SandBox1.前言2.SandBox簡介3. 簡單的SandBox功能實作4. 總結

1.前言

之前初步學習了javaAgent,并做了一份總結

《JavaAgent學習筆記》

。然後在看到

《JVM-Sandbox 基于JVM的非侵入式運作期AOP解決方案》

之後,接觸到了集團的sandBox。并嘗試使用這種有真正應用場景的運作時AOP架構。

2.SandBox簡介

sandBox是集團開發的一種非侵入式的運作時AOP解決方案,它能動态地将你要實作的代碼子產品打包編織到目标代碼中,實作事件的監聽、切入與代碼增強。我們一般可以使用以下場景:

  • 線上故障定位
  • 線上系統流控
  • 線上故障模拟
  • 方法請求錄制和結果回放
  • 動态日志列印
  • 安全資訊監測和脫敏
  • 行鍊路計算和覆寫率統計

詳細的介紹可以閱讀這篇篇文章

,個人簡單總結以下幾點:

2.1 AOP切入方式

SandBox圍繞“EventListener事件監聽”、“Filter目标過濾”、“onEvent事件處理”來實作AOP的切入。

首先,SandBox将事件分類為BEFORE、 RETURN和 THROWS三個環節事件,除此之外還有個LINE。然後觀察者通過監聽這幾個環節的事件進行流轉和幹預

+-------+
                                        |       |
 +========+  <return>             +========+    | <return immediately>
 |        |  <return immediately> |        |    |
 | BEFORE |---------------------->| RETURN |<---+
 |        |                       |        |
 +========+                       +========+
     |                              |    ^
     |         <throws immediately> |    |
     |                              |    | <return immediately>
     |                              v    |
     |                            +========+
     |                            |        |
     +--------------------------->| THROWS |<---+
                    <throws>      |        |    |
        <throws immediately>      +========+    | <throws immediately>
                                        |       |
                                        +-------+           

     2.2 沙箱隔離與通訊

JavaAgent-SandBox1.前言2.SandBox簡介3. 簡單的SandBox功能實作4. 總結

1.SandBox與Tomcat同級加載,并且Tomcat是一定程度破壞了雙親委派機制的,實作各個WebApp之前的隔離。這種與Tomcat同級加載的方式,一定程度保證了類的隔離,防止沖突與污染。

2.由Bootstrap加載的Spy類實作了SandBox與被觀察的應用的事件通信,然後SandBox将這些事件資訊分發給對應的Module進行處理。

2.3 動态代碼織入

1.使用過濾器在JVM中找到目标類的資料資訊

2.trasform方法形變原生位元組碼,插入Spy類到位元組碼中,通過Spy方法中反射調用JVM-Sandbox的方法。

3. 簡單的SandBox功能實作

3.1啟動SandBox

現在集團的伺服器一般都內建了SandBox的插件環境,切換到/home/staragent/plugins/JVM-Sandbox.src/JVM-Sandbox.cur/sandbox/bin目錄,找到啟動腳本

JavaAgent-SandBox1.前言2.SandBox簡介3. 簡單的SandBox功能實作4. 總結

找到觀察的java應用程序,然後進行SandBox的attach。使用指令 ./sandbox.sh -p 程序号(建議使用admin使用者執行,否則會遇到一些問題)

JavaAgent-SandBox1.前言2.SandBox簡介3. 簡單的SandBox功能實作4. 總結

挂載成功後,會列印以下内容,其中USER_MODULE_LIB目錄是要将自己的Module挂載的目錄。将自己打包成jar形式的Module通過scp或oss等方式,上傳到該目錄後,執行./sandbox.sh -p 程序号 -f 重新整理Module,

重新整理後,我們看一下Module是否挂載成功

JavaAgent-SandBox1.前言2.SandBox簡介3. 簡單的SandBox功能實作4. 總結

3.2 Module子產品開發

SandBox底層提供了一個HTTP-SERVER(Jetty),通過HTTP協定完成

sandbox.sh

和沙箱的控制互動,同時也給各個子產品提供了基于HttpServlet和WebSocket規範的API,各子產品可以複用沙箱完成各自子產品的控制與互動。

1.首先需要在resource的META-INF.servicesn内配置一個檔案,配置一個Module的入口。

JavaAgent-SandBox1.前言2.SandBox簡介3. 簡單的SandBox功能實作4. 總結

2.編寫Module類

需要實作Module, ModuleLifecycle這2個接口,添加@Information注釋,然後編寫具體的增強代碼。這裡使用了@Http,通過指令,實作退這些方法的調用。這裡需要自己寫Filter與EventListener,進行目标對象的過濾與監聽,還有事件處理。

需要moduleEventWatcher加載這個監聽器,然後使用moduleContraller激活這個監聽器。

@Information(version = GlobalConfig.VERSION, author = "[email protected]",
    id = GlobalConfig.NAME, isActiveOnLoad = false)
public class DrillModule implements Module, ModuleLifecycle {

    @Resource
    private ModuleController moduleController;

    @Resource
    private ModuleEventWatcher moduleEventWatcher;

    @Override
    public void onLoad() throws Throwable {
        //TODO
    }

    @Override
    public void loadCompleted() {
        //TODO
    }

    @Override
    public void onUnload() throws Throwable {
        //TODO
    }

    @Override
    public void onActive() throws Throwable {
        //TODO
    }

    @Override
    public void onFrozen() throws Throwable {
        //TODO
    }

    @Http("/mps")
    public void mockProvideSentinel(HttpServletRequest request, HttpServletResponse response) throws Throwable {
        //擷取入參
        MockConfigModel mockConfigModel = null;

        String mockClassName = null;
        String mockMethodName = null;
        String uniqueCode = null;
        PrintWriter writer = null;
        try {
            mockConfigModel = processMockRequest(request);
            mockClassName = mockConfigModel.getMockClassName();
            mockMethodName = mockConfigModel.getMockMethodName();
            uniqueCode = buildUniqueCode(mockConfigModel);
            writer = new PrintWriter(response.getWriter(), true);
        } catch (Throwable throwable) {
            logger.error("mockProvideSentinel get param error {} of {}", mockMethodName, mockClassName, throwable);
        }
        if (methodMap.get(uniqueCode) != null) {
            writer.write("mockProvideSentinel fail mockModel:{" + uniqueCode +"} has exist!");
            writer.close();
            logger.warn("mockProvideSentinel fail {} of {} has exist!", mockMethodName, mockClassName);
            return;
        }
        //實作aop切入,傳回watcherId
        try {
            int watcherId = moduleEventWatcher.watch(new Hsf2InvokeFilter(), new HsfListener(mockClassName, mockMethodName, new HsfInvokeSentinelHandler()), Type.BEFORE);
            //生成mock業務
            watcherIds.add(watcherId);
            methodMap.put(uniqueCode, watcherId);
            watcherMap.put(watcherId, uniqueCode);
            moduleController.active();
            //傳回mock結果
            writer.write(watcherId);
            writer.close();
            logger.info("mockProvideSentinel success mock {} of {}. watcherId: {}", mockMethodName, mockClassName, watcherId);
        } catch (ModuleException e) {
            writer.write(e.toString());
            writer.close();
            logger.error("mockProvideSentinel error {} of {}", mockMethodName, mockClassName, e);
        }

    }    

}
           

3. 以上代碼是模拟HSF的用戶端對某個API的限流

./sandbox.sh -p 程序号 -d 'tfdrill-module/mps?mockClassName=EmployeeAPI',  針對這個api進行限流

JavaAgent-SandBox1.前言2.SandBox簡介3. 簡單的SandBox功能實作4. 總結

4. 總結

暫時,我們利用SandBox實作了簡單的故障模拟,後續還有跟多的功能開發出來