2. Redis核心数据结构回顾:String、List、Set、Sorted Set、Hash在任务队列中的应用场景

在构建异步任务编排系统时,Redis的五种核心数据结构各自承担着不同的角色。理解它们的特点与适用场景,是设计高效、可靠任务队列的基础。本章将逐一回顾这些数据结构,并重点分析它们在任务队列中的典型应用。

2.1 数据结构概览与任务队列映射

下表总结了五种数据结构的基本特性及其在任务队列中的常见定位:

数据结构 底层实现 核心特性 任务队列典型角色
String 动态字符串 简单键值、原子计数、过期时间 任务锁、计数器、任务结果暂存
List 双向链表 / 压缩列表 有序、阻塞弹出(BLPOP/BRPOP) 经典FIFO任务队列、延迟队列
Set 哈希表 / 整数集合 无序、去重、集合运算 任务去重、白名单/黑名单、任务标签
Sorted Set 跳表 + 哈希表 有序、按分值范围查询、排名 延迟队列、优先级队列、任务调度
Hash 压缩列表 / 哈希表 字段级操作、内存紧凑 任务元数据存储、任务状态跟踪
设计原则: 没有一种数据结构能解决所有问题。实际项目中,通常需要组合使用多种数据结构来构建完整的任务编排链路。

2.2 String:任务锁与计数器

String是Redis最基础的数据结构,在任务队列中主要用于以下两个场景:

  • 分布式任务锁: 使用 SET key value NX EX seconds 命令实现互斥锁,防止同一任务被多个消费者重复执行。
  • 任务计数器: 使用 INCR / DECR 原子操作统计任务处理速率、失败次数或重试次数。
// 获取任务锁(Java伪代码)
Boolean locked = redisTemplate.opsForValue()
    .setIfAbsent("task:lock:order_123", "worker_1", Duration.ofSeconds(30));
if (Boolean.TRUE.equals(locked)) {
    try {
        // 执行任务
    } finally {
        redisTemplate.delete("task:lock:order_123");
    }
}

// 任务失败计数
redisTemplate.opsForValue().increment("task:fail:order_123");

2.3 List:经典FIFO任务队列

List是构建任务队列最直观的选择。生产者使用 LPUSH 将任务推入队列头部,消费者使用 BRPOP 从队列尾部阻塞弹出,实现先进先出(FIFO)语义。

关键命令: BRPOP key timeout —— 阻塞式弹出,当队列为空时等待指定时间,避免空轮询浪费CPU。
// 生产者:推送任务
redisTemplate.opsForList().leftPush("task:queue:email", taskJson);

// 消费者:阻塞获取任务
String taskJson = redisTemplate.opsForList()
    .rightPop("task:queue:email", 5, TimeUnit.SECONDS);
if (taskJson != null) {
    processTask(taskJson);
}

List的局限性在于:不支持优先级,且无法直接实现延迟执行。这些需求需要借助Sorted Set或第三方插件。

2.4 Set:任务去重与标签管理

Set天然具备去重能力,在任务队列中常用于:

  • 任务ID去重: 在任务入队前,先检查Set中是否已存在该任务ID,防止重复提交。
  • 任务标签系统: 为任务打上多个标签(如“高优先级”、“需重试”),通过 SINTER / SUNION 进行灵活筛选。
// 任务去重检查
Boolean added = redisTemplate.opsForSet()
    .add("task:ids:email", "order_123");
if (Boolean.TRUE.equals(added)) {
    // 新任务,允许入队
    pushToList(taskJson);
} else {
    // 重复任务,忽略或更新
    log.warn("Duplicate task detected: order_123");
}

2.5 Sorted Set:延迟队列与优先级调度

Sorted Set是构建延迟队列和优先级队列的核心数据结构。它使用分值(score)作为排序依据,天然支持按时间或优先级调度。

  • 延迟队列: 将任务执行时间戳作为score,消费者定期使用 ZRANGEBYSCORE 拉取到期的任务。
  • 优先级队列: 将优先级数值(如1-10)作为score,高优先级任务分值更小,优先被取出。
// 延迟队列:任务在5分钟后执行
long executeAt = System.currentTimeMillis() + 300_000;
redisTemplate.opsForZSet()
    .add("task:delay:order", "order_123", executeAt);

// 消费者:每秒拉取到期任务
Set<String> tasks = redisTemplate.opsForZSet()
    .rangeByScore("task:delay:order", 0, System.currentTimeMillis());
for (String taskId : tasks) {
    // 处理任务
    redisTemplate.opsForZSet().remove("task:delay:order", taskId);
}
注意: 使用Sorted Set实现延迟队列时,需要配合轮询或定时任务。对于高精度延迟场景,建议结合Redis Keyspace Notifications或Redisson的RDelayedQueue。

2.6 Hash:任务元数据与状态跟踪

Hash允许以字段(field)为单位存储任务的多维属性,非常适合管理任务的完整生命周期:

  • 任务元数据: 存储任务类型、创建时间、重试次数、回调地址等。
  • 状态跟踪: 使用字段 status 记录任务当前状态(pending / running / success / failed),支持原子更新。
// 存储任务元数据
redisTemplate.opsForHash()
    .putAll("task:meta:order_123", Map.of(
        "type", "email",
        "status", "pending",
        "retry", "0",
        "created_at", String.valueOf(System.currentTimeMillis())
    ));

// 原子更新状态
redisTemplate.opsForHash()
    .put("task:meta:order_123", "status", "running");

2.7 综合架构:五种数据结构的协作

下图展示了一个典型的异步任务编排系统中,五种数据结构如何协同工作:

Redis五种数据结构在任务队列中的协作架构 生产者 Set 去重检查 List 主队列 Sorted Set 延迟队列 Hash 任务元数据 消费者 String 任务锁 String 计数器 协作流程说明: 1. 任务先经Set去重,再进入List或Sorted Set 2. 消费者从队列取出任务后,更新Hash元数据 3. String锁保证互斥,计数器记录执行结果

2.8 场景选择指南

在实际项目中,可以根据以下条件快速选择合适的数据结构:

业务需求 推荐数据结构 理由
简单FIFO队列 List LPUSH + BRPOP 天然支持阻塞消费
延迟执行任务 Sorted Set 以时间戳作为score,范围查询到期任务
任务去重 Set SADD 自动去重,O(1) 判断是否存在
任务状态跟踪 Hash 字段级更新,支持存储多维属性
分布式锁 String SET NX EX 原子操作,简单可靠
优先级调度 Sorted Set 以优先级数值作为score,支持范围获取
最佳实践: 在复杂任务编排系统中,建议将List作为主队列,Sorted Set作为延迟/优先级补充,Hash存储元数据,Set用于去重,String用于锁和计数。这种组合既能保证核心流程的高效,又能灵活应对各种边界场景。