ThinkPHP6多租户SaaS系统架构实战:动态数据库切换方案
一、多租户架构设计模式
三种主流多租户实现方案对比:
方案类型 | 优点 | 缺点 |
---|---|---|
独立数据库 | 数据完全隔离,安全性高 | 运维成本高 |
共享数据库独立Schema | 平衡隔离与成本 | 需要数据库权限 |
共享数据表 | 成本最低 | 隔离性差 |
本文采用独立数据库+动态切换方案
二、核心实现方案
1. 数据库配置动态化
// config/database.php return [ 'default' => env('database.default', 'mysql'), 'connections' => [ 'mysql' => [ 'type' => 'mysql', 'hostname' => env('database.hostname', '127.0.0.1'), 'database' => env('database.database', ''), 'username' => env('database.username', 'root'), 'password' => env('database.password', ''), // 其他公共配置... ], // 租户数据库模板 'tenant_template' => [ 'type' => 'mysql', 'hostname' => env('tenant.db_host', '127.0.0.1'), 'username' => env('tenant.db_user', 'tenant_user'), 'password' => env('tenant.db_pass', ''), 'charset' => 'utf8mb4', 'prefix' => '', ] ] ];
2. 租户识别中间件
// app/middleware/Tenancy.php class Tenancy { public function handle($request, Closure $next) { // 从域名或请求头获取租户标识 $tenantId = $this->getTenantId($request); // 初始化租户上下文 TenantContext::init($tenantId); // 动态切换数据库 $this->switchDatabase($tenantId); return $next($request); } protected function switchDatabase($tenantId) { // 获取租户专属数据库名 $dbName = 'tenant_' . $tenantId; // 动态修改配置 config([ 'database.connections.tenant' => array_merge( config('database.connections.tenant_template'), ['database' => $dbName] ), 'database.default' => 'tenant' ]); // 重新连接数据库 Db::connect('tenant')->reconnect(); } }
3. 租户上下文管理
// app/common/TenantContext.php class TenantContext { protected static $tenantId; public static function init($tenantId) { self::$tenantId = $tenantId; app()->bind('tenant', function() use ($tenantId) { return TenantModel::find($tenantId); }); } public static function getTenantId() { return self::$tenantId; } public static function getCurrent() { return app('tenant'); } }
4. 租户数据范围控制
// app/model/BaseModel.php class BaseModel extends Model { protected static function boot() { parent::boot(); // 自动限定租户数据范围 static::addGlobalScope('tenant', function($builder) { if ($tenantId = TenantContext::getTenantId()) { $builder->where('tenant_id', $tenantId); } }); } }
三、关键问题解决方案
1. 租户数据库自动化创建
// 使用事件监听器自动建库 Event::listen('TenantCreated', function($tenant) { $dbName = 'tenant_' . $tenant->id; // 使用主连接创建数据库 Db::connect('mysql')->execute( "CREATE DATABASE IF NOT EXISTS `{$dbName}` CHARACTER SET utf8mb4" ); // 执行基础SQL脚本 $sql = file_get_contents(app_path('database/tenant_template.sql')); Db::connect('tenant')->execute($sql); });
2. 跨租户数据迁移工具
class TenantDataMigrator { public function migrate($fromTenant, $toTenant) { $tables = ['users', 'products', 'orders']; foreach ($tables as $table) { $this->migrateTable($fromTenant, $toTenant, $table); } } protected function migrateTable($from, $to, $table) { $data = Db::connect("tenant_$from") ->table($table) ->get() ->toArray(); Db::connect("tenant_$to") ->table($table) ->insert($data); } }
四、性能优化建议
- 连接池管理:使用Hyperf数据库连接池
- 配置缓存:缓存租户数据库配置
- 查询优化:避免跨库JOIN操作
- 分库策略:按租户活跃度分组部署