3112 字
16 分钟
数据库流量过大:中间件防护与数据库保护

一、问题背景#

当大量请求同时写入数据库时,数据库可能出现以下问题:

  1. 连接数耗尽:大量请求占用数据库连接,新请求无法获取连接。
  2. CPU 和磁盘 I/O 过高:数据库处理不过来,SQL 执行时间变长。
  3. 锁竞争严重:多个事务同时修改相同的数据,互相等待。
  4. 请求超时:数据库响应变慢,应用请求不断超时。
  5. 重试导致流量放大:请求超时后不断重试,进一步加重数据库压力。
  6. 服务雪崩:数据库变慢导致应用线程堆积,最终影响整个系统。

核心问题:请求进入数据库的速度超过了数据库的处理能力。

因此,不能只依赖数据库自身配置,还需要在应用和数据库之间建立流量控制机制

复习:SQL 慢查询怎么排查

二、整体解决方案#

推荐的整体链路:

用户请求 → 网关限流 → 应用服务 → 消息队列 → 消费者 → 数据库

同时配合 Redis 缓存、数据库连接池、SQL 优化、监控告警等措施。

1. 网关:入口限流#

作用:在请求进入业务服务之前,限制流量。

常见措施:

  • 按用户、接口或业务维度设置请求速率限制。
  • 限制突发流量,超过阈值时拒绝请求或返回友好提示。
  • 对非核心业务进行降级,优先保障核心业务。

例如:某个接口正常情况下每秒处理 2,000 个请求,突然收到 10,000 个请求,可以通过限流机制控制进入后端的流量。

注意:具体限流阈值需要根据压测结果和业务要求确定。

2. 消息队列:削峰填谷#

常见中间件:Kafka、RabbitMQ。

作用:将突发的写入请求暂存在消息队列中,让数据库按照自身的处理能力逐步消费。

原本的同步写入:

用户请求 → 应用服务 → 数据库

改造后的异步写入:

用户请求 → 应用服务 → 消息队列 → 消费者 → 数据库

例如:

数据库稳定处理能力为每秒 2,000 条写入,突然来了每秒 10,000 条写入请求。

此时可以将请求写入消息队列,由消费者控制写入速度,而不是让所有请求同时冲击数据库。

需要注意:

  • 消息队列不是无限容量的,必须监控积压量和消息延迟。
  • 消费者不能无限增加并发,否则仍然会压垮数据库。
  • 应设置队列容量或积压告警,并制定过载处理方案。
  • 需要考虑消息确认、失败重试、死信处理和重复消费。
  • 只有适合异步处理的业务才能这样改造;必须立即返回最终写入结果的场景,需要重新设计接口语义。

3. 消费者:控制数据库写入速度#

作用:决定消息队列中的数据以多快的速度进入数据库。

主要措施:

  • 限制消费者数量和并发线程数。
  • 限制每个消费者同时执行的数据库任务数量。
  • 根据数据库 CPU、连接数、响应时间动态调整消费速度。
  • 合理批量写入,减少频繁执行 SQL 和提交事务的开销。
  • 避免无限重试,失败消息采用退避重试,必要时进入死信队列。

例如:

数据库经过压测,发现每秒稳定处理 2,000 条写入。可以先将消费者目标处理速率设为每秒 1,500 条,再根据实际监控逐步调整。

这只是示例,不能直接将该数值用于所有生产环境。

重点:消息队列负责缓冲,消费者负责控速,数据库负责最终落盘。

4. Redis:缓存与分布式限流#

Redis 主要有两种用途。

第一,缓存热点查询结果,减少数据库的读取压力。

例如:商品详情、配置数据等被大量重复读取,可以优先从 Redis 获取。

第二,使用 Redis 计数器或令牌桶等机制实现分布式限流,避免多个应用实例各自限流后,总流量仍然超过数据库承载能力。

注意:

  • Redis 缓存主要减少读压力,不能直接解决所有写入压力。
  • 对库存、余额等关键数据,不能为了减轻数据库压力而随意使用缓存代替数据库事务。
  • 分布式限流需要处理 Redis 故障、限流规则和关键业务的可用性。

5. Sentinel:限流、熔断与降级#

Sentinel 可用于 Java 应用的流量控制。

主要作用:

  • 限流:请求过多时限制进入业务方法的流量。
  • 熔断:依赖服务持续异常时,暂时停止部分调用,避免故障扩散。
  • 降级:必要时关闭非核心功能,优先保障核心业务。

例如:数据库持续超时,可以限制非核心查询接口的请求,避免业务线程持续等待数据库。

注意:Sentinel 不是数据库本身的保护机制,也不能代替消息队列的异步缓冲。

6. ShardingSphere:读写分离与分库分表#

当数据库长期达到处理能力或存储容量上限,经过 SQL 优化、缓存和流量控制后仍然无法满足业务需求,可以考虑扩展数据库架构。

  • 读写分离:将部分查询请求分配给只读副本,降低主库读取压力。
  • 分库分表:按照用户 ID、订单 ID 等合适的分片键,将数据分布到多个数据库或数据表中。
  • 水平扩展:增加数据库节点,提高整体处理能力。

注意:

  • 读写分离主要缓解读压力,不能直接解决主库写入过载。
  • 分库分表会增加事务、跨分片查询、数据迁移和运维复杂度。
  • 不能把分库分表当作流量过大时的第一反应。

