探究Redis 03:列表

Redis列表(list)

为了解释Redis列表我们首先需要一点理论知识,由于List常常被信息技术领域人误用,例如:"Python Lists"并不是从命名中推测的链表结构, 而是数组结构 (同样的数据结构在Ruby中叫做Array)。

从通用的角度看Redis列表只是一组有序元素。例如:10,20,1,2,3 就构成了一个列表。但是用数组结构实现的列表和用链表结构实现的列表性质上有很大不同。

Redis列表使用链表结构实现。这意味着即便已经保存了百万个元素,添加一个元素到列表头和列表尾部也只需要常数时间开销。也就是说,使用LPUSH 命令添加一个元素到一个有10个元素列表的头部和将其添加到一个有1000万元素的列表头部速度是一样的。

这样有什么弊端吗?在数组实现的列表中,根据下标查找元素的操作速度极快,而在链表实现的列表中比较慢 (这个操作与查询的下标位置偏移成正比)。

Redis 列表选择链表结构的原因是,对于数据库系统来说,能快速将元素添加到一个长列表中是至关重要的。另一个优势是Redis 列表支持在常数时间内获取固定数量元素。
当快速获取大集合中间的元素很重要时,可以使用另一个数据结构排序集合(sorted sets)。此数据结构稍后会介绍。

初步使用Redis列表

通过LPUSH 命令,可以向列表头添加新元素。RPUSH 命令则将元素添加到列表尾部。最后通过LRANGE 命令可以抽取一段列表元素。

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

需要注意的是LRANGE 接收两个下标作为参数, 表示获取的列表开始和结束位置。两个下标都可以为负数,代表倒数第几个元素:例如:-1是最后一个元素,-2代表列表的倒数第二个元素,以此类推。

从示例中可以看到,RPUSH 添加元素到列表右侧,而最后一个LPUSH 添加元素到最左侧。

两个命令都支持可变参数,也就是说可以一次放入多个元素到列表:

> 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列表定义的一个重要操作是弹出元素的功能。弹出元素是指从列表中检索元素并同时删除元素的操作。您可以从左侧和右侧弹出元素,与放入元素类似:

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

示例中,我们添加了三个元素,弹出了三个元素,所以在这几个命令执行之后,列表是空的,没有其他元素可以弹出。尝试再弹出一个元素,我们会得到下面的结果:

> rpop mylist
(nil)

Redis返回NULL 值表明列表中不再有其他元素可以弹出。

列表的常见使用场景

列表非常适合记录任务,两个典型的应用如下:

  • 记住用户在社交网络上发布的最新更新。
  • 用生产者消费者模型进行进程间通信,生产者将任务放入列表,消费者(通常是工作者)获取任务并执行。Redis支持特别的列表操作,使这个用例更加可靠和高效。

例如:两个著名的Ruby类库 resquesidekiq 使用Redis列表作为背后存储用于实现后台任务。
著名的Twitter社交网络从用户推送到Redis 列表中的信息获取最近的tweets

假设您的主页显示了共享网络中发布的最新照片,并且希望加速照片访问。

  • 每次用户上传照片,使用LPUSH将ID加入一个Redis列表。.
  • 当用户访问主页,我们使用LRANGE 0 9命令获取最近10条发布内容。

限制列表

在许多应用场景下我们需要存储最近的内容。 有可能是社交网络更新,日志或其他任何信息。Redis列表作为有限集合,支持通过使用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 mylist <some element>
LTRIM mylist 0 999

上面的操作加入了一个元素,同时删除了1000个元素之后的旧元素。之后通过LRANGE 命令,可以获取最新元素并无需关心数据过多的问题。需要注意的是尽管理论上LRANGE是O(N) 复杂度命令,从Redis列表头或尾部获取一小段范围的元素只需要常数时间。

列表阻塞操作

Redis列表支持一些特殊特性可以方便的实现队列场景, 一般用于实现系统进程间通信的阻塞操作。
假设你希望使用一个进程将任务推送到列表并使用另一个进程来实际处理任务。类似生产者/消费者模型,可以通过以下简单方式实现:

  • 要将任务推送到列表中,生产者调用LPUSH
  • 要从列表中提取任务,消费者需要调用RPOP

但是,有时列表可能是空的,所以RPOP只返回空值。这种情况下,消费者需要等待一段时间再用RPOP重试。这种轮询操作有几个缺点:

  1. 强制Redis和客户端处理无命令(当列表为空时,所有请求都不会执行实际任务,它们只返回空值)。
  2. 因为在工作进程收到空值之后会等待一段时间,导致产生处理延时。

为了解决此问题,Redis实现了BRPOPBLPOP 命令,他们是RPOPLPOP命令在空队列时的阻塞版本。这两个命令只在新元素入队列后,或指定等待时间超时情况下,才会返回。阻塞等待操作可以使用0作为超时值,此时将永久等待元素。也可以指定同时等待多个列表,并在任何列表收到元素时得到通知。

这是一个任务处理进程的BRPOP示例:

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

它的意思是:“等待列表中的元素任务,但如果5秒后没有元素可用,则返回”。

关于 BRPOP有几点需要注意:

  1. 客户端遵循公平等待原则:第一个阻塞的客户端首先获得通知。
  2. 返回值格式与rpop不同:返回值是一个两个两元素组成的数组,因为brpop和blpop支持等待来自多个列表的元素,因此返回内容包含列表名称。
  3. 如果超时,则返回空值。

关于列表和阻塞操作,如需了解更多,建议阅读以下内容:

  • 使用RPOPLPUSH构建更安全的队列或旋转队列。
  • 该命令还有一个阻塞版本,称为BRPOPLPUSH

Redis键的自动创建和删除

到目前为止,我们看到示例中,Redis在放入元素前无需创建空列表。同时元素清空时也无需删除空列表。Redis会自动处理列表的创建与删除。这种特性不止限于列表,在Redis内部的其他数据结构上同样适用。例如:Streams, Sets, Sorted Sets and Hashes.

这种特性可以总结如下:

  1. 当向集合数据类型添加元素时,如果Redis键不存在,则在添加元素之前创建一个空的集合数据类型。
  2. 当从集合数据类型中删除元素时,如果集合为空,则键将自动销毁。流数据类型是此规则的唯一例外。
  3. 调用只读命令例如:LLEN (返回列表的长度),或删除集合对应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
string

规则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的示例:

> del mylist
(integer) 0
> llen mylist
(integer) 0
> lpop mylist
(nil)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容