关于jvm-sandbox-repeater dubbo回放异常的问题处理

news2025/3/9 22:24:35

还是引流回放的问题,今天测试的同学反馈说他做了流量回放,但是回放的好几个接口报错了,都是抛出来的服务器错误,请联系管理员,与预期的结果不符,但是实际这块的逻辑是没有改动的,所以也只能是dubbo回放的问题了。

分析

我们先看下问题的现象是怎么样子的, 如下图:

在这里插入图片描述
预期是返回不存在的工单的结果才是对的,但是确实服务器异常的结果,

在这里插入图片描述
从这个结果来看就是dubbo的调用返回了null, 所以才会导致了这个问题。那dubbo的调用其实就是被我们mock的结果了,而且看代码我们看录制好的子调用数据

在这里插入图片描述

业务其实就只有一个dubbo的子调用,所以预期结果应该就是这个 getWorkOrderDetailByUid 返回了我们需要的不存在的工单才对,可是我们看到这个结果的 response 以及 throwable 都为空,这个就是为什么会返回null的原因了。

那我们又要去思考,为什么response与throwable都为null呢。 这个就要去看下录制的逻辑了,

/**
     * 处理return事件
     *
     * @param event return事件
     */
    protected void doReturn(ReturnEvent event) {
        if (RepeatCache.isRepeatFlow(Tracer.getTraceId())) {
            return;
        }
        Invocation invocation = RecordCache.getInvocation(event.invokeId);
        if (invocation == null) {
            log.debug("no valid invocation found in return,type={},traceId={}", invokeType, Tracer.getTraceId());
            return;
        }
        invocation.setResponse(processor.assembleResponse(event));
        invocation.setEnd(System.currentTimeMillis());
        listener.onInvocation(invocation);
    }

以上的内容 我们会发现在 doReturn 方法的时候会去获取processor.assembleResponse(event) 的结果,我们看看针对dubbo这块的逻辑是怎么获取结果的。

@Override
    public Object assembleResponse(Event event) {
        // 在onResponse的before事件中组装response
        if (event.type == Event.Type.RETURN) {
            Object appResponse = ((ReturnEvent) event).object;
            try {
               return MethodUtils.invokeMethod(appResponse, "getValue");
            } catch (Exception e) {
                // ignore
                LogUtil.error("error occurred when assemble dubbo response", e);
            }
        }
        return null;
    }

这里我们需要知道appResonse 这里是个什么类型,其实他是dubbo调用的结果也就是RpcResult这个类。

public class RpcResult implements Result, Serializable {
    private static final long serialVersionUID = -6925924956850004727L;
    private Object result;
    private Throwable exception;
    private Map<String, String> attachments = new HashMap();

		public RpcResult(Object result) {
        this.result = result;
    }

    public RpcResult(Throwable exception) {
        this.exception = exception;
    }
    /** @deprecated */
    @Deprecated
    public Object getResult() {
        return this.getValue();
    }

    /** @deprecated */
    @Deprecated
    public void setResult(Object result) {
        this.setValue(result);
    }

    public Object getValue() {
        return this.result;
    }

    public void setValue(Object value) {
        this.result = value;
    }

		public Throwable getException() {
        return this.exception;
    }

    public void setException(Throwable e) {
        this.exception = e;
    }

		...
}

我们发现这里有一个问题,如果出现dubbo的调用的服务抛出异常的时候,实际上result这个结果就是null了, 而在exception这个值才能够知道具体是什么异常了。所以上述的代码逻辑有问题就是result为null的情况下,有可能是exception有值的。所以需要做如下的改动:

@Override
    public Object assembleResponse(Event event) {
        // 在onResponse的before事件中组装response
        if (event.type == Event.Type.RETURN) {
            Object appResponse = ((ReturnEvent) event).object;
            try {
                Object result =  MethodUtils.invokeMethod(appResponse, "getValue");
                if (result == null) {
                    return MethodUtils.invokeMethod(appResponse, "getException");
                }else {
                    return result;
                }
            } catch (Exception e) {
                // ignore
                LogUtil.error("error occurred when assemble dubbo response", e);
            }
        }
        return null;
    }

先尝试获取value,如果为null的情况下 再去获取到exception的值。 并且还没完 因为刚才获取到的值其实是直接赋值给到了 invocation的response了即 invocation.setResponse(processor.assembleResponse(event));,这里我们需要如果有异常的时候将值赋值给到throwable才是对的。

