Implementing a FIFO Export Queue for Large Data Exports in a Spring Backend
The article describes a backend solution that uses a fixed‑size FIFO queue to throttle concurrent MySQL export operations, provides Java implementations of the ExportQueue, an abstract EasyExcel‑based exporter, a concrete ExportImpl service, and a test controller, and discusses remaining challenges and alternative approaches.
The business requirement is to limit simultaneous full‑data exports because MySQL queries and file I/O can heavily impact server performance; a FIFO queue with a fixed maximum size is proposed to serialize export tasks.
The ExportQueue class maintains a bounded LinkedList<ExportUser> and provides synchronized add and getNextSysUser methods that block when the queue is full or empty, respectively.
package com.example.system.config;
import com.example.system.api.domain.ExportUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.LinkedList;
@Slf4j
@Component
public class ExportQueue {
private final int MAX_CAPACITY = 10; // 队列最大容量
private LinkedList
queue; // 用户队列
public ExportQueue(LinkedList
queue) {
this.queue = new LinkedList<>();
}
public synchronized LinkedList
add(ExportUser sysUser) {
while (queue.size() >= MAX_CAPACITY) {
try {
log.info("当前排队人已满,请等待");
wait();
} catch (InterruptedException e) {
e.getMessage();
}
}
queue.add(sysUser);
log.info("目前导出队列排队人数:" + queue.size());
notifyAll();
return queue;
}
public synchronized ExportUser getNextSysUser() {
while (queue.isEmpty()) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
ExportUser sysUser = queue.remove();
notifyAll(); //唤醒
return sysUser;
}
}The AbstractExport abstract class defines a generic EasyExcel export workflow, handling pagination, sheet creation, and response streaming, while leaving concrete methods for counting records, fetching data, and template‑based fills to subclasses.
package com.example.system.config;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.PageUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.example.system.api.domain.ExportUser;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
@Slf4j
public abstract class AbstractExport
{
public abstract void export(ExportUser sysUser) throws InterruptedException;
public void export(HttpServletResponse response, int pageSize, T t, Class
k, String fileName) throws Exception {
ExcelWriter writer = null;
try {
writer = getExcelWriter(response, fileName);
int total = this.countExport(t);
int loopCount = PageUtil.totalPage(total, pageSize);
BeanUtil.setProperty(t, "pageSize", pageSize);
for (int i = 0; i < loopCount; i++) {
BeanUtil.setProperty(t, "pageNum", PageUtil.getStart(i + 1, pageSize));
List
kList = this.getExportDetail(t);
WriteSheet writeSheet = EasyExcel.writerSheet(fileName).head(k).build();
writer.write(kList, writeSheet);
}
} catch (Exception e) {
String msg = "导出" + fileName + "异常";
log.error(msg, e);
throw new Exception(msg + e);
} finally {
if (writer != null) {
writer.finish();
}
}
}
public ExcelWriter getExcelWriter(HttpServletResponse response, String fileName) throws IOException {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
String fileNameUtf = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileNameUtf + ".xlsx");
return EasyExcel.write(response.getOutputStream()).build();
}
public abstract void complexFillWithTable(T t, String fileName, HttpServletResponse response);
public abstract int countExport(T t);
public abstract List
getExportDetail(T t);
}The concrete ExportImpl class wires the queue, logs progress, simulates export latency with Thread.sleep , and delegates the actual Excel writing to the superclass.
package com.example.system.service.impl;
import com.alibaba.excel.ExcelWriter;
import com.example.system.api.domain.ExportUser;
import com.example.system.config.AbstractExport;
import com.example.system.config.ExportQueue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
@Service
@Slf4j
public class ExportImpl extends AbstractExport {
@Autowired
private ExportQueue exportQueue;
@Override
public void export(ExportUser sysUser) throws InterruptedException {
log.info("导出文件方法执行~~~~~~~~~");
LinkedList
queue = exportQueue.add(sysUser);
log.info("导出队列:" + queue);
Thread.sleep(20000);
ExportUser nextSysUser = exportQueue.getNextSysUser();
log.info("移除后获取下一个排队的用户: " + nextSysUser.getUserName());
}
@Override
public void export(HttpServletResponse response, int pageSize, Object o, Class k, String fileName) throws Exception {
super.export(response, pageSize, o, k, fileName);
}
@Override
public ExcelWriter getExcelWriter(HttpServletResponse response, String fileName) throws IOException {
return super.getExcelWriter(response, fileName);
}
@Override
public void complexFillWithTable(Object o, String fileName, HttpServletResponse response) {
}
@Override
public int countExport(Object o) {
return 0;
}
@Override
public List getExportDetail(Object o) {
return null;
}
}A simple ExportController launches a new thread for each export request, creates an ExportUser with the thread name, and invokes the service.
package com.example.system.controller;
import com.example.system.api.domain.ExportUser;
import com.example.system.service.impl.ExportImpl;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/export")
@Slf4j
public class ExportController {
@Autowired
private ExportImpl export;
@PostMapping("/exportFile")
public void exportFile() {
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
Thread thread1 = Thread.currentThread();
ExportUser sysUser = new ExportUser();
sysUser.setUserName(thread1.getName());
export.export(sysUser);
}
}).start();
}
}Test results show that when the queue reaches its limit of ten concurrent exports, additional requests are blocked until a slot frees; the first completed export removes its user from the queue, allowing the next waiting user to proceed.
The article notes that further work is needed for file‑table design, OSS upload, download handling, and high‑concurrency edge cases, and mentions that alternative queue implementations such as Redis could also be used.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.