介绍一种非侵入式运行期AOP解决方案—JVM-SANDBOX

sandbox

简介

随着软件部署规模的扩大,系统功能的细化,系统间耦合度和链路复杂度不断加强。要继续保持现有规模系统的稳定性,需要实现并完善监控体系、故障定位分析、流量录制回放、强弱依赖检测、故障演练等支撑工具平台。

出于对服务器规模和业务稳定性的考量,这些配套工具平台要具备无侵入、实时生效、动态可插拔的特点。JVM-SANDBOX就是这样一种基于JVM的非侵入式运行期AOP解决方案,由阿里测试开发专家鸾伽(花名)开发,现已在Github上开源。

本文根据 GitHub 收录的文档简单整理,方便后面查看使用。

Github地址:https://github.com/alibaba/jvm-sandbox

[TOC]

项目介绍

沙箱的特性

  • 无侵入:目标应用无需重启也无需感知沙箱的存在

  • 类隔离:沙箱以及沙箱的模块不会和目标应用的类相互干扰

  • 可插拔:沙箱以及沙箱的模块可以随时加载和卸载,不会在目标应用留下痕迹

  • 多租户:目标应用可以同时挂载不同租户下的沙箱并独立控制

  • 高兼容:支持JDK[6,11]

沙箱常见应用场景

要保持系统的稳定性,需要的能力有系统限流、故障模拟、信息监控、链路跟踪、问题定位、业务回归、风险评估等等模块

  • 线上故障定位:无需重启,attach到目标机器上,获取系统和方法信息
  • 线上系统流控:为应用设定最大服务能力阈值,超过阈值自动,系统不提供服务,保证本系统的稳定,防止流量过大压垮系统
  • 线上故障模拟:向系统内注入故障,验证系统的稳定性
  • 流量录制回放:线上录制流量,线下回放,完成回归
  • 性能压测:使用录制流量,脱敏后,为压测提供流量
  • 链路跟踪:系统间和系统内链路跟踪
  • 动态日志打印
  • 安全信息监测和脱敏

实时无侵入AOP框架

proxy方案:基于对目标方法采用代理模式,常见的AOP框架实现方案中,有静态代理和动态代理两种。但是无论是静态织入还是动态织入的代理模式都必须新增一个方法来完成,而JDK的规范中运行期禁止对类进行新增方法操作,从而限制了现有AOP方案只能在JVM启动时、或代码编译时生效。

埋点方案:不修改原代码的情况下,动态将字节码注入到运行的类中,实现问题跟踪定位、数据获取等功能。

proxy的解决方案实现了标准API,减少重复投入,但是不能实时生效,灵活度不够;埋点方案与之相反。

JVM-Sandbox为AOP提供了一个新的实现方案:以插桩代替代理。基于JVMTI技术规范,为观察和改变代码运行结果提供了即插即用模块接口的容器。使用字节码增强技术使JVM-Sandbox的模块能在不违反JDK约束情况下实现对目标应用方法的无侵入运行时AOP拦截。

快速开始

安装沙箱

环境要求:

  1. JDK[6,11];

  2. Linux/UNIX/MacOS;

下载安装:

1
2
3
4
5
# 下载最新版本的JVM-SANDBOX
wget http://ompc.oss-cn-hangzhou.aliyuncs.com/jvm-sandbox/release/sandbox-stable-bin.zip

# 解压
unzip sandbox-stable-bin.zip

