PostgreSQL 在遭遇恶意 CC 攻击或突发高并发流量时,最致命的后果往往不是 CPU 飙升,而是数据库连接池被瞬间打满。一旦连接数触及上限,PostgreSQL 会直接拒绝新的连接请求,导致正常业务瘫痪。这种故障在日志中通常表现为“FATAL: sorry, too many clients already”,意味着数据库完全失去了对外服务能力。解决这个问题的核心思路,不是无限放大连接数,而是通过连接数限制与精细化的连接管理,让数据库在高负载下依然保持可用。

理解 PostgreSQL 连接机制与资源消耗

PostgreSQL 采用多进程架构,每一个客户端连接都会在操作系统层面 fork 出一个独立的服务进程。这与其他数据库的线程模型有本质区别。每个后端进程大约会占用 5 到 10 MB 的私有内存,这还不包括查询执行过程中动态申请的 work_mem 和共享缓冲区。当数百个连接同时存在时,仅进程调度和上下文切换的开销就足以让系统性能急剧下降。更危险的是,如果这些连接都在执行复杂查询,内存消耗会呈指数级增长,极易触发 OOM Killer,导致整个数据库实例崩溃。因此,连接数限制不仅是逻辑层面的防护,更是对操作系统物理资源的保护。

核心参数 max_connections 的正确配置

max_connections 是 PostgreSQL 控制最大并发连接数的直接参数。很多人误以为这个值越大越好,实际上恰恰相反。设置过大等于把系统推向崩溃边缘。一个合理的数值需要根据服务器物理内存和业务查询复杂度来推算。一个实用的公式是:预留 20% 内存给操作系统和其他服务,剩余内存中减去 shared_buffers 的占用,再除以单连接的平均内存消耗。例如一台 32GB 内存的服务器,shared_buffers 设为 8GB,操作系统预留 6GB,剩下 18GB 可用。如果单连接平均消耗 50MB,那么 max_connections 理论上不应超过 360。但实际生产环境建议进一步压缩到 200 以下,为突发流量留出缓冲空间。修改该参数需要重启数据库,因此前期压测和容量规划至关重要。

利用超级用户预留连接确保紧急通道

当普通连接数耗尽时,DBA 往往连登录数据库执行诊断命令的机会都没有。superuser_reserved_connections 参数就是为这种场景设计的。它从 max_connections 中预留出指定数量的连接,只允许超级用户使用。建议至少保留 3 到 5 个预留连接。这样即使数据库被 CC 攻击打满,你仍然可以通过预留通道登录进去,执行终止异常连接、分析锁等待或调整配置等操作。这是灾难恢复的最后一道保险,很多团队在初期部署时容易忽略,直到故障发生时才追悔莫及。

通过角色级连接限制实现精细隔离

全局连接限制无法区分不同应用的优先级。PostgreSQL 支持通过 ALTER ROLE 语句为特定数据库用户设置连接限制。比如你可以限制只读报表用户只能建立 10 个连接,而核心交易用户可以获得 100 个连接配额。具体实现如下:

ALTER ROLE report_user CONNECTION LIMIT 10;
ALTER ROLE core_app CONNECTION LIMIT 100;

这种机制在微服务架构中特别有用。每个服务使用独立的数据库账号,你就能精确控制各服务对数据库资源的占用上限。某个非关键服务即使出现连接泄漏,也不会耗尽全部连接而影响核心链路。配合 pg_stat_activity 视图持续监控各角色的实际连接数,可以及时发现异常增长趋势。

数据库端连接超时与断开策略

CC 攻击的典型特征是大量连接处于空闲或半开状态,既不执行查询也不断开。PostgreSQL 提供了多个超时参数来清理这类僵尸连接。idle_in_transaction_session_timeout 是最关键的参数,它强制终止那些开启事务后长时间没有任何活动的连接。很多应用框架会在事务中执行外部网络调用,导致事务悬挂,这个参数能有效回收这类连接。设置 30 秒到 2 分钟比较合理。另一个参数 idle_session_timeout 针对的是不在事务中的纯空闲连接,可以设置得稍长一些,比如 10 分钟。statement_timeout 则限制单条 SQL 的最长执行时间,防止慢查询长期占用连接。这些参数的组合使用,能够在连接被异常占用时主动释放资源,而不是被动等待客户端断开。

客户端连接池的架构级防护

把数据库连接数限制在较低水平的前提,是应用层必须使用连接池。PgBouncer 是 PostgreSQL 生态中最成熟的轻量级连接池中间件。它支持三种池化模式:会话模式、事务模式和语句模式。对于 Web 应用,事务模式是最佳选择,它在事务结束时立即回收连接,使得数百个应用线程可以复用几十个数据库连接。PgBouncer 本身也支持连接数限制,包括最大客户端连接数和最大数据库连接数。你可以将 PgBouncer 的 default_pool_size 设置为比数据库 max_connections 低 10% 到 20% 的值,让连接池在数据库之前就进行流量削峰。部署时建议将 PgBouncer 与应用部署在同一台机器或同机房内网,以减少网络延迟。

操作系统层面的网络连接限制

PostgreSQL 的连接限制只作用于数据库层面,但 TCP 连接本身消耗操作系统资源。内核参数 net.core.somaxconn 决定了 PostgreSQL 监听队列的最大长度。如果该值过小,即使数据库还有连接余量,新连接请求也会在 TCP 层面被丢弃。建议将其设置为 4096 或更高。另外,net.ipv4.tcp_max_syn_backlog 控制 SYN 队列大小,在遭遇 SYN Flood 攻击时尤为重要。通过 iptables 的 recent 模块或 nftables 可以实现简单的连接速率限制,例如限制单 IP 每分钟最多新建 30 个连接。这种网络层的防护能在恶意流量抵达数据库之前就进行拦截,与 PostgreSQL 自身的连接限制形成纵深防御。

实时监控与动态干预机制

连接数限制的配置不是一劳永逸的。你需要建立实时监控体系,当连接数达到阈值的 80% 时立即告警。pg_stat_activity 视图是排查连接占用的核心工具。以下查询可以快速统计各状态下的连接数和来源 IP:

SELECT state, count(*) 
FROM pg_stat_activity 
GROUP BY state;

SELECT client_addr, count(*) 
FROM pg_stat_activity 
WHERE client_addr IS NOT NULL 
GROUP BY client_addr 
ORDER BY count(*) DESC;

一旦发现某个 IP 的连接数异常高,可以使用 pg_terminate_backend 函数批量终止该 IP 的所有连接。编写一个简单的自动化脚本,当检测到单 IP 连接数超过阈值时自动执行清理,能在人工介入前就压制住攻击流量。这种自愈机制是应对 CC 攻击最有效的手段之一。

连接限制与业务连续性的平衡艺术

严格的连接限制必然会牺牲部分并发能力,但这是保证核心业务可用的必要代价。你需要接受“宁可拒绝部分请求,也不能让整个系统崩溃”的设计哲学。在 PostgreSQL 15 及之后的版本中,增加了连接抢占和更好的资源隔离特性,建议尽量升级到较新版本。同时,定期进行故障演练,模拟连接数打满的场景,验证告警、自动干预和恢复流程的有效性。只有经过实战检验的配置,才能在真正的攻击中保护你的数据库。