高并发请求折叠:从 Hystrix 到埋点聚批

导读:高并发里,瓶颈常常是「调用太碎」——十个线程各打一条 SQL,连接、上下文切换、下游 QPS 一起被放大。请求折叠(Request Collapsing) 的做法是:在极短窗口内把同类请求攒成一批,用一次下游调用完成,再按原顺序把结果拆回去。Hystrix 拿它合并并发读,Kafka 拿它攒发送批次,HTTP 网关拿它合并同 URL 请求;APP 埋点上报 则在写路径上用「时间 + 数量」双阈值做聚批落库。下文按读路径、写路径各举一种典型实现,并落到一次真实接口设计。


阅读导航


什么是请求折叠

一句话:短时间内的多个请求,合并成一次下游调用,再把结果按原顺序还回去。

不折叠 vs 折叠,差别在下游眼里长什么样:

  【不折叠】

  线程1 ──▶ SELECT id=1 ──▶ DB
  线程2 ──▶ SELECT id=2 ──▶ DB
  线程3 ──▶ SELECT id=3 ──▶ DB
         ... 共 N 次往返

  【折叠后】

  线程1 ──┐
  线程2 ──┼──▶ Collapser 缓冲 ──▶ SELECT id IN (...) ──▶ DB
  线程3 ──┘         │
                    │ timer 到期 / 凑满 N 条
                    ▼
              拆回各线程结果

能换来什么

说明
连接与线程10 次 HTTP/DB 往返 → 1 次
下游吞吐批量 SQL、批量 RPC 通常比循环单条更省
热点保护ID 生成、库存扣减等可「预取一批,内存里发」

要付什么

说明
延迟得等凑批窗口或凑满条数,P99 会上浮
复杂度分组键、回填顺序、超时、拒绝策略都要想清楚
前提下游得支持批量,或业务接受异步 / 最终一致

打散 是反方向:分库分表、LongAdder 多 cell 是把热点拆开;折叠是把并发 收拢 再处理。Leaf 号段模式则是折叠 + 预取——DB 一次取 1000 个号,内存里逐个发。


Hystrix 请求折叠

Hystrix Request Collapsing 面向 读路径:大量并发、参数可合并的查询,例如「根据 userId 查用户信息」。

整体链路

           +----------------------+
           |  RequestCollapser    |
           +----------------------+
                      |
                      v
           +----------------------+
           |   RequestBatch.run   |
           +----------------------+
                      |
                      v
           +----------------------+
           | mapResponseToRequests|
           +----------------------+

触发条件(满足任一):timer 到期(如 10ms),或批次条数达上限(如 50)。

关键组件

组件干什么
RequestCollapser接收单次 submit,写入当前批次
RequestBatch本批所有请求的容器
Timer第一个请求进来时注册,到期触发执行
你的 BatchCommand一次 RPC/SQL 查回多条,再 map 回去

配置与实现示意:

public class UserBatchCommand extends CollapserCommand<List<User>> {
    private final List<Long> userIds;

    UserBatchCommand(List<Long> userIds) {
        super(Setter.withCollapserKey(CollapserKey.Factory.asKey("UserBatch"))
            .andCollapserPropertiesDefaults(
                HystrixCollapserProperties.Setter()
                    .withTimerDelayInMilliseconds(10)   // 合并窗口
                    .withMaxRequestsInBatch(50)));      // 单批上限
        this.userIds = userIds;
    }

    @Override
    protected List<User> run() {
        return userDao.batchSelectByIds(userIds);
    }

    @Override
    protected List<User> mapResponseToRequests(List<User> batchResponse) {
        return batchResponse;  // 与 submit 顺序一一对应
    }
}

值得记住的几点

优点局限
折叠调度不单独占业务线程池默认靠 CollapserKey 分组,细粒度分组要自己设计
调用方拿 Future/Observable 等结果条数没凑满也会被 timer 拉走,窗口要调
和熔断、隔离在同一条治理链上Hystrix 已停更,新项目借鉴思路即可

Kafka 与同类实现

写路径上更常见 先缓冲、再批量刷出。Kafka Producer 的 RecordAccumulator 是工业界用得最多的样板。

双阈值怎么工作

              Producer thread
                     |
                     | append(record)
                     v
           +----------------------+
           |  RecordAccumulator   |
           +----------------------+
                     |
           +---------+---------+
           v                   v
      batch full           linger.ms
           +---------+---------+
                     v
           +----------------------+
           |    Sender thread     |
           +----------------------+
                     v
                  Broker
参数含义
batch.size单分区批次字节上限(常见 16KB)
linger.ms没攒满也最多等多久(常见 10ms)

Topic-Partition 分组,每条 record 进对应 batch;满了或时间到了,Sender 线程统一发出去,Producer 侧可用 Future 感知成败。整体偏吞吐,不追求单条最低延迟。

同类思路还有:TCP Nagle(小包合并)、快手 BufferTrigger(时间 + 数量,偏 fire-and-forget)、Leaf 号段(DB 取一段号,内存逐个发)。


HTTP 折叠框架要点

如果要在 Web 入口 合并「同一 URL、参数可合并」的并发 HTTP 请求,可以参考开源 collapse-executor。处理链路如下(每步含义见下表):

                 HTTP Filter
                      |
                      v
           +----------------------+
           |    InputGrouper      |
           +----------------------+
                      |
                      v
           +----------------------+
           |       Bundle         |
           +----------------------+
                      |
                      v
           +----------------------+
           |   BatchCollector     |
           +----------------------+
                      |
                      v
           +----------------------+
           |     doExecute()      |
           +----------------------+
                      |
                      v
           +----------------------+
           |     bindOutput()     |
           +----------------------+
