Redis List 的详细介绍
以下是 Redis List 的详细介绍,从基础命令、内部编码和使用场景三个维度展开:
一、基础命令
Redis List 支持双向操作(头尾插入/删除),适用于队列、栈等场景,以下是核心命令分类:
1. 插入与删除
-
LPUSH / RPUSH:从左侧(头部)或右侧(尾部)插入元素。
LPUSH mylist "A" # 头部插入 "A",返回列表长度 RPUSH mylist "B" # 尾部插入 "B",返回列表长度
-
LPOP / RPOP:从头部或尾部移除并返回元素。
LPOP mylist # 移除头部元素 "A" RPOP mylist # 移除尾部元素 "B"
-
LINSERT:在指定元素前/后插入新元素。
LINSERT mylist BEFORE "B" "A" # 在 "B" 前插入 "A"
-
LREM:删除指定数量的匹配元素。
LREM mylist 2 "A" # 删除最多2个 "A"
2. 查询与截断
-
LRANGE:获取指定索引范围内的元素。
LRANGE mylist 0 -1 # 获取所有元素
-
LINDEX:获取指定位置的元素。
LINDEX mylist 0 # 返回头部元素
-
LLEN:获取列表长度。
LLEN mylist # 返回列表元素总数
-
LTRIM:保留指定范围内的元素,其余删除。
LTRIM mylist 0 4 # 仅保留前5个元素
3. 阻塞操作
-
BLPOP / BRPOP:阻塞式弹出元素,适用于消息队列。
BLPOP task_queue 10 # 阻塞10秒等待头部元素
二、内部编码
Redis List 的底层实现根据数据规模动态选择编码方式:
1. listpack(Redis 7.0+)
-
条件:当所有元素满足以下条件时使用 listpack(连续内存结构):
- 元素数量 ≤
list-max-listpack-size
(默认 512)。 - 每个元素的大小 ≤
list-max-listpack-value
(默认 64 字节)。
- 元素数量 ≤
-
特点:
- 内存紧凑,无指针开销,适合小规模数据。
- 规避了 ziplist 的连锁更新问题(见 Redis 7.0+ 的改进)。
2. quicklist(Redis 3.2+)
-
触发条件:超出 listpack 容量时自动切换。
-
结构:由多个 listpack 节点组成的双向链表,平衡内存与操作效率。
- 每个 listpack 节点存储多个元素。
- 支持头尾快速插入/删除(时间复杂度 O(1))。
-
配置调优:
list-max-listpack-size -2 # 默认每个 listpack 节点大小 8KB list-compress-depth 0 # 压缩深度(0 表示不压缩)
三、使用场景
1. 消息队列
-
生产者:
LPUSH
插入任务到队列头部。 -
消费者:
BRPOP
阻塞获取任务,避免轮询。# 生产者 LPUSH task_queue "task1" # 消费者 BRPOP task_queue 0 # 0 表示无限阻塞
2. 最新消息排行榜
-
插入:
LPUSH
将新消息加入列表头部。 -
展示:
LRANGE 0 9
获取最新的 10 条消息。 -
清理:
LTRIM 0 99
保留最近 100 条消息。package com.example.redis.list; /** * 描述: 消息绑 * * @author ZHOUXIAOYUE * @date 2025/4/17 15:07 */ import redis.clients.jedis.Jedis; import java.util.List; public class LatestNewsBoard { public static void main(String[] args) { // 连接 Redis 服务,默认地址 localhost 和端口 6379 Jedis jedis = new Jedis("localhost", 6379); // 定义 Redis List 的 key String listKey = "latest:news"; for (int x= 0; x < 100; x++) { // 模拟插入一条新消息 String newMessage = "Breaking News: Redis 使用场景持续扩展!"+x; // 使用 LPUSH 命令将消息插入到列表头部 jedis.lpush(listKey, newMessage); System.out.println("新消息已插入: " + newMessage); } // 使用 LLEN 命令获取 // 数据清理:使用 LTRIM 保留最近 100 条消息 jedis.ltrim(listKey, 0, 99); System.out.println("消息列表已清理,保留最近 100 条记录。"); // 展示最新 10 条消息:使用 LRANGE 命令获取列表中 0~9 范围内的消息 List<String> latestMessages = jedis.lrange(listKey, 0, 9); System.out.println("最新的 10 条消息如下:"); for (String message : latestMessages) { System.out.println(message); } // 关闭 Redis 连接 jedis.close(); } }
3. 历史记录
-
用户操作日志:
LPUSH
记录用户行为,LTRIM
限制记录数量。LPUSH user:1001:logs "click_button_A" LTRIM user:1001:logs 0 99 # 保留最近 100 条日志
4. 栈(LIFO)
-
入栈:
LPUSH
,出栈:LPOP
。LPUSH my_stack "data1" LPOP my_stack # 返回 "data1"
5. 数据分片存储
- 大列表拆分为多个 quicklist 节点,减少单节点内存压力。
四、性能与调优建议
-
优先使用
LPUSH
/RPUSH
+LTRIM
:- 实现固定长度列表(如最新 100 条消息),避免无限增长。
-
避免大范围
LRANGE
操作:- 获取全部数据时,使用
LRANGE 0 -1
可能导致阻塞,建议分页或迭代遍历。
- 获取全部数据时,使用
-
合理配置 quicklist:
- 调整
list-max-listpack-size
平衡内存与性能(默认 8KB)。 - 启用压缩(
list-compress-depth
)节省内存,但会增加 CPU 开销。
- 调整
总结
-
核心优势:支持双向操作、阻塞弹出、动态分片存储。
-
适用场景:消息队列、实时排行榜、操作日志、栈/队列实现。
-
版本差异:
- Redis 3.2+ 使用 quicklist,替代旧版的 ziplist + linkedlist。
- Redis 7.0+ 使用 listpack 替代 ziplist,提升安全性和性能。
-
命令选择:高频写入用
LPUSH
/RPUSH
,阻塞消费用BRPOP
,精确控制用LTRIM
。
使用案例 :
-
博客信息展示 列表
package com.example.redis.list; /** * 描述: * * @author ZHOUXIAOYUE * 每篇文章使用哈希结构存储(字段:title、timestamp、content),文章的 key 格式为 "article:{id}"。 * 每个用户有自己的文章列表,列表 key 格式为 "user:{id}:articles",列表中存储文章 id,利用 LPUSH 添加文章(最新的在列表头部)。 * 分页获取用户文章列表,示例中获取用户 id=1 的前 10 篇文章(即列表索引 0~9),再根据文章 id 获取对应的文章详细信息。 * @date 2025/4/17 16:35 */ import redis.clients.jedis.Jedis; import java.util.Map; import java.util.HashMap; import java.util.List; import java.util.ArrayList; public class UserArticlesPagination { // 定义文章类 static class Article { private String id; private String title; private String timestamp; private String content; public Article(String id, String title, String timestamp, String content) { this.id = id; this.title = title; this.timestamp = timestamp; this.content = content; } public String getId() { return id; } public String getTitle() { return title; } public String getTimestamp() { return timestamp; } public String getContent() { return content; } } // 添加文章,将文章哈希存储,并将文章 id 插入到用户的文章列表中 public static void addArticle(Jedis jedis, String userId, Article article) { // 定义文章的 key 格式: article:{id} String articleKey = "article:" + article.getId(); Map<String, String> articleMap = new HashMap<>(); articleMap.put("title", article.getTitle()); articleMap.put("timestamp", article.getTimestamp()); articleMap.put("content", article.getContent()); // 使用 HMSET 存储文章哈希 jedis.hmset(articleKey, articleMap); // 将文章 id 添加到用户文章列表中,列表 key 格式: user:{id}:articles String userArticlesKey = "user:" + userId + ":articles"; // 使用 LPUSH 保证最新文章位于列表头部 jedis.lpush(userArticlesKey, article.getId()); } // 分页获取用户的文章列表,并返回文章详情集合 public static List<Map<String, String>> getUserArticles(Jedis jedis, String userId, int page, int pageSize) { String userArticlesKey = "user:" + userId + ":articles"; // 计算起始和结束索引(分页从 1 开始) int start = (page - 1) * pageSize; int end = start + pageSize - 1; // 获取文章 id 列表 List<String> articleIds = jedis.lrange(userArticlesKey, start, end); List<Map<String, String>> articles = new ArrayList<>(); // 根据文章 id 获取文章详情 for (String articleId : articleIds) { String articleKey = "article:" + articleId; Map<String, String> articleData = jedis.hgetAll(articleKey); // 如果文章数据存在,则加入结果列表 if (!articleData.isEmpty()) { articles.add(articleData); } } return articles; } public static void main(String[] args) { // 连接 Redis 服务,默认地址 localhost 与端口 6379 Jedis jedis = new Jedis("localhost", 6379); // 假设用户 id 为 "1",添加几篇文章测试 String userId = "1"; Article article1 = new Article("101", "Redis 入门", "2023-10-01 10:00:00", "Redis 是一个键值存储系统..."); Article article2 = new Article("102", "Redis 高级特性", "2023-10-02 11:30:00", "Redis 支持多种数据结构..."); Article article3 = new Article("103", "Redis 实战案例", "2023-10-03 14:20:00", "通过案例来掌握 Redis 应用..."); addArticle(jedis, userId, article1); addArticle(jedis, userId, article2); addArticle(jedis, userId, article3); // 分页获取用户文章列表:获取第一页,页面大小为 10 List<Map<String, String>> articles = getUserArticles(jedis, userId, 1, 10); System.out.println("用户 " + userId + " 的文章列表:"); for (Map<String, String> articleData : articles) { System.out.println("标题:" + articleData.get("title") + ", 时间:" + articleData.get("timestamp")); System.out.println("内容:" + articleData.get("content")); System.out.println("--------------------------"); } // 关闭 Jedis 连接 jedis.close(); } }