PHP 高性能隊(duì)列探索:從 SQLite 到內(nèi)存,我們?cè)撊绾芜x擇?

在現(xiàn)代 PHP 應(yīng)用開(kāi)發(fā)中,將耗時(shí)任務(wù)(如郵件發(fā)送、報(bào)表生成、數(shù)據(jù)同步)從主請(qǐng)求流程中剝離出來(lái)進(jìn)行異步處理,是提升用戶(hù)體驗(yàn)和系統(tǒng)吞吐量的關(guān)鍵。雖然 RabbitMQ、Kafka 等專(zhuān)業(yè)消息隊(duì)列是標(biāo)準(zhǔn)解決方案,但對(duì)于中小型項(xiàng)目或追求輕量級(jí)架構(gòu)的場(chǎng)景,我們往往希望找到一個(gè)更“接地氣”的方案。

本文記錄了一次完整的技術(shù)探索之旅:從一個(gè)簡(jiǎn)單的想法開(kāi)始,如何利用 PHP 生態(tài)中已有的工具,構(gòu)建一個(gè)能夠應(yīng)對(duì)“大量寫(xiě)入、少量消費(fèi)”場(chǎng)景的高性能隊(duì)列。

需求起點(diǎn):一個(gè)輕量級(jí)的任務(wù)隊(duì)列

我們的核心需求很簡(jiǎn)單:
1. 輕量級(jí):不希望引入新的、重型的服務(wù)依賴(lài)(如 Java/Erlang 系的消息中間件)。
2. 可靠:任務(wù)不能輕易丟失。
3. 高性能:能夠承受 Web 應(yīng)用在高并發(fā)下的大量任務(wù)寫(xiě)入。

很自然地,我們的第一個(gè)想法落在了 SQLite 身上。它無(wú)需額外服務(wù)、零配置、就是一個(gè)文件,完美符合“輕量級(jí)”的要求。

還有一個(gè)需求,這個(gè)針對(duì)的是單機(jī)內(nèi)部的隊(duì)列,比如日志記錄,日志先記錄到隊(duì)列中,再通過(guò)消費(fèi)者統(tǒng)一記錄到集中機(jī)器上。

思路一:SQLite 作為隊(duì)列的初步嘗試與瓶頸

使用 SQLite 做隊(duì)列的思路非常直接:生產(chǎn)者 (PHP-FPM) 在 Web 請(qǐng)求中向 tasksINSERT 一條記錄;消費(fèi)者 (CLI) 則是一個(gè)后臺(tái)腳本,循環(huán) SELECT 任務(wù)來(lái)處理。

然而,當(dāng)面臨“大量寫(xiě)入、少量消費(fèi)”的場(chǎng)景時(shí),瓶頸很快出現(xiàn):寫(xiě)入并發(fā)。SQLite 在默認(rèn)模式下,任何時(shí)刻只允許一個(gè)寫(xiě)入者。當(dāng)成百上千個(gè) PHP-FPM 進(jìn)程同時(shí)嘗試插入任務(wù)時(shí),會(huì)激烈地爭(zhēng)奪數(shù)據(jù)庫(kù)的寫(xiě)鎖,導(dǎo)致大量請(qǐng)求失敗或超時(shí)。

基礎(chǔ)優(yōu)化

為了解決并發(fā)問(wèn)題,一些基礎(chǔ)優(yōu)化是必須的:
1. 開(kāi)啟 WAL 模式PRAGMA journal_mode=WAL;,允許一個(gè)寫(xiě)入者和多個(gè)讀取者并發(fā),極大緩解了鎖爭(zhēng)用。
2. 設(shè)置超時(shí)PRAGMA busy_timeout = 5000;,讓寫(xiě)入失敗的進(jìn)程等待一段時(shí)間而不是立即報(bào)錯(cuò)。
3. 使用高速磁盤(pán) (SSD):從物理層面提升 I/O 性能。

這些優(yōu)化能解決大部分問(wèn)題,但如果寫(xiě)入壓力達(dá)到極限,我們還能做什么?

思路二:探索純內(nèi)存方案

為了徹底消除磁盤(pán) I/O 瓶頸,我們自然想到了內(nèi)存。

方案 A:APCu - 一個(gè)美麗的陷阱

APCu 是 PHP 的一個(gè)共享內(nèi)存緩存。我們可以用一個(gè)共享數(shù)組來(lái)當(dāng)隊(duì)列。但這很快暴露了新問(wèn)題:

CPU 爭(zhēng)用:為了保證并發(fā)安全,我們必須使用 apcu_cas (Compare-And-Swap) 循環(huán)來(lái)寫(xiě)入。在高并發(fā)下,大量進(jìn)程會(huì)在此處“忙等待” (Busy-Waiting),瘋狂空轉(zhuǎn) CPU,導(dǎo)致系統(tǒng)負(fù)載飆升。

數(shù)據(jù)易失:服務(wù)重啟,內(nèi)存中的所有任務(wù)全部丟失。

結(jié)論:APCu 方案在高并發(fā)寫(xiě)入下性能不佳且有數(shù)據(jù)丟失風(fēng)險(xiǎn),不推薦。

方案 B:Redis - 專(zhuān)業(yè)選手,但增加了依賴(lài)