当前我使用的是jvm-sandbox@1.2.1版本,文件目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
./sandbox/
+--bin/
| `--sandbox.sh
|
+--cfg/
| +--sandbox-logback.xml
| +--sandbox.properties
| `--version
|
+--example/
| `--sandbox-debug-module.jar
|
+--install-local.sh
|
+--lib/
| +--sandbox-agent.jar
| +--sandbox-core.jar
| `--sandbox-spy.jar
|
+--module/
| `--sandbox-mgr-module.jar
|
+--provider/
| `--sandbox-mgr-provider.jar
|
`--sandbox-module/

启动沙箱

沙箱有两种启动方式:ATTACHAGENT

  • ATTACH方式启动

    即插即用的启动模式,可以在不重启目标JVM的情况下完成沙箱的植入。

    1
    2
    3
    4
    5
    6
    7
    8
    # 查看进程号
    ps -aux |grep java

    # 进入沙箱执行脚本
    cd sandbox/bin

    # 假设目标JVM进程 18233
    ./sandbox.sh -p 18233

    注:sandbox.sh是整个沙箱的主要操作客户端,可以通过 ./sandbox.sh -h 命令输出帮助信息。详见:sandbox.sh命令详解

  • 挂载成功后会提示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ./sandbox.sh -p 18233
    NAMESPACE : default
    VERSION : 1.2.1
    MODE : ATTACH
    SERVER_ADDR : 0.0.0.0
    SERVER_PORT : 29071
    UNSAFE_SUPPORT : ENABLE
    SANDBOX_HOME : /opt/quote/sandbox/bin/..
    SYSTEM_MODULE_LIB : /opt/quote/sandbox/bin/../module
    USER_MODULE_LIB : /opt/quote/sandbox/sandbox-module;
    SYSTEM_PROVIDER_LIB : /opt/quote/sandbox/bin/../provider
    EVENT_POOL_SUPPORT : DISABLE
  • 查看挂载模块

    1
    2
    3
    4
    5
    ./sandbox.sh -p 18233 -l
    sandbox-info ACTIVE LOADED 0 0 0.0.4 luanjia@taobao.com
    sandbox-module-mgr ACTIVE LOADED 0 0 0.0.2 luanjia@taobao.com
    sandbox-control ACTIVE LOADED 0 0 0.0.3 luanjia@taobao.com
    total=3
  • 查看沙箱日志

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    cat ~/logs/sandbox/sandbox.log |more
    2019-08-06 17:43:19 default INFO
    ___ ____ __ ____ _ _ _ ____ ____ _____ __
    | \ \ / / \/ | / ___| / \ | \ | | _ \| __ ) / _ \ \/ /
    _ | |\ \ / /| |\/| |____\___ \ / _ \ | \| | | | | _ \| | | \ /
    | |_| | \ V / | | | |_____|__) / ___ \| |\ | |_| | |_) | |_| / \
    \___/ \_/ |_| |_| |____/_/ \_\_| \_|____/|____/ \___/_/\_\
    2019-08-06 17:43:19 default INFO initializing logback success. file=/opt/quote/sandbox/bin/../cfg/sandbox-logback.xml;
    ...
    2019-08-06 17:43:20 default INFO initialized server. actual bind to 0.0.0.0:36406
    2019-08-06 17:43:20 default INFO server[0.0.0.0:0] bind success.
  • AGENT方式启动

    有些时候我们需要沙箱工作在应用代码加载之前,或者一次性渲染大量的类、加载大量的模块,此时如果用ATTACH方式加载,可能会引起目标JVM的卡顿或停顿(GC),这就需要启用到AGENT的启动方式。

    假设SANDBOX被安装在了/opt/quote/sandbox,需要在JVM启动参数中增加上

    1
    -javaagent:/opt/quote/sandbox/lib/sandbox-agent.jar

卸载沙箱

1
2
./sandbox.sh -p 18233 -S
jvm-sandbox[default] shutdown finished.

配置与使用

沙箱配置

配置文件说明

./sandbox/cfg/
沙箱配置文件存放目录,里面存放了沙箱的所有配置文件

  • ./sandbox/cfg/version
    沙箱容器版本号
  • ./sandbox/cfg/sandbox.properties
    存放沙箱容器的配置信息,配置文件只会在沙箱容器启动的时候加载一次。详见:详细配置解释
  • ./sandbox/cfg/sandbox-logback.xml
    Logback日志配置

详细配置解释

配置文件:./cfg/sandbox.properties

配置项 说明 默认值
system_module 系统模块本地路径 ./module
provider 强化服务提供包本地路径 ./provider
user_module 用户模块本地路径 ~/.sandbox-module
server.ip 沙箱HTTP服务IP 0.0.0.0
server.port 沙箱HTTP服务端口 0(随机端口)
unsafe.enable 是否允许增强JDK自带类 false
  • user_module

    用户模块本地路径是一个多值通配符表达式,如果用户模块散落在本地多个不同的路径下,可以通过配置多个路径(;分割),亦或者可以配置通配符表达式。

    这里,我将用户文件路径配置为:

    1
    user_module=/opt/quote/sandbox/sandbox-module
  • unsafe.enable

    控制容器是否能让模块增强JDK自带的类,默认值为FALSE,即不允许增强JDK自带的类。

沙箱使用

快速入门

快速开始写一个入门模块:修复一个损坏了的钟

进阶使用

沙箱分发包中自带了实用工具的例子./example/sandbox-debug-module.jar,代码在沙箱的sandbox-debug-module模块。

例子 例子说明
DebugWatchModule.java 模仿GREYS的watch命令
DebugTraceModule.java 模仿GREYES的trace命令
DebugRalphModule.java 无敌破坏王,故障注入(延时、熔断、并发限流、TPS限流)
LogExceptionModule.java 记录下你的应用都发生了哪些异常 $HOME/logs/sandbox/debug/exception-monitor.log
LogServletAccessModule.java 记录下你的应用的HTTP服务请求 $HOME/logs/sandbox/debug/servlet-access.log

这里可以将sandbox-debug-module.jar包复制到上面配置的/opt/quote/sandbox/sandbox-module用户模块文件路径下,重新挂载后查看挂载模块如下

1
2
3
4
5
6
7
8
9
10
11
./sandbox.sh -p 18233 -l
debug-ralph ACTIVE LOADED 0 0 0.0.2 luanjia@taobao.com
debug-exception-logger ACTIVE LOADED 1 5 0.0.2 luanjia@taobao.com
sandbox-info ACTIVE LOADED 0 0 0.0.4 luanjia@taobao.com
sandbox-module-mgr ACTIVE LOADED 0 0 0.0.2 luanjia@taobao.com
debug-trace ACTIVE LOADED 0 0 0.0.2 luanjia@taobao.com
debug-lifecycle ACTIVE LOADED 0 0 0.0.1 luanjia@taobao.com
sandbox-control ACTIVE LOADED 0 0 0.0.3 luanjia@taobao.com
debug-watch ACTIVE LOADED 0 0 0.0.2 luanjia@taobao.com
debug-servlet-access ACTIVE LOADED 2 2 0.0.2 luanjia@taobao.com
total=9
  • 不增强任何类,只为体验沙箱模块生命周期

    1
    ./sandbox.sh -p 18233 -d 'debug-lifecycle/control'
  • 验证限流 并发请求数

    class 执行类、method 指定方法、c 制定速度

    1
    ./sandbox.sh -p 18233 -d 'debug-ralph/c-limit?class=<CLASS>&method=<METHOD>&c=<CONCURRENT>'
  • 验证限流 TPS

    class 执行类、method 指定方法、r 制定速度

    1
    ./sandbox.sh -p 18233 -d 'debug-ralph/r-limit?class=<CLASS>&method=<METHOD>&r=<RATE>'
  • 注入方法异常

    访问直接抛出异常

    1
    ./sandbox.sh -p 18233 -d 'debug-ralph/wreck?class=<CLASS>&method=<METHOD>&type=<EXCEPTION-TYPE>'
  • 注入方法延时

    class 执行类、 method 指定方法、 delay 指定速度

    1
    ./sandbox.sh -p 18233 -d 'debug-ralph/delay?class=<CLASS>&method=<METHOD>&delay=<DELAY(ms)>'
  • trace

    模仿Greys的trace命令,控制台输出方法调用链

    1
    ./sandbox.sh -p 18233 -d 'debug-trace/trace?class=<CLASS>&method=<METHOD>'
  • watch

    模仿Greys的watch命令,匹配方法执行 at 方法执行 watch 观察点,OGNL表达式

    1
    ./sandbox.sh -p 18233 -d 'debug-watch/watch?class=<CLASS>&method=<METHOD>&watch={return}&at=RETURN'
  • 流量录制回放

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    # 录制
    ## 1. 修改录制的HTTP的URL ~/.sandbox-module/cfg/repeater-config.json
    ### 配置URL
    "httpEntrancePatterns": [
    "^/regress/.*$",
    "^/.*$"
    ]
    ### 配置 MOCK ⽅法点 【可以确定回放是否成功】
    "javaSubInvokeBehaviors": [
    {
    "classPattern": "xx.IndexService",
    "includeSubClasses": false,
    "methodPatterns": [
    "get"
    ]
    }
    ## 2. 浏览器访问 (命令⼿动制定traceId和浏览量器访问)
    http://localhost:8080/index?a=2&b=x
    ## 3. 查看⽇志 ~/logs/sandbox/repeater/repeater.log
    broadcast success,traceId=127000001001156272798561510001ed,resp=success
    ## 3.1 查看录制⽂件信息 ~/.sandbox-module/repeater-data
    # 回放
    ## ⽅式1 http _data 是hessian编码
    curl http://127.0.0.1:3658/sandbox/default/module/http/repeater/repeat?_data=
    bash sandbox.sh -p 18233 -d 'repeater/repeat?_data='
    ## ⽅式2
    curl -s 'http://localhost:8080/index?a=2&b=x' -H 'Repeat-TraceId-X:127000001001156274301881610001ed'
    curl -s 'http://localhost:8080/index?a=2&b=x&Repeat-TraceId-X=127000001001156274301881610001ed'
    • 基于JVM-Sandbox的录制/回放通用解决方案

      jvm-sandbox-repeater是JVM-Sandbox生态体系下的重要模块,它具备了JVM-Sandbox的所有特点,插件式设计便于快速适配各种中间件,封装请求录制/回放基础协议,也提供了通用可扩展的各种丰富API。

  • 记录异常日志

  • 监听TCP数据包

编写你自己的模块

我们可以根据需要编写自己的模块,参考资料如下:

沙箱实践介绍:https://github.com/alibaba/jvm-sandbox/wiki/EVENT-INTRODUCE

模块工程介绍:https://github.com/alibaba/jvm-sandbox/wiki/MODULE-LIFECYCLE