使用Redis队列优化内存队列

文章目录

问题描述

项目中遇到一个情况,执行批量扫描任务,并使用内存队列对任务进行排队。接口请求时采用异步,快速响应接口,后台排队执行任务。但是,在部署到服务器后,只要升级服务,没跑完的任务就会丢失。所以需要对内存队列进行优化,想到了两个方案:

  • Redis队列
  • 消息队列

如何选型

考虑业务的批量任务只是一次发起后就可以排队处理,不会源源不断的产生新的任务,且任务失败大概率是代码逻辑问题,不是网络波动,再次跑大概率还是失败,所以不需要重试机制。且使用消息队列的三个重要特性是:限流削峰、异步、解耦,但是使用需要维护三方:生产、存储、消费。而使用Redis只需要维护一方,且使用内存速度快。综上,该业务场景使用消息队列维护成本相对较高,意义不大。

如何实现

原代码

java 复制代码
@Slf4j
@Component
public class Scheduler {

    private final ConcurrentLinkedQueue<Long> pendingQueue = new ConcurrentLinkedQueue<>();
    
		public void batchTriggerSubtasks(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            log.warn("任务ID列表为空,跳过处理");
            return;
        }
        pendingQueue.addAll(ids);
    }
		
	@Scheduled(cron = "0/10 * * * * ?")
    public void processPendingSubtasks() {
        // 从队列中取出要处理的子任务数量
        int tasksToProcess = Math.min(remainingCapacity, pendingQueue.size());	
	
		// ...

        int processedCount = 0;
        while (processedCount < tasksToProcess && !pendingQueue.isEmpty()) {
            Long subtaskId = pendingQueue.poll();
            if (subtaskId != null) {
                try {
                    //具体业务...
                } catch (Exception e) {
                    log.error("触发任务失败,ID: {}, 错误: {}", subtaskId, e.getMessage(), e);
                    // 失败的任务重新加入队列末尾,以便稍后重试
                    pendingQueue.offer(subtaskId);
                }
            }
        }
    }
}

改进后

java 复制代码
@Slf4j
@Component
public class Scheduler {

    private static final String PENDING_QUEUE = "queue:pending";
		
	@Resource
    private RedisUtils redisUtils;
    
	public void batchTriggerSubtasks(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            log.warn("任务ID列表为空,跳过处理");
            return;
        }
        redisUtils.addAll(PENDING_QUEUE, ids);
    }
		
	@Scheduled(cron = "0/10 * * * * ?")
    public void processPendingTasks() {
        // 从队列中取出要处理的子任务数量
        int tasksToProcess = (int) Math.min(remainingCapacity, redisUtils.size(PENDING_QUEUE));
        	
		// ...

        int processedCount = 0;
        while (processedCount < tasksToProcess && redisUtils.size(PENDING_QUEUE) != 0) {
            Long id = redisUtils.poll(PENDING_QUEUE);
            if (id != null) {
                try {
                    //具体业务...
                } catch (Exception e) {
                    log.error("触发任务失败,ID: {}, 错误: {}", id, e.getMessage(), e);
                    // 失败的任务重新加入队列末尾,以便稍后重试
                    redisUtils.offer(PENDING_QUEUE, id);
                }
            }
        }
    }
}
java 复制代码
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * redis工具类.
 */
@Component
public class RedisUtils {

  private boolean keyExisted = true;

  @Autowired
  private StringRedisTemplate stringRedisTemplate;

  @Resource
  private RedisTemplate<String, Object> redisTemplate;

  public RedisUtils() {
  }

  public void setVal(String key, Object value) {
    redisTemplate.opsForValue().set(key, value);
  }

  public void setValWithExpireTime(String key, Object value, long expireTime, TimeUnit timeUnit) {
    redisTemplate.opsForValue().set(key, value, expireTime, timeUnit == null
        ? TimeUnit.SECONDS : timeUnit);
  }

  public Object getVal(String key) {
    return redisTemplate.opsForValue().get(key);
  }

  public Boolean hasKey(String key) {
    return redisTemplate.hasKey(key);
  }

  public boolean tryLock(String key, long expireTime) {
    return Boolean.TRUE.equals(redisTemplate.opsForValue()
      .setIfAbsent(key, "", expireTime, TimeUnit.SECONDS));
  }

  public void delByKey(String key) {
    redisTemplate.delete(key);
  }

