使用AWS SDK for Java版本2异步(非阻塞)将InputStream上载到AWS s3

使用AWS SDK for Java版本2异步(非阻塞)将InputStream上载到AWS s3,java,amazon-web-services,asynchronous,amazon-s3,aws-sdk,Java,Amazon Web Services,Asynchronous,Amazon S3,Aws Sdk,当我同步(阻塞方式)将inputStream对象上传到s3时,它会工作 S3Client s3Client = S3Client.builder().build(); s3Client.putObject(objectRequest, RequestBody.fromInputStream(inputStream,STREAM_SIZE)); 但是,当我尝试使用S3AsyncClient时,在AsyncRequestBody上没有.fromInputStream方法 S3AsyncClient

当我同步(阻塞方式)将
inputStream
对象上传到s3时,它会工作

S3Client s3Client = S3Client.builder().build();
s3Client.putObject(objectRequest, RequestBody.fromInputStream(inputStream,STREAM_SIZE));
但是,当我尝试使用
S3AsyncClient
时,在
AsyncRequestBody
上没有
.fromInputStream
方法

S3AsyncClient s3AsyncClient = S3AsyncClient.builder().build();
s3AsyncClient.putObject(objectRequest, AsyncRequestBody.fromInputStream(inputStream,STREAM_SIZE)); // error no method named 'fromInputStream'
我不能使用
.fromByteBuffer
,因为它会将整个流加载到内存中,这是我不想要的


我感兴趣的是为什么在
AsyncRequestBody
中没有读取InputStream的方法。还有其他选择吗?

对于任何使用Kotlin和协同路由的人来说:这里有一个Kotlin包装器,它将从
输入流
创建一个异步
AsyncRequestBody
。默认情况下,包装将在后台线程中运行,但您可以传递显式的
CoroutineScope
,并在您的coroutine中运行它,这将避免创建单独的线程

import io.ktor.util.cio*
导入kotlinx.coroutines.CoroutineScope
导入kotlinx.coroutines.DelicateCoroutinesApi
导入kotlinx.coroutines.GlobalScope
导入kotlinx.coroutines.launch
导入org.reactivestreams.Subscriber
导入org.reactivestreams.Subscription
导入软件.amazon.awssdk.core.async.AsyncRequestBody
导入java.io.InputStream
导入java.nio.ByteBuffer
导入java.util*
@OptIn(精致的协同程序API::类)
类StreamAsyncRequestBody(
inputStream:inputStream,
private val coroutineScope:coroutineScope=GlobalScope
) :
异步请求体{
专用val输入通道=
inputStream.toByteReadChannel(上下文=coroutineScope.coroutineContext)
覆盖乐趣订阅(订阅方:订阅方){
onSubscribe(对象:Subscription{
私有变量done:Boolean=false
覆盖乐趣请求(n:长){
如果(!完成){
if(inputChannel.isClosedForRead){
完成()
}否则{
协同观测发射{
inputChannel.read{
subscriber.onNext(it)
if(inputChannel.isClosedForRead){
完成()
}
}
}
}
}
}
私人娱乐(全套){
subscriber.onComplete()
已同步(此){
完成=正确
}
}
覆盖乐趣取消(){
已同步(此){
完成=正确
}
}
})
}
override fun contentLength():Optional=Optional.empty()
}
用法示例:

suspend fun s3Put(objectRequest:PutObjectRequest,inputStream:inputStream)=coroutineContext{
s3Client.putObject(objectRequest,StreamAsyncRequestBody(inputStream,this)
}
如果您使用Java,则需要创建自己的包装器并使用不同的协同程序库。或者,您可以创建一个具有固定线程数的
执行器
:如果一次运行的上载太多,它们将相互阻止,但不会创建太多线程并阻止整个程序



编辑:修复了代码。我没有测试上一个版本,我测试了这个版本几次以便上传,它成功了。当然,这并不意味着它没有bug:)

经过一些研究,我发现:

  • InputStream在本质上是阻塞的,所以当您从输入流读取时,一些线程将被阻塞,在回答“”的情况下,将返回一个读取阻塞通道。因此,考虑到性能,它在某种程度上等同于在后台线程中执行Sync S3Client.fromInputStream(),您可以通过在CompletableFuture中包装它来实现这一点。
  • 其他“AsyncRequestBody”类型,如“FileAsyncRequestBody”使用带有回调的“nio”(非阻塞I/O)。也许这就是为什么AWS团队没有在“AsyncRequestBody”中包含“fromInputStream”,因为它根本不可能使用完全非阻塞的方式,这会造成混乱
  • 如果您想要一个高度可扩展的解决方案,最好的解决方案是不要同时使用InputStream,找到InputStream的发源地并使用一些支持非阻塞通道的替代方案,在我的例子中,我使用了Java Flow并将其转换为“Publisher”,并使用AsyncRequestBody.fromPublisher()

  • 你能在后台创建一个线程来上传吗?@stdunbar,但是后台线程将被阻止,直到上传正确为止?如果我有多个上传,我会很快用完线程。s3AsyncClient使用ReactiveStreams,不阻塞任何线程。是的,这基本上就是点-继续前台处理,让后台线程占用所需的时间。我同意缺少的
    .fromInputStream
    没有太大意义,但最终AWS SDK创建了一个线程来处理异步,您可以自己处理。虽然您使用的是Kotlin语言,但您仍然使用AWS SDK for Java-这里要明确一点。是的。虽然Kotlin没有AWSSDK,但java版本已经非常简洁,并且有一个1行程序可以将
    CompletableFuture
    包装到一个协同程序中(仍然通过AWS事件流完成):
    suspend fun wrapAws2Suspend(awsResponse:CompletableFuture):T=awsResponse.asDeferred().wait()