如何加快WordPress邮件发送的速度?

内容已更新到: http://www.loveyu.org/3811.html


  先挖个坑放在这里,回去的路上好好想想。
  事情是这样的,现在评论的时候有个邮件发送过程,这个步骤太慢了,得想个什么办法解决。难道得弄个异步队列么?好好考虑下,明天实现下。


  挖坑结束,采用类似RPC的形式来加快处理流程。通过一个插件来替换wp_mail()的函数,对最后的一步send()操作进行拦截,然后把将PHPmailer对象存储到数据库中,调用远程服务,程序返回,然后开始邮件发送操作。

替换wp_mail()函数

  函数主要包含了几个功能,其一替换原函数,其二添加对象到数据库,其三修改状态,其四通知RPC服务(最重要为此步)

if(!function_exists('wp_mail')) :/**
 * 该函数为对原始函数的覆盖函数
 * @param string|array $to 收信人
 * @param string       $subject 邮件标题
 * @param string       $message 邮件正文
 * @param string       $headers 头信息
 * @param array        $attachments 附件列表
 * @return bool
 */
{
    function wp_mail($to, $subject, $message, $headers = '', $attachments = array()){
        $atts = apply_filters('wp_mail', compact('to', 'subject', 'message', 'headers', 'attachments'));
        //..........................原始文件为wp-include/pluggable.php wp_mail
        //$phpmailer->Send();//从这里开始替换
        //将对象存入数据库
        $status = quick_mail_save($insert_id, $phpmailer, $to, $subject);
        if(!$status){
            try{
                if($phpmailer->send()){
                    quick_mail_set_flag($insert_id, 5);
                    return true;
                } else{
                    return false;
                }
            } catch(Exception $ex){
                return false;
            }
        }
        return true;
    }
}
endif;


/**
 * 启用数据库前对表的创建操作
 */

function quick_mail_active(){
    /**
     * @var WPDB $wpdb
     */

    global $wpdb;
    $wpdb->query("DROP TABLE IF EXISTS `{$wpdb->prefix}quick_mail`");
    $wpdb->query("CREATE TABLE `{$wpdb->prefix}quick_mail` (
  `qm_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `qm_subject` varchar(255) NOT NULL COMMENT '邮件主题',
  `qm_time` int(10) unsigned NOT NULL COMMENT '发送时间',
  `qm_to` varchar(255) NOT NULL COMMENT '收信人',
  `qm_obj` text NOT NULL COMMENT 'phpmailer 序列化对象',
  `qm_status` tinyint(3) unsigned DEFAULT '0' COMMENT '状态,0为未执行状态,1为执行状态,2为完成状态, 3为发送失败, 4为发送异常,5直接发送成功',
  PRIMARY KEY (`qm_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;"
);
}

/**
 * 设置邮件的状态
 * @param int $id     邮件ID
 * @param int $status 邮件状态 0-5
 * @return bool 是否设置成功
 */

function quick_mail_set_flag($id, $status){
    $id = (int)$id;
    if($id < 1){
        return false;
    }
    /**
     * @var WPDB $wpdb
     */

    global $wpdb;
    $rt = $wpdb->update("{$wpdb->prefix}quick_mail", ['qm_status' => $status], ['qm_id' => $id]);
    return $rt > 0;
}

/**
 * 将邮件信息保存到数据库中
 * @param int       $insert_id
 * @param PHPMailer $obj
 * @param string    $to    邮件发送的对象
 * @param string    $title 邮件标题
 * @return bool 是否成功发送,如果失败会返回原函数继续执行
 */

function quick_mail_save(&$insert_id, PHPMailer $obj, $to, $title){
    if(is_array($to)){
        $to = implode(",", $to);
    }
    $insert_id = NULL;
    /**
     * @var WPDB $wpdb
     */

    global $wpdb;
    $ret = $wpdb->insert("{$wpdb->prefix}quick_mail", [
        'qm_subject' => $title,
        'qm_to' => $to,
        'qm_time' => time(),
        'qm_obj' => base64_encode(serialize($obj))
    ]);
    if(!$ret){
        return false;
    }
    $insert_id = $wpdb->insert_id;
    return $wpdb->insert_id > 0 && quick_mail_notify($wpdb->insert_id);
}

/**
 * 通知邮件服务有新邮件需要发送
 * @param string $id
 * @return bool
 */

function quick_mail_notify($id){
    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    if(!$socket){
        return false;
    }
    if(!socket_connect($socket, "127.0.0.1", "27889")){
        return false;
    }
    if(!socket_write($socket, "{$id}")){
        socket_close($socket);
        return false;
    }
    $flag = false;
    $buff = socket_read($socket, 1024);    //服务返回的状态特别短,因此一次读取是足够的
    $buff = strtolower(trim($buff));
    if($buff == "ok"){
        $flag = true;
    }
    socket_close($socket);
    return $flag;
}

register_activation_hook(__FILE__, 'quick_mail_active');//插件激活操作

Python 实现服务功能

  py不怎么熟,就基本没怎么写注释,相对还是比较简单的。唯一的不足就是不知道啥情况日志文件死活不输出。当然这里并没有使用Python进行邮件发送,当然要发送也绝对是可以的,只是感觉没什么必要而已。利用现有的PHPmailer更方便。

import socket
import threading
import datetime
import os

BUF_SIZE = 1024
PHP_SCRIPT = "wordpress\\quick_mail.php" ###PHP执行程序


def write_log(msg):
    time = datetime.datetime.now()
    time = time.strftime('%Y-%m-%d %H:%M:%S')
    print("[%s] %s" % (time, msg))


class RunPHP(threading.Thread):
    def __init__(self, client, address):
        threading.Thread.__init__(self)
        self.client = client
        self.address = address

    def send_mail(self, qm_id):
        write_log("%s:%s run script: %s" % (self.address[0], self.address[1], qm_id))
        write_log(
            "%s:%s output: %s" % (self.address[0], self.address[1], os.popen("php %s %s" % (PHP_SCRIPT, qm_id)).read()))

    def run(self):
        flag = False
        data = self.client.recv(BUF_SIZE)
        qm_id = 0
        if data:
            qm_id = int(bytes.decode(data, "utf-8"))
            if qm_id > 0:
                flag = True
        if flag:
            self.client.send('ok'.encode())
        else:
            self.client.send('error'.encode())
        self.client.close()
        if qm_id > 0:
            self.send_mail(qm_id)


class QuickMail(threading.Thread):
    def __init__(self, ip, port):
        threading.Thread.__init__(self)
        self.port = port
        self.ip = ip
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind((ip, port))
        self.socket.listen(10)

    def run(self):
        write_log("service startup on %s:%s" % (self.ip, self.port))
        while True:
            client, address = self.socket.accept()
            RunPHP(client, address).start()


lst = QuickMail("127.0.0.1", 27889)
lst.start()

对邮件队列查询并发送邮件

  这个就相对容易看懂了,有时候我相对纠结,为啥不用WP的配置文件。那货加载了一大堆东西,感觉用不着,所以就不用管它了,每次直接PDO连接,方便快捷省事。

<?php
/**
 * 快速邮件发送执行脚本
 */

$qm_id = isset($argv[1]) ? $argv[1] : NULL;
if($qm_id === NULL){
    die("param error");
}
$qm_id = (int)$qm_id;
if($qm_id < 1){
    die("mail id read error!");
}
try{
    $pdo = new PDO("mysql:host=127.0.0.1;dbname=worpress", "root", "123456");
}catch (Exception $ex){
    die($ex->getMessage());
}
$stmt = $pdo->prepare("select `qm_obj` from `wp_quick_mail` where `qm_id`={$qm_id} and `qm_status`=0");
if(!$stmt->execute()){
    die("read database error");
}
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt->closeCursor();
set_flag($pdo, $qm_id, 1);
if(!isset($data[0]['qm_obj'])){
    die("read mail error:" . $qm_id);
}
require_once __DIR__ . '/wp-includes/class-phpmailer.php';
require_once __DIR__ . '/wp-includes/class-smtp.php';
/**
 * @var $mail PHPMailer
 */

$mail = unserialize(base64_decode($data[0]['qm_obj']));
try{
    if($mail->send()){
        set_flag($pdo, $qm_id, 2);
        die("send ok:" . $qm_id);
    } else{
        set_flag($pdo, $qm_id, 3);
        die("send fail:" . $qm_id);
    }
} catch(Exception $ex){
    set_flag($pdo, $qm_id, 4);
    die($ex->getMessage());
}

function set_flag(PDO $pdo, $qm_id, $status){
    $status = (int)$status;
    $qm_id = (int)$qm_id;
    $stmt = $pdo->prepare("update `wp_quick_mail` set `qm_status` = {$status} where `qm_id`={$qm_id}");
    if(!$stmt->execute()){
        die("mail set status error.ID:" . $qm_id . ",status:" . $status);
    }
}

运行

  最后,后台运行python程序,然后启用插件,一切就绪,不用担心失败。失败了会自动调用原有的程序,还是很靠谱的。

41条评论在“如何加快WordPress邮件发送的速度?”

    1. 一个小站用不到PDO缓存之类的吧。这个是用异步的形式发送的邮件,也可以用队列的形式。缓存是减少数据库连接和缓解磁盘压力吧。

          1. 这样的 http://image16.poco.cn/mypoco/myphoto/20141104/10/17489809920141104100657032.png

            http://image16.poco.cn/mypoco/myphoto/20141104/10/17489809920141104100712080.png?791x845_130

                1. 还有,你网站现在似乎不能访问,是不是和这点有关,再其次图片是不是用了cdn或其他缓存插件

写下你最简单的想法