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 监控告警

关键指标:

  1. 查询延迟 (jaeger_query_requests_duration_seconds)

    • 正常:P95 < 1s
    • 警告:P95 > 2s
    • 告警:P95 > 5s
  2. 查询失败率 (jaeger_query_requests_total{result="err"})

    • 正常:< 0.1%
    • 告警:> 1%
  3. 存储查询延迟(依赖存储后端指标)

    • 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 的查询和展示核心,具备以下关键特性:

  1. 灵活查询:支持多维度查询(服务、操作、标签、时间、持续时间)
  2. 智能调整:自动修正时钟偏差,规范化 span 数据
  3. 归档支持:支持独立归档存储,长期保留重要 trace
  4. 依赖分析:计算和展示服务依赖关系图
  5. 友好 UI:提供强大的 Web 界面,支持可视化和搜索

与其他模块的关系:

  • Storage 读取 trace 数据
  • UI 提供 API 和静态资源
  • 支持从 Archive Storage 查询历史 trace