先从一个具体的问题说起。
遇到问题,有时候很难找到原因,然后就卡在一个地方无法推进。
每次解决问题后,最好能复盘一下,总结下排查思路。
比如从全链路的视角来分析问题、从数据流动的方向去排查
一个导出excel的功能,接入已有的下载中心模块。执行导出,全部失败了。
在日志平台中搜到的日志不全,没有找到导致异常的原因。
涉及到的数据流:
导出功能涉及到的数据流
step1:分析日志
发现导出没有成功后,到日志平台上查日志。
可以找到“数据服务”打印的日志;
没有找到“下载中心服务”的日志;
从已有的应用日志来看,“数据服务”是正常的。
由于没有traceId,就没有办法筛选出整个请求过程的所有应用日志信息。
现在日志不全,且下载中心是初次接入,一下子想不到问题出在哪里
traceId丢失的原因: 请求“数据服务”的动作是由MQ的消费者发起的,这种场景,uat环境中的应用日志中的traceId为空 uat:User Acceptance Test,测试环境 想了解什么是traceId?见文章末尾
目前很可能“下载中心服务”出问题了。
现在这个服务又搜不到日志,怎么办?
搜不到日志有两种原因:
1、日志平台 有问题
2、搜索的关键词不合适
日志平台是基于ES实现,搜索结果与分词器、关键词 关系密切相关, 有时候关键词不对也查不到日志。
就目前的情况看,很难区分是日志平台出问题了,还是搜索的关键词不对。
毕竟是uat环境,有问题也不会花力气去处理。
step2: 阻塞了,陷入困境
数据已经取到了,为什么状态一直是失败呢?
排查一下子陷入困境。想进一步排查需要看日志,目前没搜到日志。
要想解决目前的问题,需要先解决眼前的新的问题:为什么在日志平台搜不到日志?
感觉这个问题有些花时间,且眼前还有别的问题,就没有继续排查。
有同学会问:为什么不在本地调一下? 因为uat环境和本地用的同一个MQ。 本地发的消息很可能让uat上的消费者消费了。 就是拼人品了,也蛮花时间的。
“日志平台”有没有问题?
日志平台的数据流如下所求:
日志平台的数据流
排查的顺序:
1、应用是否正常打印日志
2、应用打印的日志是否在约定的目录
3、应用的日志是否被收集到日志平台
step1:直接去uat环境上查看日志打印。由于是uat环境出问题了,本地正常不等于uat正常
有日志
日志是有的。
step2: 这个看不了。找有权限的同学查看了下,没有!!!约定好的目录下没有日志文件
指定的目录为什么没有日志文件?
这个文件是在应用中指定的。在应用中重新指定,然后发版到uat,发现日志平台上已经可以搜到日志了。
如果知道是因为日志平台的问题,其它还有其它办法来锁定:
譬如可以查看是不是整个应用都没有日志
思考:
第一次遇到这种问题时,没有往日志平台异常的方向上想,觉得这个地方已经running了这么久了,不会有问题的。
这个思考的方向,会影响到问题解决的过程,多走一些弯路。
如何避免呢?
按经验初步判断后,如果一击不中。那就按照数据流动的方向,逐个节点排查
这个办法看着比较笨,但整体上看,是可以提升解决的效率。
导出失败的原因,是接入下载中心的方式错误,根据错误日志fix下接入的姿势就好了。
在排查问题时按经验初步判断后,如果一击不中。说明问题常常在意想不到的地方
按照数据流动的方向,逐个节点排查,反而是最高效,也是最有效的。
只要给时间,没有哪个问题是解决不了的。
给Feign添加的access log也是有效的。之前在日志平台上没搜到,当时还以为是Open Feign的新版本有变化。
show the code:
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public Logger.Level loggerLevel() {
return Logger.Level.BASIC;
}
}
import feign.Logger;
import feign.Request;
import feign.Response;
import feign.Util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;
import static feign.Util.UTF_8;
import static feign.Util.decodeOrDefault;
@Slf4j
@Component
public class AdminFeignLogger extends Logger {
@Value("${log.threshold.cost.s:3}")
private long logWarnTagThreshold;
@Override
protected void log(String configKey, String format, Object... args) {
log.info("FeignLogger {} {} ", configKey, String.format(format, args));
}
@Override
protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) throws IOException {
if (response == null) {
return null;
}
String responseContent = "";
if (response.body() != null) {
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
int length = bodyData.length;
if (length > 0) {
responseContent = decodeOrDefault(bodyData, UTF_8, "Binary data");
log(response, elapsedTime, responseContent);
return response.toBuilder().body(bodyData).build();
}
}
log(response, elapsedTime, responseContent);
return response;
}
private void log(Response response, long elapsedTime, String responseContent) {
Request feignRequest = response.request();
String httpMethodName = feignRequest.httpMethod().name();
String url = feignRequest.url();
String requestBody = transferRequestBody(feignRequest);
int status = response.status();
if (elapsedTime > logWarnTagThreshold * 1000) {
log.info("FeignLogger 慢接口 【 TODOLIST 】 大于3秒 cost:[{}]ms, status {} \n{} {} \nrequest:{} \nresponse:{}", elapsedTime, status, httpMethodName, url, requestBody, responseContent);
} else {
log.info("FeignLogger cost:[{}]ms, status {} \n{} {} \nrequest:{} \nresponse:{}", elapsedTime, status, httpMethodName, url, requestBody, responseContent);
}
}
private String transferRequestBody(Request request) {
if (request.body() != null) {
return request.charset() != null
? new String(request.body(), request.charset())
: "";
}
return "";
}
@Override
protected void logRetry(String configKey, Level logLevel) {
log.info("FeignLogger {} {} ---> RETRYING", logLevel.name(), configKey);
}
@Override
protected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) {
log.info("FeignLogger {} {} cost {} {} ", logLevel.name(), configKey, elapsedTime, ioe.getMessage());
return ioe;
}
@Override
protected void logRequest(String configKey, Level logLevel, Request request) {
}
}
排查问题的速度又要提升了,开心。
补充:
什么是traceId:
traceId,可以唯一标记一次请求,可以根据traceId查看完整的调用链。
当用户的请求进入系统后,链接跟踪组件会在请求进入系统时生成一个全局唯一的标识,这个标识就是traceId。traceId会随着每一层的远程调用,不断往后传递,这样的话通过traceId就可以把一次用户请求在系统中调用的路径串联起来。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有