Skip to content

可观测性

一、可观测性三支柱

概述

┌─────────────────────────────────────────────────────────────────┐
│                   可观测性三支柱                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ┌─────────────────────────────────────────────────────────┐  │
│   │                    Observability                         │  │
│   └─────────────────────────────────────────────────────────┘  │
│              │                │                │                │
│              ▼                ▼                ▼                │
│   ┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐     │
│   │     Metrics     │ │    Logs     │ │    Traces       │     │
│   │      指标       │ │    日志     │ │    链路追踪      │     │
│   ├─────────────────┤ ├─────────────┤ ├─────────────────┤     │
│   │ 聚合数据        │ │ 离散事件    │ │ 请求路径        │     │
│   │ 趋势分析        │ │ 详细上下文  │ │ 延迟分析        │     │
│   │ 告警触发        │ │ 问题排查    │ │ 依赖关系        │     │
│   ├─────────────────┤ ├─────────────┤ ├─────────────────┤     │
│   │ Prometheus      │ │ ELK Stack   │ │ Jaeger          │     │
│   │ Grafana         │ │ Loki        │ │ Zipkin          │     │
│   │                 │ │             │ │ SkyWalking      │     │
│   └─────────────────┘ └─────────────┘ └─────────────────┘     │
│                                                                 │
│   三者关系:                                                      │
│   ┌─────────────────────────────────────────────────────────┐  │
│   │                                                          │  │
│   │   告警 (Metrics) → 定位 (Logs) → 分析 (Traces)           │  │
│   │                                                          │  │
│   │   "指标告诉你有问题"                                      │  │
│   │   "日志告诉你发生了什么"                                  │  │
│   │   "链路告诉你问题在哪里"                                  │  │
│   │                                                          │  │
│   └─────────────────────────────────────────────────────────┘  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

二、指标 (Metrics)

1. 指标类型

┌─────────────────────────────────────────────────────────────────┐
│                    Prometheus 指标类型                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Counter (计数器)                                              │
│   ─────────────────────────────────────────────────────────    │
│   只增不减,用于累计值                                          │
│   示例: 请求总数、错误总数                                      │
│   http_requests_total{method="GET", path="/api/users"} 1234    │
│                                                                 │
│   Gauge (仪表盘)                                                │
│   ─────────────────────────────────────────────────────────    │
│   可增可减,用于瞬时值                                          │
│   示例: 当前连接数、内存使用量                                  │
│   active_connections 42                                        │
│                                                                 │
│   Histogram (直方图)                                            │
│   ─────────────────────────────────────────────────────────    │
│   分布统计,自动计算分位数                                      │
│   示例: 请求延迟分布                                            │
│   http_request_duration_seconds_bucket{le="0.1"} 100           │
│   http_request_duration_seconds_bucket{le="0.5"} 150           │
│   http_request_duration_seconds_bucket{le="1"} 200             │
│                                                                 │
│   Summary (摘要)                                                │
│   ─────────────────────────────────────────────────────────    │
│   客户端计算分位数                                              │
│   http_request_duration_seconds{quantile="0.99"} 0.23          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

2. Go 指标埋点

go
import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
    // Counter
    httpRequestsTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests",
        },
        []string{"method", "path", "status"},
    )

    // Gauge
    activeConnections = prometheus.NewGauge(prometheus.GaugeOpts{
        Name: "active_connections",
        Help: "Number of active connections",
    })

    // Histogram
    httpRequestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request duration in seconds",
            Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10},
        },
        []string{"method", "path"},
    )
)

func init() {
    prometheus.MustRegister(httpRequestsTotal)
    prometheus.MustRegister(activeConnections)
    prometheus.MustRegister(httpRequestDuration)
}

// 中间件
func MetricsMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()

        activeConnections.Inc()
        defer activeConnections.Dec()

        c.Next()

        duration := time.Since(start).Seconds()
        status := strconv.Itoa(c.Writer.Status())

        httpRequestsTotal.WithLabelValues(c.Request.Method, c.FullPath(), status).Inc()
        httpRequestDuration.WithLabelValues(c.Request.Method, c.FullPath()).Observe(duration)
    }
}

// 暴露 metrics 端点
func main() {
    r := gin.New()
    r.Use(MetricsMiddleware())
    r.GET("/metrics", gin.WrapH(promhttp.Handler()))
    r.Run(":8080")
}

3. 关键业务指标

yaml
# 四个黄金信号 (Google SRE)
# 1. Latency (延迟)
- record: http_request_latency_p99
  expr: histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))

# 2. Traffic (流量)
- record: http_requests_per_second
  expr: sum(rate(http_requests_total[5m]))

# 3. Errors (错误)
- record: http_error_rate
  expr: sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))

# 4. Saturation (饱和度)
- record: cpu_saturation
  expr: avg(1 - rate(node_cpu_seconds_total{mode="idle"}[5m]))

三、日志 (Logs)

1. 结构化日志

go
import "go.uber.org/zap"

func InitLogger() *zap.Logger {
    config := zap.Config{
        Level:       zap.NewAtomicLevelAt(zap.InfoLevel),
        Development: false,
        Encoding:    "json",  // 结构化 JSON 格式
        EncoderConfig: zapcore.EncoderConfig{
            TimeKey:        "timestamp",
            LevelKey:       "level",
            NameKey:        "logger",
            CallerKey:      "caller",
            MessageKey:     "message",
            StacktraceKey:  "stacktrace",
            LineEnding:     zapcore.DefaultLineEnding,
            EncodeLevel:    zapcore.LowercaseLevelEncoder,
            EncodeTime:     zapcore.ISO8601TimeEncoder,
            EncodeDuration: zapcore.SecondsDurationEncoder,
            EncodeCaller:   zapcore.ShortCallerEncoder,
        },
        OutputPaths:      []string{"stdout"},
        ErrorOutputPaths: []string{"stderr"},
    }

    logger, _ := config.Build()
    return logger
}

// 使用
func main() {
    logger := InitLogger()
    defer logger.Sync()

    // 结构化日志
    logger.Info("user login",
        zap.String("user_id", "123"),
        zap.String("ip", "192.168.1.1"),
        zap.Duration("latency", 100*time.Millisecond),
    )

    // 输出:
    // {"timestamp":"2024-01-15T10:30:00Z","level":"info","message":"user login","user_id":"123","ip":"192.168.1.1","latency":0.1}
}

// 请求日志中间件
func LoggingMiddleware(logger *zap.Logger) gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        requestID := c.GetHeader("X-Request-ID")
        if requestID == "" {
            requestID = uuid.New().String()
        }

        c.Set("request_id", requestID)
        c.Next()

        logger.Info("request",
            zap.String("request_id", requestID),
            zap.String("method", c.Request.Method),
            zap.String("path", c.Request.URL.Path),
            zap.Int("status", c.Writer.Status()),
            zap.Duration("latency", time.Since(start)),
            zap.String("client_ip", c.ClientIP()),
            zap.String("user_agent", c.Request.UserAgent()),
        )
    }
}

2. 日志级别规范

┌─────────────────────────────────────────────────────────────────┐
│                    日志级别使用规范                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   DEBUG - 调试信息                                               │
│   ─────────────────────────────────────────────────────────    │
│   • 开发调试用,生产环境关闭                                    │
│   • 函数入参、中间状态                                          │
│                                                                 │
│   INFO - 重要业务事件                                           │
│   ─────────────────────────────────────────────────────────    │
│   • 服务启动/关闭                                               │
│   • 用户登录/登出                                               │
│   • 订单创建/完成                                               │
│                                                                 │
│   WARN - 预期内的异常                                           │
│   ─────────────────────────────────────────────────────────    │
│   • 参数校验失败                                                │
│   • 重试成功                                                    │
│   • 性能下降预警                                                │
│                                                                 │
│   ERROR - 需要关注的错误                                        │
│   ─────────────────────────────────────────────────────────    │
│   • 业务处理失败                                                │
│   • 外部服务调用失败                                            │
│   • 数据库操作失败                                              │
│                                                                 │
│   FATAL - 导致服务不可用                                        │
│   ─────────────────────────────────────────────────────────    │
│   • 配置错误无法启动                                            │
│   • 关键依赖不可用                                              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

