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 沙箱隔離與通訊
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcukzMiVzY1Y2MxEmMjZ2M5UDMwMWO5EGOxMTMiRzN2MGOvwVbvNmLj5Wat4Wd5lGbh5iY1BXLn1WauU3bop3ZuFGat42YucWbp1iMhRXYvw1LcpDc0RHaiojIsJye.png)
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目錄,找到啟動腳本
找到觀察的java應用程序,然後進行SandBox的attach。使用指令 ./sandbox.sh -p 程序号(建議使用admin使用者執行,否則會遇到一些問題)
挂載成功後,會列印以下内容,其中USER_MODULE_LIB目錄是要将自己的Module挂載的目錄。将自己打包成jar形式的Module通過scp或oss等方式,上傳到該目錄後,執行./sandbox.sh -p 程序号 -f 重新整理Module,
重新整理後,我們看一下Module是否挂載成功
3.2 Module子產品開發
SandBox底層提供了一個HTTP-SERVER(Jetty),通過HTTP協定完成
sandbox.sh
和沙箱的控制互動,同時也給各個子產品提供了基于HttpServlet和WebSocket規範的API,各子產品可以複用沙箱完成各自子產品的控制與互動。
1.首先需要在resource的META-INF.servicesn内配置一個檔案,配置一個Module的入口。
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進行限流
4. 總結
暫時,我們利用SandBox實作了簡單的故障模拟,後續還有跟多的功能開發出來