背景概要
调试宇视一体机的几个 API,因为部署在内网,需要 NAT 穿透后建立 TCP 长链接通道,在通道内进行 API 的调用。server 端用了 netty 的框架。
bug场景
API调用有时会有超时失败,当这种情况发生时,后续每次调用返回的都是上次发起请求的响应。
问题排查
- 查看发起请求的方法,响应的请求在 handler 的 channelRead0 方法中处理。
//判断属于响应类处理方式
else if (msg instanceof FullHttpResponse) {
FullHttpResponse response = (FullHttpResponse) msg;
HttpResponseFactory.responseHandle(ctx, response);
}
- 方法中调用了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 的哈希,问题解决!
总结
查找本次问题的思路:由于问题是每次超时才产生的,且下次请求能获取上次超时请求的响应,说明请求最终成功了,且暂时存储了响应的结果。由此可以找到存储介质,再对存取方法进行分析可以找出最终的问题原因。