dubbo流量录制异常(dubbo2.7.3)的问题解决排查

news2024/10/7 11:21:28

背景

我们自己基于jvm-sandbox-repeater做的流量录制出现了如下的问题, 从这个问题的堆栈信息来看,是在针对dubbo的调用的时候判断这个dubbo的返回是否有异常的时候,报了空指针异常了。
在这里插入图片描述

分析

我们看下具体出错的代码地方是怎么样的吧。

@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);
   }
/**
     * 判断是否有异常
     * @param event
     * @return
     */
    public Boolean isHasException(Event event) {
        if (event.type == Event.Type.RETURN) {
            Object appResponse = ((ReturnEvent) event).object;
            try {
                return (Boolean) MethodUtils.invokeMethod(appResponse, "hasException");
            } catch (Exception e) {
                // ignore
                LogUtil.error("error occurred when assemble dubbo response", e);
            }
        }
        return false;

    }

从上面的代码可以看出来,出错的地方是event的object为null的情况导致了这个问题。

但是这个地方怎么会出现null的,没有思路,所以我们这里借助arthas进行分析下, 我们就 watch这个 isHasException 看看这个 event 的内容是什么

watch com.alibaba.jvm.sandbox.repeater.plugin.dubbo.DubboConsumerInvocationProcessor isHasException '{params,returnObj,throwExp}'  -n 5  -x 3
method=com.alibaba.jvm.sandbox.repeater.plugin.dubbo.DubboConsumerInvocationProcessor.isHasException location=AtExit
ts=2023-06-10 16:12:50; [cost=3.393034ms] result=@ArrayList[
    @Object[][
        @ReturnEvent[
            object=null,
            processId=@Integer[8969],
            invokeId=@Integer[8969],
            type=@Type[RETURN],
        ],
    ],
    @Boolean[false],
    null,
]

从上述的代码,确实可以看到,object的结果为null, 所以这确实就会导致空指针的异常,可是问题还是没有太好的思路,因为针对ReturnEvent 这个类中的信息太少了,没有太多能够分析的,所以最好的方式还是去看 入口的 onEvent 的方法,这里是所有事件进来的入口,这样子才能更好的分析问题

[arthas@75]$ watch com.alibaba.jvm.sandbox.repeater.plugin.core.impl.api.DefaultEventListener onEvent '{params,returnObj,throwExp}'  -n 50  -x 3 
Press Q or Ctrl+C to abort.
Affect(class count: 6 , method count: 1) cost in 853 ms, listenerId: 7
method=com.alibaba.jvm.sandbox.repeater.plugin.core.impl.api.DefaultEventListener.onEvent location=AtExit
ts=2023-06-10 16:26:18; [cost=7.911702ms] result=@ArrayList[
    @Object[][
        @BeforeEvent[
            javaClassLoader=@LaunchedURLClassLoader[org.springframework.boot.loader.LaunchedURLClassLoader@8688f78],
            javaClassName=@String[org.apache.dubbo.rpc.filter.ContextFilter],
            javaMethodName=@String[invoke],
            javaMethodDesc=@String[(Lorg/apache/dubbo/rpc/Invoker;Lorg/apache/dubbo/rpc/Invocation;)Lorg/apache/dubbo/rpc/Result;],
            target=@ContextFilter[org.apache.dubbo.rpc.filter.ContextFilter@352eba13],
            argumentArray=@Object[][isEmpty=false;size=2],
            processId=@Integer[15730],
            invokeId=@Integer[15730],
            type=@Type[BEFORE],
        ],
    ],
    null,
    null,
]
...
...
method=com.alibaba.jvm.sandbox.repeater.plugin.core.impl.api.DefaultEventListener.onEvent location=AtExit
ts=2023-06-10 16:26:18; [cost=3.775966ms] result=@ArrayList[
    @Object[][
        @ReturnEvent[
            object=@AsyncRpcResult[org.apache.dubbo.rpc.AsyncRpcResult@516b9850[Completed normally]],
            processId=@Integer[15730],
            invokeId=@Integer[15730],
            type=@Type[RETURN],
        ],
    ],
    null,
    null,
]
method=com.alibaba.jvm.sandbox.repeater.plugin.core.impl.api.DefaultEventListener.onEvent location=AtExit
ts=2023-06-10 16:26:18; [cost=0.141919ms] result=@ArrayList[
    @Object[][
        @BeforeEvent[
            javaClassLoader=@LaunchedURLClassLoader[org.springframework.boot.loader.LaunchedURLClassLoader@8688f78],
            javaClassName=@String[org.apache.dubbo.rpc.filter.ContextFilter$ContextListener],
            javaMethodName=@String[onResponse],
            javaMethodDesc=@String[(Lorg/apache/dubbo/rpc/Result;Lorg/apache/dubbo/rpc/Invoker;Lorg/apache/dubbo/rpc/Invocation;)V],
            target=@ContextListener[org.apache.dubbo.rpc.filter.ContextFilter$ContextListener@1a077c5e],
            argumentArray=@Object[][isEmpty=false;size=3],
            processId=@Integer[15733],
            invokeId=@Integer[15733],
            type=@Type[BEFORE],
        ],
    ],
    null,
    null,
]
method=com.alibaba.jvm.sandbox.repeater.plugin.core.impl.api.DefaultEventListener.onEvent location=AtExit
ts=2023-06-10 16:26:18; [cost=1.434189ms] result=@ArrayList[
    @Object[][
        @ReturnEvent[
            object=null,
            processId=@Integer[15733],
            invokeId=@Integer[15733],
            type=@Type[RETURN],
        ],
    ],
    null,
    null,
]

