本文目录导读:

在 PHP 中实现租户队列隔离,核心目标是让不同租户的数据互不干扰,同时保证资源分配的公平性,以下是几种常见且有效的隔离方案:
数据库级隔离(最推荐)
按租户分表/分库
class QueueService
{
private $tenantId;
public function __construct($tenantId)
{
$this->tenantId = $tenantId;
}
// 每个租户使用独立的队列表
public function getQueueTable(): string
{
return "queue_tenant_{$this->tenantId}";
}
public function push(string $job, array $data)
{
$table = $this->getQueueTable();
// 写入对应的租户表
DB::table($table)->insert([
'job' => $job,
'data' => json_encode($data),
'created_at' => now()
]);
}
public function pop()
{
$table = $this->getQueueTable();
// 从租户表中取出任务
return DB::table($table)
->where('status', 'pending')
->lockForUpdate()
->first();
}
}
使用租户ID字段
class MultiTenantQueue
{
public function push(int $tenantId, string $job, array $data)
{
DB::table('jobs')->insert([
'tenant_id' => $tenantId,
'job' => $job,
'data' => json_encode($data),
'created_at' => now()
]);
}
// 队列消费者
public function consume(int $tenantId)
{
return DB::table('jobs')
->where('tenant_id', $tenantId)
->where('status', 'pending')
->orderBy('id')
->limit(1)
->lockForUpdate()
->get();
}
}
Redis 队列隔离
使用不同的 Redis Key 前缀
class RedisQueueIsolation
{
private $redis;
public function __construct()
{
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
}
// 每个租户有独立的队列
public function getQueueKey(int $tenantId): string
{
return "queue:tenant:{$tenantId}";
}
public function pushJob(int $tenantId, $job)
{
$key = $this->getQueueKey($tenantId);
return $this->redis->rPush($key, serialize($job));
}
public function popJob(int $tenantId)
{
$key = $this->getQueueKey($tenantId);
$job = $this->redis->lPop($key);
return $job ? unserialize($job) : null;
}
// 获取租户队列长度(用于监控和限流)
public function getQueueLength(int $tenantId): int
{
$key = $this->getQueueKey($tenantId);
return $this->redis->lLen($key);
}
}
使用 Redis Hash 存储多租户队列
class HashBasedIsolation
{
private $redis;
private $hashKey = 'multi_tenant_queue';
public function pushJob(int $tenantId, $jobData, $priority = 0)
{
$jobId = uniqid('job_', true);
$this->redis->hSet($this->hashKey, "{$tenantId}:{$jobId}", json_encode([
'data' => $jobData,
'priority' => $priority,
'tenant_id' => $tenantId,
'created_at' => time()
]));
return $jobId;
}
public function getTenantJobs(int $tenantId)
{
return $this->redis->hGetAll($this->hashKey);
}
}
消息队列中间件方案
RabbitMQ 多vhost/Exchange
class RabbitMQIsolation
{
private $connection;
private $channel;
public function __construct()
{
$this->connection = new AMQPConnection([
'host' => 'localhost',
'port' => 5672,
'login' => 'user',
'password' => 'pass'
]);
$this->connection->connect();
$this->channel = new AMQPChannel($this->connection);
}
// 每个租户一个独立队列
public function declareTenantQueue(int $tenantId)
{
$queueName = "tenant_queue_{$tenantId}";
$queue = new AMQPQueue($this->channel);
$queue->setName($queueName);
$queue->setFlags(AMQP_DURABLE);
$queue->declareQueue();
return $queue;
}
public function publishToTenantQueue(int $tenantId, $message)
{
$exchangeName = "tenant_exchange_{$tenantId}";
$exchange = new AMQPExchange($this->channel);
$exchange->setName($exchangeName);
$exchange->setType(AMQP_EX_TYPE_DIRECT);
$exchange->declareExchange();
$exchange->publish(
json_encode($message),
"route_{$tenantId}",
AMQP_NOPARAM,
['delivery_mode' => AMQP_DURABLE]
);
}
}
使用 Laravel Queue + 多连接
// config/queue.php
'connections' => [
'tenant_1' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => 'tenant_1_queue',
],
'tenant_2' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => 'tenant_2_queue',
],
],
// 使用时动态选择连接
class TenantQueueManager
{
public function dispatch(int $tenantId, $job)
{
$connection = "tenant_{$tenantId}_connection";
Queue::connection($connection)->push($job);
}
}
公平调度与租户隔离结合
class FairQueueScheduler
{
private $tenantWeights = [];
public function __construct()
{
// 租户权重配置(可动态调整)
$this->tenantWeights = [
1 => 10, // 租户1 权重10
2 => 5, // 租户2 权重5
3 => 3, // 租户3 权重3
];
}
public function getNextTenant()
{
// 简单轮询 + 权重
$tenants = array_keys($this->tenantWeights);
$totalWeight = array_sum($this->tenantWeights);
$rand = mt_rand(1, $totalWeight);
$counter = 0;
foreach ($this->tenantWeights as $tenant => $weight) {
$counter += $weight;
if ($rand <= $counter) {
return $tenant;
}
}
return $tenants[0];
}
public function processNextJob()
{
$tenantId = $this->getNextTenant();
// 从对应租户队列中取任务并处理
return $this->processTenantJob($tenantId);
}
}
监控与治理
class QueueMonitor
{
public function getTenantStats(int $tenantId): array
{
return [
'pending_count' => $this->getPendingCount($tenantId),
'processing_count' => $this->getProcessingCount($tenantId),
'failed_count' => $this->getFailedCount($tenantId),
'avg_wait_time' => $this->getAverageWaitTime($tenantId),
];
}
public function alertForAbnormalUsage(int $tenantId, float $threshold = 0.8)
{
$usage = $this->getTenantResourceUsage($tenantId);
$maxAllowed = $this->getTenantLimit($tenantId);
if ($usage > ($maxAllowed * $threshold)) {
// 发送告警或限制租户
$this->rateLimitTenant($tenantId);
}
}
}
建议
-
根据业务规模选择:
- 小规模(<100租户):数据库单表加索引,使用租户ID字段
- 中规模(100-1000租户):Redis多队列或数据库分表
- 大规模(>1000租户):RabbitMQ/Kafka等专业MQ,按租户分区
-
考虑隔离级别:
- 数据隔离:不同租户队列数据完全隔离
- 性能隔离:限制单个租户的资源使用
- 故障隔离:一个租户的异常不影响其他租户
-
动态扩容:
- 支持租户队列的动态创建和销毁
- 支持根据租户负载动态调整优先级
选择方案时,需要权衡实现复杂度、维护成本和业务需求,通常推荐使用Redis方案,因为它简单高效,且支持大多数隔离需求。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播