前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >干货|如何快速问题出在哪了?

干货|如何快速问题出在哪了?

作者头像
烟雨平生
发布2023-03-07 10:52:04
2680
发布2023-03-07 10:52:04
举报
文章被收录于专栏:数字化之路

先从一个具体的问题说起。

背景

遇到问题,有时候很难找到原因,然后就卡在一个地方无法推进。

每次解决问题后,最好能复盘一下,总结下排查思路。

比如从全链路的视角来分析问题、从数据流动的方向去排查

出现的问题

一个导出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:

代码语言:javascript
复制
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就可以把一次用户请求在系统中调用的路径串联起来。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-11-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 的数字化之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 出现的问题
  • 处理过程
  • 攻克问题的二波攻势
  • 原因
  • 小结
  • 额外的收获
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档