背景概要

调试宇视一体机的几个 API,因为部署在内网,需要 NAT 穿透后建立 TCP 长链接通道,在通道内进行 API 的调用。server 端用了 netty 的框架。

bug场景

API调用有时会有超时失败,当这种情况发生时,后续每次调用返回的都是上次发起请求的响应。

问题排查

  1. 查看发起请求的方法,响应的请求在 handler 的 channelRead0 方法中处理。
//判断属于响应类处理方式
else if (msg instanceof FullHttpResponse) {
		FullHttpResponse response = (FullHttpResponse) msg;
		HttpResponseFactory.responseHandle(ctx, response);
	} 
  1. 方法中调用了HttpResponseFactory 的 responseHandle 方法
public static final ConcurrentHashMap<Integer, String> RESPONSE_DATA = new ConcurrentHashMap<>(128);

public static void responseHandle(ChannelHandlerContext ctx, FullHttpResponse httpResponse) {
        try {
            String responseBody = httpResponse.content().toString(CharsetUtil.UTF_8);
            int iHashCodeKey = ctx.channel().hashCode();
            //收到消息的响应的长连接MAP 无 此 hashCode 则向下
            ConcurrentHashMap<Integer, String> temMap = KEEP_ALIVE_RESPONSE_MAP.get(iHashCodeKey);
            if (null != temMap){
                temMap.put(iHashCodeKey, responseBody);
            }else {
                //单设备单接口做成并行,设备这里使用通道哈希值代替
                if (null != RESPONSE_DATA.putIfAbsent(iHashCodeKey, responseBody)) {
                    //出现多条响应入队的情况,记录错误日志
                    LOGGER.error("[KEEP_ALIVE][RESPONSE]-The {}- RESPONSE queue is full,this response will be drop. {}", iHashCodeKey, responseBody);
                }
                //如果已经有响应未提取,以后面的响应为准
                RESPONSE_DATA.put(iHashCodeKey, responseBody);
            }
        } catch (Exception e) {
            LOGGER.error("Handle response failed for {}", e.getMessage(), e);
        }
    }

可以看到返回的结果放入了 RESPONSE_DATA 这个 map 中,++key 是 channel 的哈希值++。
3. 查看请求方法

    //发起请求前,先做移除
	responseKey = ctx.hashCode();
	HttpResponseFactory.RESPONSE_DATA.remove(responseKey);

这里终于找到了原因,可以看到移除的 key 取值是 ctx 的哈希,而不是前文中 channel 的哈希,问题解决!

总结

查找本次问题的思路:由于问题是每次超时才产生的,且下次请求能获取上次超时请求的响应,说明请求最终成功了,且暂时存储了响应的结果。由此可以找到存储介质,再对存取方法进行分析可以找出最终的问题原因。