Hyperf 默认不支持多租户数据源动态切换,因 DbConnection 在容器启动时即初始化并绑定到 db,无法感知请求上下文中的租户标识;必须替换为继承 Connection 的代理类 TenantDbConnection,在调用时按 tenant_id 动态解析对应数据源。
Hyperf 默认不支持多租户数据源动态切换,必须自己接管的创建和路由逻辑,否则所有请求都会走默认配置的连接。
为什么不能直接用
的默认实例
Hyperf 的
在容器启动时就完成初始化,并绑定到
这个 ID 上;它不会感知请求上下文里的租户标识,也不会在每次查询前重新解析数据库配置。一旦你注册了多个数据源(比如
、
),它们只是“存在”,但框架仍只会用
对应的那个连接。
现象:你在中间件里设置了
,但
依然查的是主库
根本原因:DB facade 底层调用的是容器中固定的
实例,不是按需构造的
绕不开的点:必须替换掉容器中
的原始定义,换成一个能根据租户动态选源的代理类
如何实现运行时数据源路由代理
核心是写一个继承自
的子类,在构造时延迟加载真实连接,并把租户 ID 从
或
中取出来。不要试图在中间件里“切换”已存在的连接,那是无效的。
Hyperf 3.1.66
本页面提供企业级 PHP 协程框架 Hyperf 3.1.66 版本的官方源码下载与完整更新日志。重点解析 v3.1.66 版本中新增的 gRPC 多客户端负载均衡支持、Pool 连接池全量刷新、Guzzle 持久化 Cookie 以及数据库 JSON 包含键查询等核心优化特性。
下载
步骤一:定义一个
类,重写
和关键方法(如
、
),内部通过
拿当前租户的真实连接
步骤二:在
中覆盖原
绑定:
步骤三:在
构造函数里不立即初始化 PDO,而是缓存
,并在首次调用查询方法时才调用
去容器里 get 对应的
注意:每个租户的数据源必须提前在
里声明,例如
,否则
会失败
租户标识从哪来?别依赖全局变量或静态属性
Hyperf 是协程环境,
、全局变量、静态属性都可能跨请求污染。必须用协程安全的方式传递租户上下文。
推荐方案:在网关或第一个中间件里,从 header(如
)、域名(
)或 JWT payload 解析出
,然后存入
:
禁止做法:用
缓存,或在
里直接读
—— 协程切换后值就不可靠
验证方式:在
里加日志,确认每次拿到的
和当前请求一致
性能与事务边界容易被忽略的点
动态切换连接本身开销不大,但如果你在一次请求里混用多个租户的查询,或者在一个事务里跨租户操作,就会出问题。
事务无法跨数据源:
只对单个
生效,切到另一个租户连接后,事务上下文就断了
连接池复用失效:每个租户连接都是独立的
实例,对应独立的连接池,要注意
配置是否足够
缓存键没隔离:如果用了
,记得把
加进 cache key,否则 A 租户查的数据可能被 B 租户命中
最麻烦的情况:队列任务里没有
,必须显式传入
并手动 set 到 Context,否则后台任务永远走默认库
DbConnectionHyperf\Database\ConnectionDbConnectiondbdb.tenant_adb.tenant_bdbtenant_id = 'a'Db::select()DbConnectionHyperf\Database\ConnectionHyperf\Database\ConnectionRequestContextApplicationContextTenantDbConnection__call()select()table()$this->getConnection()dependencies.phpHyperf\Database\Connectionreturn [
Hyperf\Database\Connection::class => \App\Tenant\TenantDbConnection::class,
];TenantDbConnection$tenantId$this->resolveConnection()db.tenant_xconfig/autoload/databases.php'db.tenant_1' => [/* config */]make()$_SERVERX-Tenant-IDtenant-a.example.comtenant_idCoroutineContextuse Hyperf\Context\Context;
Context::set('tenant_id', $tenantId);static $tenantId__construct()$_SERVERTenantDbConnection::getConnection()tenant_idDb::transaction()ConnectionConnectionmax_connectionsDb::table()->cache()tenant_idRequestContexttenant_id