APM 最佳实践:从业人员操作指南

blog-APM_Best_Practices.jpg

应用程序性能管理(APM)是定期跟踪、测量和分析软件应用程序性能与可用性的实践。APM 可帮助您洞察复杂的微服务环境,这些环境可能会让站点可靠性工程 (SRE) 团队不堪重负。所产生的见解可打造最佳用户体验,实现预期业务成效。这是一个复杂的过程,但目标很简单:确保应用程序顺利运行,满足用户和企业的期望。

清晰了解应用程序运行机制,并积极开展 APM 实践,是保障应用程序高性能运行的关键。APM 不应作为事后的补救措施,而是在系统设计之初就加以规划。通过主动实施 APM,可将监测组件直接嵌入应用程序,将其纳入软件的运行流程。

什么是应用程序性能管理?

应用程序性能管理(APM)涉及对应用程序前端和后端性能进行持续监测、分析与管理。应用程序监控正不断发展演进,但 APM 策略不应孤立制定。制定过程中需联合多方利益相关者,包括业务专家、应用开发人员和运维团队。成功的 APM 策略不仅关注正常运行时间或服务器运行状况,更应聚焦服务级别目标(SLO),在影响用户体验之前预先识别并应对问题。

现代 APM 的实施需要对应用程序进行插桩,以收集三类遥测数据跟踪(请求流)、指标(汇总度量)和日志(离散事件)。挑战不仅在于采集数据,而是要在不影响性能的前提下收集正确的数据。

了解有关可观测性度量的更多信息

目前有许多插桩方法,但最有效的策略是将自动插桩(针对框架和库)与手动插桩(针对业务逻辑)相结合。使用 OpenTelemetry 代理进行自动插桩,只需进行极少的代码修改,即可覆盖约 80% 的可观测性需求:

# Auto-instrumentation handles this automatically
@app.route('/api/orders')
def create_order():
    # Add manual span only for critical business logic
    with tracer.start_as_current_span("order.validation") as span:
        span.set_attribute("order.value", order_total)
        if not validate_order(order_data):
            span.set_status(Status(StatusCode.ERROR))
            return 400

  • 正确做法:自动插桩开始,然后为关键业务操作添加手动跨度。

  • 错误做法:切勿对每个函数进行手动插桩 — 这会带来性能开销和干扰。

  • 常见误区:过度插桩可能会增加 15%–20% 的延迟。请通过基线性能对比来监控您的监控策略。

在制定 APM 策略时,组织或企业需要考虑以下几个要素:

  • 性能监测,包括评估延迟、服务级别目标、响应时间、吞吐量和请求量

  • 错误跟踪,包括异常、崩溃和失败的 API 调用

  • 基础架构监测,包括支持应用程序的服务器、容器和云环境的运行状况与资源使用情况

  • 用户体验指标,包括加载时间、会话性能、点击路径,以及浏览器或设备的详细信息(需重点注意,即使系统指标正常,用户仍可能遇到性能问题)。

有效应用程序性能管理 (APM) 的关键原则

有效应用程序性能管理的核心原则包括:端到端可视性(从用户浏览器到数据库)、实时监测与洞察、上下文感知分析,并聚焦用户体验与业务目标。通过持续优化并提升性能,APM 有助于增强应用的可扩展性。

  • 正确做法:实施基于 SLO 的实时仪表板警报,而非任意阈值。

  • 错误做法:切勿仅依赖定期的性能评估或 CPU/内存警报 — 请对用户体验指标进行插桩。

  • 常见误区:低级系统指标可能导致告警疲劳。请专注于面向用户且能反映实际问题的 SLO。

在制定 APM 策略时,以下是需要考虑的一些关键原则:

1. 主动监测:在问题影响用户之前,通过设置警报并快速响应异常来防患于未然。但要避免警报疲劳。应在自动警报与人工监督之间取得平衡,以免遗漏重要问题,重点关注结果而非系统指标。

2. 实时见解: 不仅停留在日志记录层面 ,而是基于实时数据和实时仪表板,快速做出决策,优先处理关键业务事务。利用 遥测数据 (日志、指标和跟踪)深入分析性能表现。

