面向后端开发的故障排查方法论
# 写在文章开头
作为一个后端程序员,每天基本都在繁杂的数据流亦或者精巧的逻辑中游走,结合庞大的系统架构,日常定位问题也是一件令人头疼的事情,所以笔者纠结和近期一次修改开源项目的经历,简单介绍一下面向后端开发的一套故障排查方法论。
我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis (opens new window)。
为方便与读者交流,现已创建读者群。关注下方公众号获取我的联系方式,添加时备注加群即可加入。
# 故障说明
笔者的团队近期需要一个日志告警功能,于是接触到了一个开源项目Nightingale,对应项目地址如下:https://github.com/ccfos/nightingale (opens new window) ,感兴趣的读者可以自行了解一下。
回到问题,笔者的团队在接触这个项目的过程中发现这个工具的接入es9数据源后,执行日志查询时会抛异常:

碍于迁移成本等各方面因素,团队还是决定尝试自己先修复这个问题,于是笔者就开始拉下这个项目的源代码开始的排查和修复的过程。
# 拆解和规划排查计划
针对这种相对复杂无论是排查还是修复成本都相对高的问题,笔者总是会习惯性的将问题进行拆解,这一点也是借鉴了马斯克的工作方法论,即将一个设定模糊、宏大的目标拆解成一个个具体可操作的小目标。 这本质上就是一种对于问题的梳理和拆解,确保我们能够准确高效的完成每一个工作原子,完成故障排查和修复的闭环。
以笔者本次Nightingale兼容es9数据源的任务,在接到这个任务前,笔者也仅仅只看到了一个问题的表现,对于这款工具以及问题具体复现的方式都非常陌生,所以对于这样一个相对模糊、复杂的任务,笔者也进行了非常详尽的任务拆解,即针对Nightingale对es9数据源执行查询时报错问题修复这样一个任务拆解为:
- 了解
Nightingale明确适用场景 - 了解
Nightingale基本使用,并完成搭建 - 针对故障复现步骤使用
es7和es9分别进行尝试 - 拉取源码并跑通
- 定位报错代码源头
- 梳理故障代码段对应请求的梳理流程,绘制执行环路
- 综合分析错误原因
- 结合执行链路得出最小成本改造方案
- 尝试修复异常
可以看到,一个复杂的问题,在笔者按照细致且具体可落实的原子步骤之后,一个模糊的、繁杂的故障排查任务变得直观且可落地执行。
# 明确问题现象并复现
针对前三步笔者这里就不多做演示了,针对本次的问题笔者还是强调从官方的一手资料进行了解学习,本质上通过这些学习了解和复现,我们基本可以对问题现象有一个相对清晰的了解。
# 明确故障位置
在笔者花费了小一天时间快速了解这个项目之后,笔者就尝试拉取官方源代码并将其跑通。作为后端开发,所有问题我们首先从程序跑通后日志定位到错误的终点,以笔者本次的问题为例,对应抛出异常的源头就是这个search请求:

明确问题的终点后,我们也需要了解一个问题的起点,对于后端程序员的来说,基本可以等价理解为一次网络通信请求的发起的发起,一般情况下我们都应该通过浏览器的控制台去明确定位:

结合映射名称我们也很快的定位到映射的具体处理函数:

# 梳理功能执行流程
针对问题我们有了明确的入口和出口之后,我们就可以通需要通过断点调试理解整个功能执行的具体流程的,针对断点调试这个工序也是存在模糊和繁杂的感觉,所以笔者一般也会在这个步骤的时候再次进行拆解。
以笔者ds-query接口看到的报错并结合页面功能的理解,笔者会将代码调试拆解为如下几个步骤保证调测后能够正确的理解和处理问题:
- 梳理请求入参,结合页面进行推测,不理解的地方进行标注
- 了解出参含义
- 调试入口参数的执行、转换、如何引发报错
- 报错的源头位置发生了什么,导致这个报错的输出
- 整理功能的链路执行流程图
对应这个步骤的落地过程,感兴趣的读者可以看看笔者的这篇文章: 硬核安利一个监控告警开源项目Nightingale:https://mp.weixin.qq.com/s/Od4KUp2DNSXEB8qEMAFXBg (opens new window)
这里就记录了笔者梳理参数、调测执行链路、整理执行流程图的完整过程,读者可以参考这样一个完整的步骤作为参考。
# 综合分析根因并进行最佳修复决策
经过详尽的调试和梳理之后,我们对于问题基本上就会有非常细节的理解,以笔者整个链路的调试发现,这个问题本质上就是Nightingale底层的es客户端发出的restful请求es9无法正确处理,说白了就是语法不兼容。
于是笔者直接拿着原原本本的请求到kinbana上执行一下,果然完完整整的复现了问题:
GET /java_app_logs/_search
{
"aggregations": {
"ts": {
"date_histogram": {
"field": "timestamp",
"fixed_interval": "600s",
"min_doc_count": 1,
"offset": "-600s"
}
}
},
"query": {
"bool": {
"filter": {
"range": {
"timestamp": {
"format": "epoch_millis",
"from": 1762353000000,
"include_lower": true,
"include_upper": true,
"to": 1762353600000
}
}
},
"must": {
"query_string": {
"query": "log_level:\"ERROR\""
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
明确了问题原因之后,我们就需要寻求一种最小成本的解决方案,以笔者碰到的问题为例,面对修复底层es客户端兼容性问题,这还是一个模糊、繁杂的工序,笔者还是采用的拆解的方式进行梳理和落地:
- 了解请求参数含义
- 明确定位具体报错参数
- 寻求可以兼容的请求方式
- 寻找源码中改动最小的地方
经过拆解后,笔者最终整理出了最简修复决策,即将底层客户端拉取下来进行魔改,将from、to语法改为gte和lte:
然后将用这个魔改后的客户端替换掉Nightingale中这个已经停止维护的es客户端,于是就有了这篇文章:
基于魔改Nightingale源码浅谈go语言包模块管理: https://mp.weixin.qq.com/s/FFBpgJH2VoRMhzW556R5Kg (opens new window)
# 覆盖调测
明确问题并完成源码改动落地后,我们还需要进行回归测试,这里笔者也是借鉴了测试团队的调测风格,进行这对问题拆解出无数个测试用例进行回归测试:
- 建立测试数据,验证es7查询是否报错
- 建立测试数据,验证es7查询是否数据正确
- es8同理
- es9同理
明确问题无误后,投产,大功告成
# 小结
虽然笔者这篇文章是介绍后端开发日常故障排查与修复的通用方法论,但整个执行过程完全可以抽象为一个非常通用的工作方法,即针对问题不要恐惧,明确完成下面这3个步骤:
- 拆解将问题梳理清晰,打造可验证的闭环任务
- 严格执行并具象化结果
- 清晰并采用最精简的方式解决问题
- 完整的整理与复盘
我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis (opens new window)。
为方便与读者交流,现已创建读者群。关注下方公众号获取我的联系方式,添加时备注加群即可加入。
# 参考
工作方法论—马斯克的任务分解法 :https://blog.csdn.net/qq_21997625/article/details/132113564 (opens new window)
马斯克一个很绝的心态:10%目标定律 :https://baijiahao.baidu.com/s?id=1794022673779843963 (opens new window)