Redis 是內(nèi)存方案的工業(yè)標(biāo)準(zhǔn)。它使用高效的事件循環(huán)模型,提供原子的列表操作 (LPUSH/BRPOP),性能卓越且功能豐富。它也支持?jǐn)?shù)據(jù)持久化。唯一的“缺點(diǎn)”是,它違背了我們最初“不引入新服務(wù)”的原則。

思路三:兩全其美?SQLite on tmpfs

有沒(méi)有一種方案,既能擁有內(nèi)存的極致速度,又能利用 SQLite 成熟的并發(fā)模型,還無(wú)需修改代碼?答案是:將 SQLite 數(shù)據(jù)庫(kù)文件放在內(nèi)存文件系統(tǒng) (tmpfs) 中

在 Linux 中,tmpfs 是一個(gè)基于內(nèi)存的文件系統(tǒng)(/dev/shm 就是一個(gè)現(xiàn)成的例子)。我們可以將 SQLite 數(shù)據(jù)庫(kù)文件創(chuàng)建在這里。

優(yōu)點(diǎn)

  1. 極致性能:所有數(shù)據(jù)庫(kù)操作,包括加鎖、讀寫(xiě),都變成了內(nèi)存操作,速度極快。
  2. 健壯的并發(fā)模型:我們依然享受著 SQLite 優(yōu)秀的“阻塞等待”并發(fā)模型,沒(méi)有 APCu 的 CPU 空轉(zhuǎn)問(wèn)題。
  3. 零代碼修改:PHP 代碼無(wú)需任何改動(dòng),只需改變數(shù)據(jù)庫(kù)文件的路徑。

缺點(diǎn)

  • 數(shù)據(jù)完全易失:和所有內(nèi)存方案一樣,服務(wù)器重啟,數(shù)據(jù)灰飛煙滅。這個(gè)方案非常適合任務(wù)可再生的場(chǎng)景(如生成縮略圖、更新緩存),但不適用于關(guān)鍵業(yè)務(wù)(如訂單處理)。

最終方案:在 Docker 中優(yōu)雅地實(shí)現(xiàn) "SQLite on tmpfs"

在現(xiàn)代化的 Docker 開(kāi)發(fā)環(huán)境中,實(shí)現(xiàn)這個(gè)方案變得異常簡(jiǎn)單和優(yōu)雅。我們無(wú)需手動(dòng)在服務(wù)器上執(zhí)行 mount 命令,只需在 docker-compose.yml 中聲明即可。

# File: docker-compose.yml
version: '3.8'

services:
  php-fpm:
    build: ./php
    volumes:
      - ./src:/app
    # 關(guān)鍵配置:聲明一個(gè) tmpfs 掛載
    tmpfs:
      # 推薦方案:精確設(shè)置所有者
      - /app/ramdisk:size=256M,uid=33,gid=33

  # ... 其他服務(wù)

權(quán)限設(shè)置:專(zhuān)業(yè)做法 vs. 粗暴捷徑

由于 PHP-FPM 進(jìn)程通常以低權(quán)限用戶(hù)(如 www-data)運(yùn)行,而 tmpfs 默認(rèn)由 root 創(chuàng)建,直接寫(xiě)入會(huì)因權(quán)限不足而失敗。

專(zhuān)業(yè)做法(推薦):通過(guò) uid=33,gid=33(www-data 的典型 ID),我們精確地將內(nèi)存目錄的所有權(quán)交給了 PHP 進(jìn)程,這遵循了安全的最小權(quán)限原則。

粗暴捷徑(不推薦):使用 mode=0777。這會(huì)將目錄權(quán)限設(shè)為 rwxrwxrwx,允許容器內(nèi)的任何用戶(hù)寫(xiě)入。雖然能解決問(wèn)題,但它過(guò)度授權(quán),留下了安全隱患。

總結(jié)

由此來(lái)看,如果我們不通過(guò)Redis或其他專(zhuān)業(yè)的消息隊(duì)列服務(wù),同時(shí)要處理的任務(wù)是在單機(jī)內(nèi)的,不需要遠(yuǎn)程互聯(lián),那么我們完全可以通過(guò)sqlite實(shí)現(xiàn),再配合linux開(kāi)啟內(nèi)存文件系統(tǒng),避免觸發(fā)磁盤(pán)IO。雖然無(wú)論從寫(xiě)入、更新、刪除、讀取等任何方面,Redis都更專(zhuān)業(yè),性能更好,但我們的方案是更簡(jiǎn)單的,同時(shí)可以利用sql表結(jié)構(gòu),很省事的實(shí)現(xiàn)隊(duì)列的需求。

原文標(biāo)題: PHP 高性能隊(duì)列探索:從 SQLite 到內(nèi)存,我們?cè)撊绾芜x擇?

原文地址: https://phpreturn.com/index/a68d9e185d81e3.html

原文平臺(tái): PHP武器庫(kù)

版權(quán)聲明: 本文由phpreturn.com(PHP武器庫(kù)官網(wǎng))原創(chuàng)和首發(fā),所有權(quán)利歸phpreturn(PHP武器庫(kù))所有,本站允許任何形式的轉(zhuǎn)載/引用文章,但必須同時(shí)注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容