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

使用NestJS-MinIO-Client上传文件时遇Socket Hang Up问题求助

解决NestJS MinIO客户端长时间运行后Socket Hang Up问题

看起来你遇到的这个socket hang up错误,在Nginx反向代理+跨服务器Docker部署的场景里挺典型的。结合你的代码和架构,我帮你梳理下核心原因和对应的解决办法:

核心原因分析

这个错误本质是客户端与MinIO服务器之间的TCP连接被意外断开,但客户端仍尝试复用已失效的连接。具体诱因通常有这几个:

  • Nginx反向代理会主动回收长时间空闲的连接,如果超时设置过短,旧连接失效后客户端没感知到,就会报错。
  • nestjs-minio-client底层依赖的minio-js默认连接池配置可能不够完善,长时间运行后失效连接堆积,导致新请求拿不到可用连接。
  • 跨服务器Docker容器之间的网络连接可能存在隐性超时,进一步加剧连接失效的问题。

具体解决方案

1. 调整Nginx反向代理的超时配置

先修改Nginx的代理配置,延长超时时间并开启长连接,避免Nginx主动断开空闲连接:

server {
    # 你的其他Nginx配置
    location / {
        proxy_pass http://你的MinIO服务器地址:9000;
        
        # 延长各类超时时间,和后续客户端/MinIO配置保持一致(这里设为5分钟)
        proxy_connect_timeout 300s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;
        
        # 开启HTTP/1.1长连接,避免频繁创建新连接
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        
        # 可选:传递真实IP,方便MinIO日志排查
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

修改完后重启Nginx生效:nginx -s reload

2. 优化MinIO客户端的连接池配置

在NestJS的MinIO模块注册时,添加自定义HTTP客户端配置,强制开启连接存活检测,避免复用失效连接:

import { MinioModule } from 'nestjs-minio-client';
import * as https from 'https'; // 需要导入https模块

@Module({
    imports: [
        MinioModule.register({
            endPoint: minioConstants.MINIO_ENDPOINT,
            useSSL: true,
            accessKey: minioConstants.MINIO_ACCESSKEY,
            secretKey: minioConstants.MINIO_SECRETKEY,
            // 添加自定义HTTP客户端配置
            httpClient: {
                keepAlive: true,
                timeout: 300000, // 5分钟超时,和Nginx配置对齐
                agent: new https.Agent({
                    keepAlive: true,
                    maxSockets: 50, // 根据你的业务量调整连接池大小
                    rejectUnauthorized: false, // 如果用的是自签SSL证书,需要开启这个
                })
            }
        })
    ],
    controllers: [...],
    providers: [...],
    exports: [...]
})

3. 给上传/删除操作添加重试机制

在UploadService里对MinIO操作添加重试逻辑,当遇到socket hang up这类连接错误时,自动重试,提升稳定性:

import { Injectable, Logger, HttpException, HttpStatus } from '@nestjs/common';
import { MinioService } from 'nestjs-minio-client';
import * as crypto from 'crypto'
import { IMulterFile } from '@shared/interfaces/file.interface';

@Injectable()
export class UploadService {
    private readonly baseBucket = 'users'
    private readonly logger = new Logger(UploadService.name);
    private readonly maxRetries = 3; // 最多重试3次

    public get client() {
        return this.minio.client;
    }
    constructor(private readonly minio: MinioService) {}

    public async uploadImage(file: IMulterFile, baseBucket: string = this.baseBucket) {
        if(!(file.mimetype.includes('image/jpeg') || file.mimetype.includes('image/png'))) {
            throw new HttpException('仅支持JPG/PNG格式图片', HttpStatus.BAD_REQUEST)
        }

        // 生成文件名逻辑保持不变
        let temp_filename = Date.now().toString()
        let hashedFileName = crypto.createHash('md5').update(temp_filename).digest("hex");
        let ext = file.originalname.substring(file.originalname.lastIndexOf('.'), file.originalname.length);
        const metaData = {
            'Content-Type': file.mimetype,
            'X-Amz-Meta-Testing': 1234,
        };
        const fileName = `${hashedFileName}${ext}`;
        const fileBuffer = file.buffer;

        // 重试逻辑
        for (let retryCount = 0; retryCount < this.maxRetries; retryCount++) {
            try {
                await this.client.putObject(baseBucket, fileName, fileBuffer, metaData);
                return {filename: fileName};
            } catch (error) {
                this.logger.error(`上传失败(第${retryCount+1}/${this.maxRetries}次重试): ${error.message}`);
                // 只有是连接类错误且还没到最大重试次数时才重试
                if (error.message.includes('socket hang up') && retryCount < this.maxRetries - 1) {
                    await new Promise(resolve => setTimeout(resolve, 1000)); // 重试前延迟1秒
                    continue;
                }
                throw new HttpException('文件上传失败', HttpStatus.INTERNAL_SERVER_ERROR);
            }
        }
    }

    async delete(objectName: string, baseBucket: string = this.baseBucket) {
        for (let retryCount = 0; retryCount < this.maxRetries; retryCount++) {
            try {
                await this.client.removeObject(baseBucket, objectName);
                return;
            } catch (error) {
                this.logger.error(`删除失败(第${retryCount+1}/${this.maxRetries}次重试): ${error.message}`);
                if (error.message.includes('socket hang up') && retryCount < this.maxRetries - 1) {
                    await new Promise(resolve => setTimeout(resolve, 1000));
                    continue;
                }
                throw new HttpException('文件删除失败', HttpStatus.INTERNAL_SERVER_ERROR);
            }
        }
    }
}

4. 调整MinIO服务器的空闲连接超时

启动MinIO容器时,添加--http-idle-timeout参数,延长MinIO服务器端的空闲连接超时时间,和前面的配置保持一致:

docker run -p 9000:9000 minio/minio server ./data --http-idle-timeout 300s

验证步骤

  1. 应用所有配置后,重启Nginx、MinIO容器和你的NestJS应用。
  2. 持续进行上传/删除测试,观察是否还会出现socket hang up错误。
  3. 查看Nginx错误日志(/var/log/nginx/error.log)和MinIO容器日志,确认连接断开的情况是否减少。

内容的提问来源于stack exchange,提问作者Ana Lava

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 07:27:55