简单的介绍下消息队列,使用消息队列首先咱们得有一个队列,那么这个队列以前讲过就是先进先出的一个数据结构;那么有了队列之后咱们还须要有人在队列里面放东西,那么这个放东西的人咱们称之为生产者;有了生产者对应的须要一个消费者,没有消费者这个队列满了就会溢出。css

简单队列实现

redis 消费者 redis生产者消费者模式_数据

那么咱们Redis恰好有一个数据类型符合这个就是List。list能够实现队列(先进先出)和栈(先进后出),那么这个list又有两种插入数据的方式:头插法和尾插法。因此咱们今天使用的结构是队列,使用尾插法,关键的命令有rpush(尾插)和lpop(头部获取)。以下图所示:

git

redis 消费者 redis生产者消费者模式_redis当消息队列服务器_02

> rpush squeue zhangsan lisi mango    #插入队列(integer) 3> lpop squeue    #获取队列"zhangsan"> rpush squeue wangwu(integer) 3> lpop squeue"lisi"> lpop squeue"mango"> lpop squeue"wangwu"> lpop squeue    #空队列(nil)

上面是rpush/lpop结合使用的例子。还可使用lpush/rpop结合使用,效果是同样的。github

注意:咱们这里思考一个问题,在我们实际开发中咱们获取队列数据的时候若是这个队列里面没有任何值了,咱们会一直pop,这样咱们的程序出现了一个死循环,并且此时的redis会不断的处理服务器的pop指令使以内存增高。redis

此时mango灵光一闪,每次pop的时候判断,若是队列有值咱们获取这个值进行操做,若是队列是空队列,那么咱们此时休息三秒钟再请求,emmm,加鸡腿。shell

> lpop squeue    #空队列(nil).......sleep 3s old> lpop squeue...

队列阻塞

经过后端程序控制redis服务器休息时间是一个好办法,可是此处有一个问题,若是说服务器在休眠的时候队列忽然进来一个值,而此时须要及时反应获取这个值该如何实现呢?后端

固然,redis早就考虑到这个问题,so提供了一个叫作队列阻塞读,其命令blpop和brpop,就是lpop和rpop的阻塞读方法

bash

redis 消费者 redis生产者消费者模式_redis 消费者_03

blpop [第一个参数:key]... [第二个参数:time]key能够有多个key等待time就是阻塞时间,单位默认为秒

嗯,看似完美的解决了上面的方法服务器

问题又来了,若是说此处设置的时间稍微长一点,阻塞请求的客户端链接再多一点那么会出现下一个问题,那就是空闲链接问题。若是线程一直阻塞在哪里,Redis的客户端链接就成了闲置链接,闲置太久,服务器通常会主动断开链接,减小闲置资源占用。这个时候blpop/brpop会抛出异常来。微信

因此,鱼与熊掌不可兼得,开发者当注意此处须要捕获异常,而后从新请求。数据结构

延迟队列

上面咱们介绍了阻塞队列,深知空闲链接会被回收出现异常问题,那么咱们可不能够实现延迟队列,我提早一段时间好比5秒获取队列中的元素,当元素记录的时间到达了我再去执行这个值,而后又提早5秒时间去获取队列中的值,依次反复。便可保证我在某一时刻去执行队列中对应时间的值。

咱们来分析,首先保证队列是一个有序的咱们才能依次执行,这里咱们使用ZSet由于它带有排序且不重复,保证客户端没有提交重复数据,那么值保证了,这个排序如何设计呢?咱们不能使用'yyyyMMddHHmmssSSS'这种,第一个不适应这个排序类型可能会超出,第二就是每次转换会带来运算转换的消耗,全部这里咱们使用时间戳。那么zset提供一个获取某段存在数据指令zrangebyscore,这个指令可以获取到咱们想要的数据,而后经过zrem删除zset里面的值便可完成消费。

####假设当前时间戳是0> zadd dqueue 4 zhangsan 8 lisi 12 mango    #添加元素(integer) 3####提早获取后5秒的数据> zrangebyscore dqueue -inf 5 withscores    #提早获取5秒内的数据1) "zhangsan"    #值2) 4.0           #当前排序索引> zrem dqueue zhangsan     #消费后删除元素1####当前时间戳来到了5秒,提早5秒获取就加上这个> zrangebyscore dqueue -inf 10 withscores    #获取10之内的数据,轮训调用1) "lisi"2) 8.0

写在最后:

咱们首先实现简单的生成消费模式,针对这种简单轮询咱们经过有数据和无数据让这个轮询实现休息策略来优化,需求更改须要及时响应生产者的数据咱们使用了阻塞队列来减小响应延时,经过分析咱们又发现阻塞队列会被回收的问题,咱们又双叒叕进行了优化,经过zset来实现延迟队列。

你觉得这样就万无一失了,其实还存在一个问题那就是不能保证原子性,咱们的zrangebyscore和zrem不能在同一原子内执行,这里只是针对的一个客户端,若是有多个客户端执行那就会出现屡次消费问题,也就是资源争抢,这里咱们可使用lua脚本来执行保证原子性,即获取后删除,其值和时间戳保存到内存中,经过后端程序控制时间消费。固然,问题还会有的,咱们在下一篇来说解Redis消息队列的发布订阅模式。

一名正在抢救的coder

笔名:mangolove

GitHub地址:https://github.com/mangoloveYu