很多博客网站中都会有发送邮件这个功能,发送邮件是一个双向数据的交互过程,如果使用单线程实现,很难避免会等待很长时间的情况出现,但碍于某些框架或语言不容易实现多线程,这时可以通过进程来实现。设想一个进程能够完成发邮件的功能,那么只要通知这个进程向指定的地址发送邮件就可以了。

        通知的过程可以借助任务队列来实现。与任务队列交互的类有两种:一类是生产者,另一类是消费者。生产者会将需要的任务放入任务队列中,而消费者则不断从任务队列中读取任务信息并执行。

任务队列的优点:

1、松耦合

        生产者和消费者不需知道彼此的实现细节,只要根据事先制定的协议,就可以有条不紊执行。

2、易扩展

multiprocessing 任务队列处理完成后返回数据_伪代码

 

        Redis的任务队列实现是基于列表类型的基础上。完成发邮件的任务,生产者需要将邮件信息组成对象并序列化成字符串,然后将其加入到任务队列中,而消费者则需要循环遍历,从队列中拉取任务,伪代码如下:

while(1){
    $task = RPOP queue
    if($task != null){
        execute($task);
    }else{
        wait some second;//以免频繁访问
    }
}

        正如上面所示,如果使用RPOP命令,每次循环,至少都要等待一定时间,为了能够简化,并且一检测到有新任务,就可以马上被拉取,可以使用BRPOP命令,改进后的伪代码为:

while(1){
    $task = BRPOP queue 0

    execute($task);
}

BRPOP命令接收两个参数,第一个是键名,第二个是超时时间,单位是秒。伪代码那里设置为0,表示如果没有新元素加入列表,就一直处于等待状态。

下面是cmd窗口使用,需要开启两个以上的cmd窗口:

multiprocessing 任务队列处理完成后返回数据_等待状态_02

输入命令后,客户端一直处于等待状态,

multiprocessing 任务队列处理完成后返回数据_任务队列_03

multiprocessing 任务队列处理完成后返回数据_任务队列_04

在另一个客户端放入信息,原先客户端马上读取任务信息,并退出等待状态。

        现在又假设,博客网站还有订阅功能,这一功能也可以用任务列表实现,因为要执行的任务跟发送确认邮件一样,所以可以共用一个消费者。但是,这就出现问题了,如果现在有1000个人订阅了一位大神的博客,某一时刻这位大神发布了一篇文章,那么将会有1000封邮件加入到任务队列里面,如果每一封邮件耗时3秒,那么1000封就需要将近一个小时。在这一个小时期间,刚好有新用户想要订阅大神博客,当他提交自己的邮箱,等待查收确认邮件时,他就不得不等待一个小时的时间,因为他的任务被安排在这1000封邮件之后。

        为了解决上述这种情况,可以使用BLPOP(BRPOP)命令实现优先级队列。BLPOP可以同时监测多个键,只要其中一个键有任务,就会弹出元素,如果多个键同时都具有任务,那么优先弹出最左边的键的元素。

例子1:

先启动检测,

multiprocessing 任务队列处理完成后返回数据_伪代码_05

再在另一个客户端放入任务,

multiprocessing 任务队列处理完成后返回数据_任务队列_06

右边已检测到queue2有任务,马上弹出,第一个信息表示键,第二个信息表示键的元素。

例子2:

先在键里面放入元素,

multiprocessing 任务队列处理完成后返回数据_等待状态_07

再启动检测,

multiprocessing 任务队列处理完成后返回数据_任务队列_08

每次执行检测,只会弹出一个元素(这就是伪代码使用循环的原因),而且还是从左到右按顺序取值。连续执行了三遍检测命令,到了第三次就进入等待状态,因为前两次已经将放进去的元素都弹出来了。

        按照这样的思路,可以分别使用键名queue:confirmation:email和queue:notification:email来存储确认邮件和通知邮件的信息,一旦都有任务加入,优先弹出queue:confirmation:email的元素。