(Ryan的Redis系列博客)9.Redis的列表

前言

为了解释Redis中的列表数据类型,最好从简单的理论开始讲解,这是因为,“列表”也是一个广泛应用的专业术语,技术人员经常会将概念混淆。例如,“Python列表”就不是字面意思所说的“链式列表”,确切的说,“Python列表”是个数组。一般认为,列表是一串有序序列的元素集合,例如100,120,91,42,73这样的集合就是一个列表。实现这一原则很简单,但是,列表的底层实现是基于数组,还是基于链表这就很不一样了。在Redis中,Redis的列表是通过链表结构来实现的。这意味着,即使你向包含成百上千数据的列表的头部或者尾部新增元素,这个操作所消耗的时间也是可控的。因此,用LPUSH命令增加新元素时,不论是增加到包含10个元素的列表头部,还是,增加到包含一千万个元素的列表头部,所用的速度是一样的。那么基于链表结构实现的列表的缺点是什么呢?对,就是索引的速度。数组的索引速度明显快于链表的索引速度。综上所述,就可以解释Redis的列表为什么是基于链式表实现的了,因为,Redis首先是一个数据库系统,它需要有向一个大的列表中快速插入元素的能力,因此,这个时候使用数组的这种方式就显得不太合适了。另外,使用链式表还有一个很重要的优点,那就是,Redis的列表可以在可控的时间内分为有限的长度。当然,快速访问元素也很重要,特别是快速访问大集合的中间元素,这会用到有序集合。有序集合我之后再写文章进行说明。

使用Redis列表

使用列表的第一步是使用相关的Redis命令,我们看一下命令列表:

命令说明
LPUSH左增加,或者说向列表的头部增加元素,如果没有列表,还会生成一个新的列表
RPUSH右增加,或者说向列表的尾部增加元素,如果没有列表,还会生成一个新的列表
LRANGE从列表中提取一定范围的数据,该方法有两个参数,第一个参数表示索引的开始元素,第二个参数表示索引的结束元素,两个索引都可以为负数,例如-1表示的就是最后一个元素,-2表示倒数第二个元素,以此类推
LPOP左清除,或者说去掉列表头部元素,并将去掉的元素返回回来
RPOP右清除,或者说去掉列表尾部元素,并将去掉的元素返回回来

看下方的例子

> 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"

下面是去除元素的例子

> rpush mylist a b c
(integer) 3
> rpop mylist
"c"
> rpop mylist
"b"
> rpop mylist
"a"
> rpop mylist
(nil)

另外需要注意的是使用命令去除空的列表的时候,会报错。

一些使用列表的例子

使用列表的具有代表性的例子:

1.社交网络,可以用来记录社交网络上用户的最新更新,例如,Twitter会将最新的推文放到Redis的列表中。
2.进程间的通信,可以利用观察者模式,生产消息方向列表添加数据,消费消息方从列表中读取数据并执行任务。并且,Redis已经提供了特殊的列表操作命令来确保这个过程安全而高效的进行。例如Ruby的流行库resque和sidekiq就是利用了Redis的列表来执行后台任务的。

实际场景拆分

接下来,我们一步一步的分析讲解一个应用场景,在这个场景下,你想要在首页要展示一些用户在他们自己的社交账号上最新分享的照片,并且你想要快速访问这个照片。

1.每次用户发布新照片,我们用LPUSH向列表中增加他的ID
2.当用户访问首页的时候,我们用LRANGE 查询最新的10个发布信息

限制列表

在很多例子中,我们仅仅只想利用列表去存储最新的更新,不管是日志、社交网络更新还是别的什么。Redis允许我们用它作为一个限制性的集合,仅仅只记录最新的N个元素,并利用LTRIM命令丢弃所有老的元素。
LTRIM命令和LRANGE命令很像,不过,LTRIM并不会将元素显示出来,同时,会结合给定的范围形成新的列表,并用新的列表来替换老的列表,并且,所有不在给定范围内的元素都将会被删除。我们来看一个例子:

