快下班的时候,有个同事让我帮忙排除处理一个很奇怪的问题,问题描述如下
一.问题描述
我们有个系统,运营测可以免密跳转到租户测,同时也预留了接口,供第三方接口调用进行跳转,两个地方调用的地址分别是
www.demo.com/V1/login/op… (第三方调用)
www.demo.com/api/openapi… (本系统调用)
但是线上环境 第三方调用的接口www.demo.com/V1/login/op… 却突然访问不了,
在测试环境都是好好的,为什么在线上环境突然不能用了呢?
二.问题排查
访问不到首先查看下日志
2-1.查看nginx日志
通过排查nginx日志,发现数据是可以进来的,也有日志能打印,排除nginx的问题
2-2.网关日志排查
我们系统的入口首先是gateway,在网关会对做一些权限检验,有一段代码是
private boolean isPublicApiRequest(String contextPath) {
//System.out.println("接口访问 public-api.host.accept ==> " + JSON.toJSONString(contextPath));
if (contextPath.startsWith("/api/V1/") || contextPath.startsWith("/V1/")
|| contextPath.startsWith("/api/V2/") || contextPath.startsWith("/V2/")) {
return true;
}
return false;
}
因为我们请求的路径是 /V1/login/openApiOAuth,看代码是应该返回true,也就是不会拦截,但是为了确报还是应该要确认一下,这个时候,就需要用到arthas来监听这个方法
watch xxxx isPublicApiRequest "{params,returnObj}" -x 3后,会发现日志很多而且很快,因为所有的请求都会经过这个拦截器,所以必须要对参数进行过滤,可以直接使用
watch xxxx isPublicApiRequest "{params,returnObj}" "params[0].contains('openApiOAuth')" -x 3 用来监听方法的参数和返回值
通过监听的参数,发现返回值都是true,所以拦截器应该都放行了,但是为什么请求日志没有到达对应的controller,
2-3.监听自己的程序
接下来,就是使用arths监控没有达到请求的controller,命令也是
watch xxxx.xxController myMethod "{params,returnObj}" -x 3
发现一直没有值,于是我们去测试环境监听一下这个方法,发现确实有值的
这里就很奇怪了,同样的请求,在测试环境可以调通,但是线上环境却调不通,让人抓狂啊~~~
但是还是要继续排查,我在思考到底是请求有没有进入这个项目呢?整理一下思绪,如果请求进入项目,肯定会通过DispatchServlet,直接用arthas监听DispatchServlet 的doDispatch方法,不就行了,然后过滤出来这个路径,就能百分百判断请求是否到达这个项目
通过监控,其他请求确实能达到改项目,就是这个很特殊的请求,在测试环境能达到,但是线上环境却到达不了
通过上面分析,只能判断还是被gateway给拦截了,所以重点又要回归到gateway,通过网关这块还有一段逻辑,前面判断返回true后,还会有一个判断ip来源的地方,
public FilterHandleResult handle(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest req = exchange.getRequest();
String remoteHost = IpUtil.getRemoteIP(name -> req.getHeaders().getFirst(name), req.getRemoteAddress().getHostString());
Set<String> acceptHostSet = Arrays.stream(environment.getProperty("public-api.host.accept").split(",")).collect(Collectors.toSet());
boolean isContains = false;
//这里检验ip白名单,如果允许该ip通过,返回true
for (String acceptHost : acceptHostSet) {
if (remoteHost.contains(acceptHost)) {
isContains = true;
break;
}
}
if (!isContains) {
// 校验不通过会返回403
return FilterHandleResult.abortWith(buildMono(exchange, HttpStatus.FORBIDDEN));
}
然后通过本地配置的白名单和nginx请求日志中的请求来源加以判断,发现确实是在这里被拦截的!!!
三.问题排查结果和一些反思
网关代码不是我写的,同时告诉我第一步拦截如果返回true,就会直接放行,所以我没有仔细看网关这块的代码,通过arthas监控和对比后,发现问题只能出现在gateway,所以又再次把重点放在gateway这块,然后排查出问题的地方
通过这次排查,线上环境合理使用arths,能快速定位到问题的所在,尤其是在使用watch的时候,对参数进行过滤,能在喊如烟海的日志中,快速定位到自己想要查找的问题,做到又快又稳,20分钟搞定问题所在,nice
ps: watch的参数 params,可以通过数组下表找到这个对象,然后直接调用这个对象的API,就可以了,比如刚才的
又快又好的解决别人看了很久的问题,很nice
Good luck ~ boy~