KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

03 · 连接池、读写分离与高并发写 — keel 龙骨

这一章回答:并发上来之后,最先崩的往往是连接池——怎么配、怎么测、怎么救。

这一章回答:并发上来之后,最先崩的往往是连接池——怎么配、怎么测、怎么救。

应用层的并发上限、数据库的连接数、磁盘 IO 三者之间存在木桶效应。连接池配置不当的典型表现是:数据库还很闲,应用已经在报错了,或者反过来:应用把数据库连接打满,导致所有服务一起不可用。

一、连接池到底在限制什么

一个请求进来要经过:取连接 → 执行 SQL → 归还连接。连接池的容量决定了同时能有多少个 SQL 在执行,取不到连接的请求会排队或直接失败。

关键参数(以 SQLAlchemy 异步引擎为例):

engine = create_async_engine(
    url,
    pool_size=20,            # 常驻连接数
    max_overflow=10,         # 高峰时可临时额外创建的连接数(超出部分用后即弃)
    pool_timeout=30,         # 取连接的最长等待时间,超时抛错而不是无限等
    pool_recycle=3600,       # 定期回收连接,避免被中间设备或数据库主动断开
    pool_pre_ping=True,      # 每次取用时先探活,牺牲一点性能换稳定性
)

配置要点:

参数 常见误区 建议
pool_size + max_overflow 设得越大越好 ❌ 它必须小于数据库的 max_connections,且多个实例要乘上实例数计算
pool_timeout 用默认值不管 应配合接口超时,让排队在可接受时间内失败而不是堆到超时连锁反应
pool_pre_ping 不知道有这个 有负载均衡/长空闲的生产环境建议开启,能消除"连接已被断开"类报错
pool_recycle 从不设置 小于数据库的 wait_timeout

容量估算公式(粗估):
总连接数 = 实例数 × (pool_size + max_overflow),这个值要显著小于数据库的 max_connections(留出给管理连接、迁移任务、其他服务)。

二、异步环境下的额外注意

异步不是万能的:连接池上限不会因为用了 async 就变大。数据库侧的连接是有限资源,协程只是让等待不占用线程。

所以:

三、读写分离

什么时候引入:读压力明显大于写、且主从延迟可接受时。

写 → 主库
读取(容忍延迟)→ 只读实例
要求读到自己刚写的数据 → 主库(或强制一致性读)

三个必须处理的坑:

  1. 主从延迟:写完立刻读可能读不到。做法是关键路径读主库,或提供"写后读走主库"的开关;
  2. 大查询拖垮从库:报表、导出类查询要走独立的分析实例,不能和在线查询混用;
  3. 延迟监控:把主从延迟做成指标,超过阈值时自动把读流量切回主库。

四、高并发写的四条策略

策略 做法 适用
批量合并 多条 INSERT 合并为一条 INSERT INTO ... VALUES (...),(...),更新走批量 UPDATE 日志、流水类写入
异步落库 写请求先进 MQ/Redis,消费者批量落库 允许最终一致的场景
削峰 Redis 预扣/计数 + 队列平滑 秒杀、抢购
拆分表 单表超过千万级行数考虑分库分表 数据量本身成为瓶颈之后

顺序建议:先做批量与SQL优化,再考虑异步化,最后才上分库分表。分库分表会引入分布式主键、跨片查询、迁移运维三重复杂度,不要提前透支。

五、必须有的三条保护

  1. 连接耗尽时的降级:明确返回"系统繁忙"而不是让请求无限堆积;
  2. 不同用途用不同连接池:在线业务、后台任务、数据迁移各自独立,互不挤占;
  3. 慢查询保护:设置 max_execution_time(或等价机制),防止一条 SQL 拖死整个实例。

动手:可观察结果

产出 判断标准
一份连接池配置单据 写出计算过程:实例数 × (pool_size + max_overflow) < max_connections
一次压测 打到连接池上限,观察是快速失败还是超时崩溃;记录 P99 与错误率
连接泄漏检查 长时间压测后连接数能回落到基线(附会话泄漏检测的排查记录)
主从延迟监控 延迟超阈值自动切主库的演练记录

完成标志:给定 QPS 目标,你能算出需要的连接池容量,并说明超出时的降级行为。

故障注入

注入方式 观察
忘记关闭 AsyncSession 压测几分钟后是否出现连接耗尽
pool_size 设得远大于数据库上限 是否出现数据库拒绝连接、影响其他服务
关闭 pool_pre_ping 并长时间空闲后再请求 是否出现"连接已断开"类错误
从库人为制造 30 秒延迟 写后立即读是否读到旧数据
跑一个大查询打到同一从库 在线查询延迟是否被拉高

自测题

  1. 连接池容量应该怎么算?多实例部署时最容易犯的错误是什么?
  2. pool_pre_ping 解决了什么问题,代价是什么?
  3. 用了异步 ORM 之后,数据库连接瓶颈消失了吗?为什么?
  4. 主从延迟导致的"写后读不一致"怎么处理?
  5. 高并发写的四条策略,你的采用顺序是什么?为什么最后才考虑分库分表?

进入 keel 阅读