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 valuecount > 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:view045. 阻塞式弹出(消息队列核心)
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. 对开发的意义
- 头尾操作永远是 O(1):不管列表多大,LPUSH/RPUSH/LPOP/RPOP 都极快,放心使用。
- 中间操作是 O(n):LINDEX、LSET、LINSERT、中间位置 LREM 都需要遍历,列表越大越慢。
- 小列表内存极省:元素少时,整个列表就是一个 ziplist,连续内存占用非常小。
- 可配置优化:通过
list-max-ziplist-size可以调整每个 ziplist 的大小,平衡空间和性能。
四、实际开发中的经典应用场景
这部分是开发最关心的内容,每个场景都讲清楚「实现方式 + 优缺点 + 适用边界」。
场景1:轻量级消息队列
这是 List 最广泛的用途之一,适合非核心异步任务。
实现方式
- 生产者:
LPUSH把任务投递到队列 - 消费者:
BRPOP阻塞等待获取任务
# 生产者:投递支付回调任务 LPUSH order:pay:callback order_1001 order_1002 # 消费者:永久阻塞等待任务 BRPOP order:pay:callback 0进阶:可靠队列(避免消息丢失)
用RPOPLPUSH做消息中转:
- 消费前原子移动到「处理中队列」
- 消费成功后从处理中队列删除
- 消费失败可以从重试队列恢复
# 原子操作:待处理 → 处理中 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 或数据库。
五、生产环境避坑指南
- 禁止大列表:单个 List 建议不超过 1 万条元素,过大的列表会占用大量内存,操作时容易阻塞 Redis。数据量大请分 key 或换其他数据结构。
- 禁止全量查询大列表:绝对不要对大列表执行
LRANGE key 0 -1,会导致 Redis 长时间阻塞。 - 尽量不操作中间元素:
LINDEX、LSET、LINSERT、中间位置LREM都是 O(n),业务上尽量只操作两端。 - 阻塞命令设置超时:
BRPOP不要无脑设 0 永久阻塞,设置合理超时时间,避免连接泄漏;配合连接池使用。 - 批量插入减少 IO:用
LPUSH/RPUSH一次传多个值,比循环单次插入减少大量网络开销。 - 定长用 LTRIM 不用 LREM:
LTRIM性能远高于循环删除,做最新列表优先用裁剪。