2023年双11大促,我们一个核心API接口突然从50ms飙到3s。排查了半小时——日志没报错,监控CPU正常,数据库慢查询也没有。最后靠人工翻代码,发现是某个下游服务超时重试导致雪崩。这种故障,没有链路追踪就是瞎子。
当时我们只有Prometheus做指标监控,但指标只能告诉你“慢了”,不能告诉你“哪里慢”。于是决定上分布式链路追踪。选型时在Jaeger和Zipkin之间纠结,两个都试了一遍,踩了不少坑。这篇文章直接给结论:小团队选Zipkin,大流量选Jaeger。下面展开说为什么。
Jaeger(v1.52)和Zipkin(v2.24)都是基于Google Dapper论文实现,但架构不同:
| 指标 | Jaeger | Zipkin |
|---|---|---|
| 单机吞吐(span/s) | 15000 | 8000 |
| P99延迟(采样率100%) | 2.3ms | 5.1ms |
| 内存占用(1000 span/s) | 256MB | 180MB |
| 存储成本(ES索引) | 高(多字段索引) | 中 |
压测环境:PHP8.3 + Laravel11 + MySQL8.0.35 + Elasticsearch7.17.9,单机4C8G。使用wrk压测,每个请求产生10个span。
<?php
// composer require open-telemetry/opentelemetry-auto-jaeger
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\Contrib\Jaeger\Exporter;
$exporter = new Exporter(
'my-service',
'http://jaeger-collector:14268/api/traces'
);
$tracerProvider = new TracerProvider(
new SimpleSpanProcessor($exporter)
);
$tracer = $tracerProvider->getTracer('my-service');
// 创建根span
$span = $tracer->spanBuilder('http.request')->startSpan();
$span->setAttribute('http.method', 'GET');
$span->setAttribute('http.url', '/api/order');
// 模拟业务逻辑
usleep(100000); // 100ms
$span->end();
$tracerProvider->shutdown();<?php
// 使用Zipkin官方PHP库
use Zipkin\TracingBuilder;
use Zipkin\Samplers\BinarySampler;
use Zipkin\Reporters\Http;
$endpoint = 'http://zipkin:9411/api/v2/spans';
$reporter = new Http($endpoint);
$sampler = BinarySampler::createAsAlwaysSample();
$tracing = TracingBuilder::create()
->havingLocalServiceName('my-service')
->havingSampler($sampler)
->havingReporter($reporter)
->build();
$tracer = $tracing->getTracer();
$span = $tracer->newTrace()->setName('http.request');
$span->start();
$span->tag('http.method', 'GET');
$span->tag('http.url', '/api/order');
usleep(100000);
$span->finish();
$tracer->flush();version: '3.8'
services:
jaeger:
image: jaegertracing/all-in-one:1.52
ports:
- '16686:16686' # UI
- '14268:14268' # HTTP collector
- '4317:4317' # gRPC
environment:
- COLLECTOR_OTLP_ENABLED=true
- SPAN_STORAGE_TYPE=elasticsearch
- ES_SERVER_URLS=http://elasticsearch:9200
zipkin:
image: openzipkin/zipkin:2.24
ports:
- '9411:9411'
environment:
- STORAGE_TYPE=elasticsearch
- ES_HOSTS=http://elasticsearch:9200
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.9
environment:
- discovery.type=single-node
- ES_JAVA_OPTS=-Xms512m -Xmx512m# jaeger-sampling.yaml
service_strategies:
- service: my-service
type: probabilistic
param: 0.1 # 10%采样率
- service: my-service
type: rate_limiting
param: 10 # 每秒最多10个span<?php
// 使用Zipkin的AsyncHttpReporter实现批量发送
use Zipkin\Reporters\Http\AsyncHttpReporter;
use Zipkin\Reporters\Http\ClientFactory;
$clientFactory = new ClientFactory();
$reporter = new AsyncHttpReporter(
$clientFactory,
$endpoint,
[
'batch_size' => 100, // 每100个span发送一次
'flush_interval' => 5 // 最多5秒发送一次
]
);上线后,我们对核心接口做了压测对比:
| 场景 | 无追踪 | Jaeger | Zipkin |
|---|---|---|---|
| P99延迟 | 50ms | 52.3ms | 55.1ms |
| CPU使用率 | 30% | 35% | 33% |
| 内存占用 | 200MB | 256MB | 230MB |
结论:Jaeger性能损耗略低,但内存占用更高。Zipkin更轻量,适合资源受限场景。
坑1:采样率设100%导致存储爆炸
上线第一天,Jaeger的Elasticsearch索引每天增长50GB。原因是采样率设了100%,每个请求产生10个span,QPS 2000,一天就是17亿条span。解决方案:生产环境用自适应采样,核心接口10%,非核心1%。
坑2:Zipkin的HTTP协议在高并发下丢数据
压测到8000 QPS时,Zipkin的collector开始返回503。原因是HTTP请求排队,客户端超时丢弃。解决方案:改用Jaeger的gRPC协议,或者给Zipkin加Kafka缓冲。
坑3:时间戳不同步导致链路断裂
跨服务器调用时,如果服务器时间差超过1秒,链路会显示为两条独立trace。解决方案:所有服务器统一使用NTP,并配置chronyd同步。
坑4:Elasticsearch版本不兼容
Jaeger 1.52要求ES 7.x,Zipkin 2.24支持ES 7.x和8.x。我们一开始用了ES 8.0,Jaeger直接报错。解决方案:严格按照官方文档选择版本。
选型没有银弹。小团队、低流量选Zipkin,部署简单,资源占用低。大流量、高并发选Jaeger,性能更好,支持gRPC和自适应采样。无论选哪个,一定要控制采样率,否则存储成本会让你哭。
专注技术分享与实战