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


相关推荐
努力写代码的熊大8 小时前
List迭代器和模拟(迭代器的模拟)
数据结构·windows·list
恒悦sunsite10 小时前
Ubuntu之apt安装ClickHouse数据库
数据库·clickhouse·ubuntu·列式存储·8123
奥尔特星云大使11 小时前
MySQL 慢查询日志slow query log
android·数据库·mysql·adb·慢日志·slow query log
来自宇宙的曹先生11 小时前
MySQL 存储引擎 API
数据库·mysql
间彧11 小时前
MySQL Performance Schema详解与实战应用
数据库
间彧11 小时前
MySQL Exporter采集的关键指标有哪些,如何解读这些指标?
数据库
weixin_4462608511 小时前
Django - 让开发变得简单高效的Web框架
前端·数据库·django
mpHH11 小时前
babelfish for postgresql 分析--todo
数据库·postgresql
zizisuo12 小时前
解决在使用Lombok时maven install 找不到符号的问题
java·数据库·maven
胖咕噜的稞达鸭13 小时前
list 实现链表封装节点的底层逻辑:如何克服不连续无法正常访问挑战
windows·链表·list