redis list类型介绍
要阐述redis的list数据类型,最好以一些理论开始,因为list术语在信息化众多技术中用的并不合适,如:
如Python中的list并不是链表结构,而是数组(同样的数据结构在Ruby语言中叫Array)。以通常的观点来看,List仅仅是一个有序的元素序列:10,20,1,2,3就是一个序列。但是,由一个数组
数据结构实现List的属性与一个由链表数据结构实现的List的属性是有非常大的不同的。redis的List是由链表实现的,这就意味着:即使你有数以百万记的元素要插入到redis的List中,但在List
头部或者尾部插入一个元素的操作所执行花费的时间是固定的,也就是说使用LPUSH命令在一个已含有10个元
素的List头部插入一个元素的速度与在一个已含有10000000元素的List头部插入一个元素的速度是一样的。那么由链表实现又有什么缺陷呢?那就是访问速度。如果是由数组实现的List,可以通过索引快速访问List
中的元素(固定时间索引存取),如果是由链表实现的List,则访问List中元素开销与索引位置成比例。Redis由链表实现主要是因为:对于数据库系统来说,能够向一个非常大的List中快速插入元素是非常关键的
。
Redis List的用法简单介绍
LPUSH命令用于在一个List头部添加一个新元素。RPUSH命令用于在一个List尾部添加一个新元素。
LRANGE命令用于返回List中指定范围的元素。
注:LRANGE命令用法:LRANGE key start stop,key为要操作的集合,start为开始偏移量,stop为结束偏移量
。偏移量为元素下标,0表示第一个元素,1表示第二个元素,也可以是负数,-1表示最后一个元素,-2表示倒数
第二个元素,以此类推。
>rpush mylist A
(integer) 1
>rpush mylist B
(integer) 2
>lpush mylist first
(integer) 3
> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"
另外,LPUSH命令与RPUSH命令都支持批量添加,如:
>rpush mylist 1 2 3 4 5 "foo bar"
(integer)9
>lrange mylist 0 -1
1)"first"
2)"A"
3)"B"
4)"1"
5)"2"
6)"3"
7)"4"
8)"5"
9)"foo bar"
Redis List的pop操作是操作List的一个重要操作,此操作既是检索操作,同时也是移除操作,并且既可以从头部也可以从尾部进行pop操w作。与添加元素命令相似,从头部弹出与从尾部弹出分别由命令LPOP和RPOP控制。
>rpush mylist a b c
(integer) 3
>rpop mylist
"c"
>rpop mylist
"b"
>rpop mylist
"a"
>rpop mylist
(nil)
Redis返回null则表示List中已经没有元素了。
Redis List 的通常使用场景
Redis List对大量的任务都有用,如下是两种代表情况:
记住用户发送到社交网络的最后更新
使用生产者消费者模式的的进程间通信。生产者将数据推送到List,消费者(通常是worker)消费这些数据执行操作。Redis有特定的对List操作命令保证此种应用场景下使用Redis List的更高可靠性与高效性。
例如,比较流行的Ruby的resque库和sidekip库在引擎底层使用Redis List来实现后台jobs。Twitter社交网站都是把用户发送的最新推文(tweets)放入Redis List。
假如你的Home主页要展示一个照片分享社交网站发布的最近照片,并且想要快速的访问:
每次当用户上传一张新的照片,我们使用命令LPUSH将照片的ID放入一个list。
当用户访问Home主页时,我们使用LRANG 0 9命令来获取用户上传的最近10个照片。
Capped lists
在许多使用场景中,我们仅仅只想要使用List存储最近的数据,无论何种情况:社交网站更新、日志、或者其他任意场景。
Redis允许用户将list作为限制集合使用,可以通过使用LTRIM命令仅仅记录最近N条数据,并删除其他的数据。
LTRIM命令同LRANGE命令类似,但并不展示指定范围的元素,而是将指定范围内的元素作为新的集合元素并且将不在指定范围内的元素删除。如下
>rpush mylist 1 2 3 4 5
(integer) 5
>ltrim mylist 0 2
OK
>lrange mylist 0 -1
1)"1"
2)"2"
3)"3"
上面的LTRIM命令就是告诉Redis命令只要集合中索引从0到2的元素,其他全部舍弃。这造就了一个非常简单但是有用的模式:LPUSH(RPUSH)+LTRIM(RTRIM)两个操作一起使用来添加新元素和删除超过一定限制的元素。
注:LRANGE命令是一个时间复杂度为O(n)的命令,访问List的指向头部或者尾部的小范围元素的操作花费的时间是固定的。
Blocking operations on lists
Redis List有一个特别的功能,使他们可以实现排队,并且通常作为内部通信系统的一个阻塞块:阻塞操作。
假想你要使用一个进程向List中添加元素,并且使用不同的进程对List中的添加的元素进行其他一些操作,这就是通常的生产者消费者模式,可以简单的通过下面方法来实现:
- 向List中添加元素,生产者producer叫LPUSH命令。
- 抓取和操作List中的元素,消费者consumer叫RPOP命令。
但是,可能某些时候List是空的,没有可要操作的,RPOP命令仅仅返回NULL,这种情况下,消费者RPOP会被强制等待一些时间和再次重试,这也叫轮询并且关于这点并不是太好因为它有以下缺点:
1.强制redis与客户端执行无用的命令(当List为空时,所有请求所执行的命令操作都是无用操作,仅仅返回NULL)。
2.因为当返回Null时,等待一段时间会造成对元素操作进行的延迟,我们可以减少等待的时间,但是这样又回到了问题1。
所以redis实现了RPOP和LPOP命令的升级版BRPOP和BLPOP命令,用于List为空时的的阻塞操作。这两个命令只有当有新元素添加到List中时或者是超过用户指定的超时时间时才返回给调用者。
例如:
>brpop tasks 5
1)"tasks"
2)"do_something"
注:1)”tasks”:要操作的集合 2)”do_something”:集合中的可用元素,如果没有,则BRPOP命令一直阻塞到新元素添加或者超过5秒后返回。
BRPOP命令要注意的事项:
所有客户端按顺序的被服务。多个客户操作同一个List,那么某些客户端向List插入的元素总是被最先阻塞等待的客户端消费。
此命令返回的值与RPOP不同,它返回的是包含List名称在内的两个元素的数组。主要是因为BRPOP和BLPOP可以同时针对于多个List进行阻塞操作。
如果达到指定超时时间,那么将返回Null。
Automatic creation and removal of keys
到目前为止,前面所有例子都没有关于在向List添加元素前创建空的List,或者List中不在有元素时删除空集合的描述。因为Redis会自动在List为空时删除与在要添回到的List不存在时创建。
当然不仅仅局限于List,此种机制适用于redis支持的所有由多元素组成的数据类型:Sets,Sorted sets和Hashes。
此种机制总结:
- 当我们将元素添加到聚合数据类型时,如果目标不存在,那么在插入之前将创建一个空的目标聚合数据类型对象。
- 当从聚合数据类型删除元素时,如果值为空,对应的键会自动删除。
- 在一个空的聚合对象类型上调用一些只读操作的命令如LLEN(返回List的长度)或者一些删除元素的写操作命令时,与目标对象持有命令操作的空的聚合类型一样总是产生相同的结果。