21-优先级队列
4.4.3 优先级队列
前面说到了小白博客需要在发布文章的时候向每个订阅者发送邮件,这一步骤同样可以使用任务队列实现。由于要执行的任务和发送确认邮件一样,所以二者可以共用一个消费者。然而设想这样的情况:假设订阅小白博客的用户有1000人,那么当发布一篇新文章后博客就会向任务队列中添加1000个发送通知邮件的任务。如果每发一封邮件需要10秒,全部完成这1000个任务就需要近3小时。问题来了,假如这期间有新的用户想要订阅小白博客,当他提交完自己的邮箱并看到网页提示他查收确认邮件时,他并不知道向自己发送确认邮件的任务被加入到了已经有1000个任务的队列中。要收到确认邮件,他不得不等待近3小时。多么糟糕的用户体验!而另一方面发布新文章后通知订阅用户的任务并不是很紧急,大多数用户并不要求有新文章后马上就能收到通知邮件,甚至延迟一天的时间在很多情况下也是可以接受的。
所以可以得出结论当发送确认邮件和发送通知邮件两种任务同时存在时,应该优先执行前者。为了实现这一目的,我们需要实现一个优先级队列。
BRPOP 命令可以同时接收多个键,其完整的命令格式为 BLPOP key [key …] timeout ,如 BLPOP queue:1 queue:2 0 。意义是同时检测多个键,如果所有键都没有元素则阻塞,如果其中有一个键有元素则会从该键中弹出元素。例如打开两个redis-cli实例,在实例A中:
redis A> BLPOP queue:1 queue:2 queue:3
在实例B中:
redis B> LPUSH queue:2 task (integer) 1
则实例A中会返回:
1) "queue:2"
2) "task"
如果多个键都有元素则按照从左到右的顺序取第一个键中的一个元素。我们先在queue:2和queue:3中各加入一个元素:
redis> LPUSH queue:2 task1 1) (integer) 1
redis> LPUSH queue:3 task2 2) (integer) 1
然后执行 BRPOP 命令:
redis> BRPOP queue:1 queue:2 queue:3 0 1) "queue:2"
2) "task1"
借此特性可以实现区分优先级的任务队列。我们分别使用 queue:confirmation. email 和 queue:notification.email 两个键存储发送确认邮件和发送通知邮件两种任务,然后将消费者的代码改为:
loop
$task =
BRPOP queue:confirmation.email,
queue:notification.email,
0
execute($task[1])
这时一旦发送确认邮件的任务被加入到 queue:confirmation.email 队列中,无论 queue: notification.email 还有多少任务,消费者都会优先完成发送确认邮件的任务。