连接是数据库的入口,入口堵了,里面再快也没用。很多系统用 Dedicated Server 模式(每个连接一个服务器进程),这在连接数少的时候没问题,但当连接数飙到几千时,OS 进程数耗尽,内存耗尽,系统直接雪崩。Oracle 提供了 Shared Server 和 DRCP(Database Resident Connection Pool)来解决高并发短连接问题。这篇文章把三种连接模式的原理、适用场景、以及一个"连接风暴搞垮系统"的真实案例拆清楚。
1 Dedicated Server:简单直接,但资源消耗大
Dedicated 模式下,每个客户端连接对应一个服务器进程(shadow process)。连接建立时,Listener fork 一个进程,专门服务这个连接,直到连接断开。优点是简单、稳定、隔离性好;缺点是进程和内存开销大。每个服务器进程占用 3-10MB PGA(甚至更多),1000 个连接就是 3-10GB PGA。而且 OS 对进程数有限制(Oracle 用户 ulimit -u),超过就报 ORA-00020(maximum number of processes exceeded)。
连接风暴(Connection Storm)通常发生在应用层没有连接池,或者连接池配置错误(比如最大连接数设了 500,但应用开了 5000 个线程)。每个线程一个连接,瞬间把数据库进程数打满。新的连接进不来,报 ORA-12519(TNS:no appropriate service handler found)或 ORA-00020。更惨的是,有些应用在报错后重试,越重试连接越多,恶性循环。
2 Shared Server:古老但有效的共享模式
Shared Server 模式下,Listener 把连接交给 Dispatcher 进程,Dispatcher 把请求放入请求队列(Request Queue),多个 Shared Server 进程从队列里取请求处理,结果放入响应队列(Response Queue),Dispatcher 再返回给客户端。这样 1000 个连接可能只需要 50 个 Shared Server 进程,大大节省资源。
但 Shared Server 有局限:不适合长连接、大事务、或需要大量 PGA 的操作(比如排序、PL/SQL 集合)。因为 Shared Server 进程是共享的,一个会话占着进程不放,其他会话就等着。而且有些功能不支持 Shared Server,比如 RMAN、某些高级队列操作。所以 Shared Server 在现代架构里用得少了,被 DRCP 取代。
3 DRCP:11g 给的答案,适合短连接高并发
DRCP(Database Resident Connection Pool)是 11g 引入的,专门为短连接设计(比如 PHP 应用,每个页面请求一个连接,请求完就断)。DRCP 在数据库端维护一个连接池,应用连接进来时,从池里分配一个服务器进程;连接断开时,进程回收到池里,不销毁。这样应用看到的仍然是"连接-断开"模式,但数据库端进程数是固定的(比如最多 100 个),不受应用连接数影响。
DRCP 的配置很简单:
-- 启动 DRCP
EXEC DBMS_CONNECTION_POOL.START_POOL();
-- 配置参数
EXEC DBMS_CONNECTION_POOL.CONFIGURE_POOL(
pool_name => 'SYS_DEFAULT_CONNECTION_POOL',
minsize => 10,
maxsize => 100,
incrsize => 5,
session_cached_cursors => 20
);
应用连接串要加 (SERVER=POOLED):
jdbc:oracle:thin:@//host:1521/service_name:POOLED
4 实验:三种模式下的连接风暴对比
实验环境:Oracle 19c,8G 内存,PROCESS=300。用 JMeter 模拟 1000 并发短连接,每个连接执行 SELECT 1 FROM DUAL 后断开。
-- 模式 1:Dedicated Server
-- 默认模式,不做配置
-- 预期:1000 个并发连接,需要 1000 个服务器进程
-- 监控:SELECT COUNT(*) FROM v$process WHERE pname IS NULL; -- 服务器进程数
-- 结果:进程数飙到 1000,PGA 占用 4GB,OS 负载极高,部分连接超时
-- 模式 2:Shared Server
ALTER SYSTEM SET SHARED_SERVERS=50 SCOPE=BOTH;
ALTER SYSTEM SET DISPATCHERS='(PROTOCOL=TCP)(DISPATCHERS=5)' SCOPE=BOTH;
-- 应用连接串不变(通过 service 配置)
-- 预期:服务器进程约 50 个,内存省很多
-- 但短连接频繁分配和释放,Dispatcher 可能成为瓶颈
-- 模式 3:DRCP
EXEC DBMS_CONNECTION_POOL.START_POOL();
EXEC DBMS_CONNECTION_POOL.CONFIGURE_POOL(minsize=>20, maxsize=>100);
-- 应用连接串加 :POOLED
-- 预期:服务器进程稳定在 100 个以内,内存占用 <1GB
-- 连接建立时间比 Dedicated 略长(池分配开销),但并发能力大幅提升
实验结果:Dedicated 模式下,1000 并发时 v$process 达到 1000,PGA 总和 4.2GB,系统 load average 涨到 80,200 多个连接超时失败。Shared Server 模式下,shared server 进程 50 个,内存占用 800MB,但 Dispatcher 队列等待严重,平均响应时间 3 秒(因为请求在队列里排队)。DRCP 模式下,服务器进程稳定在 100 个,PGA 总和 900MB,1000 并发全部成功,平均响应时间 0.8 秒。
5 那个被 PHP 搞垮的案例
客户是一个电商平台,前端用 PHP,每个页面请求都新建 Oracle 连接,请求完立即关闭。大促时并发 3000,数据库 PROCESS 设了 1000,瞬间打满。应用报错后 PHP 脚本重试,连接数越积越多,最后数据库连 sys 都登不进去,只能重启。我们当时给了两个方案:一是 PHP 端用 pconnect(持久连接),但 PHP 的 pconnect 有 bug,连接不释放,导致会话数累积;二是数据库端开 DRCP,maxsize 设 150。最后选了 DRCP,PHP 连接串加 :POOLED,大促时 3000 并发稳如狗,数据库进程数始终 150 以内。
【踩坑笔记】DRCP 不适合长连接(比如连接保持几小时的报表工具),因为 DRCP 会定期回收连接,长连接可能被强制断开。DRCP 也不适合需要频繁切换 schema 的应用(因为连接池里的进程可能带着上一个会话的状态)。另外,PROCESSES 参数要按 DRCP 的 maxsize + 后台进程数 + 余量来设,别按应用并发数设。
6 总结:连接管理的三条军规
第一,应用必须有连接池(HikariCP、Druid、WebLogic 连接池),别让每个请求都新建连接。第二,如果应用端没法改(比如大量遗留 PHP/Java 应用),数据库端开 DRCP,把连接风暴挡在门外。第三,监控 v$session 和 v$process 的增长趋势,连接数 5 分钟内涨 50% 立即告警。最后送一句话:数据库是餐厅,连接是顾客,Dedicated 是每人一个服务员,DRCP 是共享服务员。顾客太多时,别让服务员先累死。