簡介
在分散式架構中,偵錯等於 console.log 不可行。当一个请求经过5个服务时,你需要可观察性来知道请求去了哪里,花了多长时间,以及哪里失败了。

1. 可觀察性的三大支柱
┌─────────────────────────────────────────────┐
│ Observability │
├──────────────┬──────────────┬───────────────┤
│ Logs │ Metrics │ Traces │
│ │ │ │
│ What happened│ How system │ Request flow │
│ (events) │ performs │ across svcs │
│ │ (numbers) │ (journey) │
│ │ │ │
│ Loki/ELK │ Prometheus │ Tempo/Jaeger │
└──────────────┴──────────────┴───────────────┘
2. 分散式追蹤
2.1 追蹤端對端請求
User clicks "Place Order" trên frontend:
Trace ID: abc-123-def
├── Span 1: Shell App → POST /api/orders (200ms)
│ ├── Span 2: API Gateway → route to Order Service (5ms)
│ │ ├── Span 3: Order Service → validate (10ms)
│ │ ├── Span 4: Order Service → call Product Service (50ms)
│ │ │ └── Span 5: Product Service → DB query (15ms)
│ │ ├── Span 6: Order Service → call Payment Service (100ms)
│ │ │ └── Span 7: Payment Service → Stripe API (80ms)
│ │ └── Span 8: Order Service → publish OrderPlaced event (5ms)
│ └── Span 9: API Gateway → response (5ms)
└── Total: 200ms
→ Bottleneck: Payment Service → Stripe API (80ms) = 40% total
2.2 OpenTelemetry 設定 (Node.js)
// tracing.js - Setup OpenTelemetry
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: 'http://otel-collector:4318/v1/traces',
}),
instrumentations: [
getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-http': { enabled: true },
'@opentelemetry/instrumentation-express': { enabled: true },
'@opentelemetry/instrumentation-pg': { enabled: true },
}),
],
serviceName: 'product-service',
});
sdk.start();
3. 指標(普羅米修斯)
3.1 關鍵指標(RED 方法)
| 指標 | 什麼 | 警報時間 |
|---|---|---|
| 速率 | 每秒請求數 | 突然下降 |
| 錯誤 | 錯誤率 (5xx) | > 1% |
| 持续时间 | 延迟(p50、p95、p99) | p99 > 2 秒 |
3.2 自訂指標
const { meter } = require('@opentelemetry/api');
const orderCounter = meter.createCounter('orders_created_total', {
description: 'Total orders created',
});
const orderDuration = meter.createHistogram('order_processing_duration_ms', {
description: 'Order processing time',
});
// Usage
orderCounter.add(1, { status: 'success', payment_method: 'stripe' });
orderDuration.record(duration, { service: 'order-service' });
4. 結構化日誌記錄 (Loki)
// Structured JSON logs
const logger = require('pino')({
level: 'info',
});
app.use((req, res, next) => {
const traceId = req.headers['x-trace-id'] || generateTraceId();
req.log = logger.child({
traceId,
service: 'product-service',
requestId: req.id,
});
next();
});
// Usage
req.log.info({ productId: '123', action: 'getProduct' }, 'Product fetched');
req.log.error({ error: err.message, stack: err.stack }, 'Product not found');
5.Grafana 堆疊
┌──────────────────────────────────────────┐
│ Grafana UI │
│ Dashboards │ Alerts │ Explore │ Traces │
├──────────┬──────────┬────────────────────┤
│ Loki │Prometheus│ Tempo │
│ (Logs) │(Metrics) │ (Traces) │
├──────────┴──────────┴────────────────────┤
│ OpenTelemetry Collector │
│ Receives → Processes → Exports │
├──────────────────────────────────────────┤
│ Applications │
│ Microservices → OTLP → Collector │
│ MFE → Web Vitals → Collector │
└──────────────────────────────────────────┘
6.前端可觀察性(微前端)
// Web Vitals tracking
import { onLCP, onFID, onCLS } from 'web-vitals';
onLCP((metric) => {
sendToCollector('web_vitals_lcp', metric.value, {
mfe: 'product-mfe',
page: window.location.pathname,
});
});
// Error tracking
window.addEventListener('error', (event) => {
sendToCollector('frontend_error', {
message: event.message,
stack: event.error?.stack,
mfe: detectMFE(event.filename),
});
});
總結
| 支柱 | 工具 | 目的 |
|---|---|---|
| 日志 | 洛基+皮诺 | 发生了什么(结构化 JSON) |
| 指标 | 普罗米修斯 | 系统如何执行(RED 方法) |
| 痕迹 | 节奏+OTEL | 跨服务的请求流 |
| 前端 | 网络生命力 | 每个 MFE 的核心 Web Vitals |
| 儀表板 | 格拉法納 | 統一視覺化 |
下一篇文章: 第 27 課:效能最佳化-前端和後端