1.前言
之前初步学习了javaAgent,并做了一份总结《JavaAgent学习笔记》。然后在看到《JVM-Sandbox 基于JVM的非侵入式运行期AOP解决方案》之后,接触到了集团的sandBox。并尝试使用这种有真正应用场景的运行时AOP框架。
2.SandBox简介
sandBox是集团开发的一种非侵入式的运行时AOP解决方案,它能动态地将你要实现的代码模块打包编织到目标代码中,实现事件的监听、切入与代码增强。我们一般可以使用以下场景:
- 线上故障定位
- 线上系统流控
- 线上故障模拟
- 方法请求录制和结果回放
- 动态日志打印
- 安全信息监测和脱敏
- 行链路计算和覆盖率统计
详细的介绍可以阅读这篇篇文章《JVM-Sandbox 基于JVM的非侵入式运行期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 沙箱隔离与通讯
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 = "lixian.wlx@alibaba-inc.com",
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实现了简单的故障模拟,后续还有跟多的功能开发出来