Jaeger-02-Query模块
1. 模块概览
1.1 模块职责
Query 模块是 Jaeger 的查询服务和 UI 前端,负责从存储后端读取 trace 数据并提供 Web 界面和 API。
核心职责:
- Trace 查询:通过 TraceID、服务名、操作名、标签、时间范围等条件查询 trace
- 服务发现:查询所有已知服务名和操作名
- 依赖分析:计算和返回服务依赖关系图
- 归档管理:支持将 trace 归档到独立存储
- UI 服务:提供 Jaeger UI Web 界面
- 时钟偏差调整:自动修正分布式系统中的时钟偏差
输入与输出:
- 输入:
- HTTP API 请求(端口 16686)
- gRPC API 请求(端口 16685)
- 输出:
- JSON 格式的 trace 数据
- HTML 格式的 UI 页面
- 依赖关系图(Dependency Graph)
1.2 上下游依赖
上游(调用 Query 的组件):
- Jaeger UI(前端)
- 外部监控系统(通过 API)
- 开发者(通过浏览器或 API)
下游(Query 调用的组件):
- Storage Reader:从存储后端读取 trace 数据
- Dependency Reader:读取服务依赖关系
- Archive Storage(可选):归档存储
1.3 生命周期
初始化 → 启动服务器 → 接收查询 → 读取存储 → 调整数据 → 返回结果 → 优雅关闭
1.4 模块架构图
flowchart TB
subgraph Client["客户端"]
UI["Jaeger UI<br/>(浏览器)"]
API["外部 API<br/>调用方"]
end
subgraph Query["Query 模块"]
direction TB
subgraph Servers["服务器层"]
HTTP["HTTP Server<br/>:16686"]
GRPC["gRPC Server<br/>:16685"]
end
subgraph Handlers["处理器层"]
APIV2["API V2 Handler<br/>(/api/traces)"]
APIV3["API V3 Handler<br/>(/api/v3/traces)"]
STATIC["Static Handler<br/>(UI Assets)"]
end
subgraph Service["服务层"]
QS["QueryService"]
PARSER["Query Parser"]
ADJ["Adjuster Chain"]
end
subgraph Readers["读取器层"]
READER["TraceReader"]
DEPREADER["DependencyReader"]
ARCHREADER["ArchiveReader<br/>(可选)"]
end
end
subgraph Storage["存储后端"]
MAINSTORE["Main Storage"]
ARCHSTORE["Archive Storage<br/>(可选)"]
end
UI -->|"HTTP GET"| HTTP
API -->|"gRPC/HTTP"| Servers
Servers --> Handlers
Handlers --> PARSER
PARSER --> QS
QS --> ADJ
ADJ --> Readers
READER --> MAINSTORE
DEPREADER --> MAINSTORE
ARCHREADER -.-> ARCHSTORE
style Servers fill:#fff4e6
style Handlers fill:#e1f5ff
style Service fill:#f3e5f5
style Readers fill:#e8f5e9
style Storage fill:#fff9c4
2. 对外 API 列表与规格
2.1 API 总览
Query 提供以下 HTTP 和 gRPC API:
| API 名称 | 协议 | 路径/方法 | 说明 |
|---|---|---|---|
| GetTrace | HTTP GET | /api/traces/{traceID} |
根据 TraceID 获取单个 trace |
| FindTraces | HTTP GET | /api/traces?service=...&start=... |
根据条件搜索 traces |
| GetServices | HTTP GET | /api/services |
获取所有服务名列表 |
| GetOperations | HTTP GET | /api/operations?service=... |
获取服务的所有操作名 |
| GetDependencies | HTTP GET | /api/dependencies?endTs=...&lookback=... |
获取服务依赖关系 |
| ArchiveTrace | HTTP POST | /api/archive/{traceID} |
归档指定 trace |
| UI | HTTP GET | / |
Jaeger UI 静态资源 |
API V3(新版本):
| API 名称 | 协议 | 路径/方法 | 说明 |
|---|---|---|---|
| GetTrace V3 | HTTP GET | /api/v3/traces/{traceID} |
V3 版本的 GetTrace |
| FindTraces V3 | HTTP GET | /api/v3/traces?query.service_name=... |
V3 版本的 FindTraces |
| GetServices V3 | HTTP GET | /api/v3/services |
V3 版本的 GetServices |
| GetOperations V3 | HTTP GET | /api/v3/operations |
V3 版本的 GetOperations |
2.2 GetTrace API
基本信息:
- 名称:
GetTrace - 协议:HTTP GET
/api/traces/{traceID} - 端口:16686
- 幂等性:是
请求参数:
| 参数 | 类型 | 位置 | 必填 | 说明 |
|---|---|---|---|---|
| traceID | string | URL Path | 是 | Trace ID(16 字节十六进制) |
| start | string | Query | 否 | 搜索开始时间(RFC3339 格式) |
| end | string | Query | 否 | 搜索结束时间(RFC3339 格式) |
| prettyPrint | bool | Query | 否 | 是否格式化 JSON 输出 |
| raw | bool | Query | 否 | 是否返回原始 trace(不调整时钟偏差) |
响应结构体:
// HTTP 200 OK
{
"data": [
{
"traceID": "abc123...",
"spans": [
{
"traceID": "abc123...",
"spanID": "def456...",
"operationName": "HTTP GET /api/users",
"references": [...],
"startTime": 1609459200000000,
"duration": 123456,
"tags": [...],
"logs": [...],
"process": {...}
}
],
"processes": {...},
"warnings": null
}
],
"total": 1,
"limit": 0,
"offset": 0,
"errors": null
}
响应字段表:
| 字段 | 类型 | 说明 |
|---|---|---|
| data | []Trace | Trace 数组(通常只有 1 个) |
| data[].traceID | string | Trace ID |
| data[].spans | []Span | Span 列表 |
| data[].processes | map | 进程信息映射(key 为 processID) |
| total | int | 总结果数 |
| errors | []Error | 错误列表(如 trace 不存在) |
入口函数与核心代码:
// HTTP Handler
func (aH *APIHandler) getTrace(w http.ResponseWriter, r *http.Request) {
// 1. 解析 TraceID
vars := mux.Vars(r)
traceIDVar := vars[traceIDParam]
traceID, err := model.TraceIDFromString(traceIDVar)
if err != nil {
http.Error(w, "invalid trace ID", http.StatusBadRequest)
return
}
// 2. 解析查询参数
start, end := aH.parseTimeParams(r)
rawTraces := r.FormValue("raw") == "true"
// 3. 调用 QueryService
trace, err := aH.queryService.GetTrace(r.Context(), querysvc.GetTraceParameters{
GetTraceParameters: spanstore.GetTraceParameters{
TraceID: traceID,
StartTime: start,
EndTime: end,
},
RawTraces: rawTraces,
})
if err != nil {
if errors.Is(err, spanstore.ErrTraceNotFound) {
http.Error(w, "trace not found", http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
// 4. 转换为 UI 格式并返回
uiTrace := uiconv.FromDomain(trace)
response := structuredResponse{
Data: []*ui.Trace{uiTrace},
Total: 1,
}
aH.writeJSON(w, r, &response)
}
QueryService.GetTrace 核心逻辑:
func (qs QueryService) GetTrace(ctx context.Context, query GetTraceParameters) (*model.Trace, error) {
// 1. 从主存储读取 trace
trace, err := qs.spanReader.GetTrace(ctx, query.GetTraceParameters)
// 2. 如果主存储未找到,尝试归档存储
if errors.Is(err, spanstore.ErrTraceNotFound) {
if qs.options.ArchiveSpanReader != nil {
trace, err = qs.options.ArchiveSpanReader.GetTrace(ctx, query.GetTraceParameters)
}
}
if err != nil {
return nil, err
}
// 3. 如果不是 raw 模式,应用 adjusters(时钟偏差调整等)
if !query.RawTraces {
qs.adjust(trace)
}
return trace, nil
}
时序图:
sequenceDiagram
autonumber
participant UI as Jaeger UI
participant H as HTTP Handler
participant QS as QueryService
participant ADJ as Adjuster
participant R as TraceReader
participant S as Storage
UI->>H: GET /api/traces/{traceID}
H->>H: 解析 TraceID<br/>解析参数
H->>QS: GetTrace(ctx, params)
QS->>R: GetTrace(ctx, traceID)
R->>S: 查询存储
alt Trace 存在
S-->>R: Trace (spans)
R-->>QS: Trace
else Trace 不存在
S-->>R: ErrTraceNotFound
alt 启用归档存储
R->>S: 查询归档存储
S-->>R: Trace (归档)
R-->>QS: Trace
else 无归档存储
R-->>QS: ErrTraceNotFound
QS-->>H: Error
H-->>UI: 404 Not Found
end
end
alt raw=false(默认)
QS->>ADJ: Adjust(trace)
ADJ->>ADJ: 时钟偏差调整<br/>span 排序<br/>标签规范化
ADJ-->>QS: 调整后的 trace
end
QS-->>H: Trace
H->>H: 转换为 UI 格式
H-->>UI: 200 OK (JSON)
2.3 FindTraces API
基本信息:
- 名称:
FindTraces - 协议:HTTP GET
/api/traces?service=...&start=...&end=... - 端口:16686
- 幂等性:是
请求参数:
| 参数 | 类型 | 位置 | 必填 | 说明 |
|---|---|---|---|---|
| service | string | Query | 是 | 服务名 |
| operation | string | Query | 否 | 操作名 |
| start | string | Query | 是 | 开始时间(微秒时间戳或 RFC3339) |
| end | string | Query | 是 | 结束时间(微秒时间戳或 RFC3339) |
| limit | int | Query | 否 | 返回 trace 数量上限(默认 20) |
| minDuration | string | Query | 否 | 最小持续时间(如 “100ms”) |
| maxDuration | string | Query | 否 | 最大持续时间(如 “5s”) |
| tags | string | Query | 否 | 标签过滤(JSON 格式,如 {"http.status_code":"200"}) |
响应结构体:
{
"data": [
{
"traceID": "abc123...",
"spans": [...],
"processes": {...}
}
],
"total": 10,
"limit": 20,
"offset": 0,
"errors": null
}
入口函数与核心代码:
func (aH *APIHandler) search(w http.ResponseWriter, r *http.Request) {
// 1. 解析查询参数
tQuery, err := aH.queryParser.parseTraceQueryParams(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// 2. 调用 QueryService
traces, err := aH.queryService.FindTraces(r.Context(), tQuery)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 3. 转换并返回
structuredRes := aH.tracesToResponse(traces, nil)
aH.writeJSON(w, r, structuredRes)
}
QueryService.FindTraces 核心逻辑:
func (qs QueryService) FindTraces(ctx context.Context, query *TraceQueryParameters) ([]*model.Trace, error) {
// 1. 从存储查询 traces
traces, err := qs.spanReader.FindTraces(ctx, &query.TraceQueryParameters)
if err != nil {
return nil, err
}
// 2. 应用 adjusters(如果不是 raw 模式)
if !query.RawTraces {
for _, trace := range traces {
qs.adjust(trace)
}
}
return traces, nil
}
时序图:
sequenceDiagram
autonumber
participant UI as Jaeger UI
participant H as HTTP Handler
participant QP as Query Parser
participant QS as QueryService
participant R as TraceReader
participant S as Storage
UI->>H: GET /api/traces?<br/>service=frontend&start=...
H->>QP: parseTraceQueryParams(r)
QP->>QP: 解析服务名<br/>解析时间范围<br/>解析标签过滤
QP-->>H: TraceQueryParameters
H->>QS: FindTraces(ctx, query)
QS->>R: FindTraces(ctx, query)
R->>S: 查询索引<br/>(服务名 + 时间范围)
S-->>R: []TraceID
loop 每个 TraceID
R->>S: GetTrace(traceID)
S-->>R: Trace (spans)
end
R-->>QS: []Trace
loop 每个 Trace
QS->>QS: adjust(trace)<br/>(时钟偏差调整)
end
QS-->>H: []Trace
H->>H: 转换为 UI 格式
H-->>UI: 200 OK (JSON)
2.4 GetServices API
基本信息:
- 名称:
GetServices - 协议:HTTP GET
/api/services - 端口:16686
- 幂等性:是
请求参数:
无
响应结构体:
{
"data": ["frontend", "backend", "database"],
"total": 3,
"errors": null
}
响应字段表:
| 字段 | 类型 | 说明 |
|---|---|---|
| data | []string | 服务名列表 |
| total | int | 服务总数 |
入口函数:
func (aH *APIHandler) getServices(w http.ResponseWriter, r *http.Request) {
// 调用 QueryService
services, err := aH.queryService.GetServices(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 返回 JSON
response := structuredResponse{
Data: services,
Total: len(services),
}
aH.writeJSON(w, r, &response)
}
2.5 GetOperations API
基本信息:
- 名称:
GetOperations - 协议:HTTP GET
/api/operations?service={serviceName} - 端口:16686
- 幂等性:是
请求参数:
| 参数 | 类型 | 位置 | 必填 | 说明 |
|---|---|---|---|---|
| service | string | Query | 是 | 服务名 |
| spanKind | string | Query | 否 | Span 类型(server、client 等) |
响应结构体:
{
"data": [
{
"name": "HTTP GET /api/users",
"spanKind": "server"
},
{
"name": "HTTP POST /api/orders",
"spanKind": "server"
}
],
"total": 2,
"errors": null
}
响应字段表:
| 字段 | 类型 | 说明 |
|---|---|---|
| data | []Operation | 操作名列表 |
| data[].name | string | 操作名 |
| data[].spanKind | string | Span 类型 |
| total | int | 操作总数 |
2.6 GetDependencies API
基本信息:
- 名称:
GetDependencies - 协议:HTTP GET
/api/dependencies?endTs={timestamp}&lookback={duration} - 端口:16686
- 幂等性:是
请求参数:
| 参数 | 类型 | 位置 | 必填 | 说明 |
|---|---|---|---|---|
| endTs | int64 | Query | 是 | 结束时间戳(毫秒) |
| lookback | int64 | Query | 是 | 回溯时间(毫秒,如 86400000 = 1 天) |
响应结构体:
{
"data": [
{
"parent": "frontend",
"child": "backend",
"callCount": 12345
},
{
"parent": "backend",
"child": "database",
"callCount": 67890
}
]
}
响应字段表:
| 字段 | 类型 | 说明 |
|---|---|---|
| data | []DependencyLink | 依赖关系列表 |
| data[].parent | string | 调用方服务名 |
| data[].child | string | 被调方服务名 |
| data[].callCount | int | 调用次数 |
时序图:
sequenceDiagram
autonumber
participant UI as Jaeger UI
participant H as HTTP Handler
participant QS as QueryService
participant DR as DependencyReader
participant S as Storage
UI->>H: GET /api/dependencies?<br/>endTs=...&lookback=...
H->>H: 解析参数<br/>endTs, lookback
H->>QS: GetDependencies(ctx, endTs, lookback)
QS->>DR: GetDependencies(ctx, params)
DR->>S: 查询依赖表<br/>(startTime, endTime)
S-->>DR: []DependencyLink
DR-->>QS: []DependencyLink
QS-->>H: []DependencyLink
H-->>UI: 200 OK (JSON)
UI->>UI: 渲染依赖图
3. 关键数据结构与 UML
3.1 核心数据结构
3.1.1 QueryService
type QueryService struct {
spanReader spanstore.Reader // Span 读取器
dependencyReader depstore.Reader // 依赖读取器
adjuster adjuster.Adjuster // Adjuster 链
options QueryServiceOptions // 配置选项
}
type QueryServiceOptions struct {
ArchiveSpanReader spanstore.Reader // 归档读取器(可选)
ArchiveSpanWriter spanstore.Writer // 归档写入器(可选)
MaxClockSkewAdjust time.Duration // 最大时钟偏差调整
}
3.1.2 Trace 和 Span(数据模型)
type Trace struct {
Spans []*Span // Span 列表
ProcessMap map[string]*Process // 进程映射
Warnings []string // 警告信息
}
type Span struct {
TraceID TraceID // Trace ID(16 字节)
SpanID SpanID // Span ID(8 字节)
OperationName string // 操作名
References []SpanRef // 父 span 引用
Flags Flags // 标志位(采样标记等)
StartTime time.Time // 开始时间
Duration time.Duration // 持续时间
Tags []KeyValue // 标签
Logs []Log // 日志事件
Process *Process // 进程信息(引用)
ProcessID string // 进程 ID
Warnings []string // 警告信息
}
type Process struct {
ServiceName string // 服务名
Tags []KeyValue // 进程标签
}
3.1.3 TraceQueryParameters
type TraceQueryParameters struct {
ServiceName string // 服务名(必填)
OperationName string // 操作名(可选)
Tags map[string]string // 标签过滤(可选)
StartTimeMin time.Time // 开始时间下限
StartTimeMax time.Time // 开始时间上限
DurationMin time.Duration // 最小持续时间
DurationMax time.Duration // 最大持续时间
NumTraces int // 返回数量上限
}
3.2 UML 类图
classDiagram
class QueryService {
-spanReader SpanReader
-dependencyReader DependencyReader
-adjuster Adjuster
-options QueryServiceOptions
+GetTrace(ctx, params) (*Trace, error)
+FindTraces(ctx, query) ([]*Trace, error)
+GetServices(ctx) ([]string, error)
+GetOperations(ctx, query) ([]Operation, error)
+GetDependencies(ctx, endTs, lookback) ([]DependencyLink, error)
+ArchiveTrace(ctx, traceID) error
-adjust(trace)
}
class SpanReader {
<<interface>>
+GetTrace(ctx, traceID) (*Trace, error)
+FindTraces(ctx, query) ([]*Trace, error)
+GetServices(ctx) ([]string, error)
+GetOperations(ctx, query) ([]Operation, error)
}
class DependencyReader {
<<interface>>
+GetDependencies(ctx, params) ([]DependencyLink, error)
}
class Adjuster {
<<interface>>
+Adjust(trace) error
}
class Trace {
+[]Span Spans
+map~string,Process~ ProcessMap
+[]string Warnings
}
class Span {
+TraceID TraceID
+SpanID SpanID
+string OperationName
+[]SpanRef References
+time.Time StartTime
+time.Duration Duration
+[]KeyValue Tags
+[]Log Logs
+*Process Process
}
class APIHandler {
-queryService QueryService
-queryParser QueryParser
+getTrace(w, r)
+search(w, r)
+getServices(w, r)
+getOperations(w, r)
+dependencies(w, r)
}
QueryService "1" --> "1" SpanReader : uses
QueryService "1" --> "1" DependencyReader : uses
QueryService "1" --> "1" Adjuster : uses
APIHandler "1" --> "1" QueryService : uses
Trace "1" --> "*" Span : contains
Span "*" --> "1" Process : references
4. 关键功能与函数详细描述
4.1 Adjuster(时钟偏差调整)
功能描述:
Adjuster 是 Query 模块的核心功能,用于修正分布式系统中的时钟偏差、规范化 span 数据。
标准 Adjuster 链:
func StandardAdjusters(maxClockSkewAdjust time.Duration) []adjuster.Adjuster {
return []adjuster.Adjuster{
adjuster.SpanIDUniquifier(), // 1. 修复重复的 SpanID
adjuster.ClockSkew(maxClockSkewAdjust), // 2. 时钟偏差调整
adjuster.SpanReferences(), // 3. 修复 span 引用
adjuster.SortTagsAndLog(), // 4. 排序标签和日志
adjuster.SortSpans(), // 5. 按时间排序 spans
}
}
时钟偏差调整算法:
// ClockSkew Adjuster
func (adjuster *clockSkewAdjuster) Adjust(trace *model.Trace) error {
if len(trace.Spans) == 0 {
return nil
}
// 1. 构建 span 树(parent-child 关系)
spanTree := buildSpanTree(trace.Spans)
// 2. 计算时钟偏差
skews := make(map[model.SpanID]time.Duration)
for _, span := range trace.Spans {
parent := findParent(span, spanTree)
if parent == nil {
continue
}
// 计算 child 相对 parent 的时钟偏差
childEnd := span.StartTime.Add(span.Duration)
parentEnd := parent.StartTime.Add(parent.Duration)
// 如果 child 结束时间晚于 parent,计算偏差
if childEnd.After(parentEnd) {
skew := childEnd.Sub(parentEnd)
if skew <= adjuster.maxClockSkewAdjust {
skews[span.SpanID] = skew
}
}
}
// 3. 应用偏差调整
for _, span := range trace.Spans {
if skew, ok := skews[span.SpanID]; ok {
span.StartTime = span.StartTime.Add(-skew)
span.Warnings = append(span.Warnings, fmt.Sprintf("Adjusted for clock skew: %v", skew))
}
}
return nil
}
时钟偏差示例:
原始数据:
Parent Span: [0ms ------------------ 100ms]
Child Span: [50ms -------------- 150ms] ← child 结束时间超出 parent
调整后:
Parent Span: [0ms ------------------ 100ms]
Child Span: [0ms ------------ 100ms] ← 调整 child 开始时间
4.2 归档 Trace
功能描述:
将 trace 从主存储复制到归档存储,用于长期保留重要 trace。
核心代码:
func (qs QueryService) ArchiveTrace(ctx context.Context, query spanstore.GetTraceParameters) error {
// 1. 检查归档存储是否配置
if qs.options.ArchiveSpanWriter == nil {
return errors.New("archive span storage was not configured")
}
// 2. 从主存储读取 trace
trace, err := qs.GetTrace(ctx, GetTraceParameters{GetTraceParameters: query})
if err != nil {
return err
}
// 3. 逐个 span 写入归档存储
var writeErrors []error
for _, span := range trace.Spans {
err := qs.options.ArchiveSpanWriter.WriteSpan(ctx, span)
if err != nil {
writeErrors = append(writeErrors, err)
}
}
return errors.Join(writeErrors...)
}
时序图:
sequenceDiagram
autonumber
participant UI as Jaeger UI
participant H as HTTP Handler
participant QS as QueryService
participant MR as Main Reader
participant MS as Main Storage
participant AW as Archive Writer
participant AS as Archive Storage
UI->>H: POST /api/archive/{traceID}
H->>QS: ArchiveTrace(ctx, traceID)
QS->>MR: GetTrace(ctx, traceID)
MR->>MS: 查询 trace
MS-->>MR: Trace (spans)
MR-->>QS: Trace
loop 每个 Span
QS->>AW: WriteSpan(ctx, span)
AW->>AS: 写入归档存储
AS-->>AW: ok
AW-->>QS: ok
end
QS-->>H: nil
H-->>UI: 200 OK
4.3 依赖关系计算
功能描述:
从 span 数据计算服务间的依赖关系,用于绘制依赖图。
计算逻辑:
依赖关系通常由存储层预计算并存储(如 Cassandra 的 dependencies 表),Query 模块直接读取。
DependencyReader 接口:
type Reader interface {
GetDependencies(ctx context.Context, params QueryParameters) ([]model.DependencyLink, error)
}
type QueryParameters struct {
StartTime time.Time
EndTime time.Time
}
type DependencyLink struct {
Parent string // 调用方服务名
Child string // 被调方服务名
CallCount uint64 // 调用次数
}
QueryService 实现:
func (qs QueryService) GetDependencies(ctx context.Context, endTs time.Time, lookback time.Duration) ([]model.DependencyLink, error) {
return qs.dependencyReader.GetDependencies(ctx, depstore.QueryParameters{
StartTime: endTs.Add(-lookback),
EndTime: endTs,
})
}
5. 配置项与最佳实践
5.1 关键配置项
| 配置项 | 默认值 | 说明 | 建议值 |
|---|---|---|---|
--query.grpc-server.host-port |
:16685 | gRPC 监听地址 | 0.0.0.0:16685 |
--query.http-server.host-port |
:16686 | HTTP 监听地址 | 0.0.0.0:16686 |
--query.max-clock-skew-adjust |
0s(禁用) | 最大时钟偏差调整 | 1m(1 分钟) |
--query.base-path |
/ | UI 的 URL 前缀 | /jaeger(反向代理) |
--query.ui-config |
空 | UI 配置文件路径 | ./ui-config.json |
--query.bearer-token-propagation |
false | 是否传递 Bearer Token 到存储 | true(需认证时) |
--query.additional-headers |
空 | UI 响应的额外 HTTP 头 | 安全头(CSP、HSTS) |
5.2 性能调优
高并发场景:
jaeger-query \
--query.max-clock-skew-adjust=1m \
--span-storage.type=elasticsearch \
--es.max-doc-count=10000 # ES 查询上限
时钟偏差调整:
# 启用时钟偏差调整(建议 1-5 分钟)
jaeger-query \
--query.max-clock-skew-adjust=5m
UI 配置(ui-config.json):
{
"dependencies": {
"menuEnabled": true
},
"archiveEnabled": true,
"tracking": {
"gaID": "UA-000000-1",
"trackErrors": true
},
"menu": [
{
"label": "Grafana",
"url": "https://grafana.example.com"
}
],
"linkPatterns": [
{
"type": "logs",
"key": "pod",
"url": "https://logs.example.com?pod=#{pod}",
"text": "View logs for pod #{pod}"
}
]
}
5.3 监控告警
关键指标:
-
查询延迟 (
jaeger_query_requests_duration_seconds)- 正常:P95 < 1s
- 警告:P95 > 2s
- 告警:P95 > 5s
-
查询失败率 (
jaeger_query_requests_total{result="err"})- 正常:< 0.1%
- 告警:> 1%
-
存储查询延迟(依赖存储后端指标)
- ES:P95 < 500ms
- Cassandra:P95 < 300ms
Prometheus 告警规则:
groups:
- name: jaeger_query
rules:
- alert: QueryHighLatency
expr: histogram_quantile(0.95, jaeger_query_requests_duration_seconds_bucket) > 2
for: 5m
annotations:
summary: "Query P95 latency is {{ $value }}s"
- alert: QueryHighErrorRate
expr: rate(jaeger_query_requests_total{result="err"}[5m]) / rate(jaeger_query_requests_total[5m]) > 0.01
for: 5m
annotations:
summary: "Query error rate is {{ $value | humanizePercentage }}"
6. 故障排查
6.1 常见问题
问题 1:查询延迟高(P95 > 2s)
原因:
- 存储后端慢查询
- 复杂标签过滤
- 时间范围过大
排查:
# 查看查询延迟指标
curl http://query:16687/metrics | grep query_requests_duration
# 检查存储后端延迟(ES 示例)
curl http://elasticsearch:9200/_cat/thread_pool?v&h=name,active,queue,rejected
解决:
- 优化存储索引(ES:增加副本、调整刷新间隔)
- 限制查询时间范围(UI 默认 1 小时)
- 使用更具体的查询条件(避免全表扫描)
问题 2:Trace 不完整(缺少部分 span)
原因:
- Collector 队列满,丢弃 span
- 存储写入失败
- 采样率过低
排查:
# 检查 Collector 拒绝指标
curl http://collector:14269/metrics | grep spans_rejected
# 检查存储写入失败指标
curl http://collector:14269/metrics | grep spans_saved_by_svc
解决:
- 增加 Collector 容量(队列大小、工作线程数)
- 检查存储后端健康状态
- 调整采样策略(增加采样率)
问题 3:时钟偏差调整不生效
原因:
- 未配置
--query.max-clock-skew-adjust - 时钟偏差超过配置上限
排查:
# 检查配置
jaeger-query --help | grep max-clock-skew-adjust
# 查看 trace 的警告信息(UI 中显示)
解决:
- 配置合理的时钟偏差上限:
--query.max-clock-skew-adjust=5m - 同步服务器时钟(使用 NTP)
7. 总结
Query 模块是 Jaeger 的查询和展示核心,具备以下关键特性:
- 灵活查询:支持多维度查询(服务、操作、标签、时间、持续时间)
- 智能调整:自动修正时钟偏差,规范化 span 数据
- 归档支持:支持独立归档存储,长期保留重要 trace
- 依赖分析:计算和展示服务依赖关系图
- 友好 UI:提供强大的 Web 界面,支持可视化和搜索
与其他模块的关系:
- 从 Storage 读取 trace 数据
- 为 UI 提供 API 和静态资源
- 支持从 Archive Storage 查询历史 trace