最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

全球实时:JVM sandbox 实现热修复示例

来源:博客园

JVM-SANDBOX简介

JVM-SANDBOX(沙箱)实现了一种在不重启、不侵入目标JVM应用的AOP解决方案。GIT 地址 https://github.com/alibaba/jvm-sandbox

具有以下特性:1)无侵入:目标应用无需重启也无需感知沙箱的存在2)类隔离:沙箱以及沙箱的模块不会和目标应用的类相互干扰3)可插拔:沙箱以及沙箱的模块可以随时加载和卸载,不会在目标应用留下痕迹4)多租户:目标应用可以同时挂载不同租户下的沙箱并独立控制5)高兼容:支持JDK[6,11]

本文将介绍利用 JVM-SANDBOX 自定义修复 exception 的方法。


(资料图片)

故障模拟

原服务模拟抛出异常的代码如下:

import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@Slf4j@RestControllerpublic class SandboxController {    @GetMapping("/test/void")    public String testVoid() {        error();        return "success";    }    private void error() {        throw new IllegalStateException("Illegal state!");    }}

此时,只要请求 /test/void 都会抛出异常。

修复示例

现在,我们提供一个热修复方案:

1. 安装jvm-sandbox。

# 下载wget https://ompc.oss.aliyuncs.com/jvm-sandbox/release/sandbox-stable-bin.zip# 解压unzip sandbox-stable-bin.zip

2. 新建工程,编写修复代码