3. 端到端可视性:监测整个环境、完整用户流程,以及从前端到后端的所有层。

4. 以用户为中心的方法:优先从最终用户的角度优化性能和体验,同时兼顾关键业务目标。

5. 真实用户监测:工作并未在用户开始使用产品时结束。通过监测他们的体验,您可以根据反馈不断迭代和改进。

6. 持续改进:利用见解持续优化,并定期发现和解决未报告的问题。应动态地解决问题,而不是在定期性能评估中发现时才处理。

7. 上下文传播:确保跟踪上下文贯穿整个请求路径,尤其是在跨服务边界时:

# Outgoing request - inject context
headers = {}
propagate.inject(headers)
response = requests.post('http://service-b/process', headers=headers)

8.采样策略:使用智能采样来平衡可见性与性能:

  • 针对高流量服务进行 1%–10% 头部采样

  • 使用尾部采样对错误和慢速请求进行 100% 采样

  • 监测插桩开销 — 目标是性能影响小于 5%

APM 实施的最佳实践

合适的应用程序性能管理 (APM) 解决方案应支持您的技术栈,并尽量减少插桩工作。OpenTelemetry 已成为行业标准,提供跨语言且不依赖供应商的插桩:

@RestController
public class OrderController {
    
    @PostMapping("/orders")
    public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
        // Auto-instrumentation captures this endpoint automatically
        // Add custom business context
        Span.current().setAttributes(Attributes.of(
            stringKey("order.value"), String.valueOf(request.getTotal()),
            stringKey("user.tier"), request.getUserTier()
        ));
        
        return ResponseEntity.ok(processOrder(request));
    }
}

  • 正确做法:实施采样策略并监测生产环境中的插桩开销。

  • 错误做法:切勿对高流量服务启用 100% 采样 — 这会影响性能并大幅增加存储成本。

  • 常见误区:头部采样可能遗漏关键错误跟踪。建议使用尾部采样,在控制数据量的同时确保错误完整捕获。

以下是正确实施的方法:

  • 选择正确的 APM 解决方案:正确的 APM 工具应符合应用程序架构和组织需求。解决方案应为企业提供监测、跟踪、测量和分析软件应用程序所需的工具和功能。企业可以使用开源可观测性框架 OpenTelemetry 来进行应用程序插桩并收集遥测数据(跟踪指标日志)。

  • 管理基数以控制成本:高基数属性可能导致度量不可用且成本高昂:
# Good - bounded cardinality
span.set_attribute("user.tier", user.subscription_tier)  # 3-5 values
span.set_attribute("http.status_code", response.status_code)  # ~10 values

# Bad - unbounded cardinality  
span.set_attribute("user.id", user.id)  # Millions of values
span.set_attribute("request.timestamp", now())  # Infinite values
  • 基于 SLO 设置智能告警,而非任意阈值。 使用错误预算来确定何时通知相关人员:
slos:
  - name: checkout_availability
    target: 99.9%
    window: 7d
  - name: checkout_latency  
    target: 95%  # 95% of requests under 500ms
    window: 7d

  • 培训团队并促进协作。APM 战略不仅影响开发人员,还涉及众多利益相关方。务必让 IT 团队及其他业务相关方共同参与跨部门协作。通过将 APM 纳入组织架构和运营流程,实现协同推进。确保制定符合业务需求的清晰目标和关键绩效指标(KPI),并充分考虑用户体验。

  • 审查和评估。APM 策略会随着应用程序和业务需求不断演变。

APM 中的监测策略

要制定成功的应用程序性能管理策略,其中一个关键要素是明确如何以及何时采用不同的监测方式。将多种监测策略结合使用至关重要,因为应用程序的不同部分(如用户体验或基础l架构)往往需要定制化的检测和解决方案。多样化的策略有助于实现全面覆盖、更快速的分析、更稳定的应用程序性能表现,并提升最终用户满意度。