@Override
protected void doReturn(ReturnEvent event) {
    if (RepeatCache.isRepeatFlow(Tracer.getTraceId())) {
        return;
    }
    Invocation invocation = RecordCache.getInvocation(event.invokeId);
    if (invocation == null) {
        log.debug("no valid invocation found in return,type={},traceId={}", invokeType, Tracer.getTraceId());
        return;
    }
    Boolean hasException = ((DubboConsumerInvocationProcessor)processor).isHasException(event);
    if (hasException) {
        invocation.setThrowable((Throwable) processor.assembleResponse(event));

        invocation.setResponse(buildExceptionResponse(invocation.getThrowable()));

    }   else {
        invocation.setResponse(processor.assembleResponse(event));
    }
    invocation.setEnd(System.currentTimeMillis());
    listener.onInvocation(invocation);
}

这里我们还做了一个小优化,因为一旦dubbo抛出异常的时候reponse就没有值了,这就导致了结果对比的地方是null, 完全没有办法看出来是异常,所以我们将异常做了一个转换再赋值给resone.即

private Map<String, String> buildExceptionResponse(Throwable e) {
    Map<String, String> map = new HashMap<String, String>();
    String clzName = e.getClass().getName();
    int index = clzName.lastIndexOf(".");
    map.put("exception", clzName.substring(index + 1));
    map.put("message", e.getMessage());
    return map;
}

将异常内容做个简化,存放到map中再进行返回。

那问题就解决了吗? 并不是的,我们这里的所有逻辑都是解决的是录制流量的问题,那回放呢,这块的逻辑是否正确呢

我们看下dubbo回放的逻辑,

@Override
    public Object assembleMockResponse(BeforeEvent event, Invocation invocation) {
        try {
            Object dubboInvocation = event.argumentArray[1];
            Object response = invocation.getResponse();

            Class<?> aClass = event.javaClassLoader.loadClass("com.alibaba.dubbo.rpc.RpcResult");
            // 调用AsyncRpcResult#newDefaultAsyncResult返回
            Constructor constructor=aClass.getDeclaredConstructor(Object.class);
            return constructor.newInstance(response);

        } catch (Exception e) {
            LogUtil.error("error occurred when assemble dubbo mock response", e);
            return null;
        }
    }

我以上的代码就是在dubbo mock返回值的逻辑了,但是我们根据刚才前面的解释,就会发现这里有一个问题,那就是RpcResult的构造函数可以有 object的参数,也有throwable的参数的,而这个地方的mock一直都是在模拟正常的结果返回,而没有业务抛出异常的逻辑,所以这个地方也是必须要修改的。

修改的代码如下:

@Override
    public Object assembleMockResponse(BeforeEvent event, Invocation invocation) {
        try {
            Object dubboInvocation = event.argumentArray[1];
            Class<?> aClass = event.javaClassLoader.loadClass("com.alibaba.dubbo.rpc.RpcResult");
            // 调用AsyncRpcResult#newDefaultAsyncResult返回
            Constructor constructor = null;
            if (invocation.getThrowable() != null) {
                constructor = aClass.getDeclaredConstructor(Throwable.class);
                return constructor.newInstance(invocation.getThrowable());
            }else {
                constructor = aClass.getDeclaredConstructor(Object.class);
                Object response = invocation.getResponse();
                return constructor.newInstance(response);
            }

        } catch (Exception e) {
            LogUtil.error("error occurred when assemble dubbo mock response", e);
            return null;
        }
    }

如果invocation 存在有throwable 的话,那就需要构造带有exception的RpcResult对象出来即可。

PS: 其实这里还有一个比较重要的”小问题”,就是我们dubbo调用过程中出现的一些业务的异常,并不能去走sanbod定义的throw的异常去,也就是以下这个代码的地方。

public void doMock(BeforeEvent event, Boolean entrance, InvokeType type) throws ProcessControlException {
    /*
     * 获取回放上下文
     */
    RepeatContext context = RepeatCache.getRepeatContext(Tracer.getTraceId());
    /*
     * mock执行条件
     */
    if (!skipMock(event, entrance, context) && context != null && context.getMeta().isMock()) {
        try {
            /*
             * 构建mock请求
             */
            final MockRequest request = MockRequest.builder()
                    .argumentArray(this.assembleRequest(event))
                    .event(event)
                    .identity(this.assembleIdentity(event))
                    .meta(context.getMeta())
                    .recordModel(context.getRecordModel())
                    .traceId(context.getTraceId())
                    .type(type)
                    .repeatId(context.getMeta().getRepeatId())
                    .index(SequenceGenerator.generate(context.getTraceId()))
                    .build();
            /*
             * 执行mock动作
             */
            final MockResponse mr = StrategyProvider.instance().provide(context.getMeta().getStrategyType()).execute(request);
            /*
             * 处理策略推荐结果
             */
            switch (mr.action) {
                case SKIP_IMMEDIATELY:
                    break;
                case THROWS_IMMEDIATELY:
                    ProcessControlException.throwThrowsImmediately(mr.throwable);
                    break;
                case RETURN_IMMEDIATELY:
                    ProcessControlException.throwReturnImmediately(assembleMockResponse(event, mr.invocation));
                    break;
                default:
                    ProcessControlException.throwThrowsImmediately(new RepeatException("invalid action"));
                    break;
            }
        } catch (ProcessControlException pce) {
            throw pce;
        } catch (Throwable throwable) {
            ProcessControlException.throwThrowsImmediately(new RepeatException("unexpected code snippet here.", throwable));
        }
    }
}

