ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Redis List 数据类型

Redis List 数据类型

Redis List 数据类型

List 本质是按插入顺序排列的字符串双向链表


一、基础概念:List 到底是什么

1. 核心特性

  • 有序性:元素严格按照插入顺序排列,支持正向索引(从左到右 0,1,2…)和负向索引(从右到左 -1,-2…,-1代表最后一个元素)。
  • 可重复:同一个值可以多次插入,没有去重能力。
  • 双向高效操作:列表头部(左端/left)和尾部(右端/right)的插入、删除操作都是O(1)时间复杂度,性能极高。
  • 随机访问慢:按下标查找中间元素、在中间位置插入删除是O(n),列表越大性能越差。
  • 元素类型:只能存字符串,单个元素最大 512MB。

✅ 实际开发定位:优先用它做「两端操作的有序集合」,绝对不要用它做随机查找


二、核心命令详解(附开发使用频率)

1. 两端插入(高频核心)

LPUSH:左端(头部)插入元素
  • 语法:LPUSH key value [value ...]
  • 作用:从列表最左边插入一个或多个元素,返回插入后的列表总长度
  • 示例:
# 插入3个元素,从左依次插入,最终顺序是 1003,1002,1001 LPUSH user:1:history 1001 1002 1003
  • 常用度:★★★★★
  • 开发提示:批量插入比循环单次插入性能高几倍,尽量一次传多个值。
RPUSH:右端(尾部)插入元素
  • 语法:RPUSH key value [value ...]
  • 作用:从列表最右边插入一个或多个元素,返回插入后的列表总长度
  • 示例:
# 插入3个任务,最终顺序是 task1,task2,task3 RPUSH order:pay:queue task1 task2 task3
  • 常用度:★★★★★

2. 两端弹出(删除并返回元素)

LPOP:左端弹出元素
  • 语法:LPOP key [count]
  • 作用:移除并返回列表最左边的元素;列表为空返回 nil;Redis 6.2+ 支持 count 参数一次弹出多个
  • 示例:
LPOP order:pay:queue # 返回 task1,列表变为 [task2, task3]
  • 常用度:★★★★★
RPOP:右端弹出元素
  • 语法:RPOP key [count]
  • 作用:移除并返回列表最右边的元素
  • 常用度:★★★★★

3. 查询类命令

LLEN:获取列表长度
  • 语法:LLEN key
  • 作用:返回列表元素总数,O(1) 复杂度(Redis 内部维护了长度字段,直接读取)
  • 示例:
LLEN order:pay:queue # 返回 2
  • 常用度:★★★★★
LRANGE:获取指定范围元素
  • 语法:LRANGE key start stop
  • 作用:获取索引[start, stop]闭区间内的所有元素,是最常用的列表查询命令
  • 示例:
# 获取全部元素(生产环境禁止对大列表使用) LRANGE order:pay:queue 0 -1 # 获取前2个元素(第1页,每页10条就是 0 9) LRANGE order:pay:queue 0 1
  • 常用度:★★★★★
  • 开发提示:-1代表最后一个元素,-2代表倒数第二个;stop 超出列表长度不会报错,自动取到末尾。
LINDEX:获取指定索引元素
  • 语法:LINDEX key index
  • 作用:返回指定下标位置的元素,O(n) 复杂度,需要从头遍历到目标位置
  • 常用度:★★☆☆☆
  • 开发提示:大列表严禁频繁使用,列表越长性能越差。

4. 删除 与 裁剪

LREM:删除指定值的元素
  • 语法:LREM key count value
    • count > 0:从左往右删除 count 个匹配值
    • count < 0:从右往左删除 |count| 个匹配值
    • count = 0:删除所有匹配值
  • 示例:
# 从左往右删除2个值为 1001 的元素 LREM user:1:history 2 1001
  • 常用度:★★★☆☆
  • 开发提示:需要遍历列表匹配,O(n) 复杂度,大列表慎用。
LTRIM:裁剪列表(定长列表神器)
  • 语法:LTRIM key start stop
  • 作用:只保留[start, stop]范围内的元素,其余全部删除
  • 示例:
# 只保留最近20条浏览记录,超出的全部删除 LTRIM user:1:history 0 19
  • 常用度:★★★★☆

和 LPUSH 配合实现最新 N 条记录,比如:浏览历史、最新评论,性能接近 O(1)。

"新增浏览商品id 1001,放到列表头部"LPUSH user:100:view1001"裁剪,只保留下标0~4,也就是最新5条"LTRIM user:100:view04

5. 阻塞式弹出(消息队列核心)

BLPOP / BRPOP:阻塞版弹出
  • 语法:BRPOP key [key ...] timeout
  • 作用:列表为空时阻塞等待,直到有元素插入或超时;有元素时立即弹出返回
    • timeout 单位是秒,设为 0 表示永久阻塞
    • 支持同时监听多个 key,哪个列表先有元素就返回哪个
    • 多个消费者同时监听时,先到先得,不会重复消费
  • 示例:
# 阻塞等待支付队列任务,最多等30秒,超时返回nil BRPOP order:pay:queue 30
  • 常用度:★★★★☆
  • 开发提示:实现简单消息队列的核心命令,比轮询查询性能高得多。
