本文目录导读:

在PHP分布式项目中控制并发数量上限,常见方案有以下几种:
Redis 分布式信号量
基于 Redis SETNX 实现
class RedisSemaphore {
private $redis;
private $key;
private $maxConcurrency;
private $timeout;
public function __construct($redis, $key, $maxConcurrency, $timeout = 10) {
$this->redis = $redis;
$this->key = "semaphore:{$key}";
$this->maxConcurrency = $maxConcurrency;
$this->timeout = $timeout;
}
public function acquire($identifier = null) {
$identifier = $identifier ?: uniqid('', true);
$startTime = time();
while (time() - $startTime < $this->timeout) {
// 使用 Lua 脚本保证原子性
$script = <<<LUA
local current = redis.call('LLEN', KEYS[1])
if current < tonumber(ARGV[1]) then
redis.call('RPUSH', KEYS[1], ARGV[2])
redis.call('EXPIRE', KEYS[1], ARGV[3])
return 1
end
return 0
LUA;
$result = $this->redis->eval($script,
[$this->key, $this->maxConcurrency, $identifier, $this->timeout],
1
);
if ($result) {
return $identifier;
}
// 等待重试
usleep(100000); // 100ms
}
return false;
}
public function release($identifier) {
$script = <<<LUA
local items = redis.call('LRANGE', KEYS[1], 0, -1)
for i, v in ipairs(items) do
if v == ARGV[1] then
redis.call('LREM', KEYS[1], 1, ARGV[1])
return 1
end
end
return 0
LUA;
return $this->redis->eval($script, [$this->key, $identifier], 1);
}
}
// 使用示例
$semaphore = new RedisSemaphore($redis, 'api_limit', 10);
$identifier = $semaphore->acquire();
if ($identifier) {
try {
// 执行需要限制并发的代码
processData();
} finally {
$semaphore->release($identifier);
}
} else {
throw new \Exception('并发上限已达到,请稍后重试');
}
基于 Redis 的令牌桶算法
class TokenBucket {
private $redis;
private $key;
private $capacity;
private $rate; // 每秒添加的令牌数
public function __construct($redis, $key, $capacity, $rate) {
$this->redis = $redis;
$this->key = "token_bucket:{$key}";
$this->capacity = $capacity;
$this->rate = $rate;
}
public function consume($tokens = 1) {
$script = <<<LUA
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local tokens = tonumber(ARGV[3])
local now = tonumber(ARGV[4])
local last_time = redis.call('GET', key .. ':time')
local current_tokens
if last_time then
last_time = tonumber(last_time)
local elapsed = now - last_time
current_tokens = tonumber(redis.call('GET', key .. ':tokens') or 0)
current_tokens = math.min(capacity, current_tokens + elapsed * rate)
else
current_tokens = capacity
end
if current_tokens >= tokens then
redis.call('SET', key .. ':tokens', current_tokens - tokens)
redis.call('SET', key .. ':time', now)
return 1
end
return 0
LUA;
return $this->redis->eval($script, [
$this->key,
$this->capacity,
$this->rate,
$tokens,
time()
], 1);
}
}
基于数据库的行锁
class DBSemaphore {
private $db;
private $table = 'semaphores';
public function acquire($name, $maxCount, $timeout = 30) {
$startTime = time();
while (time() - $startTime < $timeout) {
try {
$this->db->beginTransaction();
// 获取当前并发数(使用行锁)
$row = $this->db->select("SELECT * FROM {$this->table} WHERE name = ? FOR UPDATE", [$name]);
if (!$row) {
$this->db->insert($this->table, [
'name' => $name,
'count' => 1,
'max_count' => $maxCount
]);
$this->db->commit();
return true;
}
if ($row->count < $maxCount) {
$this->db->update($this->table,
['count' => $row->count + 1],
['name' => $name]
);
$this->db->commit();
return true;
}
$this->db->rollback();
} catch (\Exception $e) {
$this->db->rollback();
}
usleep(100000); // 100ms
}
return false;
}
public function release($name) {
$this->db->beginTransaction();
$row = $this->db->select("SELECT * FROM {$this->table} WHERE name = ? FOR UPDATE", [$name]);
if ($row && $row->count > 0) {
$this->db->update($this->table,
['count' => $row->count - 1],
['name' => $name]
);
}
$this->db->commit();
}
}
使用消息队列控制并发
class QueueBasedLimiter {
private $redis;
private $processKey = 'process:concurrent';
private $maxConcurrent = 10;
public function canProcess($taskId) {
$script = <<<LUA
local key = KEYS[1]
local max = tonumber(ARGV[1])
local task = ARGV[2]
local current = redis.call('SCARD', key)
if current < max then
redis.call('SADD', key, task)
return 1
end
return 0
LUA;
return $this->redis->eval($script,
[$this->processKey, $this->maxConcurrent, $taskId],
1
);
}
public function finishProcess($taskId) {
$this->redis->srem($this->processKey, $taskId);
}
// 消费者端
public function consumer() {
while (true) {
$task = $this->redis->lpop('task:queue');
if ($task && $this->canProcess($task)) {
try {
// 处理任务
processTask($task);
} finally {
$this->finishProcess($task);
}
} else {
usleep(100000);
}
}
}
}
分布式信号量组件
interface DistributedSemaphoreInterface {
public function acquire($key, $maxConcurrency, $timeout = 10);
public function release($key);
}
// 封装成 Trait 方便使用
trait SemaphoreTrait {
private $semaphore;
protected function withSemaphore($key, $maxConcurrency, callable $callback) {
$identifier = $this->semaphore->acquire($key, $maxConcurrency);
if (!$identifier) {
throw new \Exception("并发上限 {$maxConcurrency} 已达到");
}
try {
return $callback();
} finally {
$this->semaphore->release($key, $identifier);
}
}
}
class ApiService {
use SemaphoreTrait;
public function heavyOperation() {
return $this->withSemaphore('heavy_api', 5, function() {
// 限制最多5个并发
return $this->doHeavyWork();
});
}
}
推荐方案
- 中小规模项目:使用 Redis 信号量,性能好,实现简单
- 需要精确控制:令牌桶算法 + Redis
- 已有 Redis 集群:RedLock 或 Redisson 实现
- 需要持久化:使用数据库行锁方案
- 高并发场景:使用消息队列 + 工作池模式
选择合适的方案时要考虑:
- 业务对精确度的要求
- 系统规模和并发量
- 基础设施已有的组件
- 网络延迟和可用性要求
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播