概念含义
Bundle一次请求的参数 + 结果回填句柄
ThreadlessExecutor不另建池,借用当前 Web 工作线程做折叠与唤醒
SingleThreadExecutor单队列单线程,把多任务让步合并后交给一条请求线程执行
Filter 入口在 Servlet 层拦截,决定是否参与折叠

适合:多个客户端同时打同一读接口,且后端有 batch API。不适合:写操作彼此独立、没法合并语义的场景。


实战埋点上报聚批

APP 埋点上报 走的是写路径聚批:调用方不等落库,服务端用有界队列攒批后批量 INSERT,触发模型与 Kafka 的 linger + batch.size 同类。

业务约束

约束设计选择
峰值数百 QPS,单条 INSERT 顶不住服务端必须聚批落库
埋点数据可丢队列满则丢弃,绝不阻塞用户请求
接口要快校验完入队即返回,落库异步进行

端到端数据流

           +----------------------+
           |      APP Client      |
           +----------------------+
                      |
                      | POST
                      v
           +----------------------+
           |      Report API      |
           +----------------------+
                      |
                      v
           +----------------------+
           | ArrayBlockingQueue   |
           +----------------------+
                      |
                      v
           +----------------------+
           |    Consumer Loop     |
           +----------------------+
                      |
                      v
           +----------------------+
           |     Batch INSERT     |
           +----------------------+
                      |
                      v
           +----------------------+
           |       Database       |
           +----------------------+

Report API:校验、去重、offer 入队后立即返回。Consumer Loop:poll(1000ms) + drainTo(500)。队列满则丢弃,INSERT 失败整批丢弃。

与 Kafka Producer 的参数对照:

Kafka埋点管道
linger.msflushIntervalMs(默认 1000ms,没攒够也刷)
batch.sizebatchSize(默认 500 条)
RecordAccumulatorArrayBlockingQueue(默认 20000)
Producer 回调无——埋点可丢,不回调客户端

消费循环(简化)

List<EventRow> batch = new ArrayList<>();
while (!shuttingDown) {
    EventRow head = queue.poll(flushIntervalMs, MILLISECONDS);
    if (head != null) {
        batch.add(head);
        queue.drainTo(batch, batchSize - batch.size());
    }
    if (!batch.isEmpty()) {
        mapper.batchInsert(batch);
        batch.clear();
    }
}

poll 控制时间窗口,drainTo 控制单批条数;客户端先把多条 event 打进一个 HTTP 请求,服务端再把多批请求里的行事件二次合并成 500 行一次的 INSERT,下游 QPS 从行级降到批级。


选型对比

           +----------------------+
           |   Request Collapsing |
           +----------------------+
                      |
          +-----------+-----------+
          v                         v
      read path                 write path

   Hystrix / HTTP              Kafka / queue
   Collapser  collapse         Producer  batch
方案典型场景触发回填分组
Hystrix Collapser并发查用户/订单时间 + 数量FutureCollapserKey
Kafka Producer日志、消息字节 + lingerFuture分区
HTTP collapse-executor网关合并读请求时间 + 数量CompletableFutureInputGrouper
内存队列聚批高 QPS 写宽表、可丢poll + batchSize单管道
Leaf 号段分布式 ID号段耗尽内存发号业务 key

怎么选

  • 读、要结果、并发重复 → Hystrix 类折叠,或 HTTP collapse。
  • 写、可异步、要吞吐 → 队列 + 双阈值(Kafka / 埋点模式)。
  • 写、强一致 → 别硬折叠;用 MQ 削峰,或业务层提供真正的批量 API。

结语

请求折叠的本质是 用可控的等待换整体吞吐。读路径看 Hystrix / HTTP collapse,写路径看 Kafka / 内存队列聚批,埋点则是「客户端批 + 服务端再批」的叠加。落地前建议逐项过一遍:

#问题
1一个上游请求会放大成几次下游调用?
2下游有没有真正的批量 API?(别用 for 循环假装 batch)
3合并窗口抬高的 P99,业务能不能接受?
4分组键会不会把不该合并的请求并在一起?
5折叠失败时:阻塞、降级,还是丢弃?
6队列有没有上限?(无界队列在尖刺流量下是内存炸弹)
7调用方是「必须等结果」,还是「受理就行」?

参考资料

内容概要:本文系统研究了在有限控制集约束下,三相并网逆变器中电流与功率双模态模型预测控制(MPC)的等效机理及其性能边界。通过构建精确的预测模型,设计合理的代价函数,并结合Simulink仿真与Matlab代码实现,深入分析了电流预测控制与功率预测控制两种策略在动态响应速度、稳态精度、谐波抑制能力和抗扰性等方面的差异与内在联系。研究揭示了在特定系统参数和运行条件下,两种控制模式之间的等效转化机制,并界定了各自的适用范围与性能极限。同时,探讨了多模态控制的切换逻辑、实时性优化及预测模型不确定性对控制性能的影响,旨在提升逆变器在复杂电网环境下的综合控制品质与鲁棒性。; 适合人群:具备电力电子、自动控制或新能源并网等相关专业背景,熟悉Matlab/Simulink仿真环境,从事研究生及以上层次科研或从事高端电力电子装备研发的工程技术人员。; 使用场景及目标:①深入理解模型预测控制在并网逆变器中的具体实现方法与理论基础;②掌握电流与功率双模态MPC控制器的设计、仿真建模与性能对比评估流程;③为高动态、高精度并网控制系统的方案选型、参数优化与工程化应用提供坚实的理论依据和技术参考。; 阅读建议:建议结合所提供的Simulink仿真模型与Matlab源代码进行同步实验验证,重点关注预测模型的建立过程、控制律的数学推导以及不同工况下的仿真结果对比分析,宜配合现代控制理论、电力电子变换技术及并网标准等相关资料进行系统性学习。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值