Minio-java 8.3.1 长时断连后首次连接报流异常求助
问题解决:Minio客户端长时间闲置后首次操作报
unexpected end of stream异常 问题背景
- minio-java版本:8.3.1
- Minio服务端:未特殊配置连接相关参数
- 现象:服务器部署后,长时间未操作Minio的首次请求会抛出
java.io.IOException: unexpected end of stream on,本地运行无此问题;已启用OkHttpClient的retryOnConnectionFailure(true),但该版本SDK不支持PUT/POST请求自动重试。
解决方案
1. 配置OkHttpClient连接池,主动回收空闲失效连接
长时间闲置的连接可能被服务器或中间件(如Nginx)主动断开,但客户端仍认为连接可用,导致首次请求失败。给连接池配置空闲连接回收策略,避免复用失效连接。
修改Minio配置中的newDefaultHttpClient方法:
private OkHttpClient newDefaultHttpClient() { // 配置连接池,设置最大空闲连接数和空闲连接存活时间 ConnectionPool connectionPool = new ConnectionPool( 5, // 最大空闲连接数 5, TimeUnit.MINUTES // 空闲连接存活时间,建议小于服务器/代理的超时时间 ); return new OkHttpClient() .newBuilder() .connectTimeout(10000, TimeUnit.MILLISECONDS) .readTimeout(30000, TimeUnit.MILLISECONDS) // 增加读取超时,避免超时断开 .protocols(Arrays.asList(Protocol.HTTP_1_1)) .retryOnConnectionFailure(true) .connectionPool(connectionPool) // 拦截失效连接,触发连接池回收 .addInterceptor(chain -> { Response response = chain.proceed(chain.request()); // 检测连接是否已失效 if (response.code() == 0 || response.message().isEmpty()) { response.close(); throw new IOException("Connection closed by server"); } return response; }) .build(); }
2. 手动捕获异常,针对PUT/POST请求实现重试逻辑
由于SDK不自动重试PUT/POST,在业务代码中捕获特定异常,手动重试有限次数。
修改上传方法,添加重试逻辑:
// 定义最大重试次数 private static final int MAX_RETRY_TIMES = 2; public String uploadFile(InputStream inputStream, String bucketName, String path, String suffix) { int retryCount = 0; // 若InputStream不可重置,先转成ByteArrayInputStream ByteArrayInputStream reusableStream = null; try { byte[] data = IOUtils.toByteArray(inputStream); reusableStream = new ByteArrayInputStream(data); } catch (IOException e) { throw new RuntimeException("读取文件流失败", e); } while (retryCount <= MAX_RETRY_TIMES) { try { reusableStream.reset(); // 重置流,确保每次重试都能从头读取 boolean exists = minioClient.bucketExists(BucketExistsArgs.builder() .bucket(bucketName) .build()); if (!exists) { minioClient.makeBucket(MakeBucketArgs.builder() .bucket(bucketName) .build()); minioClient.setBucketPolicy(SetBucketPolicyArgs.builder() .bucket(bucketName) .config(config) .build()); } String dateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); String filePath = new StringBuilder() .append(path) .append(dateTime) .append("/") .append(IdUtil.fastSimpleUUID()) .append(suffix) .toString(); minioClient.putObject(PutObjectArgs.builder() .bucket(bucketName) .object(filePath) .stream(reusableStream, reusableStream.available(), -1) .build()); return filePath; } catch (IOException e) { if (e.getMessage().contains("unexpected end of stream") && retryCount < MAX_RETRY_TIMES) { retryCount++; try { Thread.sleep(500); // 重试前短暂休眠,避免频繁请求 } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } continue; } throw new RuntimeException("上传文件失败", e); } catch (Exception e) { throw new RuntimeException("上传文件失败", e); } } throw new RuntimeException("上传文件重试多次失败"); }
3. 添加定时预热任务,定期发送轻量请求保持连接活跃
通过定时任务定期调用Minio的轻量接口(如bucketExists),避免连接长时间闲置被断开。
使用Spring的@Scheduled实现定时任务:
@Component @Slf4j public class MinioConnectionWarmUp { @Autowired private MinioClient minioClient; // 配置一个已存在的bucket名称 private static final String WARM_UP_BUCKET = "your-default-bucket"; @Scheduled(fixedRate = 4 * 60 * 1000) // 每4分钟执行一次,小于服务器/代理的空闲超时时间 public void warmUpConnection() { try { minioClient.bucketExists(BucketExistsArgs.builder() .bucket(WARM_UP_BUCKET) .build()); } catch (Exception e) { log.warn("Minio连接预热失败", e); } } }
注意:需要在Spring启动类添加
@EnableScheduling注解开启定时任务。
4. 检查服务器/中间件的连接超时配置
如果服务器或前端有Nginx等反向代理,检查其keepalive_timeout等连接超时设置,确保客户端配置的空闲连接存活时间小于代理的超时时间,避免代理提前断开连接。
内容的提问来源于stack exchange,提问作者jie Old
相关产品推荐
相关产品推荐

