JavaAgent-SandBox

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 沙箱隔离与通讯

JavaAgent-SandBox

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-SandBox

找到观察的java应用进程,然后进行SandBox的attach。使用指令 ./sandbox.sh -p 进程号(建议使用admin用户执行,否则会遇到一些问题)

JavaAgent-SandBox

挂载成功后,会打印以下内容,其中USER_MODULE_LIB目录是要将自己的Module挂载的目录。将自己打包成jar形式的Module通过scp或oss等方式,上传到该目录后,执行./sandbox.sh -p 进程号 -f 刷新Module,

刷新后,我们看一下Module是否挂载成功

JavaAgent-SandBox

3.2 Module模块开发

SandBox底层提供了一个HTTP-SERVER(Jetty),通过HTTP协议完成sandbox.sh和沙箱的控制交互,同时也给各个模块提供了基于HttpServlet和WebSocket规范的API,各模块可以复用沙箱完成各自模块的控制与交互。

1.首先需要在resource的META-INF.servicesn内配置一个文件,配置一个Module的入口。

JavaAgent-SandBox

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进行限流

JavaAgent-SandBox

4. 总结

暂时,我们利用SandBox实现了简单的故障模拟,后续还有跟多的功能开发出来

上一篇:【虚拟化实战】Cluster设计之三HA


下一篇:jQuery插件开发初探