You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring WebMVC多客户端文件写入时出现I/O设备错误排查

多客户端下Spring WebMVC文件写入I/O设备错误问题分析

问题背景

使用Spring WebMVC开发的Web服务,单客户端周期性请求时运行正常,但新增两个客户端后,文件写入频繁出现500错误,日志提示I/O设备错误。同一文件名的后续请求无异常,且各客户端不会发送相同文件名的请求。

代码实现

控制器端点

@PostMapping(
        path = "append",
        consumes = MediaType.APPLICATION_JSON_VALUE
)
public String append(@RequestBody AppendRequest request){
    service.appendFile(request);
    return "OK";
}

服务层实现

@Value
private String destinationPath;

public void appendFile(AppendRequest appendRequest) {
    log.info("Incoming payload, filename: {}, total records: {}", appendRequest.getFilename(), appendRequest.getPayload().size());
    Path directory = Paths.get(destinationPath, appendRequest.getSubdirectory());
    if (!directory.toFile().exists()){
        boolean ret = directory.toFile().mkdirs();
        if(!ret)
            log.warn("Cannot create destination directory: {}", directory);
    }
    try {
        BufferedWriter writer = new BufferedWriter(new FileWriter(Paths.get(directory.toString(), appendRequest.getFilename()).toFile(), true));
        writer.write(String.join(System.lineSeparator(), appendRequest.getPayload()) + System.lineSeparator());
        writer.close();
    } catch (IOException ioe) {
        log.error("Exception while writing to destination file: " + ioe.getMessage(), ioe);
        throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Store file failed");
    }
}

实体类定义

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppendRequest {
    private String subdirectory;
    private String fileName;
    private List<String> payload;
}

可能原因分析

  • 目录创建的竞态条件:多线程并发请求时,directory.toFile().exists()检查与mkdirs()执行之间存在时间间隙。当多个线程同时检测到目录不存在并尝试创建时,其中一个线程可能创建失败,但此时目录实际已被其他线程创建完成,不过系统层面可能存在元数据同步延迟,导致该线程后续写入文件时触发I/O错误。
  • 磁盘I/O资源过载:多客户端并发写入导致磁盘I/O负载骤增,超出了磁盘的处理能力,系统暂时无法处理写入请求,返回I/O设备错误。后续请求时磁盘负载降低,即可正常写入。
  • Windows文件系统元数据延迟:日志显示路径为Windows系统,NTFS文件系统在频繁创建目录和写入文件时,可能存在短暂的元数据更新锁或同步延迟,多线程并发触发时更容易出现这类临时性错误。
  • 流资源未安全管理:当前代码中writer.close()未放在finally块中,若写入过程中出现异常,可能导致流资源泄漏,长期积累后加剧I/O资源竞争。

解决方案建议

  • 原子化创建目录:使用Files.createDirectories()替代File.mkdirs(),该方法是原子操作,能避免竞态条件,且会抛出异常明确告知目录创建失败原因:
    try {
        Files.createDirectories(directory);
    } catch (IOException e) {
        log.error("Failed to create directory: {}", directory, e);
        throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Create directory failed");
    }
    
  • 自动管理流资源:使用try-with-resources语法自动关闭流,确保资源及时释放,避免泄漏:
    try (BufferedWriter writer = new BufferedWriter(new FileWriter(Paths.get(directory.toString(), appendRequest.getFilename()).toFile(), true))) {
        writer.write(String.join(System.lineSeparator(), appendRequest.getPayload()) + System.lineSeparator());
    } catch (IOException ioe) {
        log.error("Exception while writing to destination file: {}", ioe.getMessage(), ioe);
        throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Store file failed");
    }
    
  • 添加I/O重试逻辑:针对临时性I/O错误,添加有限次数的重试机制,等待系统元数据同步或磁盘负载降低后再尝试操作:
    int maxRetries = 3;
    int retryCount = 0;
    boolean success = false;
    while (retryCount < maxRetries && !success) {
        try {
            Files.createDirectories(directory);
            try (BufferedWriter writer = new BufferedWriter(new FileWriter(Paths.get(directory.toString(), appendRequest.getFilename()).toFile(), true))) {
                writer.write(String.join(System.lineSeparator(), appendRequest.getPayload()) + System.lineSeparator());
            }
            success = true;
        } catch (IOException e) {
            retryCount++;
            log.warn("Write failed, retrying ({} of {})", retryCount, maxRetries, e);
            try {
                Thread.sleep(100);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
                throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Retry interrupted");
            }
        }
    }
    if (!success) {
        throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Store file failed after retries");
    }
    
  • 监控磁盘状态:检查磁盘剩余空间、健康状态及I/O负载,若磁盘性能不足,考虑升级存储设备或优化写入策略(如批量写入、异步写入)。

内容的提问来源于stack exchange,提问作者of32 inc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 18:33:22