成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Mix PHP V2 實(shí)例:協(xié)程池異步郵件發(fā)送守護(hù)程序

lauren_liuling / 1803人閱讀

摘要:消費(fèi)者開(kāi)發(fā)使用本例時(shí),請(qǐng)確保你使用的編譯時(shí)開(kāi)啟了本例我們采用的守護(hù)程序協(xié)程池來(lái)完成一個(gè)超高性能的郵件發(fā)送程序。

去年 Mix PHP V1 發(fā)布時(shí),我寫了一個(gè)多進(jìn)程的郵件發(fā)送實(shí)例: 使用 mixphp 打造多進(jìn)程異步郵件發(fā)送,今年 Mix PHP V2 發(fā)布,全面的協(xié)程支持讓我們可以使用一個(gè)進(jìn)程就可達(dá)到之前多個(gè)進(jìn)程都無(wú)法達(dá)到的更高 IO 性能,所以今天重寫一個(gè)協(xié)程池版本的郵件發(fā)送實(shí)例。

郵件發(fā)送是很常見(jiàn)的需求,由于發(fā)送郵件的操作一般是比較耗時(shí)的,所以我們一般采用異步處理來(lái)提升用戶體驗(yàn),而異步通常我們使用消息隊(duì)列來(lái)實(shí)現(xiàn)。

下面演示一個(gè)異步郵件發(fā)送系統(tǒng)的開(kāi)發(fā)過(guò)程,涉及知識(shí)點(diǎn):

異步

消息隊(duì)列

守護(hù)進(jìn)程

協(xié)程池

如何使用消息隊(duì)列實(shí)現(xiàn)異步

PHP 使用消息隊(duì)列通常是使用中間件來(lái)實(shí)現(xiàn),常用的消息中間件有:

redis

rabbitmq

kafka

本次我們選用 Redis 來(lái)實(shí)現(xiàn)異步郵件發(fā)送,Redis 的數(shù)據(jù)類型中有一個(gè) list 類型,可實(shí)現(xiàn)消息隊(duì)列,使用以下命令:

// 入列
$redis->lpush($key, $data);
// 出列
$data = $redis->rpop($key);
// 阻塞出列
$data = $redis->brpop($key, 10);
架構(gòu)設(shè)計(jì)

本實(shí)例由傳統(tǒng) MVC 框架投遞郵件發(fā)送需求(生產(chǎn)者),Mix PHP 編寫的守護(hù)程序執(zhí)行發(fā)送任務(wù)(消費(fèi)者)。

郵件發(fā)送庫(kù)選型

以往我們通常使用框架提供的郵件發(fā)送庫(kù),或者網(wǎng)上下載別的用戶分享的庫(kù),composer 出現(xiàn)后,https://packagist.org/ 上有大量?jī)?yōu)質(zhì)的庫(kù),我們只需選擇一個(gè)最好的即可,本例選擇 swiftmailer。

由于發(fā)送任務(wù)是由 Mix PHP 執(zhí)行,所以 swiftmailer 是安裝在 Mix PHP 項(xiàng)目中,在項(xiàng)目根目錄中執(zhí)行以下命令安裝:

composer require swiftmailer/swiftmailer
生產(chǎn)者開(kāi)發(fā)

在郵件發(fā)送這個(gè)需求中生產(chǎn)者是指投遞發(fā)送任務(wù)的一方,這一方通常是一個(gè)接口或網(wǎng)頁(yè),這個(gè)部分并不一定需 Mix PHP 開(kāi)發(fā),TP、CI、YII 這些都可以,只需在接口或網(wǎng)頁(yè)中把任務(wù)信息投遞到消息隊(duì)列中即可。

在傳統(tǒng) MVC 框架的控制器中增加如下代碼:

通??蚣苤惺褂?Redis 會(huì)安裝一個(gè)類庫(kù)來(lái)使用,本例使用原生代碼,便于理解。
// 連接
$redis = new Redis();
if (!$redis->connect("127.0.0.1", 6379)) {
    throw new Exception("Redis connect failed.");
}
$redis->auth("");
$redis->select(0);
// 投遞任務(wù)
$data = [
    "to"      => "***@qq.com",
    "body"    => "The message content",
    "subject" => "The title content",
];
$redis->lpush("queue:email", serialize($data));

通常異步開(kāi)發(fā)中,投遞完成后就會(huì)立即響應(yīng)一個(gè)消息給用戶,當(dāng)然此時(shí)該任務(wù)并沒(méi)有在生產(chǎn)者中執(zhí)行,而是待消息被消費(fèi)者獲取后才執(zhí)行。

消費(fèi)者開(kāi)發(fā)
使用本例時(shí),請(qǐng)確保你使用的 Swoole 編譯時(shí)開(kāi)啟了 openssl

本例我們采用 Mix PHP V2 的守護(hù)程序、協(xié)程池來(lái)完成一個(gè)超高性能的郵件發(fā)送程序。

因?yàn)槲覀兪情_(kāi)發(fā)一個(gè)守護(hù)程序,所以我們?cè)?applications/daemon 模塊中開(kāi)發(fā),首先我們?cè)谂渲?applications/daemon/config/main.php 中注冊(cè)一個(gè)命令:

// 命令
"commands"         => [

    "mailer" => ["Mailer", "description" => "Mailer daemon."],

],

注冊(cè)的命令中指定的 Mailer 命令類,接下來(lái)我們編寫一個(gè) MailerCommand 類:

applications/daemon/src/Commands/MailerCommand.php

 */
class MailerCommand
{

    /**
     * 退出
     * @var bool
     */
    public $quit = false;

    /**
     * 主函數(shù)
     */
    public function main()
    {
        // 捕獲信號(hào)
        ProcessHelper::signal([SIGHUP, SIGINT, SIGTERM, SIGQUIT], function ($signal) {
            $this->quit = true;
            ProcessHelper::signal([SIGHUP, SIGINT, SIGTERM, SIGQUIT], null);
        });
        // 協(xié)程池執(zhí)行任務(wù)
        xgo(function () {
            $maxWorkers = 20;
            $maxQueue   = 20;
            $jobQueue   = new Channel($maxQueue);
            $dispatch   = new Dispatcher([
                "jobQueue"   => $jobQueue,
                "maxWorkers" => $maxWorkers,
            ]);
            $dispatch->start(MailerWorker::class);
            // 投放任務(wù)
            $redis = app()->redisPool->getConnection();
            while (true) {
                if ($this->quit) {
                    $dispatch->stop();
                    return;
                }
                try {
                    $data = $redis->brPop(["queue:email"], 3);
                } catch (Throwable $e) {
                    $dispatch->stop();
                    return;
                }
                if (!$data) {
                    continue;
                }
                $data = array_pop($data); // brPop命令最后一個(gè)鍵才是值
                $jobQueue->push($data);
            }
        });
    }

}
$data = $redis->brPop(["queue:email"], 3); 外部的異常捕獲可得知,當(dāng) Redis 連接出錯(cuò)時(shí),比如 Redis 重啟、連接異常時(shí)協(xié)程池會(huì)安全退出,也就是說(shuō)當(dāng)進(jìn)程異常退出后用戶需使用 supervisor、pm2 等工具重啟守護(hù)進(jìn)程。

上面是一個(gè) Mix PHP 協(xié)程池的使用代碼,基本可以直接復(fù)制使用,框架默認(rèn)包含了協(xié)程池的 Demo,本次實(shí)例只是修改了協(xié)程池的 Worker,本命令主要是完成從 Redis 隊(duì)列中獲取消息然后 push 到 jobQueue 中,jobQueue 中的數(shù)據(jù)會(huì)被 20 個(gè) Worker 實(shí)例中某一個(gè)搶占后并行執(zhí)行,本例的郵件發(fā)送代碼邏輯就在 MailerWorker 類中:

applications/daemon/src/Libraries/MailerWorker.php

 */
class MailerWorker extends AbstractWorker implements WorkerInterface
{

    /**
     * 郵件發(fā)送器
     * @var Mailer
     */
    public $mailer;

    /**
     * 初始化事件
     */
    public function onInitialize()
    {
        parent::onInitialize(); // TODO: Change the autogenerated stub
        // 實(shí)例化一些需重用的對(duì)象
        $this->mailer = new Mailer();
    }

    /**
     * 處理
     * @param $data
     */
    public function handle($data)
    {
        // TODO: Implement handle() method.
        $data = unserialize($data);
        if (empty($data)) {
            return;
        }
        try {
            $this->mailer->send($data["to"], $data["subject"], $data["body"]);
            app()->log->info("Mail sent successfully:to {to} subject {subject}", $data);
        } catch (Throwable $e) {
            app()->log->error("Mail failed to send:to {to} subject {subject} error {error}", array_merge($data, ["error" => $e->getMessage()]));
        }
    }

}

由以上代碼可見(jiàn),Worker 在初始化時(shí),新增了一個(gè) Mailer 類的屬性,當(dāng) jobQueue 消息投遞過(guò)來(lái)時(shí)消息會(huì)傳遞到 handle 方法,在該方法中使用 Mailer 類的實(shí)例完成郵件發(fā)送任務(wù),所以我們要編寫了一個(gè) Mailer 發(fā)送程序:

applications/daemon/src/Libraries/Mailer.php

 */
class Mailer
{

    /**
     * 配置信息
     */
    const HOST = "smtpdm.aliyun.com";
    const PORT = 465;
    const SECURITY = "ssl";
    const USERNAME = "***";
    const PASSWORD = "***";

    /**
     * Mailer constructor.
     */
    public function __construct()
    {
        // 開(kāi)啟協(xié)程鉤子
        Coroutine::enableHook();
    }

    /**
     * 發(fā)送
     * @param $to
     * @param $subject
     * @param $body
     * @return int
     */
    public function send($to, $subject, $body)
    {
        // Create the Transport
        $transport = (new Swift_SmtpTransport(self::HOST, self::PORT, self::SECURITY))
            ->setUsername(self::USERNAME)
            ->setPassword(self::PASSWORD);
        // Create the Mailer using your created Transport
        $mailer = new Swift_Mailer($transport);
        // Create a message
        $message = (new Swift_Message($subject))
            ->setFrom([self::USERNAME => "**網(wǎng)"])
            ->setTo($to)
            ->setBody($body);
        // Send the message
        return $mailer->send($message);
    }

}

在 Mailer 發(fā)送程序中我們使用了前面 composer 安裝的 swiftmailer 庫(kù)來(lái)發(fā)送郵件,以上就完成了全部的代碼邏輯,現(xiàn)在我們開(kāi)始測(cè)試。

先啟動(dòng)消費(fèi)者守護(hù)程序:

[root@localhost bin]# ./mix-daemon mailer

將上文的生產(chǎn)者腳本命名為 push.php 然后在 CLI 中執(zhí)行 (開(kāi)一個(gè)新終端):

[root@localhost bin]# php /tmp/push.php

消費(fèi)者守護(hù)程序結(jié)果:

[root@localhost bin]# ./mix-daemon mailer
[info] 2019-04-15 11:48:36 [message] Mail sent successfully:to ***@qq.com subject The title content

命令行終端打印了發(fā)送成功的日志,發(fā)送完成。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/31259.html

相關(guān)文章

  • Mix PHP V2 實(shí)例:AliCloud 短信協(xié)程池異步發(fā)送守護(hù)程序

    摘要:前些時(shí)間我們發(fā)布了實(shí)例協(xié)程池異步郵件發(fā)送守護(hù)程序范例,這一次我們提供一個(gè)使用大廠通過(guò)協(xié)程化來(lái)并行執(zhí)行短信發(fā)送任務(wù),本文是一個(gè)代碼簡(jiǎn)單性能極強(qiáng)的范例。 前些時(shí)間我們發(fā)布了 Mix PHP V2 實(shí)例:協(xié)程池異步郵件發(fā)送守護(hù)程序 范例,這一次我們提供一個(gè)使用大廠 SDK 通過(guò) Swoole Hook 協(xié)程化來(lái)并行執(zhí)行短信發(fā)送任務(wù),本文是一個(gè)代碼簡(jiǎn)單、IO 性能極強(qiáng)的范例。 請(qǐng)先升級(jí)到 m...

    qc1iu 評(píng)論0 收藏0
  • Mix PHP V2 新特性:協(xié)程、定時(shí)器

    摘要:主函數(shù)查詢數(shù)據(jù)不手動(dòng)釋放的連接不會(huì)歸還連接池,會(huì)在析構(gòu)時(shí)丟棄執(zhí)行結(jié)果為,說(shuō)明是并行執(zhí)行的。主函數(shù)查詢數(shù)據(jù)即便拋出了異常,仍然能執(zhí)行到,沒(méi)有導(dǎo)致內(nèi)的一直處于阻塞狀態(tài)。主函數(shù)一次性定時(shí)持續(xù)定時(shí)停止定時(shí) 協(xié)程 Mix PHP V2 基于 Swoole 4 的 PHP Stream Hook 協(xié)程技術(shù)開(kāi)發(fā),協(xié)程使用方式與 Golang 幾乎一致,包括框架封裝的協(xié)程池、連接池、命令行處理都大量參...

    Nosee 評(píng)論0 收藏0
  • Mix PHP V2 生態(tài):讓 Guzzle 支持 Swoole 的 Hook 協(xié)程

    摘要:是一個(gè)非常流行的的客戶端,現(xiàn)在各大廠的也都開(kāi)始基于開(kāi)發(fā),因?yàn)橹恢С值膮f(xié)程,而默認(rèn)是使用擴(kuò)展的,所以開(kāi)發(fā)了,能在不修改源碼的情況下讓協(xié)程化。 Guzzle 是一個(gè)非常流行的 PHP 的 HTTP 客戶端,現(xiàn)在各大廠的 SDK 也都開(kāi)始基于 Guzzle 開(kāi)發(fā),因?yàn)?Swoole 只支持 PHP Stream 的協(xié)程 Hook ,而 Guzzle 默認(rèn)是使用 cURL 擴(kuò)展的,所以 Mix...

    Flands 評(píng)論0 收藏0
  • swoole通用協(xié)程池的實(shí)現(xiàn)

    摘要:之前過(guò)行代碼實(shí)現(xiàn)通用協(xié)程池今天看了下相關(guān)文檔,用也實(shí)現(xiàn)了一個(gè),由于沒(méi)有的,所以實(shí)現(xiàn)的有點(diǎn)簡(jiǎn)單,但是實(shí)用性還可以,通過(guò)工廠函數(shù)實(shí)現(xiàn)了通用性。官方的協(xié)程池是用只能用在。因?yàn)閰f(xié)程池代碼層耦合了實(shí)例化邏輯。 之前過(guò)golang40行代碼實(shí)現(xiàn)通用協(xié)程池 今天看了下swoole相關(guān)文檔,用PHP也實(shí)現(xiàn)了一個(gè),由于swoole沒(méi)有g(shù)olang的select,所以實(shí)現(xiàn)的有點(diǎn)簡(jiǎn)單,但是實(shí)用性還可以,通過(guò)...

    wanghui 評(píng)論0 收藏0
  • PHP回顧之協(xié)程

    摘要:本文先回顧生成器,然后過(guò)渡到協(xié)程編程。其作用主要體現(xiàn)在三個(gè)方面數(shù)據(jù)生成生產(chǎn)者,通過(guò)返回?cái)?shù)據(jù)數(shù)據(jù)消費(fèi)消費(fèi)者,消費(fèi)傳來(lái)的數(shù)據(jù)實(shí)現(xiàn)協(xié)程。解決回調(diào)地獄的方式主要有兩種和協(xié)程。重點(diǎn)應(yīng)當(dāng)關(guān)注控制權(quán)轉(zhuǎn)讓的時(shí)機(jī),以及協(xié)程的運(yùn)作方式。 轉(zhuǎn)載請(qǐng)注明文章出處: https://tlanyan.me/php-review... PHP回顧系列目錄 PHP基礎(chǔ) web請(qǐng)求 cookie web響應(yīng) sess...

    Java3y 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<