Redis List实战:从队列到最新列表的高效应用

引言

上一篇我们吃透了 String 类型,这篇接着讲第二种类型------**List(列表)**​。如果说 String 是"一个 Key 存一个值",那 List 就是"一个 Key 存一串有序的值",非常适合表达"按顺序排列的一组数据"。

List 在 Redis 里有个特别亮眼的用途:消息队列。很多中小项目不想引入 Kafka、RocketMQ 这么重的东西,就干脆用 Redis 的 List 做轻量级队列。这篇文章我会把 List 的底层结构、命令、内部编码和应用场景一次讲清楚,最后落在一个消息队列的实战例子上。

一、List 是什么

Redis 的 List 是一个有序、可重复 的字符串列表,底层是双向链表。它的最大特点是:

  • 头尾操作极快:从头部或尾部插入、弹出元素都是 O(1)。
  • 中间操作较慢 :按下标随机访问(LINDEX)是 O(n),因为它要顺着链表找。

所以 List 天生适合做"先进先出"的队列,或"后进先出"的栈,而不适合频繁按下标随机访问的场景。

二、底层结构:从 ziplist + linkedlist 到 quicklist

Redis 的 List 底层实现经过了几次演进,面试常问:

  1. 早期 :元素少且短时用 ziplist(压缩列表) ,元素多或长时用 linkedlist(双向链表),两者二选一。
  2. Redis 3.2 之后 :统一改用 quicklist 。quicklist 本质是"​linkedlist + ziplist 的组合"------一个由多个 ziplist 节点串起来的链表,兼顾了链表的灵活和 ziplist 的紧凑内存。
  3. Redis 7.0 之后 :ziplist 又被 listpack 逐步替代(listpack 修复了 ziplist 级联更新的缺陷)。

记住一个结论即可:**现代 Redis 的 List 用 quicklist(内部是 listpack)实现,兼顾内存紧凑和头尾操作效率。**​

三、常用命令详解

3.1 从两端插入 / 弹出

plaintext

复制代码
127.0.0.1:6379> RPUSH mylist a b c    # 从右边(尾部)插入
(integer) 3
127.0.0.1:6379> LPUSH mylist 0        # 从左边(头部)插入
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1    # 查看全部(0 到 -1 表示从头到尾)
1) "0"
2) "a"
3) "b"
4) "c"
127.0.0.1:6379> LPOP mylist           # 从左边弹出
"0"
127.0.0.1:6379> RPOP mylist           # 从右边弹出
"c"

3.2 查看与修改

plaintext

复制代码
127.0.0.1:6379> LLEN mylist           # 列表长度
(integer) 2
127.0.0.1:6379> LINDEX mylist 0       # 按下标取元素(O(n))
"a"
127.0.0.1:6379> LSET mylist 0 A       # 按下标修改
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "A"
2) "b"

3.3 插入与删除指定元素

plaintext

复制代码
127.0.0.1:6379> RPUSH mylist x y x
(integer) 5
127.0.0.1:6379> LREM mylist 2 x       # 从头删除 2 个 x
(integer) 2
127.0.0.1:6379> LINSERT mylist BEFORE y new   # 在 y 前面插入 new
(integer) 6

3.4 截取(保留区间,其余删除)

plaintext

复制代码
127.0.0.1:6379> RPUSH news 1 2 3 4 5 6 7 8 9 10
(integer) 10
127.0.0.1:6379> LTRIM news 0 4        # 只保留前 5 个
OK
127.0.0.1:6379> LRANGE news 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"

LTRIM 是实现"最新 N 条"的利器:每插入一条就 trim 一下,列表永远只留最新的一批。

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

plaintext

复制代码
# 客户端 A:BLPOP 阻塞等待,超时时间 0 表示一直等
127.0.0.1:6379> BLPOP queue 0
# ... 此时会阻塞,直到有元素进来 ...

# 客户端 B:往队列里推一条
127.0.0.1:6379> LPUSH queue hello
(integer) 1

# 客户端 A 立即收到
1) "queue"
2) "hello"

BLPOP / BRPOP 是阻塞命令:队列为空时会一直等待,有元素时立即弹出。这避免了"轮询空队列"白白消耗 CPU 的问题,是 Redis 做消息队列的关键。

3.6 移动元素(可靠队列基础)

plaintext

复制代码
127.0.0.1:6379> RPUSH source 1 2 3
(integer) 3
127.0.0.1:6379> RPOPLPUSH source backup   # 从 source 尾部弹出,塞到 backup 头部
"3"

RPOPLPUSH(新版本叫 LMOVE)常用于实现"可靠队列":消费时先把消息转移到备份队列,处理完再删除,这样消费者崩溃时消息不会丢。

四、内部编码速查

