列表List

列表(list)类型是用来存储多个有序的字符串,如图2-18所示,a、b、c、d、e五个元素从左到右组成了一个有序的列表,列表中的每个字符串称为元素(element),一个列表最多可以存储232-1个元素。在Redis中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等(如图2-18和图2-19所示)。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。


image

子列表获取、删除等操作列表类型有两个特点:第一、列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表,例如要获取图2-19

的第5个元素,可以执行lindex user:1:message4(索引从0算起)就可以得到元素e。第二、列表中的元素可以是重复的,例如图2-20所示列表中包含了两个字符串a。


image

这两个特点在后面介绍集合和有序集合后,会显得更加突出,因此在考虑是否使用该数据结构前,首先需要弄清楚列表数据结构的特点。

命令


image

插入

1.rpush key value [value ...] 从右边插入元素 lpush key value [value ...] 左边

rpush listkey c b a

查询

2.lrange0-1 lrange key start end 命令可以从左到右获取列表的所有元素。

索引下标有两个特点:第一,索引下标从左到右分别是0到N-1,但是从右到左分别是-1到-N。第二,lrange中的end选项包含了自身。

3.linsert key before|after pivot value 向某个元素前或者后插入元素

linsert listkey before b java 在列表的元素b前插入java

4.lindex key index 获取列表指定索引下标的元素。

5.llen key 获取列表长度。

删除

6.lpop key 从列表左侧弹出元素 rpop key右侧弹出。

7.lrem key count value 删除指定元素。lrem命令会从列表中找到等于value的元素进行删除,根据count的不同

分为三种情况:

count>0,从左到右,删除最多count个元素。

count<0,从右到左,删除最多count绝对值个元素。

count=0, 删除所有/

8. ltrim key start end 按照索引范围修剪列表

ltrim listkey 1 3 下面操作会只保留列表listkey第2个到第4个元素.

修改

9. lset key index newValue 修改指定索引下标的元素

10. blpop key [key ...] timeout brpop key [key ...] timeout blpop和brpop是lpop和rpop的阻塞版本,它们除了弹出方向不同,使用方法基本相同。

内部编码

列表类型的内部编码有两种。

·ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置

(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时

(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使

用。

·linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用

linkedlist作为列表的内部实现。

内部数据结构

redis3.2之前,List类型的value对象内部以linkedlist或者ziplist来实现, 当list的元素个数和单个元素的长度比较小的时候,Redis会采用ziplist(压缩列表)来实现来减少内存占用。否则就会采用linkedlist(双向链表)结构。

redis3.2之后,采用的一种叫quicklist的数据结构来存储list,列表的底层都quicklist实现。

这两种存储方式都有优缺点,双向链表在链表两端进行push和pop操作,在插入节点上复杂度比较低,但是内存开销比较大;

ziplist存储在一段连续的内存上,所以存储效率很高,但是插入和删除都需要频繁申请和释放内存;

quicklist仍然是一个双向链表,只是列表的每个节点都是一个ziplist,其实就是linkedlist和ziplist的结合,quicklist中每个节点ziplist都能够存储多个数据元素,在源码中的文件为【quicklist.c】,在源码第一行中有解释为:Adoubly linked list of ziplists意思为一个由ziplist组成的双向链表;


使用场景

1.消息队列

如图2-21所示,Redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端使用lrpush从列表左插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。

2.文章列表

每个用户有属于自己的文章列表,现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。


image

//1) 每篇文章使用哈希结构存储,例如每篇文章有3个属性title、 timestamp、content:

hmset acticle:1 title xx timestamp 1476536196 content xxxx

...

hmset acticle:k title yy timestamp 1476512536 content yyyy ...

//2) 向用户文章列表添加文章,user:{id}:articles作为用户文章列表的

lpush user:1:acticles article:1 article3 ...

lpush user:k:acticles article:5 ...

// 3) 分页获取用户文章列表,例如下面伪代码获取用户id=1的前10篇文章:

articles = lrange user:1:articles 0 9 for article in {articles} hgetall {article}

使用列表类型保存和获取文章列表会存在两个问题。第一,如果每次分页获取的文章个数较多,需要执行多次hgetall操作,此时可以考虑使用 Pipeline(第3章会介绍)批量获取,或者考虑将文章数据序列化为字符串类型,使用mget批量获取。第二,分页获取文章列表时,lrange命令在列表两端性能较好,但是如果列表较大,获取列表中间范围的元素性能会变差,此时可以考虑将列表做二级拆分,或者使用Redis3.2的quicklist内部编码实现,它结合ziplist和linkedlist的特点,获取列表中间范围的元素时也可以高效完成。

开发提示

实际上列表的使用场景很多,在选择时可以参考以下口诀: ·lpush+lpop=Stack(栈)

·lpush+rpop=Queue(队列)

·lpsh+ltrim=Capped Collection(有限集合)

·lpush+brpop=Message Queue(消息队列)

待续

来自《Redis运维与开发》