有多种监测方法可供考虑:
  • 实时监测:持续以亚秒级精度跟踪实时系统性能。针对业务逻辑与技术指标定义自定义指标:
order_processing_duration = Histogram(
    "order_processing_seconds",
    "Time to process orders", 
    ["payment_method", "order_size"]
)

with order_processing_duration.labels(
    payment_method=payment.method,
    order_size=get_size_bucket(order.total)
).time():
    process_order(order)
  • 合成监测:通过模拟用户交互,在真实用户受到影响之前发现潜在问题。 对外部依赖至关重要:
// Synthetic check for critical user flow
const syntheticCheck = async () => {
    const span = tracer.startSpan('synthetic.checkout_flow');
    try {
        await loginUser();
        await addItemToCart();
        await completePurchase();
        span.setStatus({code: SpanStatusCode.OK});
    } catch (error) {
        span.recordException(error);
        span.setStatus({code: SpanStatusCode.ERROR});
        throw error;
    } finally {
        span.end();
    }
};

  • 深度诊断与性能剖析:有助于排查复杂的性能瓶颈问题,这些问题可能源于第三方插件或工具。通过对应用程序进行性能剖析,您可以更深入地理解数据的运行表现,并分析其在各项功能中的执行效率。

  • 分布式追踪:对微服务架构至关重要。谨慎处理跨异步边界的上下文传播:
# Event-driven systems - propagate context through messages
def publish_order_event(order_data):
    headers = {}
    propagate.inject(headers)
    
    message = {
        'data': order_data,
        'trace_headers': headers  # Preserve trace context
    }
    kafka_producer.send('order-events', message)

APM 数据分析与见解

监测和收集数据只是起点。企业需要理解如何解释应用性能管理数据,以便进行调整和决策。

识别趋势和模式有助于团队主动发现问题。使用关联分析将用户投诉与后端性能联系起来。请参阅此处使用 ES|QL(Elastic 的查询语言)

FROM traces-apm*
| WHERE user.id == "user_12345" 
  AND @timestamp >= "2024-06-06T09:00:00" 
  AND @timestamp <= "2024-06-06T10:00:00"
| EVAL duration_ms = transaction.duration.us / 1000
| KEEP trace.id, duration_ms, transaction.name, service.name, transaction.result
| WHERE duration_ms > 2000
| SORT duration_ms DESC
| LIMIT 10

检测瓶颈:APM 揭示了常见的性能反模式,例如 n+1 问题,这些问题可以在下面的代码中看到。使用 APM 来优化代码:

# N+1 query problem detected by APM
def get_user_orders_slow(user_id):
    user = User.query.get(user_id)
    orders = []
    for order_id in user.order_ids:  # Each iteration = 1 DB query
        orders.append(Order.query.get(order_id))
    return orders

# Optimized after APM analysis
def get_user_orders_fast(user_id):
    return Order.query.filter(Order.user_id == user_id).all()  # Single query

关联指标,并将用户投诉与包括历史数据在内的后端性能数据联系起来,有助于揭示系统各组件之间的相互作用。这将帮助团队更准确地诊断根本原因,并全面理解性能问题的影响范围。

实现自动化根本原因分析并借助 AIOps 等基于 AI/机器学习的工具,有助于精准定位问题来源、缩短故障时间并释放资源,从而加快诊断与修复流程。

建立全面的数据视图对于未来决策至关重要。拥有的数据越丰富,能挖掘的价值就越多。

  • 正确做法:使用分布式跟踪来识别导致性能下降的具体服务与操作。

  • 错误做法:切勿将相关性误认为因果关系,而是应通过代码级性能剖析数据进行验证。

  • 常见误区:遗留系统通常在追踪中显示为黑盒。可通过日志关联和合成跨度来保持可见性。

高级实现模式

复杂的生产环境带来了独特的挑战,需要高级实现策略。本节涵盖了处理多语言架构、遗留系统集成和复杂关联分析的实用方法。

多语言环境中的上下文传播:在不同语言和框架之间维护跟踪上下文需要特别关注传播机制:

// Java - Auto-propagation with Spring Cloud
@PostMapping("/orders")
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
    Span.current().setAttributes(Attributes.of(
        stringKey("order.type"), request.getOrderType(),
        longKey("order.value"), request.getTotalValue()));
    
    // OpenFeign automatically propagates context to downstream services
    return paymentClient.processPayment(request.getPaymentData());}
// Go - Manual context extraction and propagation
func processHandler(w http.ResponseWriter, r *http.Request) {
    ctx := otel.GetTextMapPropagator().Extract(r.Context(), 
                                              propagation.HeaderCarrier(r.Header))
    ctx, span := tracer.Start(ctx, "process_payment")
    defer span.End()
    // Continue with trace context maintained}

遗留系统集成:为无法直接插桩的系统创建可观测桥接:

# Synthetic spans with correlation IDs for mainframe calls
with tracer.start_as_current_span("mainframe.account_lookup") as span:
    correlation_id = format(span.get_span_context().trace_id, '032x')
    
    logger.info("CICS call started", extra={
        "correlation_id": correlation_id,
        "trace_id": span.get_span_context().trace_id
    })
    
    result = call_mainframe_service(account_data, correlation_id)
    span.set_attribute("account.status", result.status)

使用 ES|QL 进行高级跟踪分析:使用 Elastic 的查询语言将用户投诉与后端性能联系起来:

-- Find slow requests during complaint timeframe
FROM traces-apm*
| WHERE user.id == "user_12345" AND @timestamp >= "2024-06-06T09:00:00"
| EVAL duration_ms = transaction.duration.us / 1000
| WHERE duration_ms > 2000
| STATS avg_duration = AVG(duration_ms) BY service.name, transaction.name
| SORT avg_duration DESC

-- Correlate errors across service boundaries
FROM traces-apm*
| WHERE trace.id == "44b3c2c06e15d444a770b87daab45c0a"
| EVAL is_error = CASE(transaction.result == "error", 1, 0)
| STATS error_rate = SUM(is_error) / COUNT(*) * 100 BY service.name
| WHERE error_rate > 0

事件驱动架构模式:通过消息头显式传播上下文以进行异步处理:

# Producer - inject context into message
headers = {}
propagate.inject(headers)
message = {
    'data': order_data,
    'trace_headers': headers  # Preserve trace context
}
await kafka_producer.send('order-events', message)

# Consumer - extract and continue trace
trace_headers = message.get('trace_headers', {})
context = propagate.extract(trace_headers)
with tracer.start_as_current_span("order.process", context=context):
    await process_order(message['data'])

  • 正确做法:使用 ES|QL 进行复杂追踪数据分析,应对传统仪表板无法胜任的场景。

  • 错误做法:切勿直接对遗留系统进行插桩,而是应使用关联 ID 和合成跨度。

  • 常见误区:消息队列和异步处理会破坏跟踪上下文,除非通过消息头显式传递上下文信息。

  • 关键见解:在复杂或混合环境中,完美插桩往往难以实现。通过合理运用关联 ID、合成跨度和智能查询,仍可实现全面的可观测性。

使用 Elastic Observability 进行性能优化的 APM

Elastic Observability 通过提供统一的可观测性平台,将应用性能数据与日志、指标和追踪整合于一体,使应用性能管理策略的实施变得更加高效顺畅。在 Elastic’s Distributions of OpenTelemetry (EDOT) 的支持下,APM 数据收集变得简单易行且快捷高效。

开发人员可以设置异常警报,利用分布式追踪优化特定服务或交易,降低延迟,并通过负载均衡和缓存提升 Elastic 中的性能稳定性

通过代码剖析,团队可以识别性能瓶颈、低效的代码路径、内存泄漏或占用大量资源的操作,这些问题会导致应用程序变慢。企业可以创建定制仪表板来跟踪 KPI,从而支持更好的业务成果。

探索 Elastic Observability 实验室,获取更多技术性可观测资源

其他 APM 资源

本文中描述的任何功能或功能性的发布和时间均由 Elastic 自行决定。当前尚未发布的任何功能或功能性可能无法按时提供或根本无法提供。