这里我把中间的链路简化了一些,从这里我们就可以看到一个大概的流程情况了,

Before(Invoke) → Return(Invoke) → Before(OnResponse)→ Return(OnResponse)

这个链路其实都是一个dubbo的接口的调用的整体的流程,所以到这里我们就知道了,就是在OnResonseReturnEvent 的object 为null 导致了这个问题了,这个流程其实就有问题 因为 我们既然已经在invoke 那里监听了return事件的话,那 onResponse 的return, 其实也没有意义。

DubboProviderPlugin.java

@Override
protected List<EnhanceModel> getEnhanceModels() {
    EnhanceModel onResponse = EnhanceModel.builder().classPattern("org.apache.dubbo.rpc.filter.ContextFilter$ContextListener")
            .methodPatterns(EnhanceModel.MethodPattern.transform("onResponse"))
            .watchTypes(Event.Type.BEFORE, Event.Type.RETURN, Event.Type.THROWS)
            .build();
    EnhanceModel invoke = EnhanceModel.builder().classPattern("org.apache.dubbo.rpc.filter.ContextFilter")
            .methodPatterns(EnhanceModel.MethodPattern.transform("invoke"))
            .watchTypes(Event.Type.BEFORE, Event.Type.RETURN, Event.Type.THROWS)
            .build();
    return Lists.newArrayList(invoke, onResponse);
}

所以这里的好的方式是去掉 invoke中的 Event.Type.Return 但是这个改动并不能解决刚才空指针的问题哦。

这里我们还要做一个修改就是,我们不在OnResponseReturn 方法去做返回值结果的获取,而是在 OnResponseBefore 的事件做处理即可。分析修改刚才有问题的 isHasException 以及

assembleResponse 如下:

public Boolean isHasException(Event event) {
    if (event.type == Event.Type.BEFORE && ON_RESPONSE.equals(((BeforeEvent)event).javaMethodName)) {
        Object appResponse = ((BeforeEvent) event).argumentArray[0];
        try {
            return (Boolean) MethodUtils.invokeMethod(appResponse, "hasException");
        } catch (Exception e) {
            // ignore
            LogUtil.error("error occurred when assemble dubbo response", e);
        }
    }
    return false;
}

@Override
public Object assembleResponse(Event event) {

    if (event.type == Event.Type.BEFORE && ON_RESPONSE.equals(((BeforeEvent)event).javaMethodName)) {
        Object appResponse = ((BeforeEvent) event).argumentArray[0];
        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;
}

全部都切换成在Before的时候做数据的获取即可了。

再思考

这个问题这么修改就可以了吗,不一定,尤其呢这个改动其实挺大的,尤其我们在代码增强的时候还去掉了 invoke的 return的增强。

另外其实还有一个比较重要的事情,这里忽略掉了,这个问题出现之前其实我们的流量录制在其他项目也跑过,dubbo 接口的录制其实是没有问题的,但是在这个项目上运行就不听的报错。这个就是非常奇怪的问题了,而出现这个问题能想到的原因很大概率都是dubbo版本的情况了,检查后确实发现dubbo版本存在有差异,我们没问题的版本是2.7.18, 但是有问题的dubbo版本是2.7.3。 这两个dubbo版本的差异在哪呢。

所以我们重点需要看下 我们前面一开始做的代码增强的类。也就是 org.apache.dubbo.rpc.filter.ContextFilter

以下是2.7.18版本的, 以下的代码都去掉了一下不重要的部分内容

package org.apache.dubbo.rpc.filter;

/**
 * ContextFilter set the provider RpcContext with invoker, invocation, local port it is using and host for
 * current execution thread.
 *
 * @see RpcContext
 */
@Activate(group = PROVIDER, order = Integer.MIN_VALUE)
public class ContextFilter implements Filter, Filter.Listener {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
       ...
    }

    @Override
    public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
        // pass attachments to result
        appResponse.addObjectAttachments(RpcContext.getServerContext().getObjectAttachments());
    }

    @Override
    public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {

    }
}

