填坑日志(20200611) Feign入参为Null引起的血案

表现:接口偶然失败

某天我正开心的在GitHub上乱晃悠,测试妹子就丢了个Bug过来。
“这个接口请求偶尔会失败。”附了一张截图。
“OK,交给我”。看了下接口,里面就是个列表查询,顺手开了PostMan对着Idea开始怼之后就暂时没管了,心说过半小时再说。半个小时之后,发现并没有什么异常。
咨询测试妹子后得知重现步骤大概如下:查询列表,点击某条数据,弹出详情页之后然后关掉详情页返回列表页,此时Bug有几率重现。

排查过程

搜集日志

开了Debug之后准备进行重现,大概重复了几次操作之后确实发现存在请求失败情况,而且观察到一条很奇怪的日志:

Hystrix circuit short-circuited and is OPEN

熟悉的朋友都知道这是Hystrix接口开启熔断的日志。说起来这个接口确实存在RPC调用,不过奇怪的是另一个服务并没有出现异常,这就有点见鬼了。更见鬼的是我重新对接口进行了暴力测试,单独测试接口并不会出现异常。

思考

既然出现了熔断,说明RPC调用确实出错了。偶尔出现的情况也符合熔断的规则。既然我单独测试列表接口并不会触发,那么就说明其实真正的出错的位置并不在这里。那么会不会是详情接口调用了相同的接口进而导致熔断呢?
于是我盯上了查询详情接口,查看代码后发现两个接口确实都调用了同一个接口,大致长这样:

	@GetMapping("getById")
    XXX getById(@RequestParam("id") String id);

按道理这样简单的查询不应该出错才对。捞了一批数据Debug之后发现了问题,当传递的id为空的情况下确实会触发参数异常进而导致熔断。

Why?

平常都话我们如果这么请求:getById?id=,那么其实是不会报错的。怎么到了Feign这里就走不通了呢?
继续Debug,跟踪到ReflectiveFeign这个类的时候发现了端倪。
核心代码如下:

 @Override
    public RequestTemplate create(Object[] argv) {
      RequestTemplate mutable = RequestTemplate.from(metadata.template());
      mutable.feignTarget(target);
      if (metadata.urlIndex() != null) {
        int urlIndex = metadata.urlIndex();
        checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
        mutable.target(String.valueOf(argv[urlIndex]));
      }
      Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
      for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
        int i = entry.getKey();
        Object value = argv[entry.getKey()];
        if (value != null) { // Null values are skipped.
          if (indexToExpander.containsKey(i)) {
            value = expandElements(indexToExpander.get(i), value);
          }
          for (String name : entry.getValue()) {
            varBuilder.put(name, value);
          }
        }
      }

      RequestTemplate template = resolve(argv, mutable, varBuilder);
      if (metadata.queryMapIndex() != null) {
        // add query map parameters after initial resolve so that they take
        // precedence over any predefined values
        Object value = argv[metadata.queryMapIndex()];
        Map<String, Object> queryMap = toQueryMap(value);
        template = addQueryMapQueryParameters(queryMap, template);
      }

      if (metadata.headerMapIndex() != null) {
        template =
            addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);
      }

      return template;
    }

可以看到,此方法是用来创建代理的。Null values are skipped.这句注释也说明当参数为空时Feign就不会传输此参数了。
到了我们这个接口就要命了,如果传递Id为空,那么其实是可以正常处理的。可是不传递参数就会直接报错了。

解决方式

考虑到线上数据确实可能存在异常,因此修改接口如下:

	@GetMapping("getById")
    XXX getById(@RequestParam(value = "id",required = false) String id);

在具体实现前添加断言,问题解决。

总结

这下整个流程就比较清楚了。首先是参数估计传的null 导致参数异常,达到了Hystrix阈值之后直接触发熔断,熔断时间过后服务恢复,可以正常查询。之所以单独测试列表接口无法重现,是因此此接口对熔断的RPC接口强依赖,详情页接口则是弱依赖,并且触发点并不是列表接口。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值