Using mixphp to create multi process asynchronous mail sending

Time:2021-1-28

Note: This is an example of mixphp v1

E-mail sending is a very common requirement. Because the operation of sending e-mail is generally time-consuming, we generally use asynchronous processing to improve the user experience, while asynchronous processing is usually implemented by using message queue.

Due to the lack of multi process development ability, the traditional MVC framework usually uses the same script to execute multiple times to generate multiple processes. Mixhp encapsulates the task executor for multi process development, and users can easily develop highly available multi process applications with perfect functions.

The following shows the development process of an asynchronous mail sending system, involving knowledge points:

  • asynchronous
  • Message queuing
  • Multiprocess
  • Daemons

How to use message queue to realize asynchronous communication

PHP uses message queue, which is usually implemented by middleware

  • redis
  • rabbitmq
  • kafka

This time, we use redis to send asynchronous e-mail. There is a list type in redis data type, which can realize message queue. Use the following command:

//Listed
$redis->lpush($key, $data);
//Out of line
$data = $redis->rpop($key);
//Block out
$data = $redis->brpop($key, 10);

architecture design

In this example, the traditional MVC framework delivers the mail sending requirements, and mixphp multi process performs the sending task.

Mail delivery library selection

In the past, we used to use the email sending library provided by the framework, or download the library shared by other users on the Internet,composerWhen it appears,https://packagist.org/There are a large number of high-quality libraries on. We just need to choose the best one. In this case, we choose swift mail.

Since the sending task is executed by mixphp, swiftmailer is installed in the mixphp project. Execute the following command in the root directory of the project to install it:

composer require swiftmailer/swiftmailer

Producer development

In the requirement of e-mail sending, the producer refers to the party who delivers the sending task, which is usually an interface or web page. This part does not necessarily need to be developed by mixhp. TP, CI and Yii are all OK. Just deliver the task information to the message queue in the interface or web page.

Add the following code to the controller of traditional MVC framework:

Generally, a class library will be installed to use redis in the framework. This example uses native code, which is easy to understand.

//Connection
$redis = new \Redis();
if (!$redis->connect('127.0.0.1', 6379)) {
    throw new \Exception('Redis Connect Failure');
}
$redis->auth('');
$redis->select(0);
//Delivery task
$data = [
    'to'      => ['***@qq.com' => 'A name'],
    'body'    => 'Here is the message itself',
    'subject' => 'The title content',
];
$redis->lpush('queue:email', serialize($data));

Usually, in asynchronous development, a message will be responded to the user immediately after the delivery is completed. Of course, the task is not executed at this time.

Consumer development

In this example, we use mixphp’s multi process development tool taskexecutor to complete this requirement. We usually use resident processes to process queue consumption, so we use taskexecutor’s type_ Daemon type, mode_ Push mode.

Mode of taskexecutor_ There are two processes in push mode

  • Left process: responsible for fetching task data from message queue and releasing it to middle process.
  • Middle process: responsible for sending mail.

PushCommand.php The code is as follows:

<?php

namespace apps\daemon\commands;

use mix\console\ExitCode;
use mix\facades\Input;
use mix\facades\Redis;
use mix\task\CenterProcess;
use mix\task\LeftProcess;
use mix\task\TaskExecutor;

/**
 *Push mode example
 *@ author Liu Jian< coder.liu @ qq.com >
 */
class PushCommand extends BaseCommand
{

    //Configuration information
    const HOST = 'smtpdm.aliyun.com';
    const PORT = 465;
    const SECURITY = 'ssl';
    const USERNAME = '****@email.***.com';
    const PASSWORD = '****';

    //Initialization events
    public function onInitialize()
    {
        parent::onInitialize(); // TODO: Change the autogenerated stub
        //Get program name
        $this->programName = Input::getCommandName();
        //Set pidfile
        $this->pidFile = "/var/run/{$this->programName}.pid";
    }

    /**
     *Access to services
     * @return TaskExecutor
     */
    public function getTaskService()
    {
        return create_object(
            [
                //Classpath
                'class'         => 'mix\task\TaskExecutor',
                //Service name
                'name'          => "mix-daemon: {$this->programName}",
                //Execution type
                'type'          => \mix\task\TaskExecutor::TYPE_DAEMON,
                //Execution mode
                'mode'          => \mix\task\TaskExecutor::MODE_PUSH,
                //Number of left processes
                'leftProcess'   => 1,
                //Number of processes in progress
                'centerProcess' => 5,
                //Task timeout (seconds)
                'timeout'       => 5,
            ]
        );
    }

    //Start up
    public function actionStart()
    {
        //Pretreatment
        if (!parent::actionStart()) {
            return ExitCode::UNSPECIFIED_ERROR;
        }
        //Start up服务
        $service = $this->getTaskService();
        $service->on('LeftStart', [$this, 'onLeftStart']);
        $service->on('CenterStart', [$this, 'onCenterStart']);
        $service->start();
        //Return exit code
        return ExitCode::OK;
    }

    //Left process start event callback function
    public function onLeftStart(LeftProcess $worker)
    {
        try {
            //The model uses a long connection version of the database component, so that the component will automatically help you maintain the connection line
            $queueModel = Redis::getInstance();
            //After the loop ends, the current process will exit, and the main process will restart a new process to continue to execute the task. This is to avoid long-term memory overflow
            for ($j = 0; $j < 16000; $j++) {
                //Get a message from Message Queuing Middleware block
                $data = $queueModel->brpop('queue:email', 10);
                if (empty($data)) {
                    continue;
                }
                list(, $data) = $data;
                //Push the message to the process to process, push has length limit( https://wiki.swoole.com/wiki/page/290.html )
                $worker->push($data, false);
            }
        } catch (\Exception $e) {
            //Take a break to avoid 100% CPU error
            sleep(1);
            //Throw an error
            throw $e;
        }
    }

    //Process start event callback function in
    public function onCenterStart(CenterProcess $worker)
    {
        //After the loop ends, the current process will exit, and the main process will restart a new process to continue to execute the task. This is to avoid long-term memory overflow
        for ($j = 0; $j < 16000; $j++) {
            //Preempt a message from the process message queue
            $data = $worker->pop();
            if (empty($data)) {
                continue;
            }
            //Processing messages
            try {
                //Processing messages,比如:发送短信、发送邮件、微信推送
                var_dump($data);
                $ret = self::sendEmail($data);
                var_dump($ret);
            } catch (\Exception $e) {
                //Fallback data to message queue
                $worker->rollback($data);
                //Take a break to avoid 100% CPU error
                sleep(1);
                //Throw an error
                throw $e;
            }
        }
    }

    //Send mail
    public static function sendEmail($data)
    {
        // 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($data['subject']))
            ->Setfrom ([self:: user name = > '* *'net'])
            ->setTo($data['to'])
            ->setBody($data['body']);
        // Send the message
        $result = $mailer->send($message);
        return $result;
    }

}

test

  1. Start the push resident program in the shell.
[[email protected] bin]# ./mix-daemon push start
mix-daemon 'push' start successed.
  1. Call the interface to drop the task to the message queue.

At this time, the shell terminal will print:

Test email received successfully:

MixPHP

GitHub: https://github.com/mixstart/m…
Official website:http://www.mixphp.cn/

Recommended Today

How to Build a Cybersecurity Career

Original text:How to Build a Cybersecurity Career How to build the cause of network security Normative guidelines for building a successful career in the field of information security fromDaniel miesslerstayinformation safetyCreated / updated: December 17, 2019 I’ve been doing itinformation safety(now many people call it network security) it’s been about 20 years, and I’ve spent […]