三、数据库自身的保护措施#

中间件之外,还需要从数据库自身进行优化。

1. 限制连接数#

设置合理的数据库最大连接数,并通过应用连接池控制每个实例的连接数量。

注意:多个应用实例的连接池总量不能超过数据库可承受范围。

连接数限制可以避免连接资源被耗尽,但不等于严格限制所有 SQL 的执行并发。

2. 优化 SQL 和索引#

  • 为高频查询和更新条件建立合适的索引。
  • 避免不必要的全表扫描。
  • 避免一次查询或更新过多无关数据。
  • 分析慢 SQL,减少不必要的数据库访问。
  • 关注索引维护开销,避免建立过多无用索引。

3. 控制事务#

  • 缩短事务执行时间。
  • 避免在事务中执行耗时的外部网络请求。
  • 减少长事务和不必要的锁竞争。
  • 避免大事务一次修改过多数据,必要时分批处理。

4. 合理批量写入#

对于大量数据写入,可以采用批量插入或批量更新,减少频繁执行 SQL 的开销。

但要控制批次大小和事务范围,避免单批操作过大,造成锁等待、日志压力或回滚成本过高。

5. 监控与告警#

重点关注:

  • 数据库 CPU 和内存使用情况。
  • 磁盘 I/O 和存储空间。
  • 活跃连接数和连接池等待数。
  • 慢 SQL、锁等待和死锁。
  • 事务执行时间。
  • 消息队列积压量、消费延迟和失败率。
  • 应用请求超时率与重试次数。

当指标达到阈值时,应及时限流、降低消费速度或暂停非核心任务,并排查真正的瓶颈。

四、不同问题如何选择方案?#

具体问题优先考虑的方案
突发写入流量过大消息队列削峰填谷、消费者控速
请求入口流量过大网关限流、Sentinel
热点查询过多Redis 缓存、查询优化
数据库连接耗尽连接池限制、连接数监控
慢 SQL、锁竞争严重索引优化、SQL 优化、缩短事务
数据库长期达到性能上限压测、容量评估、必要时扩容
单库存储或写入能力不足评估分库分表
数据库超时后重试风暴超时控制、退避重试、重试上限、熔断

五、必须考虑的可靠性问题#

引入消息队列后,并不是所有问题都自动解决,还需要考虑以下事项。

1. 消息重复消费#

消费者可能已经完成数据库写入,但确认消息之前发生故障,导致消息再次被消费。

解决方案:

  • 为业务请求设置唯一业务 ID 或幂等键。
  • 在数据库中建立唯一约束,或使用可靠的幂等记录。
  • 使用事务或适当的状态流转,避免重复执行关键业务。

2. 消息丢失#

需要合理配置消息持久化、生产者确认、消费者确认和重试机制。

具体保障取决于消息队列的配置与故障场景,不能仅凭使用了消息队列就认为绝对不会丢消息。

3. 消息积压#

如果消息进入队列的速度长期高于消费速度,积压会越来越大。

解决方案:

  • 监控积压量和消息延迟。
  • 排查慢 SQL、数据库瓶颈和消费失败。
  • 在数据库有余量时适当提高消费者并发。
  • 必要时限制入口流量,暂停非核心业务。
  • 评估队列容量、数据保留时间和过载处理策略。

4. 数据一致性#

如果接口采用异步写入,需要明确接口返回的含义:是消息已被可靠接收,还是数据已真正写入数据库。

如果业务要求最终一致性,还要设计失败补偿、状态查询和异常恢复机制。

六、面试口播答案#

如果数据库写入流量过大,我会从入口流量控制、中间件削峰和数据库自身保护三个层面处理。

首先,在网关或应用层设置限流,避免请求无限进入系统。

其次,对于突发的大量写入,我会引入 Kafka 或 RabbitMQ 消息队列,将同步写入改成异步处理。由消费者控制并发和消费速度,让数据库按照自身的承载能力逐步处理,实现削峰填谷。

然后,在数据库层面限制连接数,优化 SQL、索引和事务,必要时采用批量写入。同时使用 Redis 缓存热点查询,减少不必要的数据库访问。

如果数据库持续过载,还可以使用 Sentinel 对非核心业务进行限流和降级;如果经过优化后,单库容量或处理能力仍然不足,再评估读写分离、分库分表或扩容。

最后,我会监控数据库负载、慢 SQL、消息积压和请求超时,并通过幂等、失败重试和异常恢复保证数据可靠性。

七、记忆框架#

记住这六步:

  1. 入口限流:不要让流量无限进入。
  2. 队列削峰:把突发写入暂存起来。
  3. 消费控速:控制进入数据库的速度。
  4. 数据库优化:连接、SQL、索引、事务。
  5. 缓存与扩容:减少不必要的访问,必要时提升容量。
  6. 可靠性保障:幂等、重试、积压监控、数据一致性。

最终要表达的核心是:先控制流量,再提高处理效率,最后根据实际瓶颈扩容,而不是一上来就分库分表。

相关文章#

数据库流量过大:中间件防护与数据库保护
https://blog.xqcherry.top/posts/interview/scenarios/database-traffic-overload-protection/
作者
xqcherry
发布于
2026-10-22
许可协议
CC BY-NC-SA 4.0