以下是2.7.3 版本的

package org.apache.dubbo.rpc.filter;

@Activate(
    group = {"provider"},
    order = -10000
)
public class ContextFilter extends ListenableFilter {
    private static final String TAG_KEY = "dubbo.tag";

    public ContextFilter() {
        super.listener = new ContextListener();
    }

    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        ...
    }

    static class ContextListener implements Filter.Listener {
        ContextListener() {
        }

        public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
            appResponse.addAttachments(RpcContext.getServerContext().getAttachments());
        }

        public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
        }
    }
}

对比这两个类,我们就明显发现了区别的地方。在2.7.3的版本,onResponse是在 ContextFilter 下,但是在2.7.18的版本 onResponse却是在 ContextFilter的内部类ContextListener 中,所以这个我们就知道为什么了,因为我们的代码增强的逻辑里面 并没有增强ContextFilter 类下的onResponse.

所以回过来,我们刚才的修改就明显有问题了,这样子的修改就会导致2.7.3版本的dubbo录制没有返回值录制到的情况,所以我们还需要再做一点修改。也就是如下:

@Override
    protected List<EnhanceModel> getEnhanceModels() {
        EnhanceModel onResponse = EnhanceModel.builder().classPattern("org.apache.dubbo.rpc.filter.ContextFilter")
                .methodPatterns(EnhanceModel.MethodPattern.transform("onResponse"))
                .watchTypes(Event.Type.BEFORE, Event.Type.RETURN, Event.Type.THROWS).build();

        // 兼容dubbo 2.7.3版本
        EnhanceModel onResponseV2 = EnhanceModel.builder().classPattern("org.apache.dubbo.rpc.filter.ContextFilter$ContextListener")
                .methodPatterns(EnhanceModel.MethodPattern.transform("onResponse"))
                .watchTypes(Event.Type.BEFORE, Event.Type.RETURN, Event.Type.THROWS).build();

        EnhanceModel invoke = EnhanceModel.builder().classPattern("org.apache.dubbo.rpc.filter.ContextFilter")
                .methodPatterns(EnhanceModel.MethodPattern.transform("invoke"))
                .watchTypes(Event.Type.BEFORE, Event.Type.THROWS).build();
        return Lists.newArrayList(invoke, onResponse, onResponseV2);
    }

增加如上的逻辑即可了。

如此即解决了流量录制的问题了。

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

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

相关文章

chatgpt赋能python:Python怎么判断素数:一篇完整指南

Python怎么判断素数&#xff1a;一篇完整指南 Python是一种广泛应用于编程领域的语言&#xff0c;它非常适合初学者。在许多编程任务中&#xff0c;一个常见问题是需要判断一个数字是否是素数。本篇文章将介绍Python是如何判断素数的&#xff0c;并带领读者详细了解其中的细节…

Java入门之String 学习随记(一)

一. 前置知识 API-Application Programming Interface-应用程序编程接口,接口可以简单理解为别人写好的东西,我们拿过来直接使用即可.顾名思义,JavaAPI指的就是JDK提供的各种功能的Java类,它们将底层的实现封装了起来. 二. java.lang.String 该类为字符串,在Java中所有字符串…

chatgpt赋能python:如何正确删掉Python代码

如何正确删掉Python代码 介绍 在编写Python代码时&#xff0c;难免会出现一些多余或者错误的代码。为了保持代码的整洁和高效&#xff0c;我们需要学会如何正确地删掉Python代码。本文将介绍一些实用的方法和技巧&#xff0c;帮助您轻松删除不必要的代码。 方法 1. 手动删除…

前端基于radio增强单选框组件

前端基于radio增强单选框组件, 下载完整代码请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id12977 效果图如下: # #### 使用方法 使用方法 <!-- radioData:单选数据 curIndex&#xff1a;当前选择序列 change&#xff1a;单选事件 --> <ccRadio…

软考A计划-系统架构师-学习笔记-第一弹

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

QNX交叉编译及运行摆脱IDE

工具链及Demo工程介绍 把交叉编译工具链qnx_cross_compile_toolchain.zip拷贝到交叉编译主机目录下&#xff0c;用unzip命令解压&#xff08;不会unzip可以自行百度linux 下unzip命令&#xff09;&#xff0c;如Ubuntu22.04等。 解压后可以用vscode打开交叉编译工具链的目录。…

JavaScript 流程控制-分支 流程控制-循环

3.1分支结构 由上到下执行代码的过程中&#xff0c;根据不同的条件&#xff0c;执行不同的路径代码(执行代码多选一的过程&#xff09;&#xff0c;从而得到不同的结果。 JS语言提供了两种分支语句 if语句switch 语句 3.2 if 语句 1.语句结构 //条件成立执行代码&#xff…

