使用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;
  }

}

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

相关推荐
CSDN_Colinw9 分钟前
Python GUI开发:Tkinter入门教程
jvm·数据库·python
Hui Baby10 分钟前
TIDB分布式数据库提交设想
数据库·分布式·tidb
ZhengEnCi17 分钟前
J7A-已有数据表如何安全添加新字段 🛡️
数据库
2401_8331977320 分钟前
用Python制作一个文字冒险游戏
jvm·数据库·python
一叶飘零_sweeeet27 分钟前
数据库连接池天花板之争:HikariCP 与 Druid 底层原理 + 高并发调优全拆解
数据库·hikaricp·数据库连接池·druid
GoodStudyAndDayDayUp27 分钟前
RUO-VUE-PRO权限关联sql
java·数据库·sql
@insist12329 分钟前
数据库系统工程师-SQL 数据定义语言(DDL)核心知识点与软考实战指南
数据库·oracle·软考·数据库系统工程师·软件水平考试
专利观察员30 分钟前
情报升维,决策降本:2026年专利数据库和专利检索实践的演进逻辑和实测
数据库
次旅行的库34 分钟前
【问渠哪得清如许-数据分析】学习笔记-下
数据库·笔记·sql·学习