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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值