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

}

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

相关推荐
XDHCOM1 天前
ORA-32484重复列名错误,ORACLE数据库CYCLE子句故障修复与远程处理方案
数据库·oracle
翻斗包菜1 天前
PostgreSQL 日常维护完全指南:从基础操作到高级运维
运维·数据库·postgresql
呆瑜nuage1 天前
MySQL表约束详解:8大核心约束实战指南
数据库·mysql
liliangcsdn1 天前
Agent Memory智能体记忆系统的示例分析
数据库·人工智能·全文检索
那个失眠的夜2 天前
Mybatis延迟加载策略
xml·java·数据库·maven·mybatis
Rick19932 天前
SQL 执行流程
数据库·sql
M--Y2 天前
Redis常用数据类型
数据结构·数据库·redis
猿小喵2 天前
MySQL慢查询分析与处理-第二篇
数据库·mysql·性能优化
Y001112362 天前
MySQL-进阶
开发语言·数据库·sql·mysql
徒 花2 天前
数据库知识复习01
数据库