hyperf 多租户动态数据库连接通过绕过配置文件限制、运行时按 tenant_id 创建独立 pdo 实例并交由 database 组件管理实现,核心是连接工厂 + 租户上下文 + 连接池隔离协同。

Hyperf 多租户场景下动态创建数据库连接,核心在于**绕过配置文件预定义限制,按租户 ID 在运行时生成独立的 PDO 实例,并交由 Hyperf 的 Database 组件管理**。不依赖多组静态配置,而是用“连接工厂 + 租户上下文 + 连接池隔离”三者协同实现。
1. 设计租户识别与上下文传递
在请求进入时(如通过中间件或全局事件),从请求头、域名、子路径等提取 tenant_id,并存入 CoroutineContext 或自定义的 TenantContext 中:
- 推荐使用
Hyperf\Context\Context::set()存储当前租户标识,确保协程安全 - 避免用全局变量或静态属性,防止跨协程污染
- 可结合
Hyperf\HttpServer\Contract\RequestInterface提取租户信息,例如$request->header('X-Tenant-ID')
2. 构建动态连接工厂
继承或组合 Hyperf\Database\ConnectionResolver,重写 connection() 方法,使其支持传入租户参数并返回对应连接:
- 连接信息(host、database、username 等)从租户元数据表(如
tenants表)中实时查询,而非硬编码 - 每个租户连接使用唯一 name(如
"mysql_tenant_123"),避免连接复用冲突 - 首次调用时创建连接并注册到
ConnectionResolver,后续复用已注册连接 - 注意设置
'prefix' => "tenant_123_"(如需表前缀隔离)或切换 database 名
3. 替换默认 DB Facade 行为
Hyperf 的 DB Facade 默认调用 ConnectionResolver::connection(),需将其代理逻辑改为:先读取当前租户上下文,再调用动态工厂获取连接:
- 可通过
DI容器绑定自定义ConnectionResolverInterface实现类 - 或在模型基类(
Model)中重写getConnection(),根据TenantContext::getTenantId()动态指定连接名 - 关键点:所有 Eloquent 查询、Query Builder 操作都必须走这个动态解析链路
4. 连接池与生命周期管理
Hyperf 的连接池默认按连接名隔离,因此每个租户连接天然独占一个池子。但要注意:
- 在
config/autoload/databases.php中预留基础配置模板(如mysql_tenant_*使用通配符配置),或完全跳过配置,纯代码注册 - 连接不可长期缓存——租户库可能被删除/迁移,建议对连接做轻量健康检查(如执行
SELECT 1)或设置max_idle_time - 若租户量极大(如万级),考虑连接懒加载 + LRU 缓存连接实例,避免内存膨胀
不复杂但容易忽略:务必在每次请求结束时清理租户上下文,防止协程复用导致上下文错乱;同时确保 migration、seed 等命令行操作走默认连接,避开租户逻辑。











