总结过往遇到的一些常见技术问题以及可能的原因和解决的思路方法
🎞️关于故障的一些历史故事:
- 客户来电,某公务员办公平台服务访问很慢,大量白屏
- 线上告警,集群机器内存 16 台全部飙高
- 那些以为不能重现的问题,一到用户那里就复现
案例太多,经历也很惨痛。 因此对这些历史问题做一个大乱炖....
一、网络和反向代理等
按照一个 http 请求的经过的链路节点开始入手排查。如下图所示:(任何问题就从这条链路开始排查)
根据请求服务不可用,按照区域来圈定问题,然后逐一排查。(解决问题需要先圈定问题出错范围,由大到小原则)
1.1 网络问题
网络问题导致的服务不能访问 (比如和客户私有环境建立通信时,会经常需要解决各种网络问题)
网络命令 | 描述 | 举例 |
---|---|---|
ping | 检查网络联通(注意目标ip是否禁用ping命令) | 检查网络是否能正常访问,比如当前网络无法访问 YouTube |
telnet | 检查网络端口 | ip 端口;或者 telnet 域名 端口 |
nc | 网络查看 | 实现任意TCP/UDP端口的侦听,nc可以作为server以TCP或UDP方式侦听指定端口;端口的扫描,nc可以作为client发起TCP或UDP连接;nc -vzw2 ip port |
netstat | 统计 TCP、UDP 的连接数等 | 检查 TCP、UDP 等连接数 |
技术|netstat 的10个基本用法 (linux.cn)
查看远程端口是否开发
-
nc -vzw2 10.154.143.32 8080
-
telnet ip port 可以通过这个命令快速检查对方网络是否对你的网络进行端口开放。
比如各种云厂商的 MySQL 数据库,如果你的应用需要直接访问它们,都需要在安全组添加你的出口IP。可以通过这几个命令快速检查网络情况。
网络不通,可能导致的原因:
- IP 白名单限制,为了安全应用之间的通信会添加黑白名单;如IP名单、人员名单等。(和三方对接,应用之间添加黑白名单过期导致服务不能正常访问)
- VPN 等有代理软件,忘记关闭,正常网络不能访问。 (如果其他人能正常访问服务,你不能,则可以考虑原因之一)
如何加白网络
如果告诉对方自己的 IP 进行加白呢。特别注意,加白的 IP 为我们的出口 IP。
arduino
// 查询自己的出口 IP
curl cip.cc
查看出自己的 IP,注意是出口IP。不是ifconifg查询出来的ip。(具体是NAT转换)
arduino
// 举例说明添加 IP 段
10.178.0.0/16
10.178 开头的IP都加入白名单
网络问题导致的服务访问慢
如果是访问慢,可能原因比较综合,有可能是带宽小,或者请求流量过大,也有可能是 DDoS等
可能情况 | 具体描述 |
---|---|
出口带宽过小 | 曾经有一个客户出口带宽为 1M,服务上线后客流量上来导致问题 |
流量请求过大/ DDoS 攻击等攻击 | 洪流导致/流量请求过大 |
关于 http 状态码的错误理解(都是基础问题):
-
404 的错误理解:404 应用没有日志,怀疑还没有请求到应用。(实际请求到后端,资源未找到)
-
302 的错误理解:302 应用没有日志,怀疑还没有请求到应用。(请求到了,可能302到错误页面了。)
状态码 | |
---|---|
404 | 错误通常是由于服务器上未能找到所请求的页面或资源引起 的; 可以检查自己的 url 是否正确或者查看代码是否部署成功。(以前因为没有代码没提交导致 404) |
302 | 请求转发 |
正确理解各种状态码是解决问题的基础,http、tcp知识,不仅仅在面试时候需要,在出现各种网络问题时也同样重要。
1.2 nginx
nginx 配置请求日志,如果服务请求没有达到应用,但是不确定是否达到服务器,可以排查 nginx 日志; 另外也可以通过抓包工具排查是否到达服务器。 nginx 遇到的问题汇总:
问题描述 | 详细 |
---|---|
header 中的下划线的字段丢失 | 情况描述:nginx默认request的header的那么中包含'_'时,会自动忽略掉。 解决方法是:在nginx里的 nginx.conf 配置文件中的http部分中添加如下配置:underscores_in_headers on; 默认为 off |
文件上传限制,默认1M,导致上传一直失败 | http { client_max_body_size 3m; } |
其他情况:
- 限流配置
- 反向代理配置错误
- 未配置 ssl 等
如果排查网络问题没有问题,那么像 nginx 等问题也需要重点排查, nginx 有对应日志,可查看日志排查请求是否正常。 像常见的 cookie 超大等问题,nginx 日志也是会打印出来的。
查看日志是解决问题最简单直接也可能最有效的方式。
经验:一个新应用的 nginx 最好的方式是从一个老的、稳定的应用拷贝过滤修改。默认的 nginx 可能会遇到诸多问题。
1.3 简易抓包工具 tcpdump
通过 tpcdump 来判断是否将请求打到服务端应用。
shell
-- get请求
sudo tcpdump -vvAls0 | grep 'GET'
-- post 请求
sudo tcpdump -vvAls0 | grep 'POST'
抓取 get, url为'/project/projectsList' 请求
perl
sudo tcpdump -vvAls0 | grep 'GET' | grep '/project/projectsList' -C 100
通过简单抓包,可以查看是否已经达到这台服务器。检查一些诡异的请求问题。
其他抓包工具:
抓包工具 | |
---|---|
charles-mac | 非常好用的charles,只针对 mac 用户,对于 APP 端的抓包非常方便,学习成本低,对于 APP 不知道如何抓包的,用这个工具就十分友好 |
wireshark | 专业,有一定学习成本,windows、mac 都有版本 |
尤其对于 APP 的抓包,对于服务端的同学,使用这几个工具再方便不过了。
1.4 其他
- DNS 域名劫持
- tcp 重传率高,网络不稳定等
其他网络排查的细节,还需要多多学习专业知识;上面所列举的问题也只是冰山一角。多总结多积累。
二、服务器
关于服务端的问题,已经总结过两篇相关文章。
但当 arthas 启动不了,不能使用的时候,则需要通过其他工具进行辅助排查。
2.1 cpu 飙高
通用解决方法。很标准的方式 (适用于 Java 开发者)
其中 top 命令查看整个机器负载情况。
markdown
1.. top -c
查找占用较高的进程
2. top -Hp 8773
查找占用较高的线程
3. printf "%x\n"8773
查找占用较高的线程
4. jstack 进程号 | grep 线程Id
查找线程信息
占用 cpu 资源的行为,独占时间过长的行为。
可能的原因 |
---|
执行大任务 |
规则引擎,导致计算时间过程 |
正则表达式匹配 |
..... |
2.2 磁盘满了
查看磁盘占用较大目录
shell
$df -hl
展示文件且按大小排序**du -sh * | sort -hr | head**
或者使用找出特定路径下的大于1G的文件 **du -sh /home/admin/* |grep G**
导致的原因 | 如何处理 |
---|---|
大量打印日志,未及时清理,导致磁盘满了;重复打印的日志;或者打印过多无用日志导致磁盘满了 | 需要定期清理;配置水位线等告警 |
2.3 内存飙高
- 内存泄漏
- 常常与CPU飙高同时发生,内存不足,fullGC等出现,gc线程一直处于活跃状态。
markdown
1. top -c 获取 pid
1. top -Hp pid 查询该 pid 线程情况
1. printf '%X' **threadId** 将转换线程 id 为 16 进制
1. jstack pid | grep '**threadId**' -C 40
2.4 网络
- 没有及时释放 tcp 链接。
makefile
SS -S
$ss -s
Total: 273 (kernel 0)
TCP: 106863 (estab 205, closed 106635, orphaned 970, synrecv 0, timewait 1/0), ports 0
Transport Total IP IPv6
* 0 - -
RAW 0 0 0
UDP 2 2 0
TCP 228 228 0
INET 230 230 0
FRAG 0 0 0
ss 是 Socket Statistics 的缩写,用来统计 socket 连接的相关信息,它跟 netstat 差不多,但有着比 netstat 更强大的统计功能,能够显示更多更详细的连接信息。
或者使用 netstat 查看 tcp、udp 连接数
css
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
主动关闭的一方在发送最后一个 ack 后,就会进入 TIME_WAIT 状态 停留2MSL(max segment lifetime)时间. 但是没有关闭,导入不能再建立链接。
time-wait 过多的原因可能包括以下几个方面:
网络短暂不稳定 :当网络短暂不稳定或者存在网络拥塞时,系统可能会出现大量的 time-wait 状态,导致 time-wait 过多。
系统负载高 :当系统负载过高时,可能会导致系统处理不过来大量的连接请求,从而产生大量的 time-wait 状态。
配置不当 :如果系统的 TCP 配置不当,比如超时时间设置过长,连接数限制过小等,都可能导致 time-wait 过多。
攻击或者恶意行为:有些攻击或者恶意行为可能会导致系统产生大量的无效连接,从而导致 time-wait 过多。
综上所述,time-wait 过多的原因可能非常复杂,需要根据具体情况进行分析和解决
三、应用
3.1 数据库
可能存在的问题:
- sql 慢,大表 join, 大的 dml 操作
- 数据库量大导致瓶颈性能问题
- 压测故障导致问题
- 死锁问题导致问题
- 未限流,大流量的雪崩等
常备运维命令,增加监控策略, 做好资源规划,做好性能调优等
3.2 资源未关闭
- 有一次写一个锁的组件,使用 redis 的链接未关闭,导致应用最后出现问题。所有的资源应该在使用后关闭。
例如,还有大量其他的连接池等未正确关闭。
- redis连接池
- 数据库连接池
- IO未关闭
在编程和 review 的时候需要特别注意。
3.3 Tomcat 的最大 HTTP 请求线程数
可以修改 tomcat 的最大 http 请求数据,默认是 200,可以进行更改。
未充分利用资源的性能,一直用最小的配置。需要进行优化。
- 线程池配置数量太小
- 数据库连接池配置数量太小
- tomcat 默认的连接池太小
很多资源池都是用的默认值,务必根据实际需求调整适合自己的大小。
3.4 日志
一般可以通过日志就能定位问题所在。
- 分类别存储日志
- 采集日志到分布式系统
- 关键要素的打印
- 日志输出的规范
- 好的错误码设计
- 不要吞掉异常信息
- 钉钉机器人通知: 还可以打印IP,这样更加容易定位问题
- 不要打印一些没有用的日志 && 不要重复打日志 && 关键场合打印 (遵循两个原则:给谁看,看了能够干什么)
日志平时不重要,只有关键时刻才能知道它有多重要
一个漂亮的requestId,给你的问题排查增加一些便利。
设计思想:前端在每一个请求上携带requestId,后端在日志文件中配置requestId并打印。 方便问题链路的排查。如果是三方API请求:请求的时候不携带requestId,但是返回参数设计 返回一个requestId,方便链路排查。
类似于traceId。在海量日志追踪的时候,你会知道它有多香!
警惕:可能存在全局日志未采集成功,错误判断未请求;类似的,没有查询到日志,错误以为mq没有消费。
用好日志,让问题无处可藏!
3.5 其他可能出现的问题
- 全局配置变更导致推送异常
- 使用 snapshot包,不稳定。
- 应用升级未做好兼容导致
- 接口不一致
- 未做好幂等
- 锁提前释放
- 接口超时
- 数据不一致
- 预发、线上逻辑不一致等
....
问题很多,那些你觉得可能出现问题的地方也一定会出问题!
警惕问题
- 发布版本注意前后兼容;注意升级兼容
- 考虑并发问题时要周全,避免出现线程不安全
- 幂等性,避免重复请求带来的数据异常;不考虑分布式事务、分布式锁等问题
- 避免脏数据导致程序崩溃问题。曾经因为一个脏数据,导致整个页面挂掉
技术越高的人,胆子越小(越谨慎)!
四、正确的使用
4.1 搜索分词理解不到位
搜索问题,需要注意分词,搜索为不到结果,并不一定是没有,可能是关键词并非关键分词
perl
# 从指定文件中搜索关键字,并打印上下20行。
grep '搜索关键字' 文件名称 -A 20 -C 20
例如: grep 'NullPointorException' application.log -A 20 -C 20
查找历史数据,比如使用 tail -100f application | grep '关键字'
不知道分词,搜索无结果的错误判断,导致错误判断!!!
搜索引擎
- 使用双引号括起来能精准匹配
- 相似的关键词
"NullPointorException is xxx"
将需要搜索的文章双引号包裹起来,就不会被分词了
其他搜索引擎也是一样的。特别注意分词的使用,大多数并不会模糊搜索。
比如:NullPointorException
,输入 NullPointorEx
并不能被检索到。
掌握错了工具,或者错误地使用了工具,会给问题排查增加更多的困难!!!
4.3 常备工具箱
熟悉排查问题的工具箱,把枪案子磨亮一些!
- arthas
- jvm
- linux
- 数据库常用运维脚本工具
- 抓包工具
- chatgpt ,非常好用的工具,特别适合排查问题。贴上一段错误日志,可能快速得到答案;用好 chatgpt 能解决大部分问题。
.......
善假于物!
五、警惕坏习惯
5.1 恶心人的行为
- 总是在程序里面搞一套自己的实现,比如 mybatis,有用苞米豆的,有用 dynamic mybatis的。
- 如果 filter 自己搞一套等
- 多一些团队考虑,少一些个人主义;多一些务实,少一些炫技。
保持统一非常重要,方便迭代、维护; 没有团队精神
5.2 坏习惯
- threadLocal 不及时remove; 不及时释放各种资源;不使用池化技术
- 不处理异常,从不考虑超时
- 不考虑 NPE,总以为所有逻辑都按照正常逻辑运行
- 不考虑边界。
- redis/ hashMap做缓存,不设置过期时间
- 吞掉异常; 转换异常类型,不传递任何错误msg和参数信息
- 不分轻重的打印日志,监控错误
- 从不压测;不考虑性能;以及从不考虑资源等影响;也从不考虑限流; 也从不考虑服务降级。有错误直接404等
- 不注重规范
.......
很多问题就是一些坏习惯导致的。很多难棘手的问题最后发现是一些低级错误导致的。
六、解决思路总结
6.1 经验
- 精准定位问题,不要找错方向,不要被表象误导,延误了排查问题的时机。 这个非常重要、非常重要、非常重要
- 谨慎看待别人给的结论,针对排查问题,有一些同学经验不足,会给出一些错误的结论。从而导致排查出现问题
- 多参与问题的排查,积累经验,经验越多,排查问题越多、排查的经验越丰富;那么解决问题自然就越快。
6.2 错误排查思路
- 总结、学习、归纳。从菜鸟走向高手。
- 逐渐形成自己的排查思路。
- 大胆猜测、小心论证
6.3 团队作战
紧急、重要的问题需要拉团队同学一起排查,别单打独斗。
- 提前做好团队紧急问题/重大问题处理方案
- 定期做一下团队演练。
七、规避问题而不是制造问题
学会从事情规避问题,而不是事后去查找问题。
提前考虑边界,提前考虑异常;增强程序健壮性,而不是让问题出现。
- 规范、约束、监控、应急
- 优雅关闭,避免极端异常;避免小概率问题出现。
- requestId 或者 traceId 全局唯一,方便路径排查;分布式日志
- 考虑程序幂等
- 线程池自定义线程名称,方便定位
- 大任务、慢任务采用不同的线程处理
- ......
规避问题而不是制造问题; 多考虑一步,再考虑一步!
限于篇幅,也不可能一一详尽,后续再根据具体模块,具体案例对此类问题进行总结。
八、最后感想
喜欢研究问题,那么解决问题就会越来越快;
喜欢追本溯源,那么解决问题也会越来越快;
希望帮助别人解决问题,那么解决问题也会越来越快;
不断地学习,不断地积累。🖊️ 无他,唯手熟尔,最后兵贵神速!!