import com.alibaba.jvm.sandbox.api.Information;import com.alibaba.jvm.sandbox.api.Module;import com.alibaba.jvm.sandbox.api.ProcessController;import com.alibaba.jvm.sandbox.api.annotation.Command;import com.alibaba.jvm.sandbox.api.listener.ext.Advice;import com.alibaba.jvm.sandbox.api.listener.ext.AdviceListener;import com.alibaba.jvm.sandbox.api.listener.ext.EventWatchBuilder;import com.alibaba.jvm.sandbox.api.resource.ModuleEventWatcher;import org.kohsuke.MetaInfServices;import javax.annotation.Resource;/** * @author zhengqian * @date 2023.02.06 */@MetaInfServices(Module.class)@Information(id = "exception-handler")public class ExceptionHandlerModule implements Module {    @Resource    private ModuleEventWatcher moduleEventWatcher;    @Command("repairExceptionVoid")    public void repairExceptionVoid() {        new EventWatchBuilder(moduleEventWatcher)                .onClass("xxx.xxx.controller.SandboxController") //对应类名                .onBehavior("error")  // 对应方法名                .onWatch(new AdviceListener() {                    /**                     * 拦截指定方法,当这个方法抛出异常时将会被                     * AdviceListener#afterThrowing()所拦截                     */                    @Override                    protected void afterThrowing(Advice advice) throws Throwable {                        // 在此,你可以通过ProcessController来改变原有方法的执行流程                        // 这里的代码意义是:改变原方法抛出异常的行为,变更为立即返回;void返回值用null表示                        ProcessController.returnImmediately(null);                    }                });    }}

工程依赖参考

    4.0.0    org.example    jvm-sandbox-tool    1.0-SNAPSHOT            true        true                ${project.name}-${project.version}                                    org.apache.maven.plugins                maven-compiler-plugin                3.8.0                                    1.8                    1.8                    UTF-8                    true                                                        org.apache.maven.plugins                maven-assembly-plugin                                                                                        attached                                                package                                                                                    jar-with-dependencies                                                                                                                                        com.alibaba.jvm.sandbox            sandbox-module-starter            1.4.0            pom                            com.alibaba.jvm.sandbox            sandbox-api            1.4.0            provided                            com.alibaba.jvm.sandbox            sandbox-common-api            1.4.0                            org.kohsuke.metainf-services            metainf-services            1.9            provided                                    org.apache.commons            commons-lang3            3.4                            commons-collections            commons-collections            3.2.2                                    ognl            ognl            3.0.8                            com.google.guava            guava            18.0                                    org.slf4j            slf4j-api            1.7.24                            ch.qos.logback            logback-classic            1.2.1                            javax.servlet            javax.servlet-api            4.0.1            provided                            com.alibaba.fastjson2            fastjson2            2.0.23            

3. 打包,启动

# 打包mvn clean package# 将打包好的内容复制到第一步sandbox-module的目录位置,注意改成你自己的目录cp target/jvm-sandbox-tool-1.0-SNAPSHOT-jar-with-dependencies.jar ~/install/jvm-sandbox/sandbox/sandbox-module/.

4. 启动

# 这里假设 27377 是目标进程号(也就是报异常的原服务)./sandbox.sh -p 27377

5. 执行修复指令

./sandbox.sh -p 27377 -d "exception-handler/repairExceptionVoid"

此时再请求 /test/void 会正常返回,不会报错。使用指令./sandbox.sh -p 27377 -S停用sandbox后,服务恢复异常,实现了热插拔。

改为可传参的通用版本

import com.alibaba.fastjson2.JSONObject;import com.alibaba.jvm.sandbox.api.Information;import com.alibaba.jvm.sandbox.api.Module;import com.alibaba.jvm.sandbox.api.ProcessController;import com.alibaba.jvm.sandbox.api.annotation.Command;import com.alibaba.jvm.sandbox.api.http.printer.ConcurrentLinkedQueuePrinter;import com.alibaba.jvm.sandbox.api.http.printer.Printer;import com.alibaba.jvm.sandbox.api.listener.ext.Advice;import com.alibaba.jvm.sandbox.api.listener.ext.AdviceListener;import com.alibaba.jvm.sandbox.api.listener.ext.EventWatchBuilder;import com.alibaba.jvm.sandbox.api.listener.ext.EventWatcher;import com.alibaba.jvm.sandbox.api.resource.ModuleEventWatcher;import org.apache.commons.lang3.StringUtils;import org.kohsuke.MetaInfServices;import javax.annotation.Resource;import java.io.PrintWriter;import java.util.ArrayList;import java.util.Map;@MetaInfServices(Module.class)@Information(id = "exception-repair", version = "0.0.1", author = "zhengqian@rd.netease.com")public class ExceptionRepairModule extends ParamSupported implements Module {    @Resource    private ModuleEventWatcher moduleEventWatcher;    @Command("returnObject")    public void returnObject(final Map param, final PrintWriter writer) {        final String cnPattern = getParameter(param, "class");        final String mnPattern = getParameter(param, "method");        final String rtPattern = getParameter(param, "return");        final String rtString = getParameter(param, "returnString");        final Printer printer = new ConcurrentLinkedQueuePrinter(writer);        final EventWatcher watcher = new EventWatchBuilder(moduleEventWatcher)                .onClass(cnPattern)                .onBehavior(mnPattern)                .onWatch(new AdviceListener() {                    /**                     * 拦截指定方法,当这个方法抛出异常时将会被                     * AdviceListener#afterThrowing()所拦截                     */                    @Override                    protected void afterThrowing(Advice advice) throws Throwable {                        Class clazz = Class.forName(rtPattern);                        Object object;                        if (StringUtils.isEmpty(rtString)) {                            object = clazz.newInstance();                            printer.print("repair exception, return empty object");                        } else {                            object = JSONObject.parseObject(rtString, clazz);                            printer.print("repair exception, return object: " + object.toString());                        }                        ProcessController.returnImmediately(object);                    }                });        try {            printer.println(String.format(                    "tracing on [%s#%s].\nPress CTRL_C abort it!",                    cnPattern,                    mnPattern            ));            printer.waitingForBroken();        } finally {            watcher.onUnWatched();        }    }    @Command("returnEmptyList")    public void returnList(final Map param, final PrintWriter writer) {        final String cnPattern = getParameter(param, "class");        final String mnPattern = getParameter(param, "method");        final String rtPattern = getParameter(param, "return");        final Printer printer = new ConcurrentLinkedQueuePrinter(writer);        final EventWatcher watcher = new EventWatchBuilder(moduleEventWatcher)                .onClass(cnPattern)                .onBehavior(mnPattern)                .onWatch(new AdviceListener() {                    /**                     * 拦截指定方法,当这个方法抛出异常时将会被                     * AdviceListener#afterThrowing()所拦截                     */                    @Override                    protected void afterThrowing(Advice advice) throws Throwable {                        ProcessController.returnImmediately(new ArrayList<>());                    }                });        try {            printer.println(String.format(                    "tracing on [%s#%s].\nPress CTRL_C abort it!",                    cnPattern,                    mnPattern            ));            printer.waitingForBroken();        } finally {            watcher.onUnWatched();        }    }}

故障模拟

@Slf4j@RestControllerpublic class SandboxController {    @GetMapping("/test/object")    public SandboxReturnType testObject() {        SandboxReturnType type = errorObject();        log.info("type:{}", type);        return type;    }    private SandboxReturnType errorObject() {        throw new IllegalStateException("Illegal state!");    }}
@Datapublic class SandboxReturnType {    private String name;}

执行指令,可以指定作用的类、方法、以及需要返回的object(传入的是json字段串,且这里的 returnString 做了urlencode)

./sandbox.sh -p 47620 -d "exception-repair/returnObject?class=org.example.demo.controller.SandboxController&method=errorObject&return=org.example.demo.controller.SandboxReturnType&returnString=%7B%22name%22%3A%22111%22%7D"

此时,请求 /test/object 时可以正常返回传入的数据,实现热修复

{  "name": 111}

可传参的通用代码可以打包上传,需要时直接下载使用即可,不需要每次重复写代码、打包处理。

关键词: 抛出异常 故障模拟 新建工程