思考的过程比结论更重要
背景遇到问题,有时候很难找到原因,然后就卡在一个地方无法推进。
需要总结下排查思路,提升排查问题的效率。
比如从全链路的视角来分析问题、从数据流动的方向去逐个排查
出现的问题一个导出excel的功能,接入已有的下载中心模块后,状态一直是失败。
在日志平台中也搜不到相关的日志,正常的日志打印和异常的都没有。
处理过程涉及功能的数据流如下所示:
导出功能涉及到的数据流
step1:分析日志
发现导出没有成功后,就到日志平台上查日志。
可以找到“数据服务”打印的日志;
没有找到“下载中心服务”的日志;
从已有的应用日志来看,“数据服务”是正常的。
由于没有traceId,就没有办法筛选出整个请求过程的所有应用日志信息。
traceId丢失的原因:请求“数据服务”的动作是由MQ的消费者发起的,这种场景,uat【User Acceptance Test】环境中的应用日志中的traceId为空
那么问题很可能是在“下载中心服务”。
现在这个服务又搜不到日志,怎么办?
搜不到日志有两种原因:
1、日志平台 有问题
2、搜索的关键词不合适
日志平台是基于ES实现,搜索结果与分词器、关键词 关系密切相关, 有时候关键词不对也查不到日志。
就目前的情况看,很难区分是日志平台出问题了,还是搜索的关键词不对。毕竟是uat环境,有问题也不会花力气去处理。
step2: 阻塞了,陷入困境
数据已经取到了,为什么状态一直是失败呢?
排查一下子陷入困境。想进一步排查需要看日志,目前没搜到日志。
要想解决目前的问题,需要先解决眼前的新的问题:为什么在日志平台搜不到日志?
感觉这个问题有些花时间,且眼前还有别的问题,就没有继续排查。
攻克问题的二波攻势有同学会问:为什么不在本地调一下?
因为uat环境和本地用的同一个MQ。本地发的消息很可能让uat上的消费者消费了。
就是拼人品了,也蛮花时间的。
“日志平台”有没有问题?
梳理下日志平台收集数据的原理,顺着数据流排查一下。
日志平台的数据流
排查的顺序:
1、应用打印日志的配置是否正常
2、应用打印的日志是否在约定的目录
3、应用的日志是否被收集到日志平台
step1:直接去uat环境上查看日志打印。由于是uat环境出问题了,本地正常不等于uat正常
有日志
日志是有的。
step2: 这个看不了。找有权限的同学查看了下,没有!!!
指定的目录为什么没有日志文件?
这个文件是在应用中指定的。在应用中重新指定,然后发版,发现日志平台上已经可以搜到日志了。
思考:
第一次遇到这种问题时,没有往日志平台异常的方向上想,觉得这个地方已经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) {
}
}
又提升了排查问题的速度,节省了时间。