本文目录导读:

PHP 分库多租户的切换是实现多租户架构的核心问题,根据不同业务场景,主要有以下几种方案,我来详细说明:
核心方案概览
按租户ID分库(最常见)
class TenantDatabaseManager
{
private static $connections = [];
public static function getConnection(int $tenantId): PDO
{
// 根据租户ID计算数据库分片
$dbIndex = $tenantId % 10; // 假设分10个库
if (!isset(self::$connections[$dbIndex])) {
$config = [
'host' => "db_{$dbIndex}.example.com",
'dbname' => "tenant_db_{$dbIndex}",
'user' => "tenant_user_{$dbIndex}",
'pass' => "password_{$dbIndex}"
];
self::$connections[$dbIndex] = $this->createConnection($config);
}
return self::$connections[$dbIndex];
}
}
动态切换数据库连接
class MultiTenantService
{
private $connection;
private $currentTenant;
public function switchTenant(int $tenantId): void
{
$this->currentTenant = $tenantId;
$this->connection = $this->createTenantConnection($tenantId);
}
private function createTenantConnection(int $tenantId): PDO
{
$config = $this->getTenantConfig($tenantId);
$dsn = "mysql:host={$config['host']};dbname={$config['database']}";
$pdo = new PDO($dsn, $config['username'], $config['password']);
return $pdo;
}
}
高级实现方案
基于中间件的动态路由
class TenantRouter
{
private $tenantConfigs = [];
public function routeRequest(Request $request): void
{
$tenantId = $this->extractTenantId($request);
// 使用中间件动态选择数据库
$this->setActiveConnection($tenantId);
// 使用容器管理依赖注入
app()->bind(DB::class, function () use ($tenantId) {
return $this->getTenantConnection($tenantId);
});
}
private function extractTenantId(Request $request): int
{
// 从JWT、Session或Header中获取租户ID
return (int) $request->header('X-Tenant-ID');
}
}
Laravel框架实现(推荐)
// app/Providers/TenantServiceProvider.php
class TenantServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton('tenant.manager', function ($app) {
return new TenantManager();
});
}
public function boot()
{
// 通过中间件切换数据库
$this->app['router']->aliasMiddleware('tenant', TenantMiddleware::class);
}
}
// app/Http/Middleware/TenantMiddleware.php
class TenantMiddleware
{
public function handle($request, Closure $next)
{
$tenantId = $request->header('X-Tenant-ID', 1);
// 动态配置数据库连接
Config::set('database.connections.tenant', [
'driver' => 'mysql',
'host' => "db_" . ($tenantId % 10),
'database' => "tenant_db_" . $tenantId,
'username' => config('database.connections.mysql.username'),
'password' => config('database.connections.mysql.password'),
]);
// 切换默认连接
DB::setDefaultConnection('tenant');
return $next($request);
}
}
// 使用示例
class UserController extends Controller
{
public function index()
{
// 自动使用当前租户的数据库
return User::all();
}
}
进阶优化方案
连接池优化
class TenantConnectionPool
{
private static $pool = [];
private static $MAX_POOL_SIZE = 10;
public static function getConnection(int $tenantId): PDO
{
$key = "tenant_{$tenantId}";
if (!isset(self::$pool[$key]) || count(self::$pool[$key]) === 0) {
// 创建新连接
$connection = self::createConnection($tenantId);
} else {
// 从池中获取连接
$connection = array_pop(self::$pool[$key]);
}
return $connection;
}
public static function returnConnection(PDO $connection, int $tenantId): void
{
$key = "tenant_{$tenantId}";
if (!isset(self::$pool[$key])) {
self::$pool[$key] = [];
}
if (count(self::$pool[$key]) < self::$MAX_POOL_SIZE) {
self::$pool[$key][] = $connection;
}
}
}
缓存机制
class TenantConfigCache
{
private static $cache = [];
private static $ttl = 3600; // 1小时
public static function getTenantConfig(int $tenantId): array
{
$cacheKey = "tenant_config_{$tenantId}";
if (!isset(self::$cache[$cacheKey]) ||
time() - self::$cache[$cacheKey]['time'] > self::$ttl) {
// 从配置中心获取
self::$cache[$cacheKey] = [
'time' => time(),
'config' => self::loadFromConfigCenter($tenantId)
];
}
return self::$cache[$cacheKey]['config'];
}
}
批量操作处理
// 批量处理不同租户的数据
class TenantBatchProcessor
{
public function processAllTenants(callable $callback)
{
$tenantIds = $this->getAllTenantIds();
foreach ($tenantIds as $tenantId) {
$this->switchTenant($tenantId);
try {
$result = $callback($tenantId);
$this->logSuccess($tenantId);
} catch (\Exception $e) {
$this->logError($tenantId, $e);
continue;
}
}
}
}
具体应用示例
单一进程内切换
// 处理单个用户的请求
function handleUserRequest(int $userId)
{
// 获取用户所属租户
$tenantId = User::find($userId)->tenant_id;
// 切换到租户数据库
switchTenant($tenantId);
// 执行业务逻辑
$orders = Order::where('user_id', $userId)->get();
// 操作完成后切换回默认数据库
switchToDefault();
return $orders;
}
基于Domain/子域名
class DomainTenantResolver
{
public function resolveByDomain(string $domain): int
{
$domainMap = [
'company1.example.com' => 1,
'company2.example.com' => 2,
];
return $domainMap[$domain] ?? 0;
}
}
数据库读写分离增强
class TenantReadWriteManager
{
public function getReadConnection(int $tenantId): PDO
{
// 使用从库进行读操作
$config = $this->getTenantReadConfig($tenantId);
return $this->createConnection($config);
}
public function getWriteConnection(int $tenantId): PDO
{
// 使用主库进行写操作
return TenantDatabaseManager::getConnection($tenantId);
}
}
最佳实践建议
-
使用容器管理连接依赖
- 不要到处直接创建连接
- 使用依赖注入框架管理
-
合理设计分片策略
// 考虑租户数据量,选择合适的算法 function getShardId(int $tenantId): int { // 支持范围分片 if ($tenantId < 1000) return 1; if ($tenantId < 2000) return 2; // 或哈希分片 return $tenantId % 10; } -
实现故障转移
class FailoverManager { public function getConnection(int $tenantId): PDO { try { return TenantDatabaseManager::getConnection($tenantId); } catch (PDOException $e) { // 切换到备用数据库 return $this->getBackupConnection($tenantId); } } } -
监控和日志
- 记录每次租户切换
- 监控连接池使用情况
- 追踪分布式事务
这些方案可以根据你的实际需求进行组合和调整,关键是建立统一的租户上下文管理机制,并在应用层面做好抽象和封装。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播