Java入门之String学习随记(二)

一. 字符串的常用方法 public char charAt(int index) 根据索引返回字符 public int length() 返回字符串的长度 注意:获得字符串的长度和获得数组的长度不同,数组的长度是数组的属性 数组名.length() 属性 字符串.length() …

通知神器——java调用钉钉群自定义机器人----Jay

其中webhook非常重要&#xff0c;下文详述。点击设置说明可以看相关使用文档&#xff0c;文档链接见本文末尾 创建群自定义机器人 其中webhook非常重要&#xff0c;下文详述。点击设置说明可以看相关使用文档&#xff0c;文档链接见本文末尾 使用HTTP POST请求发送消息…

如何从linux社区下载和合入内核patch?

参考 git - How do I get a linux kernel patch set from the mailing list? - Unix & Linux Stack Exchangehttps://unix.stackexchange.com/questions/80519/how-do-i-get-a-linux-kernel-patch-set-from-the-mailing-list 方法 发现使用b4这个工具非常合适。 下面是…

【MySQL】数据库的查询语言DQL

目录 前言&#xff1a; 一.基本查询 1.1查询多个字段 1.2设置别名 1.3去除字段中重复的值 二.条件查询 2.1条件的种类 2.1.1比较运算符 2.1.2逻辑运算符 三.结尾 前言&#xff1a; 在前面讲完了如何增删改数据表中的记录后&#xff0c;那么如何使用这些数据就成了另一…

chatgpt赋能python:Python如何加断点

Python如何加断点 什么是断点 在程序执行时&#xff0c;开发人员可以设置断点&#xff0c;使得程序在断点处暂停执行&#xff0c;从而方便调试程序。当程序停在断点处时&#xff0c;可以查看变量的值、执行语句等&#xff0c;以找出程序中的错误。 Python加断点的方法 在Py…

chatgpt赋能python:Python中的元组及其自身的特性说明

Python 中的元组及其自身的特性说明 在 Python 中&#xff0c;元组是一组有序的值&#xff0c;可以存储各种不同类型的数据。与列表不同的是&#xff0c;元组是不可变的&#xff0c;一旦创建就不能修改。由于元组不可更改&#xff0c;因此它们的值在创建后是固定的。 由于元组…

C语言之预处理那点事

文章目录 一、程序的翻译和执行环境二、预定义符号的介绍1.预定义符号2.#define3.宏和函数的比较4.条件编译 总结 在C语言中&#xff0c;曾出现各种各样新的标准&#xff0c;有的昙花一现&#xff0c;有的则源远流传。我们这篇来看流传下来的&#xff0c;简化开发者编程和提升性…

FFmpeg音视频处理工具介绍及应用

1 FFmpeg介绍 FFmpeg项目由 Fabrice Bellard在2000年创立。到目前为止&#xff0c;FFmpeg项目的开发者仍然与VLC、MPV、dav1d、x264等多媒体开源项目有着广泛的重叠。Ffmpeg&#xff08;FastForward Mpeg&#xff09;是一款遵循GPL的开源软件&#xff0c;在音视频处理方面表现…

算法刷题-数组-移除元素

27. 移除元素 力扣题目链接 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并原地修改输入数组。 元素的顺序可以改变。你不需…

chatgpt赋能python:Python如何编写优化SEO的软件

Python如何编写优化SEO的软件 作为一种功能强大且易于学习的编程语言&#xff0c;Python已经成为广泛使用的开发工具之一&#xff0c;其用户群体涵盖从初学者到专业开发人员。然而&#xff0c;在Python编写SEO相关软件时&#xff0c;开发人员需要遵循一些最佳实践&#xff0c;…

chatgpt赋能python:Python中如何加空格

Python中如何加空格 Python是一门广泛应用于科学计算、数据分析、人工智能、Web开发等领域的高级编程语言。在Python编程过程中&#xff0c;经常需要使用到空格&#xff0c;以实现程序的格式化和美观&#xff0c;同时也有助于提高代码的可读性和可维护性。本文主要介绍Python中…

人工蜂群算法(Artificial Bee Colony (ABC) Algorithm,附简单案例及详细matlab源码)

作者&#xff1a;非妃是公主 专栏&#xff1a;《智能优化算法》 博客地址&#xff1a;https://blog.csdn.net/myf_666 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录 专栏推荐一、人工蜂群算法二、伪代码三…

前端vue地图定位并测算当前定位离目标位置距离

前端vue地图定位并测算当前定位离目标位置距离, 下载完整代码请访问uni-app插件市场地址: https://ext.dcloud.net.cn/plugin?id12974 效果图如下: # #### 使用方法 使用方法 <!-- // 腾讯地图key注册地址&#xff08;针对H5端&#xff0c;manifest.json中web配置&…