Redis列表(List):实现队列/栈的利器,底层原理与实战
1. Redis列表概述
1.1 什么是Redis列表
Redis列表(List)是一个有序的字符串元素集合 ,支持在头部和尾部 进行高效的插入和删除操作。它可以实现栈(Stack)、**队列(Queue)**等多种数据结构的功能。
1.2 列表的特点
特性 | 描述 | 优势 |
---|---|---|
有序性 | 元素按插入顺序排列 | 保持数据的时间序列 |
双端操作 | 支持头尾两端插入删除 | 实现多种数据结构 |
索引访问 | 支持按索引访问元素 | 灵活的数据读取 |
阻塞操作 | 支持阻塞式弹出 | 实现生产者消费者模式 |
2. 底层实现原理
2.1 编码方式演进
Redis版本 | 编码方式 | 特点 |
---|---|---|
3.2之前 | ziplist + linkedlist | 双编码切换 |
3.2-6.2 | quicklist | 统一实现 |
7.0+ | listpack | 内存优化 |
2.2 quicklist实现详解
quicklist结构特点:
- 由多个ziplist节点组成的双向链表
- 每个节点包含一个ziplist
- 兼顾内存效率和操作性能
2.3 配置参数
conf
# redis.conf 列表相关配置
list-max-ziplist-size -2 # 单个ziplist大小限制(8KB)
list-compress-depth 0 # 压缩深度(0=不压缩)
3. 基本列表操作
3.1 插入操作
bash
# 头部插入
127.0.0.1:6379> LPUSH mylist "a" "b" "c"
(integer) 3
# 尾部插入
127.0.0.1:6379> RPUSH mylist "d" "e"
(integer) 5
# 指定位置插入
127.0.0.1:6379> LINSERT mylist BEFORE "b" "new"
(integer) 6
3.2 删除操作
bash
# 头部弹出
127.0.0.1:6379> LPOP mylist
"c"
# 尾部弹出
127.0.0.1:6379> RPOP mylist
"e"
# 按值删除
127.0.0.1:6379> LREM mylist 2 "a"
(integer) 1
3.3 查询操作
bash
# 范围查询
127.0.0.1:6379> LRANGE mylist 0 -1
1) "new"
2) "b"
3) "a"
4) "d"
# 索引查询
127.0.0.1:6379> LINDEX mylist 0
"new"
# 长度查询
127.0.0.1:6379> LLEN mylist
(integer) 4
4. 阻塞式操作
4.1 阻塞弹出
bash
# 阻塞式左弹出
127.0.0.1:6379> BLPOP list1 list2 10
1) "list1"
2) "element"
# 阻塞式右弹出
127.0.0.1:6379> BRPOP myqueue 0
# 阻塞式移动
127.0.0.1:6379> BRPOPLPUSH source dest 30
4.2 应用模式
bash
# 栈模式(LIFO)
LPUSH stack "item1"
LPOP stack
# 队列模式(FIFO)
LPUSH queue "task1"
RPOP queue
# 双端队列
LPUSH deque "left"
RPUSH deque "right"
5. 实战应用场景
5.1 消息队列系统
java
@Service
public class RedisMessageQueue {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 发送消息
*/
public void sendMessage(String queueName, String message) {
redisTemplate.opsForList().leftPush(queueName, message);
}
/**
* 接收消息(阻塞)
*/
public String receiveMessageBlocking(String queueName, long timeout, TimeUnit unit) {
return redisTemplate.opsForList().rightPop(queueName, timeout, unit);
}
/**
* 获取队列长度
*/
public Long getQueueSize(String queueName) {
return redisTemplate.opsForList().size(queueName);
}
}
5.2 任务队列处理器
java
@Service
public class TaskQueueProcessor {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String TASK_QUEUE = "task:queue";
private static final String PROCESSING_QUEUE = "task:processing";
/**
* 添加任务
*/
public void addTask(String task) {
redisTemplate.opsForList().leftPush(TASK_QUEUE, task);
}
/**
* 处理任务
*/
public void processTask() {
// 从任务队列移动到处理队列(原子操作)
String task = redisTemplate.opsForList()
.rightPopAndLeftPush(TASK_QUEUE, PROCESSING_QUEUE);
if (task != null) {
try {
// 执行任务
executeTask(task);
// 任务完成,从处理队列移除
redisTemplate.opsForList().lrem(PROCESSING_QUEUE, 1, task);
} catch (Exception e) {
// 任务失败处理
handleTaskFailure(task, e);
}
}
}
private void executeTask(String task) {
// 具体任务执行逻辑
System.out.println("执行任务: " + task);
}
private void handleTaskFailure(String task, Exception e) {
// 将失败任务移动到失败队列
redisTemplate.opsForList().lrem(PROCESSING_QUEUE, 1, task);
redisTemplate.opsForList().leftPush("task:failed", task);
}
}
5.3 最近访问记录
java
@Service
public class RecentAccessService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final int MAX_RECENT_COUNT = 100;
/**
* 记录用户访问
*/
public void recordAccess(String userId, String resource) {
String key = "recent:access:" + userId;
String accessRecord = resource + ":" + System.currentTimeMillis();
// 添加新访问记录
redisTemplate.opsForList().leftPush(key, accessRecord);
// 保持最多100条记录
redisTemplate.opsForList().ltrim(key, 0, MAX_RECENT_COUNT - 1);
// 设置过期时间
redisTemplate.expire(key, 30, TimeUnit.DAYS);
}
/**
* 获取最近访问记录
*/
public List<String> getRecentAccess(String userId, int count) {
String key = "recent:access:" + userId;
return redisTemplate.opsForList().range(key, 0, count - 1);
}
}
5.4 实时日志收集
java
@Service
public class LogCollectorService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String LOG_QUEUE = "logs:queue";
/**
* 收集日志
*/
public void collectLog(String level, String message) {
String logEntry = String.format("[%s] %s - %s",
level, new Date(), message);
redisTemplate.opsForList().leftPush(LOG_QUEUE, logEntry);
}
/**
* 批量获取日志
*/
public List<String> batchGetLogs(int batchSize) {
List<String> logs = new ArrayList<>();
for (int i = 0; i < batchSize; i++) {
String log = redisTemplate.opsForList().rightPop(LOG_QUEUE);
if (log == null) break;
logs.add(log);
}
return logs;
}
/**
* 阻塞式获取日志
*/
public String getLogBlocking(long timeout, TimeUnit unit) {
return redisTemplate.opsForList().rightPop(LOG_QUEUE, timeout, unit);
}
}
6. Java编程实践
6.1 完整的List工具类
java
@Component
public class RedisListUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// ==================== 插入操作 ====================
public Long leftPush(String key, Object value) {
return redisTemplate.opsForList().leftPush(key, value);
}
public Long leftPushAll(String key, Object... values) {
return redisTemplate.opsForList().leftPushAll(key, values);
}
public Long rightPush(String key, Object value) {
return redisTemplate.opsForList().rightPush(key, value);
}
// ==================== 弹出操作 ====================
public Object leftPop(String key) {
return redisTemplate.opsForList().leftPop(key);
}
public Object rightPop(String key) {
return redisTemplate.opsForList().rightPop(key);
}
public Object leftPopBlocking(String key, long timeout, TimeUnit unit) {
return redisTemplate.opsForList().leftPop(key, timeout, unit);
}
public Object rightPopAndLeftPush(String sourceKey, String destinationKey) {
return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey, destinationKey);
}
// ==================== 查询操作 ====================
public List<Object> range(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
public Object index(String key, long index) {
return redisTemplate.opsForList().index(key, index);
}
public Long size(String key) {
return redisTemplate.opsForList().size(key);
}
// ==================== 修改操作 ====================
public void set(String key, long index, Object value) {
redisTemplate.opsForList().set(key, index, value);
}
public Long remove(String key, long count, Object value) {
return redisTemplate.opsForList().remove(key, count, value);
}
public void trim(String key, long start, long end) {
redisTemplate.opsForList().trim(key, start, end);
}
}
7. 性能优化与最佳实践
7.1 性能特点
操作类型 | 时间复杂度 | 性能说明 |
---|---|---|
LPUSH/RPUSH | O(1) | 头尾插入高效 |
LPOP/RPOP | O(1) | 头尾弹出高效 |
LINDEX | O(N) | 随机访问慢 |
LRANGE | O(S+N) | S为起始偏移量 |
LREM | O(N+M) | M为删除的元素数 |
7.2 最佳实践
7.2.1 优先使用头尾操作
java
// ✅ 推荐:优先使用头尾操作
redisTemplate.opsForList().leftPush(key, value); // O(1)
redisTemplate.opsForList().rightPop(key); // O(1)
// ❌ 避免:频繁使用中间位置操作
redisTemplate.opsForList().index(key, 1000); // O(N)
redisTemplate.opsForList().set(key, 1000, value); // O(N)
7.2.2 控制列表长度
java
@Service
public class OptimizedListService {
private static final int MAX_LIST_SIZE = 10000;
/**
* 安全的列表插入
*/
public void safeListPush(String key, Object value) {
Long size = redisTemplate.opsForList().size(key);
if (size >= MAX_LIST_SIZE) {
redisTemplate.opsForList().rightPop(key);
}
redisTemplate.opsForList().leftPush(key, value);
}
/**
* 批量插入时的长度控制
*/
public void batchPushWithLimit(String key, List<Object> values) {
redisTemplate.opsForList().leftPushAll(key, values);
redisTemplate.opsForList().trim(key, 0, MAX_LIST_SIZE - 1);
}
}
7.2.3 使用Pipeline优化
java
/**
* Pipeline批量操作
*/
public void batchOperations(String key, List<String> values) {
redisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) {
for (String value : values) {
connection.lPush(key.getBytes(), value.getBytes());
}
return null;
}
});
}
7.3 监控和维护
java
@Service
public class ListMonitorService {
/**
* 监控列表状态
*/
public Map<String, Object> getListStats(String key) {
Map<String, Object> stats = new HashMap<>();
stats.put("size", redisTemplate.opsForList().size(key));
stats.put("exists", redisTemplate.hasKey(key));
// 获取内存使用和编码信息
String script =
"local memory = redis.call('memory', 'usage', KEYS[1]) " +
"local encoding = redis.call('object', 'encoding', KEYS[1]) " +
"return {memory, encoding}";
List<Object> result = (List<Object>) redisTemplate.execute(
new DefaultRedisScript<>(script, List.class),
Collections.singletonList(key)
);
if (result != null && result.size() >= 2) {
stats.put("memory_usage", result.get(0));
stats.put("encoding", result.get(1));
}
return stats;
}
}
总结
Redis列表是一个功能强大的有序集合数据结构:
核心知识点
- 底层原理:quicklist实现,兼顾内存效率和性能
- 基本操作:头尾插入删除、索引访问、范围查询
- 阻塞操作:实现生产者消费者模式的关键
- 应用场景:消息队列、任务队列、访问记录、日志收集
- 性能优化:优先头尾操作,控制列表长度
关键要点
- 双端高效:头尾操作时间复杂度O(1)
- 阻塞机制:支持阻塞式操作,实现队列功能
- 原子性保证:单个操作和移动操作都是原子的
- 内存优化:quicklist结构平衡内存和性能
实战建议
- 合理选择操作:根据场景选择合适的插入删除方式
- 控制数据规模:避免单个列表过大影响性能
- 监控列表状态:定期检查内存使用和编码类型
- 结合业务特点:根据访问模式选择最优的数据结构
通过本文学习,你应该能够熟练使用Redis列表实现各种队列功能。
下一篇预告 :《Redis集合(Set):去重与交集/并集操作,这些场景必用》