第一章:Go数据库连接池并发适配的核心矛盾与观测视角
Go 应用在高并发场景下常遭遇数据库连接池资源耗尽、响应延迟陡增或连接复用异常等问题。其根源并非单纯配置调优,而是底层运行时模型与数据库协议语义之间的结构性张力:database/sql 的连接池采用懒加载 + LRU 驱逐策略,而 Go 的 goroutine 调度器以轻量级、高密度为设计目标,二者在“连接生命周期”“空闲连接回收”“错误连接清理”三个维度存在天然错位。
连接生命周期的语义鸿沟
当 sql.DB.SetMaxOpenConns(20) 时,Go 并不保证最多仅创建 20 个物理连接——它仅限制同时处于 active 状态的连接数。若事务未显式提交/回滚、或 defer rows.Close() 被遗漏,连接将长期滞留于 used 状态,导致池中可用连接迅速枯竭。可通过以下方式观测真实占用:
# 查看当前活跃连接(以 PostgreSQL 为例)
psql -c "SELECT pid, state, query FROM pg_stat_activity WHERE state = 'active';"
空闲连接回收的时序陷阱
sql.DB.SetMaxIdleConns(10) 和 sql.DB.SetConnMaxLifetime(30 * time.Minute) 共同作用,但 ConnMaxLifetime 仅对已归还至空闲队列的连接生效;若连接正被 goroutine 持有(即使无实际 I/O),该计时器不会启动。这意味着连接可能长期驻留内存却无法被复用或驱逐。
错误连接清理的静默失效
当网络闪断或数据库重启后,部分空闲连接仍保留在池中,但下次 db.Query() 时会触发隐式重连失败,返回 driver: bad connection。默认情况下,database/sql 不主动探测空闲连接健康状态。启用连接验证需显式配置:
db, _ := sql.Open("postgres", dsn)
db.SetConnMaxIdleTime(5 * time.Minute) // Go 1.15+ 引入,替代旧版 ConnMaxLifetime 对空闲连接的约束
// 注意:需配合驱动支持(如 pgx/v5 已原生实现 PingContext on idle check)
| 观测维度 | 推荐工具/方法 | 关键指标 |
|---|---|---|
| 连接分配速率 | db.Stats().OpenConnections |
持续高于 MaxOpenConns 表明泄漏 |
| 空闲连接堆积 | db.Stats().IdleConnections |
长期 > MaxIdleConns 且无新请求 |
| 连接错误率 | 捕获 driver.ErrBadConn 出现频次 |
突增通常指向网络或 DB 端异常 |
第二章:pgx/v5连接池参数底层机制解析
2.1 maxOpen与连接生命周期的并发语义映射
maxOpen 并非简单限制连接总数,而是定义了连接池在并发请求压力下可维持的活跃连接上限,其语义需与连接创建、复用、回收的生命周期深度耦合。
连接状态流转关键点
- 创建:仅当空闲连接不足且未达
maxOpen时触发同步新建(阻塞调用) - 复用:从空闲队列原子获取,成功则延长租约(
leaseTime) - 回收:归还时校验是否超时或失效,决定丢弃或重置
// HikariCP 源码片段:获取连接的核心并发控制
final long timeout = unit.toNanos(connectionTimeout);
while (poolState == RUNNING && !isConnectionAlive(connection)) {
if (getActiveConnections() >= config.getMaxPoolSize()) { // 关键阈值判断
connection = null; // 拒绝新租约
break;
}
}
此处
getActiveConnections()是原子计数器,反映瞬时活跃连接数,而非已创建总数;config.getMaxPoolSize()即maxOpen的运行时表示,直接参与 CAS 决策,确保线程安全下的状态一致性。
并发语义映射表
| 生命周期阶段 | 并发操作 | maxOpen 约束作用点 |
|---|---|---|
| 获取连接 | 多线程竞争空闲队列 | 阻止超出阈值的新连接创建 |
| 归还连接 | 异步回调执行 | 触发空闲连接清理与容量再评估 |
graph TD
A[线程请求连接] --> B{active < maxOpen?}
B -->|Yes| C[分配空闲连接或新建]
B -->|No| D[阻塞/超时失败]
C --> E[连接使用中]
E --> F[归还连接]
F --> G[更新active计数]
2.2 maxIdle对空闲连接复用率的实测影响建模
在高并发连接池场景下,maxIdle 参数直接约束空闲连接保留在池中的上限,进而影响连接复用概率。我们通过压测工具模拟每秒500次请求,固定 maxTotal=100,仅调节 maxIdle 值并采集30秒内实际复用次数。
实验参数配置
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxTotal(100); // 总连接上限
config.setMaxIdle(20); // 当前测试值:20 → 50 → 80
config.setMinIdle(0); // 禁用保底空闲连接
config.setBlockWhenExhausted(true);
逻辑分析:setMaxIdle(20) 意味着即使有足够空闲物理连接,池也仅保留最多20个;超出部分将被逐出,导致后续请求被迫新建连接,降低复用率。
复用率对比(固定负载下)
| maxIdle | 平均复用率 | 连接创建频次(次/s) |
|---|---|---|
| 20 | 63.2% | 4.8 |
| 50 | 89.7% | 1.1 |
| 80 | 92.1% | 0.9 |
复用行为决策流
graph TD
A[请求获取连接] --> B{池中是否有空闲连接?}
B -->|是| C[返回空闲连接]
B -->|否| D[创建新连接或阻塞]
C --> E{空闲连接数 ≥ maxIdle?}
E -->|是| F[关闭最久未用连接]
E -->|否| G[归还至空闲队列]
可见,maxIdle 并非越高越好——过高的值会增加内存驻留开销,而80已趋近复用率收敛点。
2.3 连接获取阻塞时间与goroutine调度延迟的耦合分析
当连接池 Get() 调用遭遇空闲连接耗尽时,goroutine 会进入 select 阻塞等待 pool.queue 或超时通道。此时其状态从 running 切换为 waiting,需经调度器重新唤醒。
阻塞与调度的临界耦合点
select {
case conn := <-pool.queue: // 唤醒后需重新抢占 P,存在调度延迟
return conn, nil
case <-time.After(pool.timeout): // 超时路径绕过调度竞争
return nil, ErrTimeout
}
逻辑分析:<-pool.queue 是同步 channel 操作;若 sender 尚未就绪,当前 goroutine 被挂起并加入 waitq,唤醒时刻受 P 可用性、全局队列迁移开销影响。pool.timeout 默认 30s,但高负载下实际阻塞时间 = 网络等待 + 调度延迟(通常 100μs–2ms)。
关键影响因子对比
| 因子 | 对阻塞时间影响 | 对调度延迟影响 |
|---|---|---|
| P 数量不足 | 无直接影响 | 显著增加(需 steal) |
| 全局运行队列积压 | 延长唤醒等待 | 直接拉高延迟 |
GOMAXPROCS 设置 |
间接(限制并发 P) | 决定调度器吞吐上限 |
graph TD A[Get() 调用] –> B{pool.queue 是否有可用 conn?} B –>|是| C[立即返回 conn] B –>|否| D[goroutine park 并入 waitq] D –> E[sender 发送 conn 或 timeout 触发] E –> F[调度器唤醒 G 并分配 P] F –> G[继续执行]
2.4 pgx/v5连接复用路径中TLS握手与认证开销的量化验证
实验环境与测量方法
使用 pgx/v5 的 pgconn.Config 配置启用 KeepAlive 与 TLSConfig,通过 runtime/pprof 采集 TLS handshake 耗时及 auth 阶段 CPU 时间戳。
关键代码片段
cfg := pgx.ConnConfig{
Host: "db.example.com",
Port: 5432,
User: "app",
Password: "secret",
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
// 禁用证书验证仅用于基准测试(生产禁用)
InsecureSkipVerify: true,
},
}
该配置强制每次新连接执行完整 TLS 握手;InsecureSkipVerify: true 消除证书链验证开销,聚焦于密钥交换与会话协商阶段。
开销对比数据
| 场景 | 平均 TLS 耗时(ms) | 认证延迟(μs) |
|---|---|---|
| 首连(无复用) | 18.7 | 24,300 |
| 连接池复用(同一Conn) | 0.0 | 120 |
复用路径流程
graph TD
A[pgx.Pool.Acquire] --> B{Conn in pool?}
B -->|Yes| C[TLS session resumption]
B -->|No| D[Full TLS handshake + SCRAM-SHA-256]
C --> E[Zero-Round-Trip auth]
D --> E
复用连接跳过证书校验与密钥交换,认证降为单次 SASL token 验证。
2.5 连接泄漏检测阈值与并发峰值下的池状态漂移现象
连接池在高并发场景下易因泄漏检测机制与真实负载节奏错配,引发状态漂移——即活跃连接数、空闲连接数与实际业务请求量呈现非线性偏移。
检测阈值与漂移的耦合关系
当 leakDetectionThreshold=30000(毫秒)时,若平均请求耗时波动剧烈(如从80ms突增至1200ms),泄漏探测器可能误判正常长事务为泄漏,触发过早回收。
// HikariCP 配置片段:泄漏检测阈值与超时协同配置
HikariConfig config = new HikariConfig();
config.setLeakDetectionThreshold(60_000); // 建议 ≥ P99 请求耗时 × 2
config.setConnectionTimeout(5_000);
config.setMaxLifetime(1800_000); // 避免因 maxLifetime 截断导致的伪泄漏
逻辑分析:
leakDetectionThreshold应显著高于服务P99响应时间,否则在突发慢查询期间,未归还连接被反复标记为泄漏;maxLifetime若设置过短(如60s),会强制关闭仍被应用线程持有的连接,制造“假泄漏”日志,干扰真实泄漏定位。
并发峰值下的池状态漂移表现
| 状态指标 | 峰值前(稳态) | 峰值中(1k TPS) | 漂移成因 |
|---|---|---|---|
| activeCount | 12 | 47 | 连接复用率下降 + 泄漏误判回收 |
| idleCount | 8 | 2 | 回收激进 + 新建延迟 |
| totalConnections | 20 | 49 | 自适应扩缩滞后 |
漂移演化路径
graph TD
A[并发突增] --> B[请求排队加剧]
B --> C[连接持有时间延长]
C --> D[leakDetectionThreshold 被频繁触发]
D --> E[连接被强制标记并销毁]
E --> F[池主动新建连接补偿]
F --> G[active/idle 比例失衡 → 状态漂移]
第三章:高并发场景下连接池参数匹配的工程实践法则
3.1 基于QPS与平均查询耗时的maxOpen理论推导公式
数据库连接池的 maxOpen 并非经验配置,而是可由负载特征严格推导的关键参数。
核心约束条件
在稳态下,连接池需满足:
- 每秒并发请求数(QPS) × 单次查询平均耗时(
avg_ms,单位:秒) ≤maxOpen
即:
$$ \text{maxOpen} \geq \text{QPS} \times \frac{\text{avg_ms}}{1000} $$
推导验证示例
假设系统 QPS = 120,平均查询耗时为 85 ms:
qps = 120
avg_ms = 85
min_max_open = qps * avg_ms / 1000 # ≈ 10.2 → 向上取整为 11
print(f"理论最小maxOpen: {min_max_open:.2f} → 实际设为 {int(min_max_open) + 1}")
逻辑分析:
qps * avg_ms / 1000将毫秒转为秒,得到瞬时并发连接数期望值;因连接不可分割且需冗余缓冲,结果向上取整并加安全裕量(+1)。参数avg_ms应取 P95 值,避免尾部延迟导致连接耗尽。
关键影响因子对比
| 因子 | 影响方向 | 说明 |
|---|---|---|
| QPS 提升 20% | 线性增加 maxOpen 需求 |
流量突增时首当其冲 |
avg_ms 从 50→200ms |
maxOpen 需翻两番 |
低效 SQL 比高并发更易击穿连接池 |
graph TD
A[QPS] --> C[并发连接需求]
B[Avg Query Time] --> C
C --> D[maxOpen ≥ QPS × avg_s]
D --> E[连接复用率优化]
3.2 maxIdle与maxOpen比值在不同负载曲线下的最优区间实证
负载敏感性验证实验设计
在阶梯式(Step)、脉冲式(Burst)和稳态(Steady)三类负载下,固定 maxOpen=50,扫描 maxIdle 从 5 到 45(步长 5),采集连接池平均等待时间与拒绝率。
关键阈值观测结果
| 负载类型 | 最优 maxIdle/maxOpen 区间 |
对应拒绝率峰值下降幅度 |
|---|---|---|
| Steady | 0.6–0.8 | ↓72% |
| Step | 0.5–0.7 | ↓64% |
| Burst | 0.7–0.9 | ↓51%(需更高冗余缓冲) |
生产配置推荐逻辑
// Spring Boot + HikariCP 动态调优片段
if (loadProfile.equals("BURST")) {
hikariConfig.setMaximumPoolSize(50);
hikariConfig.setIdleTimeout(30_000); // 避免过早回收
hikariConfig.setConnectionInitSql("/*+ MAX_IDLE_RATIO=0.85 */"); // 注释仅作语义标记
}
该配置将 maxIdle 设为 42(≈0.85×50),适配突发流量下连接预热与复用平衡;idleTimeout 延长保障空闲连接存活,避免高频重建开销。
比值失效边界现象
当 maxIdle/maxOpen < 0.4 时,所有负载下均出现连接争抢加剧;>0.9 后内存占用线性上升但吞吐无增益——证实存在收益拐点。
3.3 连接池饱和态下goroutine排队长度与P99延迟的非线性关系
当连接池容量固定(如 MaxOpen=10),并发请求超过可用连接时,新请求进入阻塞队列。此时 goroutine 排队长度 $Q$ 与 P99 延迟并非线性增长——初期缓存局部性与调度器批处理掩盖延迟,但越过临界点后发生级联等待。
排队模型示意
// 模拟连接获取超时逻辑(单位:ms)
db.SetConnMaxLifetime(30 * time.Second)
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
// 当前排队 goroutine 数 = db.Stats().WaitCount - 上一采样点值
WaitCount 是原子累加计数器,反映因连接耗尽而阻塞的 goroutine 总数;需配合 WaitDuration 才能还原真实排队耗时分布。
非线性拐点现象
| 排队长度 Q | P99 延迟(ms) | 增长因子 |
|---|---|---|
| 1–3 | 12–18 | ≈1.0× |
| 4–8 | 35–72 | ≈3.2× |
| ≥9 | 210+ | ≥12× |
调度放大效应
graph TD
A[goroutine 请求 DB] --> B{连接可用?}
B -- 是 --> C[执行 SQL]
B -- 否 --> D[加入 sync.Pool 等待队列]
D --> E[netpoll 唤醒 + GMP 调度重入]
E --> F[实际执行延迟含调度抖动]
关键在于:Goroutine 唤醒后仍需竞争 M/P,导致排队长度每增加 1,P99 延迟呈指数级抬升——尤其在 runtime 调度器负载 >70% 时。
第四章:真实业务压测中的参数调优闭环方法论
4.1 使用pprof+pg_stat_activity联合定位连接瓶颈的诊断流程
场景切入
当 PostgreSQL 出现连接数陡增、idle in transaction 比例升高或 backend_start 集中老化时,需区分是应用层连接泄漏,还是数据库侧锁等待/长事务阻塞。
联动采集策略
- 在应用节点运行
go tool pprof -http=:8080 http://app:6060/debug/pprof/goroutine?debug=2,捕获 goroutine 栈快照; - 同时在数据库执行:
SELECT pid, usename, application_name, backend_start, state,
now() - backend_start AS uptime,
state_change,
now() - state_change AS state_duration,
wait_event_type, wait_event
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY state_duration DESC LIMIT 10;
此查询聚焦活跃非空闲会话,按状态持续时间倒序,精准暴露卡在
ClientRead、Lock或WalWrite的后端。state_duration揭示阻塞时长,wait_event直接指向底层等待类型(如Lock对应行级锁争用)。
关联分析路径
graph TD
A[pprof goroutine] -->|发现大量阻塞在 db.Query| B[匹配 pid]
B --> C[查 pg_stat_activity 中对应 pid 的 wait_event]
C --> D{wait_event = 'Lock'?}
D -->|Yes| E[结合 pg_locks 定位 blocker]
D -->|No| F[检查 application_name + idle_in_transaction]
典型瓶颈对照表
| wait_event_type | wait_event | 常见根因 |
|---|---|---|
Lock |
relation |
DDL 未提交或长事务持有锁 |
Client |
ClientRead |
应用未读完结果集或超时未关闭 |
IO |
DataFileRead |
磁盘 I/O 瓶颈或索引缺失 |
4.2 混合读写负载下连接复用率与事务隔离级别的交叉影响实验
在高并发混合负载场景中,连接池复用率(reuse_ratio = active_time / total_lifetime)与事务隔离级别形成强耦合效应。低隔离级别(如 READ COMMITTED)缩短锁持有时间,提升连接释放速度,从而推高复用率;而 SERIALIZABLE 显著延长事务窗口,导致连接阻塞堆积。
实验关键观测指标
- 连接平均复用次数(per-connection)
- 事务平均等待时长(ms)
- 死锁发生频次(/min)
配置对比表
| 隔离级别 | 平均复用率 | P95等待延迟 | 死锁率 |
|---|---|---|---|
| READ UNCOMMITTED | 8.2 | 12 ms | 0.03% |
| REPEATABLE READ | 4.7 | 41 ms | 1.2% |
| SERIALIZABLE | 2.1 | 186 ms | 4.8% |
-- 设置会话级隔离级别并启用连接生命周期追踪
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT pg_backend_pid(), now() AS acquired_at;
-- 注:acquired_at 用于计算连接实际复用间隔
该语句触发 PostgreSQL 后端进程标识绑定,并打点连接获取时间戳,为复用率统计提供原子锚点;pg_backend_pid() 确保跨事务上下文的连接粒度可追溯。
复用瓶颈传导路径
graph TD
A[高写入比例] --> B[行锁升级]
B --> C[事务阻塞延长]
C --> D[连接释放延迟]
D --> E[复用率下降]
4.3 基于Prometheus指标构建连接池健康度SLO的监控告警体系
核心SLO指标定义
连接池健康度SLO聚焦三个维度:
- 可用性:
pool_up{job="app"} == 1(服务端点存活) - 饱和度:
rate(connection_acquire_failures_total[5m]) / rate(connection_acquires_total[5m]) < 0.01 - 响应时效:
histogram_quantile(0.95, sum(rate(connection_acquire_duration_seconds_bucket[5m])) by (le)) < 0.2
Prometheus采集配置示例
# scrape_config for connection pool metrics
- job_name: 'app-pool'
static_configs:
- targets: ['app-service:8080']
metrics_path: '/actuator/prometheus' # Spring Boot Actuator endpoint
此配置启用对Spring Boot应用暴露的
/actuator/prometheus端点轮询,自动抓取hikaricp_connections_active、hikaricp_connections_idle等标准指标。
SLO告警规则(Prometheus Rule)
| 告警名称 | 表达式 | 阈值 | 持续时间 |
|---|---|---|---|
PoolSaturationHigh |
rate(hikaricp_connections_acquire_failures_total[5m]) > 0.005 |
失败率 > 0.5% | 3m |
PoolLatencyP95Breached |
histogram_quantile(0.95, sum(rate(hikaricp_connections_acquire_duration_seconds_bucket[5m])) by (le)) > 0.3 |
P95 > 300ms | 2m |
告警分级与处置路径
graph TD
A[PoolSaturationHigh] --> B[检查DB负载与网络延迟]
A --> C[扩容连接池maxPoolSize]
D[PoolLatencyP95Breached] --> E[分析慢SQL或锁竞争]
D --> F[优化连接复用策略]
4.4 动态调整maxOpen/maxIdle的K8s HPA适配策略与灰度验证方案
核心挑战
数据库连接池参数(maxOpen/maxIdle)静态配置易导致资源争用或连接泄漏,需与HPA联动实现弹性伸缩。
自定义指标采集
通过Prometheus Exporter暴露连接池指标:
# prometheus-metrics-config.yaml
- job_name: 'db-pool'
static_configs:
- targets: ['app:9090']
labels:
app: 'order-service'
该配置使
jdbc_connections_active和jdbc_connections_idle成为HPA可引用的自定义指标源,单位为整数,精度达毫秒级采样。
HPA策略配置
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metrics:
- type: Pods
pods:
metric:
name: jdbc_connections_active
target:
type: AverageValue
averageValue: "80" # 触发扩容阈值:平均活跃连接数超80
灰度验证流程
- 阶段1:5%流量接入新HPA策略
- 阶段2:监控
connection_wait_time_msP95 ≤ 50ms - 阶段3:全量切换前执行连接池压测(JMeter并发500线程)
| 验证项 | 合格阈值 | 检测工具 |
|---|---|---|
| 连接复用率 | ≥ 92% | Grafana SQL |
| maxOpen动态误差 | ≤ ±3 | kubectl get hpa |
graph TD
A[HPA检测active连接数] --> B{>80?}
B -->|Yes| C[触发scale-up]
B -->|No| D[检查idle连接是否>maxIdle*0.7]
D -->|Yes| E[调用API动态缩减maxIdle]
第五章:从连接池到全链路资源协同的演进思考
连接池的边界与瓶颈
在某电商平台大促压测中,MySQL连接池(HikariCP)配置为 maxPoolSize=50,但监控显示平均活跃连接仅12,而下游Redis集群CPU持续超载。根源并非连接不足,而是业务线程在获取DB连接后,阻塞等待Redis响应长达320ms——连接池只管理“连接生命周期”,却无法感知下游服务的资源水位。
全链路资源视图的构建实践
团队在Spring Cloud微服务架构中集成Micrometer + Prometheus + Grafana,构建统一资源度量平面。关键改造包括:
- 在Feign Client拦截器中注入
ResourceContext,透传请求级资源配额标签(如tenant_id=finance,priority=L2); - 自定义
Resilience4jRateLimiter实现,依据实时Prometheus指标动态调整限流阈值; - 数据库代理层(ShardingSphere-Proxy)嵌入资源评分模块,对慢SQL自动降权并触发熔断。
跨组件协同调度机制
下表对比传统连接池与协同调度模式在秒杀场景的表现:
| 维度 | 传统连接池 | 全链路协同调度 |
|---|---|---|
| DB连接分配策略 | FIFO队列 | 基于下游Redis负载加权轮询 |
| 超时决策依据 | 固定timeout=3s | 动态计算:min(3s, Redis_p95_latency×2) |
| 故障隔离粒度 | 单实例连接失效 | 按租户+优先级熔断,保障核心订单链路 |
真实案例:支付网关资源再平衡
2023年双11期间,支付网关遭遇突发流量,原方案通过扩容DB连接池至200解决,但导致Redis连接数激增引发雪崩。新方案引入基于eBPF的内核级资源探测,在网关节点部署以下协同逻辑:
# resource-policy.yaml
rules:
- when: redis_cpu_usage > 85% && db_active_connections < 30
then: redirect_traffic_to_backup_db: true
action: throttle_non_critical_requests: "tenant!=vip"
协同调度的基础设施依赖
实现全链路协同需三类基础设施支撑:
- 可观测性底座:OpenTelemetry Collector统一采集JVM堆内存、网络FD、数据库连接池状态等17类指标;
- 控制面协议:采用gRPC+Protobuf定义
ResourceAdjustmentRequest消息,支持毫秒级策略下发; - 执行引擎:Envoy作为数据面代理,通过xDS API动态重写路由权重,实现实时流量整形。
技术债的转化路径
某金融系统迁移过程中,将原有Druid连接池的maxWaitMillis参数重构为ResourceConstraint对象,其字段包含:
public class ResourceConstraint {
String upstreamService; // "payment-service"
String downstreamDependency; // "redis-cluster-01"
double maxConcurrencyRatio; // 0.7 → 当下游负载>70%时触发降级
Duration gracePeriod; // 30s缓冲窗口避免抖动
}
验证效果的关键指标
在物流调度系统上线协同调度后,核心链路SLO达标率从92.3%提升至99.6%,具体提升体现在:
- DB连接复用率从68%→91%(减少空闲连接占用);
- 跨服务调用P99延迟下降41%(从890ms→525ms);
- 故障自愈平均耗时从4.2分钟缩短至23秒。
未被覆盖的协同盲区
当前方案仍无法处理异步消息场景:Kafka消费者组在消费积压时,无法联动调整上游HTTP服务的并发线程数。团队正基于Kafka AdminClient开发TopicLoadWatcher,计划通过Consumer Lag指标反向驱动Spring Boot线程池动态缩容。