> rpush mylist 1 2 3 4 5
(integer) 5
> ltrim mylist 0 2
OK
> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"

结合上边的例子,我们可以写一个更新的模型:添加新的,然后丢弃超过范围的元素

LPUSH mylist <some element>
LTRIM mylist 0 999

注意:LRANGE 的算法时间是O(N),访问一个小范围消耗的时间是可以确定的。

基于列表的阻塞操作

列表还有一个非常重要的特性,使它们很适合去实现队列,因此,一般情况下,列表可以为进程间通信系统构建阻塞。假设,你想要在进程A中增加一个数据到列表A中,同时,利用进程B来操作列表A中的数据,这是一个常见的观察者模式,可以按照如下步骤实现:

1.生产者用LPUSH命令向列表添加数据
2.消费者用RPOP命令提取或者处理该列表中的数据

然而,有时可能列表是空的,或者没有什么需要处理的,因此,RPOP命令会返回NULL,在这个例子中,消费者被强制等待,然后重新尝试调用RPOP命令。这个过程就是轮询(polling),当然,在这个场景下,轮询肯定不是最好的选择,因为,轮询有一些缺点:

1.强制Redis和客户端处理无用的命令(当列表为空的时候,RPOP命令实际上什么都没有做,但是还要返回一个NULL)
2.增加了处理任务的延迟时间。

因此Redis提供了BRPOP 和BLPOP 来替代RPOP 和LPOP,BRPOP 和BLPOP在列表为空的时候也可以进行阻塞,而不会做一些无用的操作。BRPOP 和BLPOP只返回请求本身,不论是新元素的插入,还是特殊用户操作超时。

下面看一个关于BRPOP 的例子,这个例子的意思是:在5秒内等待处理任务,如果5秒内无任务可做则返回。

brpop tasks 5
1) "tasks"
2) "do_something"

注意:

1.可以将时间设置为0来表示永远等待
2.可以指定多个列表进行任务等待,不管哪有个列表收到了一个元素,就会通知客户端
3.客户端处理列表是有顺序的,第一个阻塞的列表,第一个进行处理
4.返回值跟RPOP不一样,BRPOP和BLPOP因为可以指定多个列表,因此,当接收到相关的任务键值,命令的返回值会是包含2个元素的数组
5.如果超时,则返回NULL

另外,可以在看看RPOPLPUSH和BRPOPLPUSH命令
1.用RPOPLPUSH建立安全队列或者选择队列
2.BRPOPLPUSH是阻塞版的RPOPLPUSH变体命令

自动化创建和丢弃键

到目前为止,我们从没有被要求在添加元素前创建一个空的列表,或者删除一个没有任何元素的列表。因为,自动化创建和丢弃键是Redis的职责。因此,不仅仅列表是这样的,集合,有序集合和散列都是这样的。接下来,我结合本文总结三个原则:

自动创建

1.向一个聚合型数据类型中增加元素时,如果该数据类型不存在,会自动创建一个聚合型的数据类型。

> del mylist
(integer) 1
> lpush mylist 1 2 3
(integer) 3

注意,数据类型遵循优先原则。

> set foo bar
OK
> lpush foo 1 2 3
(error) WRONGTYPE Operation against a key holding the wrong kind of value
> type foo
strings

自动删除

2.从一个聚合型数据类型中删除元素时,如果删除掉某元素后,该数据类型中没有一个元素了,则会自动将该聚合型数据类型销毁。

> lpush mylist 1 2 3
(integer) 3
> exists mylist
(integer) 1
> lpop mylist
"3"
> lpop mylist
"2"
> lpop mylist
"1"
> exists mylist
(integer) 0

3.请求只读型命令,例如LLEN(返回列表长度),或者,删除一个空键位的元素,都会返回相同的结果,也就是说,这个命令会去寻找某种聚合型类型的空类型,并返回给调用者。

> del mylist
(integer) 0
> llen mylist
(integer) 0
> lpop mylist
(nil)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值