我们可以发现 这里的mock策略会根据mr.action进行走具体哪个策略逻辑,(不过我们要注意一点,dubbo的子调用的一些异常我们仍然需要走的是 RETURN_IMMEDIATELY 这个是比较关键的)那mr的action又是怎么来的呢 我们就需要看下 execute的逻辑了

response = MockResponse.builder()
                        .action(invocation.getThrowable() == null ? Action.RETURN_IMMEDIATELY : Action.THROWS_IMMEDIATELY)
                        .throwable(invocation.getThrowable())
                        .invocation(invocation)
                        .build();

这里我们没有截取比较多的代码,我们可以看到这里的action是根据是否有存在throwable来进行处理的, 所以一旦我们的逻辑这么走的话,就会进到 THROWS_IMMEDIATELY 就其实mock失败了,所以这里的代码我们还需要再改动一下。

response = MockResponse.builder()
                        .action(invocation.getThrowable() == null || invocation.getType().name().equals(InvokeType.ALIBB_DUBBO.name()) ? Action.RETURN_IMMEDIATELY : Action.THROWS_IMMEDIATELY)
                        .throwable(invocation.getThrowable())
                        .invocation(invocation)
                        .build();

如果是dubbo的调用也不用抛出异常了。

总结

dubbo这块的mock与http等的差异还是比较大的,所以这块的mock其实还是有很多待解决的问题的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/450706.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ingonyama团队的ZKP加速

1. PipeMSM&#xff08;cloud-ZK&#xff09;&#xff1a;ZKPFPGA Ingonyama团队2022年发表了论文《PipeMSM: Hardware Acceleration for Multi-Scalar Multiplication》&#xff0c;尝试将ZK操作与FPGA结合&#xff0c;并为未来ZK与ASIC&#xff08;Application Specific Int…

无法解析的外部符号 __mingw_vsprintf

windows下的ffmpeg是采取mingw平台上编译&#xff0c;本人采用的是msys2&#xff0c;本人需要h264&#xff0c;于是先在msys2里面编译了x264静态库&#xff0c;注意这里是静态库&#xff0c;动态库经过了链接&#xff0c;不会出现下面的问题&#xff0c;然后在ffmpeg里面用下面…

【C++类】

目录 前言类的定义类的访问限定符及封装访问限定符封装 类的大小为什么需要内存对齐为什么成员函数不占用类的内存&#xff1f;为什么空类的大小是1个字节&#xff1f; 前言 今天是少年正式认识"对象"的第一天,虽然此"对象"非彼对象&#xff0c;但是少年也…

使用Jmeter进行http接口测试

前言&#xff1a; 本文主要针对http接口进行测试&#xff0c;使用Jmeter工具实现。 Jmter工具设计之初是用于做性能测试的&#xff0c;它在实现对各种接口的调用方面已经做的比较成熟&#xff0c;因此&#xff0c;本次直接使用Jmeter工具来完成对Http接口的测试。 一、开发接口…

经典算法之快速排序

快速排序 【思想】选择一个元素作为标准&#xff0c;分别将小于该元素的元素放入该元素左边&#xff0c;大于该元素的元素放到该元素的右边&#xff0c;接下来分别对左右两边区间进行同样操作&#xff0c;直到整个数组有序。 【例子】 上述是一个未排序的数组&#xff0c;首…

前端三个小妙招

整理下本人在工作中撸代码遇到的一些刚看时一脸懵逼&#xff0c;实则很简单就能解决的小妙招&#xff0c;希望对大家有所帮助哟~ 伪元素动态改变其样式 我们都用过伪元素&#xff0c;什么::before,::after啊这些等等&#xff0c;但是他们都不会直接在代码里html中生成标签&am…

使用MASA全家桶从零开始搭建IoT平台(一)环境准备

前言 本系列文章以IoT开发小白的角度&#xff0c;从零开始使用.Net为主要技术搭建一个简单的IoT平台&#xff0c;由于是入门系列&#xff0c;所有代码以围绕IoT基础业务场景为主&#xff0c;不会涉及DDD等设计思想。 架构图 这里是我们整个IoT平台的架构图。 一、设备接入…

深入了解Synchronized同步锁的优化

大家好&#xff0c;我是易安&#xff01;今天我们来聊一下Synchronized同步锁的优化。 在并发编程中&#xff0c;多个线程访问同一个共享资源时&#xff0c;我们必须考虑如何维护数据的原子性。 在JDK1.5之前&#xff0c;Java是依靠Synchronized关键字实现锁功能来做到这点的。…

Java核心技术 卷1-总结-10

Java核心技术 卷1-总结-10 通配符类型通配符概念通配符的超类型限定无限定通配符通配符捕获 通配符类型 通配符概念 通配符类型中&#xff0c;允许类型参数变化。 例如&#xff0c;通配符类型Pair<? extends Employee>表示任何泛型Pair类型&#xff0c;它的类型参数是…

LeetCode_动态规划_中等_1105.填充书架

目录 1.题目2.思路3.代码实现&#xff08;Java&#xff09; 1.题目 给定一个数组 books &#xff0c;其中 books[i] [thicknessi, heighti] 表示第 i 本书的厚度和高度。你也会得到一个整数 shelfWidth。 按顺序将这些书摆放到总宽度为 shelfWidth 的书架上。 先选几本书放…

机器学习基本模型与算法在线实验闯关

机器学习基本模型与算法在线实验闯关 文章目录 机器学习基本模型与算法在线实验闯关一、缺失值填充二、数据标准化三、支持向量机分类模型及其应用四、逻辑回归模型及其应用五、神经网络分类模型及其应用六、线性回归模型及其应用七、神经网络回归模型及其应用八、支持向量机回…

AIGC跨过奇点时刻,亚马逊云科技展露新峥嵘

AIGC是云计算的Game changer&#xff0c;将从根本上改变云计算乃至整个科技行业的游戏规则&#xff0c;作为云计算行业的Game Rulemaker&#xff0c;亚马逊云科技也展露出新的峥嵘。4月13日&#xff0c;亚马逊云科技宣布推出生成式AI新工具&#xff0c;包括Amazon Bedrock和Ama…

Java核心技术 卷1-总结-11

Java核心技术 卷1-总结-11 Java 集合框架将集合的接口与实现分离Collection接口迭代器泛型实用方法集合框架中的接口 Java 集合框架 将集合的接口与实现分离 Java集合类库将接口&#xff08;interface&#xff09;与实现&#xff08;implementation&#xff09;分离。 例如队…

把Windows装进内存条里,提前感受超越PCIe 6.0固态的顶级体验

这两年电脑内存条是越来越白菜价了&#xff0c;看到大伙儿慢慢富足起来的内存容量&#xff0c;小忆是由衷地感到高兴。 不过话说&#xff0c;动不动 32G、64G 内存你真能用得完吗&#xff1f;为了榨干大家真金白银买来的空闲内存价值。 咱这期整个骚操作——将 Windows 11 系统…

虚拟机安装linux系统centos(保姆级)

虚拟机安装linux系统centos 1.软硬件准备2.虚拟机准备1.打开VMware选择新建虚拟机2.典型安装与自定义安装3.虚拟机兼容性选择4.选择稍后安装操作系统5.操作系统的选择7.处理器与内存的分配8.网络连接类型的选择&#xff0c;网络连接类型一共有桥接、NAT、仅主机和不联网四种。9…

ArcMap气温数据插值处理

一、插值数据处理 1.先把气温excel在excel中另存为气温.csv&#xff08;网盘链接中有转好的csv文件&#xff09;&#xff0c;导入数据江苏.shp和jiangsustation.shp 和气温.csv 数据在文末百度网盘链接中 2.打开jiangsustation.shp的属性表&#xff0c;连接与字段-连接-连接的…

RabbitMQ的五种工作模式

目录 前言介绍 &#xff08;1&#xff09;启动RabbitMQ &#xff08;2&#xff09;账户管理 一、简单模式 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;生产者代码 &#xff08;3&#xff09;消费者代码 二、工作队列模式 &#xff08;1&#xff09;概念…

LLVM编译流程

一、LLVM 1.1 LLVM概述 LLVM是构架编译器(compiler)的框架系统,以C编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本.LLVM计划启动于2000年,最初由美国…

Dynamics 365 开启 Modern advanced find后如何创建个人视图

本篇是自己在新的环境被新的高级查找晃的没找到如何创建个人视图而发。如何在老的高级查找页面创建这里就不表了。 D365 2022 Wav1后有了新的feature叫Modern advanced find&#xff0c;开启方式如下&#xff0c;进Power Platform的管理中心&#xff0c;找到你对应的环境&#…

Java核心技术 卷1-总结-6

Java核心技术 卷1-总结-6 接口示例接口与回调Comparator接口对象克隆 lambda表达式为什么引入lambda表达式lambda表达式的语法 接口示例 接口与回调 回调&#xff08;callback&#xff09;是一种常见的程序设计模式。在这种模式中&#xff0c;可以指出某个特定事件发生时应该…