0. 写在前面:为什么你需要“神器”而非“常用命令
大家好,欢迎来到干货、技术、专业全方位遥遥领先的老杨的博客.
帮老杨点赞、转发、在看以及打开小星标哦
攒今世之功德,修来世之福报
运维人对于自己负责的业务线架构理解决定了自己的薪资和可替代性.
老杨见过太多的"盲人摸象"式排查。其实找到系统链路中的薄弱环节,真的不需要凭运气和直觉。
今天老杨就来聊聊如何避免盲人摸象式的排查.乡亲们如果有什么想法也可以评论区留言一起聊聊.
我的核心逻辑很简单——先搞清楚调用关系,用数据定位问题区域,然后从资源和网络层面确认根因。听起来简单,但魔鬼都在细节里。
说实话,工具选择这事儿得接地气:
kubectl logshey、wrk这些轻量级的就够用top、iostat、vmstat、ss这些老朋友pt-query-digest这是最关键的一步。很多人上来就开始瞎猜,其实你得先知道A服务到底调用了哪些下游服务。
如果你用的是SkyWalking或者Jaeger,它们都有现成的服务拓扑图。我一般会先用API拉取服务列表:
# 获取所有服务名称
$ curl -s 'http://jaeger-query:16686/api/services' | jq '.'输出大概是这样:
[
"order-service",
"user-service",
"payment-service",
"product-service"
]没有链路追踪系统也没关系,在K8s环境下可以通过service来大致了解调用关系:
$ kubectl get svc -A -o wide | head -5NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
prod order-svc ClusterIP 10.96.23.12 <none> 80/TCP 120d
prod user-svc ClusterIP 10.96.45.88 <none> 80/TCP 120d
...有了这个"怀疑名单",后面的排查就有方向了。
这一步是要找出到底哪个服务的延迟异常高或者错误率飙升。Prometheus在这里就派上用场了。
我经常用的一个查询是看各服务的95分位延迟:
$ curl -s 'http://prometheus:9090/api/v1/query?query=histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le,service))'假设得到的结果是:
{
"status":"success",
"data":{"result":[
{"metric":{"service":"order-service"},"value":[1620000000,"0.42"]},
{"metric":{"service":"payment-service"},"value":[1620000000,"1.85"]},
{"metric":{"service":"user-service"},"value":[1620000000,"0.10"]}
]}
}一眼就能看出来,payment-service的95分位延迟是1.85秒,比其他服务高出太多了。这基本就锁定了问题范围。
同时我也会查一下错误率:
$ curl -s 'http://prometheus:9090/api/v1/query?query=sum(rate(http_requests_total{code=~"5.."}[5m])) by (service)'有了怀疑目标,接下来就要看具体是哪个环节慢了。链路追踪在这里特别有用,能告诉你时间都花在哪儿了。
# 查询payment-service的慢请求追踪
$ curl -s 'http://jaeger-query:16686/api/traces?service=payment-service&limit=5&duration=300000' | jq '.data[0]'典型的输出可能是:
{
"traceID":"abcd1234",
"spans":[
{"operationName":"HTTP GET /order","duration":120000, "process":{"serviceName":"order-service"}},
{"operationName":"HTTP POST /payment/charge","duration":110000, "process":{"serviceName":"payment-service"}},
{"operationName":"SELECT FROM payments","duration":105000, "process":{"serviceName":"mysql"}}
]
}这个追踪结果就很清晰了:总耗时120ms,其中110ms花在payment服务的charge操作上,而charge操作里又有105ms是在等MySQL的SELECT查询。问题基本定位到数据库层面了。
光有数字还不够,得看看具体的错误信息和异常栈。
$ kubectl logs deploy/payment-deployment -n prod --tail 200 | grep -i error经常能看到这样的日志:
2024-12-01T10:12:05Z ERROR PaymentProcessor.java:78 - Timeout when calling db: SELECT * FROM payments WHERE user_id=? (timeout 1000ms)
2024-12-01T10:12:06Z WARN RateLimiter - Connection retry attempt 3/3 failed
...到这里基本就能确认是数据库连接超时导致的问题了。
最后还得确认是应用逻辑的问题,还是底层资源不够用了。
# 看Pod的资源使用情况
$ kubectl top pod payment-deployment-xxxxx -n prod
# 如果能SSH到宿主机,看看IO情况
$ iostat -x 1 2假设得到:
# kubectl top pod输出
NAME CPU(cores) MEMORY(bytes)
payment-deployment-xxxxx 900m 1.2Gi
# iostat输出显示磁盘IO等待时间很高
Device r/s w/s await
sda 10.2 85.3 60.32如果看到await这么高(60ms),基本就是磁盘IO成为瓶颈了。可能是数据库所在的存储性能跟不上,或者有其他进程在疯狂写磁盘。
这个问题特别隐蔽,容器看起来CPU使用率不高,但实际上被cgroup限流了。
# 找到容器ID
$ docker ps --format '{{.ID}} {{.Names}}' | grep payment
c3f2a7b9db9a payment-deployment-xxxxx
# 查看CPU统计
$ cat /sys/fs/cgroup/cpu,cpuacct/docker/c3f2a7b9db9a/cpu.stat如果看到:
nr_periods 240
nr_throttled 56
throttled_time 48000000nr_throttled大于0就说明被限流了。这时候要么调整Pod的resource limits,要么考虑换到CPU资源更充足的节点。
有时候问题出在网络层面,特别是在容器环境下。
# 从Pod内部ping一下下游服务
$ kubectl exec -it payment-pod -- ping -c 5 mysql-0.mysql.prod.svc.cluster.local
# 测试网络带宽
$ iperf3 -c 10.10.0.5 -p 5201 -t 10如果ping的时延超过几十毫秒,或者iperf3显示带宽明显偏低,就要怀疑网络或者CNI插件的问题了。
数据库问题基本占了我遇到过的性能问题的一半以上。
# 看看有多少慢查询
$ mysql -e "SHOW GLOBAL STATUS LIKE 'Slow_queries';"
# 用pt-query-digest分析慢日志(这个工具真的好用)
$ pt-query-digest /var/log/mysql/slow.log | head -50pt-query-digest的输出特别直观:
# Query 1: 0.12 QPS, 1.45x concurrency, ID 0x8DFD0D6B15D10734...
# Time range: 2024-12-01T09:00:00 to 2024-12-01T10:00:00
# Response time: 95% 1.2s, 99% 2.5s
SELECT user_id, payment_status FROM payments WHERE created_at > ?一看就知道这个查询占用了绝大部分数据库时间,而且响应时间很高。
把上面这些检查项串起来,写成一个脚本,这样每次遇到问题都能快速过一遍:
#!/bin/bash
# quick_diagnose.sh - 老杨的快速诊断脚本
SERVICE_NS=prod
SERVICE_NAME=payment-deployment
PROM_URL=http://prometheus:9090
JAEGER_URL=http://jaeger-query:16686
echo"=== 资源使用情况 ==="
kubectl top pod -l app=${SERVICE_NAME} -n ${SERVICE_NS} || echo"无法获取资源信息"
echo -e "\n=== 延迟情况 ==="
curl -s "${PROM_URL}/api/v1/query?query=histogram_quantile(0.95,sum(rate(http_request_duration_seconds_bucket{service=\"payment-service\"}[5m])) by (le))" | jq -r '.data.result[0].value[1] // "无数据"' | awk '{printf "95分位延迟: %.2fs\n", $1}'
echo -e "\n=== 最新错误日志 ==="
kubectl logs -l app=${SERVICE_NAME} -n ${SERVICE_NS} --tail 20 | grep -i "error\|exception\|timeout" | tail -5
echo -e "\n=== 节点IO情况 ==="
NODE=$(kubectl get pod -l app=${SERVICE_NAME} -n ${SERVICE_NS} -o jsonpath='{.items[0].spec.nodeName}')
if [ ! -z "$NODE" ]; then
echo"节点: $NODE"
ssh $NODE"iostat -x 1 1" 2>/dev/null || echo"无法连接到节点"
fi这个脚本跑一遍,基本就能对问题有个大概的了解了。
排查出问题后,还要决定先解决哪个。我一般按这个优先级:
光解决问题还不够,还得防止类似问题再次发生:
设置监控告警:关键服务的P95延迟、错误率都要有告警阈值,出问题了第一时间知道。
建立SLO:比如订单服务的P99延迟不超过500ms,支付服务的可用性不低于99.9%。有了具体目标,优化就有方向了。
定期压测:每次发版前,或者定期在低峰期做一次压测,确保系统能承受预期的流量。
记录排查过程:每次解决问题后,都把排查过程和解决方案记录下来。下次遇到类似问题,直接参考之前的经验。
信老杨,半夜无故障,信老杨, 半夜不起床.
老杨的博文质量高,大佬也多,评论区一起聊聊呀!万一你被哪个大哥相中了呢?
嗯,挺好这个结尾水的一塌糊涂.
这里老杨先声明一下,日常生活中大家都叫老杨波哥,跟辈分没关系,主要是岁数大了.就一个代称而已. 老杨的00后小同事老杨喊都是带哥的.张哥,李哥的. 但是这个称呼呀,在线下参加一些活动时.金主爸爸也这么叫就显的不太合适. 比如上次某集团策划总监,公司开大会来一句:“今个咱高兴!有请IT运维技术圈的波哥讲两句“ 这个氛围配这个称呼在互联网这行来讲就有点对不齐! 每次遇到这个情况老杨老杨周末浅聊服务器开在公网的那些坑老杨干了,你们随意!” 所以以后咱们改叫老杨,即市井又低调.还挺亲切,老杨觉得挺好.
运维X档案系列文章:
企业级 Kubernetes 集群安全加固全攻略( 附带一键检查脚本)
看完别走.修行在于点赞、转发、在看.攒今世之功德,修来世之福报
老杨AI的号: 98dev