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):去重与交集/并集操作,这些场景必用》


相关推荐
憧憬成为web高手11 分钟前
皮卡丘靶场速通--sql 2
数据库·sql·mybatis
段一凡-华北理工大学23 分钟前
向量数据库实战:选型、调优与落地~系列文章12:文本分块策略实战:chunk_size 怎么选?重叠多少?
开发语言·数据库·后端·oracle·rust·工业智能体·高炉智能化
二宝哥1 小时前
CentOS 7.9 系统下 Redis 5.0.4 单机安装与源码编译安装指南
redis·centos
oradh2 小时前
Oracle XTTS实现跨版本迁移和升级(Oracle 11g单库升级至19C RAC集群)
数据库·oracle·11g升级19c·xtts跨版本迁移和升级
z123456789863 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
程序猿DD3 小时前
一个 API Key,统一调用大模型、生图和联网搜索
数据库·网关
TlSfoward4 小时前
抓包代理链路下的 TLS 指纹变化分析 TLSFOWARD抓包工具
数据库·爬虫·网络协议·搜索引擎·php
数智化管理手记5 小时前
账龄分析手工统计易遗漏?自动账龄分析工具怎么搭建
大数据·网络·数据库·人工智能·数据挖掘
LabVIEW开发5 小时前
NI Package Manager安装LabVIEW时InternalServerError错误
网络·数据库·labview·labview知识·labview功能·labview程序
SelectDB5 小时前
PostgreSQL 实时同步到 Apache Doris:一站式 CDC 解决方案
数据库