文件导出: 防止短时间内大量请求导致系统崩溃

79 阅读2分钟

通常情况,文件导出在我们的系统中是必不可少的一项功能。如果短时间内有大量的导出请求,并且导出的数据又比较多,则很可能导致系统性能下降,甚至使系统崩溃。下面我来介绍一种方法,供大家参考,如有不对的地方,欢迎各位大佬指正。

场景假设

如下图,一个到到处服务,部署了3个实例,为了防止短时间内有大量的导出请求,每个实例上同时最多有10个导出请求,以保护系统稳定运行。

解决方案

  1. 为每个实例创建一个唯一的标识。
  2. 使用redis的list,实现一个队列。
  3. 在导出前,将请求通过leftpush放入队列。
  4. 导出完成后,通过rightpop弹出。

代码及配置

实例唯一标识

spring:
  application:
    # 应用名称
    name: exporter-server
    # 实例ID, 用于区分多个实例
    instanceId: ${spring.application.name}:01

创建队列检查工具

instanceId:因为每个实例的ID是唯一的,所以用instanceId作为redis的key,可以确保每个实例都维护一个队列,且互不影响。

日志: 将请求参数和用户ID作为队列元素,放入队列,请求前后作为日志记录。

限制:在每次请求时,检查队列长度,如果 >=max限制,则拒绝用户的导出请求。

@Slf4j
@Component
public class ExportQueueCheck {

    /**
     * 每个实例的唯一ID,作为队列的key, 每个实例最多同时支持MAX个导出任务
     */
    @Value("${spring.application.instanceId}")
    private String instanceId;

    /**
     * 导出队列最大长度
     */
    private static final long MAX = 10;

    @Resource
    private RedisService redisService;

    /**
     * 检查队列是否已满
     *
     * @return 如果队列已满,则返回true;否则返回false
     */
    public boolean isFull(){
        // 获取当前实例队列的大小,并判断是否达到最大容量
        return redisService.getQueueSize(instanceId) >= MAX;
    }

    /**
     * 将指定的元素添加到Redis列表的左侧
     */
    public void checkAndAdd(Object value){
        if (isFull()) {
            throw new ServiceException("导出队列已满,请稍后再试!");
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (loginUser == null) {
            throw new ServiceException("获取当前用户信息失败!");
        }
        Map<String, Object> element = new HashMap<>();
        element.put("user", loginUser.getSysUser().getUserId());
        element.put("param", value);
        redisService.leftPush(instanceId, JSONUtil.toJsonStr(element));
        log.warn("队列长度:{}", redisService.getQueueSize(instanceId));
    }

    /**
     * 从Redis列表的右侧弹出一个元素
     */
    public void pop(){
        Object element = redisService.rightPop(instanceId);
        log.warn("从队列中弹出元素:{}", element);
    }

}

导出业务限制

@PostMapping("/export")
public void export(EventInfo eventInfo, HttpServletResponse response) {
    // 导出队列检查, 防止短时间内大量导出
    exportQueueCheck.checkAndAdd(eventInfo);

    // 导出数据
    eventService.export(eventInfo, response);

    // 从队列弹出已完成的任务
    exportQueueCheck.pop();
}

创作不易,您的赏识是我前进的动力!

收款码.png