  public String getRedisStrValue(String key) {
    return (String) this.stringRedisTemplate.opsForValue().get(key);
  }

  public void setRedisStrSecondValue(String key, String value, long senconds) {
    this.stringRedisTemplate.opsForValue().set(key, value, senconds, TimeUnit.SECONDS);
  }

  public boolean expire(String key, long time) {
    try {
      if (time > 0L) {
        this.redisTemplate.expire(key, time, TimeUnit.SECONDS);
      }

      return true;
    } catch (Exception var5) {
      var5.printStackTrace();
      return false;
    }
  }

  /**
   * 批量添加任务到指定队列.
   */
  public <T> void addAll(String queueKey, Collection<T> items) {
    if (items != null && !items.isEmpty()) {
      redisTemplate.opsForList().rightPushAll(queueKey, items.toArray());
    }
  }

  /**
   * 获取队列大小.
   *
   * @param queueKey 队列名
   * @return 队列大小.
   */
  public long size(String queueKey) {
    Long size = redisTemplate.opsForList().size(queueKey);
    return size != null ? size : 0L;
  }

  /**
   * 从队列头部取出任务.
   */
  @SuppressWarnings("unchecked")
  public <T> T poll(String queueKey) {
    return (T) redisTemplate.opsForList().leftPop(queueKey);
  }

  /**
   * 从队列头部取出任务,带超时时间.
   */
  @SuppressWarnings("unchecked")
  public <T> T poll(String queueKey, long timeout, TimeUnit timeUnit) {
    return (T) redisTemplate.opsForList().leftPop(queueKey, timeout, timeUnit);
  }

  /**
   * 添加单个任务到队列.
   */
  public <T> void offer(String queueKey, T item) {
    if (item != null) {
      redisTemplate.opsForList().rightPush(queueKey, item);
    }
  }

  /**
   * 批量取出指定数量的任务.
   */
  @SuppressWarnings("unchecked")
  public <T> List<T> pollBatch(String queueKey, int batchSize) {
    List<Object> results = redisTemplate.executePipelined(new SessionCallback<Object>() {
      @Override
      public Object execute(RedisOperations operations) {
        for (int i = 0; i < batchSize; i++) {
          operations.opsForList().leftPop(queueKey);
        }
        return null;
      }
    });

    return results.stream()
      .filter(obj -> obj != null)
      .map(obj -> (T) obj)
      .collect(Collectors.toList());
  }

  /**
   * 查看队列头部的任务但不移除.
   */
  @SuppressWarnings("unchecked")
  public <T> T peek(String queueKey) {
    return (T) redisTemplate.opsForList().index(queueKey, 0);
  }

  /**
   * 清空指定队列.
   */
  public void clear(String queueKey) {
    redisTemplate.delete(queueKey);
  }

  /**
   * 检查队列是否存在.
   */
  public boolean exists(String queueKey) {
    Boolean exists = redisTemplate.hasKey(queueKey);
    return exists != null && exists;
  }

}

以上为个人学习分享,如有问题,欢迎指出:)

相关推荐
+VX:Fegn08952 分钟前
计算机毕业设计|基于springboot + vue建筑材料管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
2301_800256114 分钟前
B+树:数据库的基石 R树:空间数据的索引专家 四叉树:空间划分的网格大师
数据结构·数据库·b树·机器学习·postgresql·r-tree
大厂技术总监下海27 分钟前
用户行为分析怎么做?ClickHouse + 嵌套数据结构,轻松处理复杂事件
大数据·数据结构·数据库
alonewolf_9936 分钟前
深入理解MySQL事务与锁机制:从原理到实践
android·数据库·mysql
朝依飞1 小时前
fastapi+SQLModel + SQLAlchemy2.x+mysql
数据库·mysql·fastapi
3***g2051 小时前
redis连接服务
数据库·redis·bootstrap
m0_598177231 小时前
SQL 方法函数(1)
数据库
oMcLin1 小时前
如何在Oracle Linux 8.4上通过配置Oracle RAC集群,确保企业级数据库的高可用性与负载均衡?
linux·数据库·oracle
信创天地1 小时前
核心系统去 “O” 攻坚:信创数据库迁移的双轨运行与数据一致性保障方案
java·大数据·数据库·金融·架构·政务
胖咕噜的稞达鸭1 小时前
进程间的通信(1)(理解管道特性,匿名命名管道,进程池,systeam V共享内存是什么及优势)重点理解代码!
linux·运维·服务器·数据库