Redis列表(List):实现队列/栈的利器,底层原理与实战

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列表是一个功能强大的有序集合数据结构:

核心知识点

  1. 底层原理:quicklist实现,兼顾内存效率和性能
  2. 基本操作:头尾插入删除、索引访问、范围查询
  3. 阻塞操作:实现生产者消费者模式的关键
  4. 应用场景:消息队列、任务队列、访问记录、日志收集
  5. 性能优化:优先头尾操作,控制列表长度

关键要点

  • 双端高效:头尾操作时间复杂度O(1)
  • 阻塞机制:支持阻塞式操作,实现队列功能
  • 原子性保证:单个操作和移动操作都是原子的
  • 内存优化:quicklist结构平衡内存和性能

实战建议

  1. 合理选择操作:根据场景选择合适的插入删除方式
  2. 控制数据规模:避免单个列表过大影响性能
  3. 监控列表状态:定期检查内存使用和编码类型
  4. 结合业务特点:根据访问模式选择最优的数据结构

通过本文学习,你应该能够熟练使用Redis列表实现各种队列功能。


下一篇预告《Redis集合(Set):去重与交集/并集操作,这些场景必用》


相关推荐
一枝小雨2 小时前
【C++】list 容器操作
开发语言·c++·笔记·list·学习笔记
秋难降2 小时前
零基础学习SQL(十一):SQL 索引结构|从 B+Tree 到 Hash,面试常问的 “为啥选 B+Tree” 有答案了
数据库·后端·mysql
代码的余温3 小时前
Linux内核调优实战指南
linux·服务器·数据库
almighty273 小时前
C# DataGridView表头自定义设置全攻略
数据库·c#·winform·datagridview·自定义表头
ljh5746491193 小时前
mysql 必须在逗号分隔字符串和JSON字段之间二选一,怎么选
数据库·mysql·json
论迹3 小时前
【Redis】-- 持久化
数据库·redis·缓存
getdu3 小时前
Redis面试相关
数据库·redis·面试
TDengine (老段)3 小时前
TDengine 选择函数 TOP() 用户手册
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
m0_694845573 小时前
教你使用服务器如何搭建数据库
linux·运维·服务器·数据库·云计算