後台耗頻寬/資源任務(匯出大報表、視頻轉碼、批次推播、爬蟲抓取),需**限制同時執行實例數**,防止資源耗盡或影響線上服務。
1, 'lasttime' => time()];
} else {
// 超時重置
if (time() - $list[$name]['lasttime'] > self::TIMEOUT) {
$list[$name]['count'] = 1;
} else {
$list[$name]['count']++;
}
$list[$name]['lasttime'] = time();
}
self::setList($list);
}
/**
* 任務結束:計數 -1
*/
public static function destroy(): void {
$list = self::getList();
$name = self::getName();
if (isset($list[$name])) {
if ($list[$name]['count'] <= 1) {
unset($list[$name]);
} else {
$list[$name]['count']--;
$list[$name]['lasttime'] = time();
}
self::setList($list);
}
}
/** 取得目前計數 */
public static function getCount(): int {
$list = self::getList();
$name = self::getName();
return $list[$name]['count'] ?? 0;
}
/** 取得上限 */
public static function getMaxnum(): int {
$name = self::getName();
return C($name) ?: self::DEFAULT_MAX;
}
/** 是否超過上限 */
public static function isOvertop(): bool {
return self::getCount() > self::getMaxnum();
}
/** 取得最後活動時間 */
public static function getLasttime(): int {
$list = self::getList();
$name = self::getName();
return $list[$name]['lasttime'] ?? 0;
}
/** 清除所有計數 */
public static function clear(): void {
F(self::KEY_PREFIX, null);
}
// --- 內部方法 ---
private static function getName(): string {
return (defined('GROUP_NAME') ? GROUP_NAME . '_' : '') . MODULE_NAME . '_' . ACTION_NAME;
}
private static function getList(): array {
$list = F(self::KEY_PREFIX);
return is_array($list) ? $list : [];
}
private static function setList(?array $list): void {
if (empty($list)) {
F(self::KEY_PREFIX, null);
} else {
F(self::KEY_PREFIX, $list);
}
}
}
?>
error('系統繁忙,當前匯出任務已達上限,請稍後再試');
return;
}
// 執行耗時匯出邏輯...
set_time_limit(0);
$this->doHeavyExport();
$this->success('匯出完成');
}
}
?>
| 議題 | 現狀 | 建議改進 |
|---|---|---|
| 儲存介質 | F() 預設檔案快取 | 生產環境**必須改用 Redis/Memcached**,多伺服器共享計數 |
| 競態條件 | 讀-改-寫非原子 | Redis 用 Lua 腳本或 INCR/DECR 原子操作 |
| 進程異常結束 | 依賴 `__destruct` + 600s 超時重置 | 註冊 register_shutdown_function、信號處理 pcntl_signal |
| 分散式擴展 | 單機計數 | 鍵名加入 Server ID、或用 Redis 全域計數 |
| 監控整合 | 無 | 暴露 getCount() 供 Prometheus/監控抓取 |
redis = \think\facade\Cache::store('redis')->handler();
$this->key = "process:limit:$name";
$this->max = $max;
}
public function acquire(): bool {
// Lua: 如果 count < max 則 incr 並回傳 1,否則 0
$script = "
local cnt = tonumber(redis.call('GET', KEYS[1]) or '0')
if cnt < tonumber(ARGV[1]) then
redis.call('INCR', KEYS[1])
redis.call('EXPIRE', KEYS[1], ARGV[2])
return 1
end
return 0
";
return (bool)$this->redis->eval($script, 1, $this->key, $this->max, $this->ttl);
}
public function release(): void {
// Lua: 如果 count > 0 則 decr
$script = "
local cnt = tonumber(redis.call('GET', KEYS[1]) or '0')
if cnt > 0 then
redis.call('DECR', KEYS[1])
redis.call('EXPIRE', KEYS[1], ARGV[1])
end
";
$this->redis->eval($script, 1, $this->key, $this->ttl);
}
public function getCount(): int {
return (int)($this->redis->get($this->key) ?? 0);
}
}
// 使用
$limiter = new ProcessRedis('export_report', 3);
if (!$limiter->acquire()) { throw new \Exception('並發上限'); }
try {
// do work
} finally {
$limiter->release();
}
?>
Process 類提供**輕量級進程計數**機制,解決「限制同時執行數」的即時需求。核心價值在於**簡單易嵌入**、**支援動態上限**、**自動超時清理**。生產環境請務必升級至 **Redis 原子操作版本**,並整合監控與優雅關機機制,構建穩健的併發治理體系。