用 Jaeger 分析分布式系统中跨服务调用延迟
先说我遇到的情况
最近有个下单接口,P99 突然从 300ms 涨到 1.2s,日志里每个服务都说自己挺快,结果用户就是不买账。一开始只能靠猜:网关是不是慢了?订单服务是不是在等支付?支付服务是不是在等数据库?说实话,跨服务延迟这种事,光看日志真的容易吵架。后来把 Jaeger 拉起来跑了一遍,才看到时间到底耗在哪儿。
把 Jaeger 跑起来
测试环境我一般直接上 all-in-one,别一上来就搞 collector、存储后端那一套,先把链路看到再说。命令大概是这样:
docker run -d --name jaeger \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
-e COLLECTOR_OTLP_ENABLED=true \
docker.1ms.run/jaegertracing/all-in-one:1.62
我这里顺手配了加速域名,不需要的或者拉的时候报错了,可以把 docker.1ms.run/ 删掉,没准儿你用的时候这个加速域名已经不可用了。跑起来之后打开 http://localhost:16686,能看到 Jaeger 的搜索页面就说明 UI 没问题。
注意一下端口:16686 是 UI,4317 是 OTLP gRPC,4318 是 OTLP HTTP。现在新一点的客户端都走 OTLP,老项目如果还在用 Jaeger client 直接上报 14268,那就另说。all-in-one 默认把数据放内存里,重启就没了,所以这玩意儿只适合本地和测试环境,生产别这么干。
服务怎么接进来
别手写一堆 span,真的没必要。用 OpenTelemetry 的 instrumentation 先把 Flask、requests 这类常见库自动埋上,跨服务调用基本就能串起来。以 Python 为例,装这些包:
pip install opentelemetry-api opentelemetry-sdk \
opentelemetry-exporter-otlp-proto-http \
opentelemetry-instrumentation-flask \
opentelemetry-instrumentation-requests
然后每个服务复制同一段初始化代码,只改 OTEL_SERVICE_NAME。比如订单服务叫 order-service,支付服务叫 payment-service:
import os
from flask import Flask
from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor
resource = Resource.create({
"service.name": os.getenv("OTEL_SERVICE_NAME", "order-service")
})
provider = TracerProvider(resource=resource)
provider.add_span_processor(
BatchSpanProcessor(
OTLPSpanExporter(
endpoint=os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318") + "/v1/traces"
)
)
)
trace.set_tracer_provider(provider)
app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)
RequestsInstrumentor().instrument()
@app.route("/order")
def order():
import requests
r = requests.get("http://payment-service:5001/pay", timeout=5)
return r.text
环境变量大概这样:
OTEL_SERVICE_NAME=order-service
OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318
这里 jaeger 是容器网络里的主机名,如果你的服务和 Jaeger 不在同一个 docker 网络里,就换成宿主机 IP。环境变量填 base 地址就行,别手贱把 /v1/traces 也带上,代码里已经拼了,重复了会报 404。requests 的 instrumentation 会自动把 traceparent 带到下游,支付服务那边也初始化之后,一条跨服务 trace 就有了。
不过异步线程池、消息队列、自己封装的 RPC 这些,自动 instrumentation 不一定全覆盖。别问我怎么知道的,层与层之间一绕,trace 就断了。遇到断链先查 context 传播,别急着怀疑 Jaeger。
查一条慢 trace
打开 Jaeger UI,在 Search 里选 service,比如 order-service,Operation 选 GET /order,Lookback 选 1 小时,Min Duration 填 500ms,点 Find Traces。列表里能看到每次请求的总耗时,点进去就是瀑布图。
重点别只看总时长,要看 span 之间的空隙和最长的那一段。比如你看到:
- 网关到订单服务的 span 总共 1.1s;
- 订单服务自己的 span 只有 40ms;
- 支付服务的 span 占了 900ms;
- 支付服务里 MySQL 的 span 又占了 800ms。
那答案就比较明显了,别急着给网关加机器,先去查支付服务那条 SQL 和索引。如果订单服务到支付服务之间有 200ms 的空隙,支付服务自己的 span 又很短,那可能是网络、连接池排队、线程池满了,或者序列化反序列化在耗时间。再看 span 的 tags:http.status_code、db.statement、rpc.grpc.status_code,这些比瞎猜有用。
几个容易踩的坑
第一,时间同步。机器之间时钟差太多,瀑布图会出现负时间或者奇怪的顺序,容器里也记得对时。第二,采样。生产环境别全量采样,可以用 OTEL_TRACES_SAMPLER=parentbased_traceidratio 和 OTEL_TRACES_SAMPLER_ARG=0.1 先采 10%,排障时再临时调高。第三,all-in-one 只适合测试,生产至少上 collector 加外部存储,不然重启一次链路全丢。第四,日志里最好把 trace_id 打出来,从 Jaeger 复制 trace_id 去日志平台搜,定位具体请求会快很多。
最后怎么用它定位问题
我的习惯是先按 P99 过滤慢请求,找一条正常 trace 和一条异常 trace 对比,盯住最长 span 和 span 之间的空隙,再关联日志确认是服务自身慢,还是调用方在等。修完再跑一遍,瀑布图里那个长条变短了,基本就能收工。Jaeger 不是银弹,但至少能让你在跨服务延迟这种问题上少拍脑袋。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。