欢迎光临
我们一直在努力

弹性伸缩与 HPA 自定义指标:电商大促下的容量治理实践

1. 背景:大促流量洪峰下的容量失控

2025 年双 11 备战期,我们负责的电商订单中台面临一个棘手的容量问题。业务侧日均订单量约 200 万,大促峰值预估达到日常的 8 倍。订单创建链路涉及库存预占、优惠计算、支付回调等多个下游依赖,其中优惠计算服务(coupon-service)对 CPU 的消耗并不突出,真正的瓶颈在于 Redis 热点 key 的访问延迟和本地缓存命中率。

为什么我会接触到 HPA 自定义指标?起因很简单:大促压测时,订单接口的 P99 延迟从平时的 120ms 飙升到 2.3s,优惠计算线程池任务堆积超过 5 万,但 Kubernetes 默认的 HPA 却判定"无需扩容"。我需要搞清楚:为什么 CPU 指标在 IO 密集型服务上失效,以及如何让 HPA 感知真实的业务压力。

2. 踩坑与现状:CPU 单指标方案的三个真实痛点

我们最初采用 Kubernetes 默认的 HPA 配置,仅基于 CPU 使用率进行扩缩容:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: couponservicehpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: couponservice
minReplicas: 6
maxReplicas: 60
metrics:
type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70

