【workbunny】高速共享緩存

webman-shared-cache
計(jì)劃 & 說(shuō)明
0.6 版本已發(fā)布
- 實(shí)驗(yàn)性功能:Channel的系統(tǒng)信號(hào)監(jiān)聽(tīng),用于緩解定時(shí)輪詢帶來(lái)的空窗問(wèn)題和資源占用問(wèn)題
實(shí)踐分享
最新文檔
https://github.com/workbunny/webman-shared-cache/blob/main/README.md
常見(jiàn)問(wèn)題
1. 它與 Redis/Memcache 的區(qū)別
- shared-cache是基于APCu的本地緩存,它的底層是帶有鎖的MMAP共享內(nèi)存;
- Redis和Memcache本質(zhì)上是“分布式”緩存系統(tǒng)/K-V數(shù)據(jù)庫(kù),存在網(wǎng)絡(luò)IO;
- shared-cache沒(méi)有持久化,同時(shí)也無(wú)法實(shí)現(xiàn)“分布式”,僅可用于本地的多進(jìn)程環(huán)境(進(jìn)程需要有親緣關(guān)系);
- shared-cache是μs級(jí)別的緩存,redis是ms級(jí)別的緩存;
- 網(wǎng)絡(luò)IO存在內(nèi)核態(tài)和用戶態(tài)的多次拷貝,存在較大的延遲,共享內(nèi)存不存在這樣的問(wèn)題;
2. 它的使用場(chǎng)景
- 可以用作一些服務(wù)器的本地緩存,如頁(yè)面緩存、L2-cache;
- 可以跨進(jìn)程做一些計(jì)算工作,也可以跨進(jìn)程通訊;
- 用在一些延遲敏感的服務(wù)下,如游戲服務(wù)器;
- 簡(jiǎn)單的限流插件;
3. 與redis簡(jiǎn)單的比較
- 運(yùn)行/tests/simple-benchmark.php
- redis使用host.docker.internal
- 在循環(huán)中增加不同的間隔,模擬真實(shí)使用場(chǎng)景
- 結(jié)果如下:
1^ "count: 100000" 2^ "interval: 0 μs" ^ "redis: 73.606367111206" ^ "cache: 0.081215143203735" ^ "-----------------------------------" 1^ "count: 100000" 2^ "interval: 1 μs" ^ "redis: 78.833391904831" ^ "cache: 6.4423549175262" ^ "-----------------------------------" 1^ "count: 100000" 2^ "interval: 10 μs" ^ "redis: 79.543494939804" ^ "cache: 7.2690420150757" ^ "-----------------------------------" 1^ "count: 100000" 2^ "interval: 100 μs" ^ "redis: 88.58958697319" ^ "cache: 17.31387090683" ^ "-----------------------------------" 1^ "count: 100000" 2^ "interval: 1000 μs" ^ "redis: 183.2620780468" ^ "cache: 112.18278503418" ^ "-----------------------------------"
簡(jiǎn)介
- 基于APCu拓展的輕量級(jí)高速緩存,讀寫(xiě)微秒級(jí);
- 支持具備親緣關(guān)系的多進(jìn)程內(nèi)存共享;
- 支持具備親緣關(guān)系的多進(jìn)程限流;
安裝
- 自行安裝APCu拓展
# 1. pecl安裝 pecl instanll apcu # 2. docker中請(qǐng)使用安裝器安裝 curl -sSL https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions -o - | sh -s apcu
- 安裝composer包
composer require workbunny/webman-shared-cache
- 使用命令進(jìn)行php.ini的配置
- 進(jìn)入 /config/plugin/workbunny/webman-shared-cache 目錄
- 運(yùn)行
# 幫助信息 sh ./shared-cache-enable.sh --help # or bash ./shared-cache-enable.sh --help
使用
注:\Workbunny\WebmanSharedCache\Cache::$fuse為全局阻塞保險(xiǎn)
1. Cache基礎(chǔ)使用
-
類(lèi)似Redis的String【使用方法與Redis基本一致】
- 支持 Set/Get/Del/Keys/Exists
- 支持 Incr/Decr,支持浮點(diǎn)運(yùn)算
- 支持 儲(chǔ)存對(duì)象數(shù)據(jù)
- 支持 XX/NX模式,支持秒級(jí)過(guò)期時(shí)間
-
類(lèi)似Redis的Hash【使用方法與Redis基本一致】
- 支持 HSet/HGet/HDel/HKeys/HExists
- 支持 HIncr/HDecr,支持浮點(diǎn)運(yùn)算
- 支持 儲(chǔ)存對(duì)象數(shù)據(jù)
- 支持 HashKey的秒級(jí)過(guò)期時(shí)間【版本 ≥ 0.5】
-
通配符/正則匹配Search
$result = []; # 默認(rèn)正則匹配 - 以50條為一次分片查詢 \Workbunny\WebmanSharedCache\Cache::Search('/^abc.+$/', function ($key, $value) use (&$result) { $result[$key] = $value; }, 50); # 通配符轉(zhuǎn)正則 \Workbunny\WebmanSharedCache\Cache::Search( \Workbunny\WebmanSharedCache\Cache::WildcardToRegex('abc*'), function ($key, $value) use (&$result) { $result[$key] = $value; } );
Tips:Cache::Search()本質(zhì)上是個(gè)掃表匹配的過(guò)程,是O(N)的操作,如果需要對(duì)特定族群的數(shù)據(jù)進(jìn)行監(jiān)聽(tīng),推薦使用Channel相關(guān)函數(shù)實(shí)現(xiàn)監(jiān)聽(tīng)。
-
原子性執(zhí)行
# key-1、key-2、key-3會(huì)被當(dāng)作一次原子性操作 # 非阻塞執(zhí)行 - 成功執(zhí)行則返回true,失敗返回false,鎖沖突會(huì)導(dǎo)致執(zhí)行失敗 $result = \Workbunny\WebmanSharedCache\Cache::Atomic('lock-test', function () { \Workbunny\WebmanSharedCache\Cache::Set('key-1', 1); \Workbunny\WebmanSharedCache\Cache::Set('key-2', 2); \Workbunny\WebmanSharedCache\Cache::Set('key-3', 3); }); # 阻塞等待執(zhí)行 - 默認(rèn)阻塞受Cache::$fuse阻塞保險(xiǎn)影響 $result = \Workbunny\WebmanSharedCache\Cache::Atomic('lock-test', function () { \Workbunny\WebmanSharedCache\Cache::Set('key-1', 1); \Workbunny\WebmanSharedCache\Cache::Set('key-2', 2); \Workbunny\WebmanSharedCache\Cache::Set('key-3', 3); }, true); # 自行實(shí)現(xiàn)阻塞 $result = false while (!$result) { # TODO 可以適當(dāng)增加保險(xiǎn),以免超長(zhǎng)阻塞 $result = \Workbunny\WebmanSharedCache\Cache::Atomic('lock-test', function () { \Workbunny\WebmanSharedCache\Cache::Set('key-1', 1); \Workbunny\WebmanSharedCache\Cache::Set('key-2', 2); \Workbunny\WebmanSharedCache\Cache::Set('key-3', 3); }); }
-
查看cache信息
# 全量數(shù)據(jù) var_dump(\Workbunny\WebmanSharedCache\Cache::Info()); # 不查詢數(shù)據(jù) var_dump(\Workbunny\WebmanSharedCache\Cache::Info(true));
-
查看鎖信息
# Hash數(shù)據(jù)的處理建立在寫(xiě)鎖之上,如需調(diào)試,則使用該方法查詢鎖信息 var_dump(\Workbunny\WebmanSharedCache\Cache::LockInfo());
-
查看鍵信息
# 包括鍵的一些基礎(chǔ)信息 var_dump(\Workbunny\WebmanSharedCache\Cache::KeyInfo('test-key'));
-
清空cache
- 使用Del多參數(shù)進(jìn)行清理
# 接受多個(gè)參數(shù) \Workbunny\WebmanSharedCache\Cache::Del($a, $b, $c, $d); # 接受一個(gè)key的數(shù)組 \Workbunny\WebmanSharedCache\Cache::Del(...$keysArray);
- 使用Clear進(jìn)行清理
\Workbunny\WebmanSharedCache\Cache::Clear();
- 使用Del多參數(shù)進(jìn)行清理
2. RateLimiter插件
高效輕量的親緣進(jìn)程限流器
- 在/config/plugin/workbbunny/webman-shared-cache/rate-limit.php中配置
- 在使用的位置調(diào)用
- 當(dāng)沒(méi)有執(zhí)行限流時(shí),返回空數(shù)組
- 當(dāng)執(zhí)行但沒(méi)有到達(dá)限流時(shí),返回?cái)?shù)組is_limit為false
- 當(dāng)執(zhí)行且到達(dá)限流時(shí),返回?cái)?shù)組is_limit為true
$rate = \Workbunny\WebmanSharedCache\RateLimiter::traffic('test'); if ($rate['is_limit'] ?? false) { // 限流邏輯 如可以拋出異常、返回錯(cuò)誤信息等 return new \support\Response(429, [ 'X-Rate-Reset' => $rate['reset'], 'X-Rate-Limit' => $rate['limit'], 'X-Rate-Remaining' => $rate['reset'] ]) }
3. Cache的Channel功能
-
Channel是一個(gè)類(lèi)似Redis-stream、Redis-list、Redis-Pub/Sub的功能模塊
-
一個(gè)通道可以被多個(gè)進(jìn)程監(jiān)聽(tīng),每個(gè)進(jìn)程只能監(jiān)聽(tīng)一個(gè)相同通道(也就是對(duì)相同通道只能創(chuàng)建一個(gè)監(jiān)聽(tīng)器)
-
向通道發(fā)布消息
- 臨時(shí)消息
# 向一個(gè)名為test的通道發(fā)送臨時(shí)消息; # 通道沒(méi)有監(jiān)聽(tīng)器時(shí),臨時(shí)消息會(huì)被忽略,只有通道存在監(jiān)聽(tīng)器時(shí),該消息才會(huì)被存入通道 Cache::ChPublish('test', '這是一個(gè)測(cè)試消息', false);
- 暫存消息
# 向一個(gè)名為test的通道發(fā)送暫存消息; # 通道存在監(jiān)聽(tīng)器時(shí),該消息會(huì)被存入通道內(nèi)的所有子通道 Cache::ChPublish('test', '這是一個(gè)測(cè)試消息', true);
- 指定workerId
# 指定發(fā)送消息至當(dāng)前通道內(nèi)workerId為1的子通道 Cache::ChPublish('test', '這是一個(gè)測(cè)試消息', true, 1);
- 臨時(shí)消息
-
創(chuàng)建通道監(jiān)聽(tīng)器
- 一個(gè)進(jìn)程對(duì)相同通道僅能創(chuàng)建一個(gè)監(jiān)聽(tīng)器
- 一個(gè)進(jìn)程可以同時(shí)監(jiān)聽(tīng)多個(gè)不同的通道
- 建議workerId使用workerman的workerId進(jìn)行區(qū)分
# 向一個(gè)名為test的通道創(chuàng)建一個(gè)workerId為1的監(jiān)聽(tīng)器; # 通道消息先進(jìn)先出,當(dāng)有消息時(shí)會(huì)觸發(fā)回調(diào) Cache::ChCreateListener('test', '1', function(string $channelKey, string|int $workerId, mixed $message) { // TODO 你的業(yè)務(wù)邏輯 dump($channelKey, $workerId, $message); });
-
移除通道監(jiān)聽(tīng)器
- 移除監(jiān)聽(tīng)器子通道及子通道內(nèi)消息
# 向一個(gè)名為test的通道創(chuàng)建一個(gè)workerId為1的監(jiān)聽(tīng)器; # 通道移除時(shí)不會(huì)移除其他子通道消息 Cache::ChRemoveListener('test', '1', true);
- 移除監(jiān)聽(tīng)器子通道,但保留子通道內(nèi)消息
# 向一個(gè)名為test的通道創(chuàng)建一個(gè)workerId為1的監(jiān)聽(tīng)器; # 通道移除時(shí)不會(huì)移除所有子通道消息 Cache::ChRemoveListener('test', '1', false);
- 移除監(jiān)聽(tīng)器子通道及子通道內(nèi)消息
-
實(shí)驗(yàn)性功能:信號(hào)通知
- 由于共享內(nèi)存無(wú)法使用事件監(jiān)聽(tīng),所以底層使用Timer定時(shí)器進(jìn)行輪詢,實(shí)驗(yàn)性功能可以開(kāi)啟使用系統(tǒng)信號(hào)來(lái)監(jiān)聽(tīng)數(shù)據(jù)的變化
// 設(shè)置信號(hào) // 因?yàn)閑vent等事件循環(huán)庫(kù)是對(duì)標(biāo)準(zhǔn)信號(hào)的監(jiān)聽(tīng),所以不能使用自定實(shí)時(shí)信號(hào)SIGRTMIN ~ SIGRTMAX // 默認(rèn)暫時(shí)使用SIGPOLL,異步IO監(jiān)聽(tīng)信號(hào),可能影響異步文件IO相關(guān)的觸發(fā) Future::$signal = \SIGPOLL; // 開(kāi)啟信號(hào)監(jiān)聽(tīng),這時(shí)候開(kāi)啟的監(jiān)聽(tīng)會(huì)觸發(fā)之前的回調(diào)和通道回調(diào),不會(huì)影響之前的回調(diào) Cache::channelUseSignalEnable(true)
- 當(dāng)使用的監(jiān)聽(tīng)信號(hào)存在已注冊(cè)的回調(diào)產(chǎn)生回調(diào)沖突時(shí),可以手動(dòng)設(shè)置回調(diào)事件共享
// 設(shè)置信號(hào) // 因?yàn)閑vent等事件循環(huán)庫(kù)是對(duì)標(biāo)準(zhǔn)信號(hào)的監(jiān)聽(tīng),所以不能使用自定實(shí)時(shí)信號(hào)SIGRTMIN ~ SIGRTMAX // 默認(rèn)暫時(shí)使用SIGPOLL Future::$signal = \SIGPOLL; // 假設(shè)\SIGPOLL存在一個(gè)已注冊(cè)的回調(diào),YourEventLoop::getCallback(\SIGPOLL)可以獲取該事件在當(dāng)前進(jìn)程注冊(cè)的回調(diào)響應(yīng) // 設(shè)置回調(diào) Future::setSignalCallback(YourEventLoop::getCallback(\SIGPOLL)); // 開(kāi)啟信號(hào)監(jiān)聽(tīng),這時(shí)候開(kāi)啟的監(jiān)聽(tīng)會(huì)觸發(fā)之前的回調(diào)和通道回調(diào),不會(huì)影響之前的回調(diào) Cache::channelUseSignalEnable(true)
通道信號(hào)監(jiān)聽(tīng)維系了一個(gè)事件隊(duì)列,多次觸發(fā)信號(hào)時(shí),回調(diào)只會(huì)根據(jù)事件隊(duì)列是否存在事件消費(fèi)標(biāo)記而執(zhí)行事件回調(diào)
其他功能具體可以參看代碼注釋和測(cè)試用例
- 由于共享內(nèi)存無(wú)法使用事件監(jiān)聽(tīng),所以底層使用Timer定時(shí)器進(jìn)行輪詢,實(shí)驗(yàn)性功能可以開(kāi)啟使用系統(tǒng)信號(hào)來(lái)監(jiān)聽(tīng)數(shù)據(jù)的變化