很多博客网站中都会有发送邮件这个功能,发送邮件是一个双向数据的交互过程,如果使用单线程实现,很难避免会等待很长时间的情况出现,但碍于某些框架或语言不容易实现多线程,这时可以通过进程来实现。设想一个进程能够完成发邮件的功能,那么只要通知这个进程向指定的地址发送邮件就可以了。
通知的过程可以借助任务队列来实现。与任务队列交互的类有两种:一类是生产者,另一类是消费者。生产者会将需要的任务放入任务队列中,而消费者则不断从任务队列中读取任务信息并执行。
任务队列的优点:
1、松耦合
生产者和消费者不需知道彼此的实现细节,只要根据事先制定的协议,就可以有条不紊执行。
2、易扩展
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窗口:
输入命令后,客户端一直处于等待状态,
在另一个客户端放入信息,原先客户端马上读取任务信息,并退出等待状态。
现在又假设,博客网站还有订阅功能,这一功能也可以用任务列表实现,因为要执行的任务跟发送确认邮件一样,所以可以共用一个消费者。但是,这就出现问题了,如果现在有1000个人订阅了一位大神的博客,某一时刻这位大神发布了一篇文章,那么将会有1000封邮件加入到任务队列里面,如果每一封邮件耗时3秒,那么1000封就需要将近一个小时。在这一个小时期间,刚好有新用户想要订阅大神博客,当他提交自己的邮箱,等待查收确认邮件时,他就不得不等待一个小时的时间,因为他的任务被安排在这1000封邮件之后。
为了解决上述这种情况,可以使用BLPOP(BRPOP)命令实现优先级队列。BLPOP可以同时监测多个键,只要其中一个键有任务,就会弹出元素,如果多个键同时都具有任务,那么优先弹出最左边的键的元素。
例子1:
先启动检测,
再在另一个客户端放入任务,
右边已检测到queue2有任务,马上弹出,第一个信息表示键,第二个信息表示键的元素。
例子2:
先在键里面放入元素,
再启动检测,
每次执行检测,只会弹出一个元素(这就是伪代码使用循环的原因),而且还是从左到右按顺序取值。连续执行了三遍检测命令,到了第三次就进入等待状态,因为前两次已经将放进去的元素都弹出来了。
按照这样的思路,可以分别使用键名queue:confirmation:email和queue:notification:email来存储确认邮件和通知邮件的信息,一旦都有任务加入,优先弹出queue:confirmation:email的元素。