参考:《Redis入门指南》第4章进阶html
http://book.51cto.com/art/201305/395461.htmredis
4.4.2 使用Redis实现任务队列数组
说到队列很天然就能想到Redis的列表类型,3.4.2节介绍了使用LPUSH和RPOP命令实现队列的概念。若是要实现任务队列,只须要让生产者将任务使用LPUSH命令加入到某个键中,另外一边让消费者不断地使用RPOP命令从该键中取出任务便可。安全
在小白的例子中,完成发邮件的任务须要知道收件地址、邮件主题和邮件正文。因此生产者须要将这三个信息组成对象并序列化成字符串,而后将其加入到任务队列中。而消费者则循环从队列中拉取任务,就像以下伪代码:服务器
到此一个使用Redis实现的简单的任务队列就写好了。不过还有一点不完美的地方:当任务队列中没有任务时消费者每秒都会调用一次RPOP命令查看是否有新任务。若是能够实现一旦有新任务加入任务队列就通知消费者就行了。其实借助BRPOP命令就能够实现这样的需求。oop
BRPOP命令和RPOP命令类似,惟一的区别是当列表中没有元素时BRPOP命令会一直阻塞住链接,直到有新元素加入。如上段代码可改写为:测试
BRPOP命令接收两个参数,第一个是键名,第二个是超时时间,单位是秒。当超过了此时间仍然没有得到新元素的话就会返回nil。上例中超时时间为"0",表示不限制等待的时间,即若是没有新元素加入列表就会永远阻塞下去。网站
当得到一个元素后BRPOP命令返回两个值,分别是键名和元素值。为了测试BRPOP命令,咱们能够打开两个redis-cli实例,在实例A中:spa
brpop:scala
返回值:
键入回车后实例1会处于阻塞状态,这时在实例B中向queue中加入一个元素:
在LPUSH命令执行后实例A立刻就返回告终果:
同时会发现queue中的元素已经被取走:
除了BRPOP命令外,Redis还提供了BLPOP,和BRPOP的区别在与从队列取元素时BLPOP会从队列左边取。具体能够参照LPOP理解,这里再也不赘述。
4.4.3 优先级队列
前面说到了小白博客须要在发布文章的时候向每一个订阅者发送邮件,这一步骤一样可使用任务队列实现。因为要执行的任务和发送确认邮件同样,因此两者能够共用一个消费者。然而设想这样的状况:假设订阅小白博客的用户有1000人,那么当发布一篇新文章后博客就会向任务队列中添加1000个发送通知邮件的任务。若是每发一封邮件须要10秒,所有完成这1000个任务就须要近3个小时。问题来了,假如这期间有新的用户想要订阅小白博客,当他提交完本身的邮箱并看到网页提示他查收确认邮件时,他并不知道向本身发送确认邮件的任务被加入到了已经有1000个任务的队列中。要收到确认邮件,他不得不等待近3个小时。多么糟糕的用户体验!而另外一方面发布新文章后通知订阅用户的任务并非很紧急,大多数用户并不要求有新文章后立刻就能收到通知邮件,甚至延迟一天的时间在不少状况下也是能够接受的。
因此能够得出结论当发送确认邮件和发送通知邮件两种任务同时存在时,应该优先执行前者。为了实现这一目的,咱们须要实现一个优先级队列。
BRPOP命令能够同时接收多个键,其完整的命令格式为BRPOP key [key …] timeout,
按参数 key 的前后顺序依次检查各个列表,弹出第一个非空列表的尾部元素
如BRPOP queue:1 queue:2 0。意义是同时检测多个键,若是全部键都没有元素则阻塞,若是其中有一个键有元素则会从该键中弹出元素。例如,打开两个redis-cli实例,在实例A中:
在实例B中:
则实例A中会返回:
若是多个键都有元素则按照从左到右的顺序取第一个键中的一个元素。咱们先在queue:2和queue:3中各加入一个元素:
而后执行BRPOP命令:
借此特性能够实现区分优先级的任务队列。咱们分别使用queue:confirmation. email和queue:notification.email两个键存储发送确认邮件和发送通知邮件两种任务,而后将消费者的代码改成:
这时一旦发送确认邮件的任务被加入到queue:confirmation.email队列中,不管queue: notification.email还有多少任务,消费者都会优先完成发送确认邮件的任务。
参考:http://book.51cto.com/art/201305/395463.htm
官网说法:
RPOPLPUSH source destination
命令 RPOPLPUSH 在一个原子时间内,执行如下两个动做:
Redis的列表常常被用做队列(queue),用于在不一样程序之间有序地交换消息(message)。一个客户端经过 LPUSH 命令将消息放入队列中,而另外一个客户端经过 RPOP 或者 BRPOP 命令取出队列中等待时间最长的消息。
不幸的是,上面的队列方法是『不安全』的,由于在这个过程当中,一个客户端可能在取出一个消息以后崩溃,而未处理完的消息也就所以丢失。
使用 RPOPLPUSH 命令(或者它的阻塞版本 BRPOPLPUSH )能够解决这个问题:由于它不只返回一个消息,同时还将这个消息添加到另外一个备份列表当中,若是一切正常的话,当一个客户端完成某个消息的处理以后,能够用 LREM 命令将这个消息从备份表删除。
最后,还可以添加一个客户端专门用于监视备份表,它自动地将超过必定处理时限的消息从新放入队列中去(负责处理该消息的客户端可能已经崩溃),这样就不会丢失任何消息了。
经过使用相同的 key 做为 RPOPLPUSH 命令的两个参数,客户端能够用一个接一个地获取列表元素的方式,取得列表的全部元素,而没必要像 LRANGE 命令那样一会儿将全部列表元素都从服务器传送到客户端中(两种方式的总复杂度都是 O(N))。
以上的模式甚至在如下的两个状况下也能正常工做:
这个模式使得咱们能够很容易实现这样一类系统:有 N 个客户端,须要接二连三地对一些元素进行处理,并且处理的过程必须尽量地快。一个典型的例子就是服务器的监控程序:它们须要在尽量短的时间内,并行地检查一组网站,确保它们的可访问性。
注意,使用这个模式的客户端是易于扩展(scala)且安全(reliable)的,由于就算接收到元素的客户端失败,元素仍是保存在列表里面,不会丢失,等到下个迭代来临的时候,别的客户端又能够继续处理这些元素了。
更多: