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

}

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

相关推荐
fen_fen1 天前
Oracle建表语句示例
数据库·oracle
砚边数影1 天前
数据可视化入门:Matplotlib 基础语法与折线图绘制
数据库·信息可视化·matplotlib·数据可视化·kingbase·数据库平替用金仓·金仓数据库
orange_tt1 天前
Djiango配置Celery
数据库·sqlite
云小逸1 天前
【nmap源码学习】 Nmap网络扫描工具深度解析:从基础参数到核心扫描逻辑
网络·数据库·学习
肉包_5111 天前
两个数据库互锁,用全局变量互锁会偶发软件卡死
开发语言·数据库·c++
霖霖总总1 天前
[小技巧64]深入解析 MySQL InnoDB 的 Checkpoint 机制:原理、类型与调优
数据库·mysql
ALex_zry1 天前
Redis Cluster 分布式缓存架构设计与实践
redis·分布式·缓存
此刻你1 天前
常用的 SQL 语句
数据库·sql·oracle
それども1 天前
分库分表的事务问题 - 怎么实现事务
java·数据库·mysql
·云扬·1 天前
MySQL Binlog 配置指南与核心作用解析
数据库·mysql·adb