redis设计用来做缓存的,但是由于它自身的某种特性使得它可以用来做消息队列,它有几个阻塞式的API可以使用,正是这些阻塞式的API让其有能力做消息队列;
另外,做消息队列的其他特性例如FIFO(先入先出)也很容易实现,只需要一个list对象从头取数据,从尾部塞数据即可;
redis能做消息队列还得益于其list对象blpop brpop接口以及Pub/Sub(发布/订阅)的某些接口,它们都是阻塞版的,所以可以用来做消息队列。
Redis队列功能介绍
List
常用命令:
Blpop删除,并获得该列表中的第一元素,或阻塞,直到有一个可用。
Brpop删除,并获得该列表中的最后一个元素,或阻塞,直到有一个可用。
Brpoplpush
Lindex获取一个元素,通过其索引列表。
Linsert在列表中的另一个元素之前或之后插入一个元素。
Llen获得队列(List)的长度。
Lpop从队列的左边出队一个元素。
Lpush从队列的左边入队一个或多个元素。
Lpushx当队列存在时,从队到左边入队一个元素。
Lrange从列表中获取指定返回的元素。
Lrem从列表中删除元素
Lset设置队列里面一个元素的值。
Ltrim修剪到指定范围内的清单。
Rpop从队列的右边出队一个元素。
Rpoplpush删除列表中的最后一个元素,将其追加到另一个列表。
Rpush从队列的右边入队一个元素。
Rpushx从队列的右边入队一个元素,仅队列存在时有效。
Redis支持php、python、c等接口。
应用场景:
Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现。
Lists 就是链表,相信略有数据结构知识的人都应该能理解其结构。使用Lists结构,我们可以轻松地实现最新消息排行等功能。
Lists的另一个应用就是消息队列,
可以利用Lists的PUSH操作,将任务存在Lists中,然后工作线程再用POP操作将任务取出进行执行。Redis还提供了操作Lists中某一段的api,你可以直接查询,删除Lists中某一段的元素。
如果需要还可以用redis的Sorted-Sets数据结构来做优先队列.可以给每条消息加上一个唯一的序号。这里就不详细介绍了。
实现方式:
Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。
示意图:
1)入队
2)出队(非阻塞模式)
lpop弹出列表首元素(即最后入队的元素)
Rpop弹出列表尾元素 (即入队的最开始的一个元素)
注意:如果要当作队列功能,应该是用这个出队。
这里的出队都是非阻塞模式,就是你用pop出队的时候,如果队列是空的话,你得到的是一个NULL的值。
3)出队(阻塞模式)
假如现在queue队列为空 我们用brpop命令。
BRPOP 是一个阻塞的列表弹出原语。 它是 RPOP 的阻塞版本,因为这个命令会在给定list无法弹出任何元素的时候阻塞连接。 该命令会按照给出的 key 顺序查看 list,并在找到的第一个非空 list 的尾部弹出一个元素。
A)
我们执行brpop命令
可以看到队列queue没有元素的时候 是阻塞的 即不返回值。
其中0是超时时间 为0表示一直等待。
B)
这个时候我们用lpush往队列里 入队一个数据“bbb”
C)
阻塞的队列立马会弹出出队元素 显示队列名字 和 出队元素 已经等待了多少时间。
D)
Brpop还能同时阻塞多个队列比如这样。
用redis的list当作队列可能存在的问题。
1)redis崩溃的时候队列功能失效。
2)如果入队端一直在塞数据,而出队端没有消费数据,或者是入队的频率大而多,出队端的消费频率慢会导致内存暴涨。
3)Redis的队列也可以像rabbitmq那样 即可以做消息的持久化,也可以不做消息的持久化。
当做持久话的时候,需要启动redis的dump数据的功能.暂时不建议开启持久化。
Redis其实只适合作为缓存,而不是数据库或是存储。它的持久化方式适用于救救急啥的,不太适合当作一个普通功能来用。应为dump时候,会影响性能,数据量小的时候还看不出来,当数据量达到百万级别,内存10g左右的时候,非常影响性能。
4)假如有多个消费者同时监听一个队列,其中一个出队了一个元素,另一个则获取不到该元素。
5)Redis的队列应用场景是一对多或者一对一的关系,即有多个入队端,但是只有一个消费端(出队)。
PHP的redis 简单操作示例。
通常使用一个list来实现队列操作,这样有一个小限制,所以的任务统一都是先进先出,如果想优先处理某个任务就不太好处理了,这就需要让队列有优先级的概念,我们就可以优先处理高级别的任务,实现方式有以下几种方式:
1)单一列表实现:队列正常的操作是 左进右出(lpush,rpop)为了先处理高优先级任务,在遇到高级别任务时,可以直接插队,直接放入队列头部(rpush),这样,从队列头部(右侧)获取任务时,取到的就是高优先级的任务(rpop)
2)使用两个队列,一个普通队列,一个高级队列,针对任务的级别放入不同的队列,获取任务时也很简单,redis的BRPOP命令可以按顺序从多个队列中取值,BRPOP会按照给出的 key 顺序查看,并在找到的第一个非空 list 的尾部弹出一个元素,redis> BRPOP list1 list2 0。
list1 做为高优先级任务队列 list2 做为普通任务队列。
这样就实现了先处理高优先级任务,当没有高优先级任务时,就去获取普通任务。
方式1最简单,但实际应用比较局限,方式3可以实现复杂优先级,但实现比较复杂,不利于维护,方式2是推荐用法,实际应用最为合适。
有两种方法:
Redis自带的PUB/SUB机制,即发布-订阅模式。这种模式生产者(producer)和消费者(consumer)是1-M的关系,即一条消息会被多个消费者消费,当只有一个消费者时即可以看做一个1-1的消息队列,但这种方式并不适合题主的场景。首先,数据可靠性的无法保障,题主的数据最终需要落库,如果消息丢失、Redis宕机部分数据没有持久化甚至突然的网络抖动都可能带来数据的丢失,应该是无法忍受的。其次,扩展不灵活,没法通过多加consumer来加快消费的进度,如果前端写入数据太多,同步会比较慢,数据不同步的状态越久,风险越大,当然可以通过channel拆分的方式来解决,虽然不灵活,但可以规避。这种方案更适合于对数据可靠性要求不高,比如一些统计日志打点。
Redis的PUSH/POP机制,利用的Redis的列表(lists)数据结构。比较好的使用模式是,生产者lpush消息,消费者brpop消息,并设定超时时间,可以减少redis的压力。这种方案相对于第一种方案是数据可靠性提高了,只有在Redis宕机且数据没有持久化的情况下丢失数据,可以根据业务通过AOF和缩短持久化间隔来保证很高的可靠性,而且也可以通过多个client来提高消费速度。但相对于专业的消息队列来说,该方案消息的状态过于简单(没有状态),且没有ack机制,消息取出后消费失败依赖于client记录日志或者重新push到队列里面。
思路:
首先一个是将这两个分为两个队列来实现, 一个用来实现消息优先级,一个来实现定时发送。
用的是redis的有序集合,用zadd添加时,将score比做是优先级,也可以用时间戳来当做score,用来表示时间。
PHP 版本简易实现
将消息加入优先级的队列,将1,2替换为时间就是定时发送的队列了。
1 $redis = new Redis();。
2 $redis->connect('127.0.0.1', 6379);。
3 $redis->zAdd('zset1', 1, 'message');。
4 $redis->zAdd('zset1', 2, 'message2');。
从队列中取出数据
1 $redis->zRevRangeByScore('zset1, '+inf', '-inf', array('withscores'=>false, 'limit'=>array(0,20)));。
这条语句表示从zset1这个队列里按照score从最大(+inf)到最小(-inf)的排序中取出20条,不带score,如果想要从小到大可以用 zRangeByScore。
如果你想让这些都运行在命令行下,可以参考下面来,当然这些是经过删减的。
1 <?php
2 while (true) {。
3 $pid = pcntl_fork();。
4 if ($pid == -1) {。
5 echo date('Y-m-d H:i:s') . "fork失败!\n";。
6 } else if ($pid == 0) {。
7 $redis = new Redis();。
8 $redis->connect('127.0.0.1', 6379);。
9 $redis->zRevRangeByScore('zset1', '+inf', '-inf', array('withscores'=>false, 'limit'=>array(0,20)));。
10 exit;。
11 } else {。
12 pcntl_wait($status);。
13 }
14 }
pcntl_fork是PHP中的生成子进程,当调用该函数时,会返回一个进程pid,当pid为0时表明是在子进程中,所以把要执行的东西全放这里。
线上的一个项目,运行几个月了,用子进程方式还没有出过问题,也没挂过,相当不错。
原文地址:http://www.qianchusai.com/redis%E9%98%9F%E5%88%97%E7%94%A8%E6%B3%95.html