RPOPLPUSH / BRPOPLPUSH:原子移动元素
  • 语法:RPOPLPUSH source destination
  • 作用:原子操作:从 source 列表右端弹出元素,插入到 destination 列表左端,返回该元素
  • 常用度:★★★☆☆
  • 开发场景:实现「可靠消息队列」——把消息从「待处理队列」移到「处理中队列」,防止消费者宕机导致消息丢失。

三、底层实现原理(理解性能的关键)

Redis 3.2 之后,List 的底层编码统一为quicklist(快速列表),替代了早期的 ziplist + linkedlist 两套编码。

1. quicklist 是什么

本质是「双向链表 + 每个链表节点是一个压缩列表(ziplist)」的混合结构:

  • 链表的每个节点叫quicklistNode,每个节点内部是一段连续内存的 ziplist,存多个元素
  • 节点之间用双向指针连接,保证头尾插入删除 O(1)
  • 节点内部用压缩列表存储,减少内存碎片,提升空间利用率

2. 对开发的意义

  1. 头尾操作永远是 O(1):不管列表多大,LPUSH/RPUSH/LPOP/RPOP 都极快,放心使用。
  2. 中间操作是 O(n):LINDEX、LSET、LINSERT、中间位置 LREM 都需要遍历,列表越大越慢。
  3. 小列表内存极省:元素少时,整个列表就是一个 ziplist,连续内存占用非常小。
  4. 可配置优化:通过list-max-ziplist-size可以调整每个 ziplist 的大小,平衡空间和性能。

四、实际开发中的经典应用场景

这部分是开发最关心的内容,每个场景都讲清楚「实现方式 + 优缺点 + 适用边界」。

场景1:轻量级消息队列

这是 List 最广泛的用途之一,适合非核心异步任务。

实现方式
  • 生产者:LPUSH把任务投递到队列
  • 消费者:BRPOP阻塞等待获取任务
# 生产者:投递支付回调任务 LPUSH order:pay:callback order_1001 order_1002 # 消费者:永久阻塞等待任务 BRPOP order:pay:callback 0
进阶:可靠队列(避免消息丢失)

RPOPLPUSH做消息中转:

  1. 消费前原子移动到「处理中队列」
  2. 消费成功后从处理中队列删除
  3. 消费失败可以从重试队列恢复
# 原子操作:待处理 → 处理中 RPOPLPUSH order:pay:callback order:pay:processing # 消费成功后删除 LREM order:pay:processing 1 order_1001
适用与不适用

✅ 适用:日志上报、短信发送、站内信、非核心数据同步等可容忍少量丢失的场景
❌ 不适用:订单支付、库存扣减等核心业务;需要广播、延迟消息、死信队列的场景

开发提示:核心业务请用 RabbitMQ、Kafka 等专业 MQ,Redis List 只做轻量补充。


场景2:最新N条记录 / 时间线

比如用户浏览历史、文章最新评论、朋友圈动态、操作日志。

实现方式

新记录用LPUSH插入头部,配合LTRIM固定长度,天然按时间倒序排列。

# 用户浏览商品,新增记录 LPUSH user:100:browse_history product_501 # 只保留最近20条,超出自动删除 LTRIM user:100:browse_history 0 19 # 查看浏览历史 LRANGE user:100:browse_history 0 -1
优势
  • 插入和裁剪都是接近 O(1),性能极高
  • 天然按时间倒序,不用额外排序
  • 内存占用小

开发提示:列表里只存 ID,不要存大对象;详情数据根据 ID 去数据库或缓存查。


场景3:栈 / 双端队列

  • 栈(先进后出)LPUSH + LPOP或者RPUSH + RPOP
  • 队列(先进先出)LPUSH + RPOP或者RPUSH + LPOP
  • 双端队列:两端都可进出,灵活适配业务

场景4:浅分页列表

比如商品评论、公告列表,按时间倒序分页:

  • 第1页:LRANGE comment:100 0 9
  • 第2页:LRANGE comment:100 10 19

开发提示:只适合小数据量、分页不深的场景。深分页的 LRANGE 需要遍历到起始位置,性能会下降;大数据量分页建议用 Sorted Set 或数据库。


五、生产环境避坑指南

  1. 禁止大列表:单个 List 建议不超过 1 万条元素,过大的列表会占用大量内存,操作时容易阻塞 Redis。数据量大请分 key 或换其他数据结构。
  2. 禁止全量查询大列表:绝对不要对大列表执行LRANGE key 0 -1,会导致 Redis 长时间阻塞。
  3. 尽量不操作中间元素LINDEXLSETLINSERT、中间位置LREM都是 O(n),业务上尽量只操作两端。
  4. 阻塞命令设置超时BRPOP不要无脑设 0 永久阻塞,设置合理超时时间,避免连接泄漏;配合连接池使用。
  5. 批量插入减少 IO:用LPUSH/RPUSH一次传多个值,比循环单次插入减少大量网络开销。
  6. 定长用 LTRIM 不用 LREMLTRIM性能远高于循环删除,做最新列表优先用裁剪。

返回列表