老楊教你快速揪出系統鏈路里的那個"拖后腿"的家伙
運維人對于自己負責的業務線架構理解決定了自己的薪資和可替代性.
老楊見過太多的"盲人摸象"式排查。其實找到系統鏈路中的薄弱環節,真的不需要憑運氣和直覺。
今天老楊就來聊聊如何避免盲人摸象式的排查。

老楊的核心思路
我的核心邏輯很簡單——先搞清楚調用關系,用數據定位問題區域,然后從資源和網絡層面確認根因。聽起來簡單,但魔鬼都在細節里。
老楊用的啥工具
說實話,工具選擇這事兒得接地氣:
- 監控指標: Prometheus + Grafana(這個組合基本是標配了)
- 鏈路追蹤: SkyWalking居多(國內用得多,中文文檔也全)
- 日志收集: 有條件上ELK,簡單場景直接kubectl logs
- 壓測工具: hey、wrk這些輕量級的就夠用
- 系統診斷: top、iostat、vmstat、ss這些老朋友
- 數據庫分析: MySQL的慢查詢日志配合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成為瓶頸了。可能是數據庫所在的存儲性能跟不上,或者有其他進程在瘋狂寫磁盤。
幾個常見坑和對應的排查命令
(1) CPU被限流了(很容易被忽略)
這個問題特別隱蔽,容器看起來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資源更充足的節點。
(2) 網絡延遲和丟包
有時候問題出在網絡層面,特別是在容器環境下。
# 從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插件的問題了。
(3) 數據庫慢查詢
數據庫問題基本占了我遇到過的性能問題的一半以上。
# 看看有多少慢查詢
$ 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這個腳本跑一遍,基本就能對問題有個大概的了解了。
決策優先級:先修什么后修什么
排查出問題后,還要決定先解決哪個。我一般按這個優先級:
- 影響面最大的先修:如果錯誤率很高,影響大量用戶,這個優先級最高
- 下游問題優先于上游:鏈路追蹤顯示是數據庫慢,就先優化數據庫,別在應用層繞彎子
- 資源瓶頸優先于代碼優化:CPU、IO、網絡這些基礎資源問題不解決,代碼優化效果有限
- 止血優先于根治:比如先把超時時間調短、重試次數減少,避免雪崩,然后再慢慢優化
建立長效機制,別讓問題重復出現
光解決問題還不夠,還得防止類似問題再次發生:
- 設置監控告警:關鍵服務的P95延遲、錯誤率都要有告警閾值,出問題了第一時間知道。
- 建立SLO:比如訂單服務的P99延遲不超過500ms,支付服務的可用性不低于99.9%。有了具體目標,優化就有方向了。
- 定期壓測:每次發版前,或者定期在低峰期做一次壓測,確保系統能承受預期的流量。
- 記錄排查過程:每次解決問題后,都把排查過程和解決方案記錄下來。下次遇到類似問題,直接參考之前的經驗。





