压测结束后我们拉取了监控数据,发现三个关键痛点:

  • CPU 与延迟脱钩:优惠计算服务是 IO 密集型,线程在等待下游 Redis 和数据库响应时处于 BLOCKED 状态,CPU 利用率始终徘徊在 40% 左右,HPA 判定无需扩容。CPU 指标完全无法反映真实的排队压力——线程池在等待 Redis 响应时并不消耗 CPU,但请求已经堵在队列里。
  • 队列积压无感知:业务线程池的活跃线程数达到上限 200,任务队列积压 5 万+,但 HPA 对此一无所知。
  • 扩容滞后:即使 CPU 指标最终上涨触发扩容,Pod 启动 + 注册到服务发现 + 连接池预热需要 90 秒以上,而流量在 30 秒内就能打满现有容量。
  • 根因清楚了:默认 HPA 只认识资源指标,不认识业务指标。我们需要让 HPA 感知"线程池排队深度"和"请求处理延迟"这类直接反映服务健康状况的信号。

    3. 方案:三种自定义指标接入路径与选型

    我们评估了三条技术路线,各有取舍。

    方案 A:Prometheus Adapter + 自定义 Metrics API

    在集群中部署 Prometheus 采集业务指标,通过 prometheus-adapter 将其暴露为 custom.metrics.k8s.io 接口,HPA 直接消费。

    优点:生态成熟,Prometheus 已是可观测性标配,指标定义灵活。
    缺点:需要维护 adapter 的指标映射规则(rules 配置),且 Prometheus 拉取周期(默认 30s)与 HPA 同步周期(默认 15s)叠加,端到端响应延迟约 45–60s,对突发流量仍偏慢。

    方案 B:KEDA(Kubernetes Event-Driven Autoscaling)

    KEDA 作为 HPA 的上层封装,内置 Redis、Kafka、RabbitMQ 等几十种 Scaler,可以直接读取 Redis 的 LLEN 命令获取队列长度,无需自建指标链路。

    优点:Redis Scaler 开箱即用,配置简单,响应速度快(秒级)。
    缺点:多引入一个控制器组件,且队列长度指标只能反映"积压",无法表达"处理能力余量"。

    方案 C:Kubernetes 原生 HPA + 自定义外部指标(External Metrics)

    通过 external.metrics.k8s.io 接口暴露业务指标,HPA 原生支持,不引入额外控制器。

    优点:架构最简洁,指标来源可以是任意监控系统。
    缺点:需要自己实现 Metrics API 服务,开发成本最高。

    选型结论

    我们最终选择 方案 A:Prometheus Adapter。理由有三:一是集群已有 Prometheus + Grafana 监控体系,指标采集链路现成;二是我们后续还要接入更多业务指标(如订单积压数、缓存命中率),adapter 的规则配置比 KEDA 的 Scaler 更灵活;三是团队对 Prometheus 的 PromQL 足够熟悉,排查问题成本低。KEDA 的 Redis Scaler 我们保留作为备选,在队列积压场景确实更简单,但当前阶段不想引入新的控制器。

    4. 实操:基于线程池活跃度的自定义 HPA

    4.1 业务侧暴露指标

    在 coupon-service 中引入 Micrometer(配合 Spring Boot 3.2),将线程池状态暴露为 Prometheus 指标:

    // pom.xml 关键依赖
    // io.micrometer:micrometer-registry-prometheus:1.12.5
    // org.springframework.boot:spring-boot-starter-actuator:3.2.4

    @Configuration
    public class ThreadPoolMetricsConfig {

    @Bean
    public ThreadPoolTaskExecutor couponExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(50);
    executor.setMaxPoolSize(200);
    executor.setQueueCapacity(5000);
    executor.setThreadNamePrefix("coupon-worker-");
    executor.initialize();

    // 暴露活跃线程数与队列深度
    Metrics.gauge("coupon_executor_active_threads", executor,
    e -> e.getThreadPoolExecutor().getActiveCount());
    Metrics.gauge("coupon_executor_queue_depth", executor,
    e -> e.getThreadPoolExecutor().getQueue().size());
    return executor;
    }
    }

    4.2 部署 Prometheus Adapter

    使用 Helm 安装 prometheus-adapter 4.10.0,核心是配置指标映射规则:

    # values.yaml 关键片段
    rules:
    seriesQuery: 'coupon_executor_queue_depth'
    resources:
    overrides:
    namespace: { resource: "namespace" }
    pod: { resource: "pod" }
    metricsQuery: 'sum(coupon_executor_queue_depth{<<.LabelMatchers>>}) by (<<.GroupBy>>)'

    4.3 定义基于自定义指标的 HPA

    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
    name: couponservicehpav2
    spec:
    scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: couponservice
    minReplicas: 6
    maxReplicas: 60
    metrics:
    type: Pods
    pods:
    metric:
    name: coupon_executor_queue_depth
    target:
    type: AverageValue
    averageValue: 800
    type: Resource
    resource:
    name: cpu
    target:
    type: Utilization
    averageUtilization: 70
    behavior:
    scaleUp:
    stabilizationWindowSeconds: 30
    policies:
    type: Percent
    value: 100
    periodSeconds: 15
    scaleDown:
    stabilizationWindowSeconds: 300

    这里的关键设计是双指标策略:队列深度超过 800 立即扩容,CPU 作为兜底;behavior.scaleUp 将扩容步长放宽到 100%/15s,缩短扩容响应时间。

    5. 踩坑记录:三个真实问题与解决过程

    5.1 指标采集不到:adapter 规则 namespace 映射错误

    报错:

    E0712 14:23:11.882312 1 manager.go:112] unable to get metric coupon_executor_queue_depth:
    no matching metrics found in the list of metrics provided by the custom metrics API

    排查:先确认 Pod 的 /actuator/prometheus 端点能拉到 coupon_executor_queue_depth 指标,再用 kubectl get –raw "/apis/custom.metrics.k8s.io/v1beta1" 查看 adapter 暴露的指标列表,发现指标名存在但 namespace 标签缺失。

    解决:adapter 的 resources.overrides 中 namespace 和 pod 的映射写反了,修正后指标正常暴露。

    5.2 扩容风暴:指标抖动导致 Pod 频繁创建销毁

    现象:大促流量回落时,队列深度在 700–900 之间波动,HPA 在 15 分钟内反复扩容缩容 8 次,造成 Pod 频繁重建,连接池反复预热。

    解决:调大 scaleDown.stabilizationWindowSeconds 到 300s,并增加 scaleDown.policies 限制单次缩容比例不超过 20%。扩容侧保持激进,缩容侧保持保守,这是大促容量治理的基本原则。

    5.3 指标延迟:Prometheus 拉取周期导致扩容滞后

    现象:压测发现从流量突增到 HPA 触发扩容,端到端延迟约 50s,比预期慢。

    解决:将 Prometheus 的 scrape_interval 从 30s 调到 10s(仅对 coupon-service 生效),HPA 的 –horizontal-pod-autoscaler-sync-period 保持默认 15s。同时把扩容步长从 100% 放宽到 200%,让单次扩容能拉起更多副本。实测端到端延迟降到 25s 以内。

    6. 验证数据与效果

    双 11 当天压测和实战数据对比如下:

    指标优化前(CPU 单指标)优化后(自定义指标)
    扩容触发延迟 50s+ 25s 以内
    P99 延迟(峰值) 2.3s 480ms
    峰值 Pod 数 42 58
    线程池队列积压峰值 5 万+ 3000 以内
    扩容次数(4 小时) 6 次 23 次(更贴合流量曲线)

    大促当天订单峰值 1600 万单/小时,优惠计算服务全程无容量告警,P99 稳定在 500ms 以内。相比去年同期的 CPU 单指标方案,扩容响应快了近一倍,且没有出现一次因容量不足导致的订单失败。

    7. 复盘:什么场景该用,什么场景不该用

    适用场景:

    • IO 密集型服务(大量等待 Redis、数据库、外部 API 响应),CPU 无法反映真实负载。
    • 有明确业务信号的场景:队列深度、请求延迟、积压消息数等。
    • 流量呈脉冲式突增(大促、秒杀、热点事件),需要秒级响应的场景。

    不适用场景:

    • 纯 CPU 密集型计算服务(如图片处理、加解密),CPU 指标已经足够准确,引入自定义指标徒增复杂度。
    • 指标采集链路本身不稳定的小集群,自定义指标依赖 Prometheus 的可用性,监控挂了扩容也就挂了。
    • 对成本极度敏感、不希望频繁扩缩容的场景,自定义指标的高灵敏度会带来更多的 Pod 创建销毁。

    最后的建议:自定义指标 HPA 不是银弹,它解决的是"指标选得对不对"的问题,而不是"容量规划做没做"的问题。大促容量治理的根基仍然是压测、限流、熔断和预案演练,HPA 只是让这些预案执行得更快、更精准。

    赞(0)
    未经允许不得转载:171主机测评 » 弹性伸缩与 HPA 自定义指标:电商大促下的容量治理实践
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址