摘要:前些時間我們發布了實例協程池異步郵件發送守護程序范例,這一次我們提供一個使用大廠通過協程化來并行執行短信發送任務,本文是一個代碼簡單性能極強的范例。
前些時間我們發布了 Mix PHP V2 實例:協程池異步郵件發送守護程序 范例,這一次我們提供一個使用大廠 SDK 通過 Swoole Hook 協程化來并行執行短信發送任務,本文是一個代碼簡單、IO 性能極強的范例。
請先升級到 mix-framework >= v2.0.5。
本范例依然使用消息隊列的方式接收短信發送任務,消息中間件使用:
redis
生產者通??蚣苤惺褂?Redis 會安裝一個類庫來使用,本例使用原生代碼,便于理解。
// 連接 $redis = new Redis(); if (!$redis->connect("127.0.0.1", 6379)) { throw new Exception("Redis connect failed."); } $redis->auth(""); $redis->select(0); // 投遞任務 for($i = 0; $i < 3; $i++){ $data = [ "phone" => "***", "templateCode" => "SMS_***", "templateParam" => ["code" => 123456], ]; $redis->lpush("queue:sms", serialize($data)); }消費者
使用的是 ali 云的短信服務,查看官方 PHP SDK 文檔 ,使用的庫為:
composer require alibabacloud/client
通過查看該庫的 composer 依賴文件,我們得知該庫基于 guzzlehttp 開發,因為 Mix PHP 提供了無需修改代碼就可 Hook Guzzle 庫可在協程中使用的工具 Mix PHP V2 生態:讓 Guzzle 支持 Swoole 的 Hook 協程,所以能基本確定該庫可在 Swoole 協程中使用。
首先我們安裝 https://github.com/mix-php/guzzle-hook 讓 alibabacloud/client 可在協程中使用:
composer require mix/guzzle-hook
然后在項目的 composer.json 文件中增加 extra 配置項,如下:
"extra": { "include_files": [ "vendor/mix/guzzle-hook/src/functions_include.php" ] }
更新自動加載:
composer dump-autoload
下面我們采用 Mix PHP V2 的守護程序、協程池來完成一個超高性能的短信發送程序。
首先我們在配置 applications/console/config/main.php 中注冊一個命令:
// 命令 "commands" => [ "smser" => [ "Smser", "description" => "SMS send daemon demo.", "options" => [ [["d", "daemon"], "description" => "Run in the background"], ], ], ],
注冊的命令中指定的 Smser 命令類,接下來我們編寫一個 SmserCommand 類:
applications/console/src/Commands/SmserCommand.php
*/ class SmserCommand { const ACCESS_KEY = "***"; const ACCESS_SECRET = "***"; /** * 退出 * @var bool */ public $quit = false; /** * 主函數 */ public function main() { // 守護處理 $daemon = Flag::bool(["d", "daemon"], false); if ($daemon) { ProcessHelper::daemon(); } // 捕獲信號 ProcessHelper::signal([SIGHUP, SIGINT, SIGTERM, SIGQUIT], function ($signal) { $this->quit = true; ProcessHelper::signal([SIGHUP, SIGINT, SIGTERM, SIGQUIT], null); }); // 設置ali云全局參數 AlibabaCloud::accessKeyClient(static::ACCESS_KEY, static::ACCESS_SECRET)->regionId("cn-hangzhou")->asDefaultClient(); // 手動關閉Swoole文件Hook,因為ali云依賴的uuid庫有文件hook協程兼容問題,Swoole 4.4已經適配該問題 Coroutine::enableHook(SWOOLE_HOOK_ALL ^ SWOOLE_HOOK_FILE); // 協程池執行任務 xgo(function () { $maxWorkers = 20; $maxQueue = 20; $jobQueue = new Channel($maxQueue); $dispatch = new Dispatcher([ "jobQueue" => $jobQueue, "maxWorkers" => $maxWorkers, ]); $dispatch->start(SmserWorker::class); // 投放任務 $redis = app()->redisPool->getConnection(); while (true) { if ($this->quit) { $dispatch->stop(); return; } try { $data = $redis->brPop(["queue:sms"], 3); } catch (Throwable $e) { $dispatch->stop(); return; } if (!$data) { continue; } $data = array_pop($data); // brPop命令最后一個鍵才是值 $jobQueue->push($data); } }); // 等待事件 Event::wait(); } }
從 $data = $redis->brPop(["queue:sms"], 3); 外部的異常捕獲可得知,當 Redis 連接出錯時,比如 Redis 重啟、連接異常時協程池會安全退出,也就是說當進程異常退出后用戶需使用 supervisor、pm2 等工具重啟守護進程。
上面是一個 Mix PHP 協程池的使用代碼,基本可以直接復制使用,框架默認包含了協程池的 Demo,本次實例只是修改了協程池的 Worker,本命令主要是完成從 Redis 隊列中獲取消息然后 push 到 jobQueue 中,jobQueue 中的數據會被 20 個 Worker 實例中某一個搶占后并行執行,本例的發送代碼邏輯就在 SmserWorker 類中:
applications/console/src/Libraries/SmserWorker.php
*/ class SmserWorker extends AbstractWorker implements WorkerInterface { /** * 郵件發送器 * @var Smser */ public $smser; /** * 初始化事件 */ public function onInitialize() { parent::onInitialize(); // TODO: Change the autogenerated stub // 實例化一些需重用的對象 $this->smser = new Smser(); } /** * 處理 * @param $data */ public function handle($data) { // TODO: Implement handle() method. $data = unserialize($data); if (empty($data)) { return; } try { $result = $this->smser->send($data["phone"], $data["templateCode"], $data["templateParam"]); app()->log->info("SMS sent successfully:phone {phone} templateCode {templateCode} result {result}", array_merge($data, ["result" => json_encode($result, JSON_UNESCAPED_UNICODE)])); } catch (Throwable $e) { app()->log->error("SMS failed to send:phone {phone} templateCode {templateCode} error {error}", array_merge($data, ["error" => $e->getMessage()])); } } }
由以上代碼可見,Worker 在初始化時,新增了一個 Smser 類的屬性,當 jobQueue 消息投遞過來時消息會傳遞到 handle 方法,在該方法中使用 Mailer 類的實例完成郵件發送任務,所以我們要編寫了一個 Smser 發送程序:
applications/console/src/Libraries/Smser.php
*/ class Smser { /** * 配置信息 */ const SIGN_NAME = "***"; /** * Smser constructor. */ public function __construct() { // 開啟協程鉤子 Coroutine::enableHook(); } /** * 發送 * @param $phone * @param $templateCode * @param $templateParam * @return array * @throws ClientException * @throws ServerException */ public function send($phone, $templateCode, $templateParam) { $result = AlibabaCloud::rpc() ->product("Dysmsapi") // ->scheme("https") // https | http ->version("2017-05-25") ->action("SendSms") ->method("POST") ->options([ "query" => [ "PhoneNumbers" => $phone, "SignName" => static::SIGN_NAME, "TemplateCode" => $templateCode, "TemplateParam" => json_encode($templateParam), ], ]) ->request(); return $result->toArray(); } }
以上就完成了全部的代碼邏輯,現在我們開始測試,先啟動消費者守護程序:
[root@localhost bin]# ./mix-console smser
將上文的生產者腳本命名為 push.php 然后在 CLI 中執行 (開一個新終端):
[root@localhost bin]# php /tmp/push.php
消費者守護程序結果:
[root@localhost bin]# ./mix-console smser [info] 2019-05-24 12:03:32 <101014> [message] SMS sent successfully:phone *** templateCode SMS_*** result {"Message":"OK","RequestId":"4071D031-6D9E-4F70-9269-6C1979080858","BizId":"939807358670612546^0","Code":"OK"} [info] 2019-05-24 12:03:32 <101014> [message] SMS sent successfully:phone *** templateCode SMS_*** result {"Message":"觸發分鐘級流控Permits:1","RequestId":"490B73D7-317E-4362-B2DD-5E2153A7B891","Code":"isv.BUSINESS_LIMIT_CONTROL"} [info] 2019-05-24 12:03:32 <101014> [message] SMS sent successfully:phone *** templateCode SMS_*** result {"Message":"觸發分鐘級流控Permits:1","RequestId":"1FD22EDB-BAA4-4416-8FF9-242EDCF34359","Code":"isv.BUSINESS_LIMIT_CONTROL"}
命令行終端打印了發送成功的日志,發送完成。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/31565.html
摘要:消費者開發使用本例時,請確保你使用的編譯時開啟了本例我們采用的守護程序協程池來完成一個超高性能的郵件發送程序。 去年 Mix PHP V1 發布時,我寫了一個多進程的郵件發送實例: 使用 mixphp 打造多進程異步郵件發送,今年 Mix PHP V2 發布,全面的協程支持讓我們可以使用一個進程就可達到之前多個進程都無法達到的更高 IO 性能,所以今天重寫一個協程池版本的郵件發送實例。...
摘要:主函數查詢數據不手動釋放的連接不會歸還連接池,會在析構時丟棄執行結果為,說明是并行執行的。主函數查詢數據即便拋出了異常,仍然能執行到,沒有導致內的一直處于阻塞狀態。主函數一次性定時持續定時停止定時 協程 Mix PHP V2 基于 Swoole 4 的 PHP Stream Hook 協程技術開發,協程使用方式與 Golang 幾乎一致,包括框架封裝的協程池、連接池、命令行處理都大量參...
摘要:是一個非常流行的的客戶端,現在各大廠的也都開始基于開發,因為只支持的協程,而默認是使用擴展的,所以開發了,能在不修改源碼的情況下讓協程化。 Guzzle 是一個非常流行的 PHP 的 HTTP 客戶端,現在各大廠的 SDK 也都開始基于 Guzzle 開發,因為 Swoole 只支持 PHP Stream 的協程 Hook ,而 Guzzle 默認是使用 cURL 擴展的,所以 Mix...
摘要:之前過行代碼實現通用協程池今天看了下相關文檔,用也實現了一個,由于沒有的,所以實現的有點簡單,但是實用性還可以,通過工廠函數實現了通用性。官方的協程池是用只能用在。因為協程池代碼層耦合了實例化邏輯。 之前過golang40行代碼實現通用協程池 今天看了下swoole相關文檔,用PHP也實現了一個,由于swoole沒有golang的select,所以實現的有點簡單,但是實用性還可以,通過...
摘要:所以與多線程相比,線程的數量越多,協程性能的優勢越明顯。值得一提的是,在此過程中,只有一個線程在執行,因此這與多線程的概念是不一樣的。 真正有知識的人的成長過程,就像麥穗的成長過程:麥穗空的時候,麥子長得很快,麥穗驕傲地高高昂起,但是,麥穗成熟飽滿時,它們開始謙虛,垂下麥芒。 ——蒙田《蒙田隨筆全集》 上篇論述了關于python多線程是否是雞肋的問題,得到了一些網友的認可,當然也有...
閱讀 2168·2021-11-24 09:39
閱讀 2781·2021-07-29 13:49
閱讀 2322·2019-08-29 14:15
閱讀 2233·2019-08-29 12:40
閱讀 3312·2019-08-26 13:42
閱讀 632·2019-08-26 12:13
閱讀 2065·2019-08-26 11:41
閱讀 3345·2019-08-23 18:32