首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

ThinkPHP 進程計數器 Process 類實作:限制併發任務數量的輕量方案

     編輯:PHP 綜合知識

應用場景

後台耗頻寬/資源任務(匯出大報表、視頻轉碼、批次推播、爬蟲抓取),需**限制同時執行實例數**,防止資源耗盡或影響線上服務。

Process 類核心設計

  • **儲存介質**:ThinkPHP `F()` 快取 (檔案/Redis/Memcached)
  • **鍵名結構**:`~Process_{GROUP}_{MODULE}_{ACTION}` 隔離不同任務
  • **計數邏輯**:啟動 +1、結束 -1、超時 600 秒自動重置
  • **上限配置**:`C('Module_action', N)` 動態調整,預設 10

完整類別碼 (ThinkPHP 3.2 風格)

 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 原子化改版 (ThinkPHP 5/6 + Redis)

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 原子操作版本**,並整合監控與優雅關機機制,構建穩健的併發治理體系。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved