AmazonS3EncryptionClientV2大文件上传慢/卡顿 如何调大单次上传块大小
问题核心原因
你遇到的小字节传输、卡顿挂起问题,核心是两个默认配置的坑:
- 你直接调用
putObject走单连接串行上传,且传入的原始InputStream如果不支持mark/reset操作,AmazonS3EncryptionClientV2为了保证上传失败时可重试,会默认用极小的缓冲区逐字节读取流做加密,不会批量读取数据,才会出现单次仅传输8字节、总请求量达数千次的问题。 - 客户端初始化时没有自定义HTTP层配置,默认的socket缓冲区、分块阈值都偏小,加上串行传输的开销,很容易出现超时、无进度挂起的情况。
所有优化都可以在保留AmazonS3EncryptionClientV2的前提下完成,不需要替换客户端,不会破坏客户端加密的业务要求。
优化方案
1. 优先改用加密分块并行上传(效果最明显)
AmazonS3EncryptionClientV2原生支持分块加密上传,配合TransferManager可以自动完成分块加密、并行传输、失败重试,从根本上减少请求次数:
- 配置分块阈值:大于8MB的文件自动触发分块上传
- 配置单块大小:设置为10MB,50-60MB的文件仅需5-6次分块请求,相比你当前的6000次请求量下降三个数量级
- 配置并行线程数:用4线程并行传输分块,拉满带宽利用率
修改后的客户端初始化代码:
public static AmazonS3EncryptionClientV2 createAmazonS3Client(final String secretKey, final String accessKey, final String keystoreLocation, final char[] keyStorePassword, final char[] keyPassword, final String publicKeyLocation) { KeyPair myKeyPair = null; try { PublicKey publicKey = getPublicKey(publicKeyLocation); PrivateKey privateKey = getPrivateKey(keystoreLocation, keyStorePassword, keyPassword, "awsKeys"); myKeyPair = new KeyPair(publicKey, privateKey); } catch (Exception e) { log.error("can't build key pair", e); return null; } try { if (accessKey == null) { log.error("accessKey is null"); return null; } if (secretKey == null) { log.error("createAmazonS3Client secretKey is null"); return null; } // 配置加密参数,设置最小加密分块大小为10MB CryptoConfigurationV2 cryptoConfigV2 = new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) .withMinPartSize(10 * 1024 * 1024); // 配置底层HTTP客户端,调大socket缓冲区为1MB,设置合理超时 ApacheHttpClientConfig httpConfig = new ApacheHttpClientConfig(); httpConfig.withSocketBufferSizeHints(1024*1024, 1024*1024); httpConfig.withConnectionTimeout(10000); httpConfig.withSocketTimeout(30000); AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); EncryptionMaterials encryptionMaterials = new EncryptionMaterials(myKeyPair); AmazonS3EncryptionClientV2 client = (AmazonS3EncryptionClientV2) AmazonS3EncryptionClientV2Builder .standard() .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration( Optional.ofNullable(System.getenv("S3_ENDPOINT")).orElse("https://s3.us-west-1.amazonaws.com"), Optional.ofNullable(System.getenv("AWS_REGION")).orElse("us-west-1"))) .withPathStyleAccessEnabled(true) .withCredentials(new AWSStaticCredentialsProvider(credentials)) .withCryptoConfiguration(cryptoConfigV2) .withEncryptionMaterialsProvider(new StaticEncryptionMaterialsProvider(encryptionMaterials)) .withHttpClientConfig(httpConfig) .build(); return client; } catch (Exception e) { log.error("exception occurred", e); return null; } }
修改后的上传逻辑代码:
public boolean uploadStreamToS3(String remoteId, InputStream is, long length) { AmazonFileTransfer amazonInstance = AmazonFileTransfer.getInstance(); AmazonS3EncryptionClientV2 encryptedS3client = amazonInstance.getClientInstance(); // 用1MB缓冲区包装原始输入流,保证支持mark/reset,避免加密客户端逐字节读取 try (BufferedInputStream bufferedIs = new BufferedInputStream(is, 1024 * 1024)) { // 初始化TransferManager,绑定加密客户端 TransferManager transferManager = TransferManagerBuilder.standard() .withS3Client(encryptedS3client) .withMultipartUploadThreshold(8 * 1024 * 1024) // 大于8MB自动走分块上传 .withMinimumUploadPartSize(10 * 1024 * 1024) // 单块大小10MB .withExecutorFactory(() -> Executors.newFixedThreadPool(4)) // 4线程并行上传 .build(); ObjectMetadata om = new ObjectMetadata(); om.setContentLength(length); PutObjectRequest s3UploadRequest = new PutObjectRequest(S3_BUCKET_NAME, remoteId, bufferedIs, om); ProgressListener progressListener = progressEvent -> log.info("====> Transferred bytes for remote Id " + remoteId + " : " + progressEvent.getBytesTransferred()); s3UploadRequest.setGeneralProgressListener(progressListener); Upload upload = transferManager.upload(s3UploadRequest); // 阻塞等待上传完成 upload.waitForCompletion(); transferManager.shutdownNow(false); return true; } catch (AmazonClientException ace) { log.error("uploadStreamToAWS - AmazonClientException " + remoteId + " error " + ace); return false; } catch (Exception e) { log.error("uploadStreamToAWS - Error putting object " + remoteId + " error " + e); return false; } }
2. 轻量优化(如果暂时不想用分块上传)
如果业务场景必须走单PutObject请求,只需要做两处修改也能大幅提升速度:
- 传入的原始
InputStream必须用BufferedInputStream包装,设置1MB以上的缓冲区,保证流支持mark/reset,加密客户端就会按缓冲区大小批量读取加密,不会逐字节传输 - 按照上面客户端初始化的配置,把底层HTTP客户端的socket读写缓冲区调到1MB以上,设置合理的连接、socket超时,避免无响应挂起
注意:以上所有修改都没有替换
AmazonS3EncryptionClientV2,所有加解密逻辑仍然在客户端侧完成,完全符合客户端加密的业务要求。
优化效果
- 单次传输块大小从8字节提升到10MB级别,总请求量下降99.9%,从根源上避免超时问题
- 并行分块上传可以充分利用出口带宽,上传速度提升5-10倍
- 可回溯的缓冲流解决了加密客户端逐字节读取的问题,不会出现无进度挂起的情况
内容的提问来源于stack exchange,提问作者VictorGram
相关产品推荐
相关产品推荐