编码 说明
quicklist 3.2+ 的默认实现,linkedlist + listpack 的组合
(旧)ziplist 早期小列表用,已逐步被 listpack 取代
(旧)linkedlist 早期大列表用

用 OBJECT ENCODING 可以查看:

plaintext

复制代码
127.0.0.1:6379> OBJECT ENCODING mylist
"listpack"

五、典型应用场景

5.1 消息队列(轻量级)

用 LPUSH + BRPOP 实现最简单的队列:

java

复制代码
// 生产者:往队列里放消息
public void produce(StringRedisTemplate redis, String message) {
    redis.opsForList().leftPush("order:queue", message);
}

// 消费者:阻塞式取消息
public String consume(StringRedisTemplate redis) {
    // rightPop(key, timeout, unit) 队列为空时阻塞等待
    return redis.opsForList().rightPop("order:queue", 0, TimeUnit.SECONDS);
}

适合小规模、对可靠性要求不高 的场景。要注意:Redis 的 List 队列是"消费即删除",如果消费者处理消息时崩溃,消息就丢了;需要可靠性的场景要用 RPOPLPUSH 做备份,或直接上专业的 MQ。

5.2 最新消息 / 最新评论列表

用 LPUSH 新消息进来 + LTRIM 只保留最新 N 条:

java

复制代码
public void addLatest(StringRedisTemplate redis, String news) {
    String key = "latest:news";
    redis.opsForList().leftPush(key, news);      // 新消息从头部进
    redis.opsForList().trim(key, 0, 99);          // 只保留最新 100 条
}

public List<String> getLatest(StringRedisTemplate redis) {
    return redis.opsForList().range("latest:news", 0, -1);
}

微博、新闻 App 的"最新动态"列表就是这么做的。

5.3 栈与普通队列

  • 栈(后进先出) :LPUSH + LPOP(同侧进出)。
  • 队列(先进先出) :LPUSH + RPOP(异侧进出)。

java

复制代码
// 栈
redis.opsForList().leftPush("stack", "a");
String top = redis.opsForList().leftPop("stack");

// 队列
redis.opsForList().leftPush("queue", "a");
String head = redis.opsForList().rightPop("queue");

5.4 简单的优先级/任务缓冲

配合多个队列(如 task:high、task:normal),消费者优先 BRPOP 高优先级队列,取不到再去低优先级队列,就能实现简单的任务分级。

六、命令速查表

命令 作用
LPUSH / RPUSH 从左 / 右插入
LPOP / RPOP 从左 / 右弹出
BLPOP / BRPOP 阻塞式弹出
LRANGE 按区间取元素
LLEN 列表长度
LINDEX 按下标取元素
LSET 按下标修改
LINSERT 在指定元素前后插入
LREM 删除指定元素
LTRIM 截取保留区间
RPOPLPUSH / LMOVE 移动元素到另一个列表

七、总结

Redis 的 List 是一个有序、可重复的双向链表结构,要点如下:

  1. 底层用 quicklist(内部 listpack),头尾操作 O(1)、中间随机访问 O(n),所以"从两端进出"是它的强项。
  2. 核心命令分几类:两端插入弹出(LPUSH/RPUSH/LPOP/RPOP)、查看修改(LRANGE/LINDEX/LSET)、条件操作(LINSERT/LREM/LTRIM)、阻塞弹出(BLPOP/BRPOP)、元素移动(RPOPLPUSH/LMOVE)。
  3. 最典型应用是轻量级消息队列(LPUSH + BRPOP)和"最新 N 条"列表(LPUSH + LTRIM),也能轻松实现栈和队列。
  4. 做消息队列时要注意"消费即删除"的特性,可靠性要求高时需要 RPOPLPUSH 备份或改用专业 MQ。

一句话总结:List 天生适合"从两端进出"的顺序数据------做队列、做栈、做最新列表,用它就对了。

相关推荐
码智社2 小时前
Redis生产环境高频踩坑总结(Java后端视角)
java·数据库·redis
江苏汉软2 小时前
制造企业为什么需要MES
java·数据库·制造·mes系统·mes软件
心.c2 小时前
从“加个缓存”到稳定上线:把 Redis 放回真实业务里
数据库·redis·ai·1024程序员节
benjormin2 小时前
MySQL数据库基础
数据库·mysql
znnnk12 小时前
数据库被AI“重写”:2026,从CPU中心走向GPU中心
数据库·人工智能·oracle
微微%12 小时前
mysql的安装和使用
数据库·mysql·mysql8安装
白山编程大哥13 小时前
Java 后端如何优雅地把 List 集合返回给前端
java·前端·list
软件派13 小时前
MiniMax M3.1 Flash 高频业务落地实战指南
大数据·数据库·人工智能
云边云科技_云网融合14 小时前
连锁门店组网怎么做?SD-WAN 多分支组网的架构选型、部署与成本结构
服务器·数据库·php