


专栏:Redis 修行录
个人主页:手握风云
目录
[一、List 类型](#一、List 类型)
[1.1. 概述与特点](#1.1. 概述与特点)
[1.2. 常见操作命令](#1.2. 常见操作命令)
[1. LPUSH 和 RPUSH](#1. LPUSH 和 RPUSH)
[2. LPUSHX 和 RPUSHX](#2. LPUSHX 和 RPUSHX)
[3. LPOP 和 RPOP](#3. LPOP 和 RPOP)
[4. LRANGE](#4. LRANGE)
[5. LINDEX、LINSERT 和 LLEN](#5. LINDEX、LINSERT 和 LLEN)
[6. LREM 和 LTRIM](#6. LREM 和 LTRIM)
[7. LSET](#7. LSET)
[1.3. 阻塞版本命令](#1.3. 阻塞版本命令)
[1.4. 内部编码机制](#1.4. 内部编码机制)
[1.5. 典型应用场景](#1.5. 典型应用场景)
一、List 类型
1.1. 概述与特点
List 类型用于存储多个有序的字符串,列表中的每个字符串称为元素(element),一个列表最多可以存储 2^32 - 1 个元素。在 Redis 中,可以对列表的两端进行插入(push)和弹出(pop)操作,使其非常灵活,可以充当栈或队列的角色。
列表类型具有以下三个核心特点:
- 有序性:列表中的元素是有序的,这意味着可以通过索引下标(正数或负数)获取某个元素或某个范围的元素列表。
- 操作区分:获取和删除是不同的操作。例如,使用 lrem 会删除元素并改变列表长度;而使用 lindex 只是获取元素,列表长度不会变化。
- 允许重复:列表中的元素是允许重复存在的。
1.2. 常见操作命令
1. LPUSH 和 RPUSH
bash
LPUSH key element [element ...]
LPUSH 命令主要用于将一个或多个指定的元素从列表(List)的左侧(即头部)插入。该命令的基本语法为 LPUSH key element [element ...],在执行时,如果指定的键(key)不存在,Redis 会在执行插入操作前自动创建一个新的空列表。从执行效率来看,插入单一元素的时间复杂度为 O(1),若在一条命令中传入多个欲插入的元素,其整体时间复杂度则为 O(N),这里的 N 代表插入元素的总数。命令执行完毕后,系统会返回一个整数,代表插入操作完成后列表的最新长度。
bash
RPUSH key element [element ...]
RPUSH 命令与 LPUSH 相对应,主要用于将一个或多个指定的元素从列表的右侧(即尾部)追加插入。它的基本语法是 RPUSH key element [element ...],同样地,如果目标键不存在,Redis 会预先创建一个空列表再进行元素的推入。其时间复杂度与 LPUSH 完全一致,每追加一个元素耗时 O(1),同时追加 N 个元素的时间复杂度则为 O(N)。在命令成功执行后,它也会返回对应列表在追加新元素后的总长度。
bash
LPUSH list1 1 2 3 4
RPUSH list1 6 5 4 3
2. LPUSHX 和 RPUSHX
bash
LPUSHX key element [element ...]
LPUSHX 命令主要用于在指定的键(key)已经存在并且是一个列表(List)的情况下,将一个或多个指定的元素从列表的左侧(即头部)插入。与 LPUSH 命令不同的是,如果目标键在此之前并不存在,LPUSHX 不会预先创建空列表,而是不执行任何操作并直接返回。在执行效率方面,插入单一元素的时间复杂度为 O(1),如果一次性传入多个元素进行插入,其整体时间复杂度则为 O(N),N 代表插入元素的总数。命令执行完毕后,会返回一个整数,表示插入操作完成后列表的最新长度。
bash
RPUSHX key element [element ...]
RPUSHX 命令则与 LPUSHX 的行为相对应,主要用于在指定的键已经存在且持有列表时,将一个或多个元素从列表的右侧(即尾部)追加插入。如果给定的键不存在,Redis 同样不会执行任何追加操作。该命令的时间复杂度与 LPUSHX 完全一致,即每追加一个元素耗时 O(1),同时追加 N 个元素耗时 O(N)。命令成功执行后,它也会返回对应的列表在追加新元素后的最新总长度。
bash
LPUSHX list1 10 20
RPUSHX list1 30 40
LPUSHX list2 1 2 3

3. LPOP 和 RPOP
bash
LPOP key [count]
LPOP 命令主要用于从列表(List)的左侧(即头部)取出一个或多个元素并将其删除。在执行时,如果不指定可选的 count 参数,默认只会从列表头部弹出一个元素;如果提供了 count 参数,则会返回多达指定数量的元素。其执行的时间复杂度为 O(N),其中 N 代表实际返回的元素数量;如果只弹出一个元素,运行极其高效,时间复杂度为 O(1)。命令执行完毕后,它会返回被取出的元素;如果指定的键不存在或列表已为空,则会返回 nil。同时,如果列表中的最后一个元素被成功弹出,Redis 会自动将这个空列表也一并删除。
bash
RPOP key [count]
RPOP 命令则与 LPOP 的行为相对应,主要用于从列表的右侧(即尾部)取出一个或多个元素并将其删除。该命令同样支持通过可选的 count 参数来控制一次性从尾部弹出的元素上限。该命令的时间复杂度也与 LPOP 完全一致,即弹出 N 个元素的时间复杂度为 O(N),仅弹出单一元素时为 O(1)。命令成功执行后,它会返回被移除的元素内容;若列表为空或目标键不存在,则会返回 nil。同样地,当列表内的所有元素都被 RPOP 弹出完毕后,系统会自动删除该空列表。
bash
LPOP list1
LPOP list1 2
RPOP list1
RPOP list1 3

4. LRANGE
LRANGE 命令主要用于获取 Redis 列表(List)中指定偏移量(索引)范围内的所有元素。该命令的基本语法为 LRANGE key start stop,其中 start 和 stop 均为基于零的整数索引,0 代表列表的第一个元素(即头部),1 代表第二个元素,依此类推。该命令同样支持负数索引,例如 -1 表示列表的最后一个元素(即尾部),-2 表示倒数第二个元素。需要特别注意的是,LRANGE 所提取的区间是左闭右闭的(inclusive),这意味着执行 LRANGE key 0 10 实际会返回 11 个元素。在处理超出范围的索引参数时,Redis 并不会抛出错误:如果传入的 start 值大于列表的实际末尾,命令会直接返回一个空列表;如果传入的 stop 值超出了列表的实际长度,Redis 会自动将其截断,按照列表的最后一个元素来处理。在执行效率方面,该命令的时间复杂度为 O(S+N),其中 S 是从列表头部或尾部(取较近的一端)到达 start 偏移量的距离,N 则是指定范围内包含的元素个数。
bash
LPUSH list1 1 2 3
RPUSH list1 6 5 4
LRANGE list1 1 4
LRANGE list1 0 -3
LRANGE list1 3 0
LRANGE list1 6 8
LRANGE list1 2 8

5. LINDEX、LINSERT 和 LLEN
bash
LINDEX key index
LINDEX 命令主要用于获取 Redis 列表(List)中指定索引位置的元素。在执行效率方面,时间复杂度为 O(N),其中 N 是到达指定索引所需遍历的元素数量,不过如果只是请求列表的第一个或最后一个元素,时间复杂度为 O(1)。如果给定的索引超出了列表的实际长度范围,该命令会返回 nil。
bash
LINDEX list1 0
LINDEX list1 3
LINDEX list1 7
LINDEX list1 -2
LINDEX list1 -1

bash
LINSERT key <BEFORE | AFTER> pivot element
LINSERT 命令主要用于在列表中特定的参考元素(pivot)之前或之后插入一个新元素。该命令的时间复杂度同样为 O(N),其中 N 代表在找到参考元素前需要遍历的元素数量;这意味着在列表头部附近插入效率接近 O(1),而在尾部附近插入耗时 O(N)。在执行时,如果目标键不存在,Redis 会将其视为空列表且不执行任何插入操作(返回 0);如果未能找到指定的参考元素,则返回 -1;如果成功插入,则返回插入后列表的新长度。
bash
LINSERT list1 BEFORE 5 7
LINSERT list1 AFTER 5 6
LINSERT list2 BEFORE 1 10
LINSERT list1 AFTER 100 200

bash
LLEN key
LLEN 命令主要用于获取指定列表当前的长度(即列表中包含的元素总数)。它的基本语法为 LLEN key。该命令的执行时间复杂度为 O(1),运行十分高效。在命令执行完毕后,如果指定的键存在,会返回列表的实际长度;如果指定的键不存在,Redis 会将其视为一个空列表并返回整数 0;但如果该键持有的值不是列表类型,则会报错。

6. LREM 和 LTRIM
bash
LREM key count element
LREM 命令主要用于从列表(List)中删除指定数量的、与给定值匹配的元素。该命令的基本语法为 LREM key count element。参数 count 决定了删除的具体行为:如果 count > 0,系统会从列表头部(左侧)向尾部方向,移除数量为 count 的匹配元素;如果 count < 0,则从列表尾部(右侧)向头部方向移除绝对值数量的匹配元素;如果 count = 0,则会删除列表中所有与指定 element 相等的元素。在执行效率方面,其时间复杂度为 O(N+M),其中 N 是列表的总长度,M 是被成功移除的元素数量。命令执行完毕后,它会返回实际被移除的元素总数。需要注意的是,如果目标键不存在,Redis 会将其视为空列表处理并直接返回 0;同时,如果操作导致列表内的所有元素都被删除,Redis 会自动将这个空列表也一并移除。
bash
LREM list1 4 5
LREM list1 -2 6
LREM list2 1 3
LREM list1 0 7

bash
LTRIM key start stop
LTRIM 命令主要用于修剪(Trim)一个已存在的列表,使其仅保留指定索引范围内的元素,而位于该范围两端的所有元素都将被删除。其基本语法为 LTRIM key start stop,其中 start 和 stop 均为基于零的整数索引,且同样支持使用负数来表示从尾部起算的偏移量。在处理越界参数时,该命令表现得十分宽容且不会抛出错误:如果 start 大于列表的实际末部,或者 start 大于 stop,执行结果将是一个空列表(进而导致该键被自动删除);如果 stop 超出了列表的实际长度,Redis 会将其自动截断并视为列表的最后一个元素。该命令的执行时间复杂度为 O(N),这里的 N 代表被该操作移除的元素数量。在实际应用中,LTRIM 经常与 LPUSH 或 RPUSH 命令结合使用,以控制列表不会超过指定的固定长度(例如用于存储固定条数的日志),在这种特定场景下,由于每次基本只需移除一个元素,其操作效率实际上可视为 O(1)。命令执行成功后,它会返回一个简单的字符串回复 OK。
7. LSET
bash
LSET key index element
LSET 命令主要用于根据索引修改 Redis 列表(List)中特定位置元素的值。在执行效率方面,其时间复杂度为 O(N),其中 N 代表列表的总长度;但如果只是修改列表的第一个元素(头部)或最后一个元素(尾部),操作会非常高效,时间复杂度仅为 O(1)。需要特别注意的是,如果传入的索引超出了列表的实际范围,该命令会返回一个错误(error)。命令成功执行完毕后,系统会返回一个简单的字符串回复 OK。

1.3. 阻塞版本命令
bash
BLPOP key [key ...] timeout
BRPOP key [key ...] timeout
在 Redis 中,BLPOP 和 BRPOP 分别是列表类型命令 LPOP 和 RPOP 的阻塞版本,它们在列表内部有元素时的表现与对应的非阻塞版本是完全一致的。这两类命令的主要区别体现在列表为空的情境下:非阻塞版本会立刻返回 nil,而阻塞版本则会根据指定的 timeout 参数使客户端阻塞等待一段时间。在这段阻塞期间,Redis 服务端依然能够正常处理其他的命令,但发起该请求的客户端会表现为阻塞状态。
同时,BLPOP 和 BRPOP 命令在语法上支持设置多个键,执行时系统会从左向右依次遍历这些被监听的键,只要其中任何一个键对应的列表中可以弹出元素,命令就会立刻结束并返回结果。
此外,如果出现多个客户端同时对同一个键执行阻塞弹出的操作,那么当新元素被加入列表时,最先执行阻塞命令的客户端将会优先得到该弹出的元素。最后,这两个命令的时间复杂度均为 O(1),当操作成功时会返回取出的元素,若达到超时时间仍无元素可弹则会返回 nil。
bash
RPUSH list2 a b c
BLPOP list1 list2 10
BLPOP list3 10

1.4. 内部编码机制
列表类型在 Redis 底层有两种内部编码实现:
- ziplist (压缩列表):当列表的元素个数小于 list-max-ziplist-entries 配置(默认 512 个),并且每个元素的长度都小于 list-max-ziplist-value 配置(默认 64 字节)时,Redis 会选用 ziplist 作为内部实现以减少内存消耗。
- linkedlist (链表):当列表类型无法满足 ziplist 的条件时(例如元素个数过多或存在大元素),Redis 会将内部实现转换为 linkedlist。
1.5. 典型应用场景
- 消息队列:可以使用 lpush 结合 brpop 命令组合实现经典的阻塞式生产者-消费者模型队列。多个消费者客户端可以阻塞式地从队列中争抢队首元素,从而保证消费的负载均衡和高可用性。
- 分频道消息队列:通过对不同的键使用 lpush 和 brpop 命令,可以模拟频道的概念,实现订阅不同频道的功能。
- 时间轴 / 文章列表 (Timeline):例如微博的时间线,可以使用列表存储文章记录,并使用 lrange 命令进行索引范围获取来实现分页展示功能。针对大列表的分页,文档也提醒在获取中间元素时性能较差,可以考虑将列表做拆分。