四、链路追踪 (Traces)

OpenTelemetry 集成

go
import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/jaeger"
    "go.opentelemetry.io/otel/sdk/resource"
    "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)

func InitTracer(serviceName string) (*trace.TracerProvider, error) {
    // Jaeger exporter
    exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(
        jaeger.WithEndpoint("http://jaeger:14268/api/traces"),
    ))
    if err != nil {
        return nil, err
    }

    // 创建 TracerProvider
    tp := trace.NewTracerProvider(
        trace.WithBatcher(exporter),
        trace.WithResource(resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceNameKey.String(serviceName),
            semconv.ServiceVersionKey.String("1.0.0"),
        )),
        trace.WithSampler(trace.AlwaysSample()),  // 采样策略
    )

    otel.SetTracerProvider(tp)
    return tp, nil
}

// HTTP 服务端中间件
func TracingMiddleware() gin.HandlerFunc {
    tracer := otel.Tracer("http-server")

    return func(c *gin.Context) {
        ctx, span := tracer.Start(c.Request.Context(), c.FullPath())
        defer span.End()

        // 设置属性
        span.SetAttributes(
            attribute.String("http.method", c.Request.Method),
            attribute.String("http.url", c.Request.URL.String()),
            attribute.String("http.user_agent", c.Request.UserAgent()),
        )

        c.Request = c.Request.WithContext(ctx)
        c.Next()

        // 记录响应状态
        span.SetAttributes(
            attribute.Int("http.status_code", c.Writer.Status()),
        )

        if c.Writer.Status() >= 400 {
            span.SetStatus(codes.Error, "request failed")
        }
    }
}

// HTTP 客户端
func CallExternalService(ctx context.Context, url string) error {
    tracer := otel.Tracer("http-client")
    ctx, span := tracer.Start(ctx, "call-external-service")
    defer span.End()

    req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)

    // 注入 trace context
    otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        span.RecordError(err)
        span.SetStatus(codes.Error, err.Error())
        return err
    }
    defer resp.Body.Close()

    span.SetAttributes(
        attribute.Int("http.status_code", resp.StatusCode),
    )

    return nil
}

五、告警策略

Prometheus 告警规则

yaml
# alerting_rules.yml
groups:
  - name: availability
    rules:
      # 服务不可用
      - alert: ServiceDown
        expr: up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "服务 {{ $labels.instance }} 不可用"

      # 高错误率
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
          / sum(rate(http_requests_total[5m])) by (service)
          > 0.01
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "{{ $labels.service }} 错误率超过 1%"

  - name: latency
    rules:
      # 高延迟
      - alert: HighLatency
        expr: |
          histogram_quantile(0.99,
            sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
          ) > 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.service }} P99 延迟超过 1 秒"

  - name: resources
    rules:
      # CPU 使用率高
      - alert: HighCPUUsage
        expr: |
          100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.instance }} CPU 使用率超过 80%"

      # 内存使用率高
      - alert: HighMemoryUsage
        expr: |
          (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)
          / node_memory_MemTotal_bytes * 100 > 85
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.instance }} 内存使用率超过 85%"

六、检查清单

指标检查

  • [ ] 是否覆盖四个黄金信号?
  • [ ] 是否有业务指标?
  • [ ] 是否配置了告警规则?
  • [ ] Dashboard 是否完善?

日志检查

  • [ ] 是否使用结构化日志?
  • [ ] 日志级别是否规范?
  • [ ] 是否包含请求 ID?
  • [ ] 日志是否有采集和检索?

链路追踪检查

  • [ ] 是否接入了链路追踪?
  • [ ] 跨服务调用是否传播 context?
  • [ ] 采样策略是否合理?
  • [ ] 是否能快速定位问题?

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布