Alpakka S3连接器流将无法处理负载,并引发akka.stream.BufferOverflowException

时间:2018-08-25 10:22:08

标签: scala amazon-s3 akka akka-http alpakka

我有一个akka-http服务,我正在尝试alpakka s3 connector来上传文件。以前,我使用的是临时文件,然后通过Amazon SDK上传。这种方法需要对Amazon SDK进行一些调整,使其更像Scala,但它一次可以处理1000个请求。吞吐量并不令人惊讶,但是所有请求最终都通过了。这是更改之前的代码,没有alpakka:

```

path("uploadfile") {
    withRequestTimeout(20.seconds) {
        storeUploadedFile("csv", tempDestination) {
            case (metadata, file) =>
                val uploadFuture = upload(file, file.toPath.getFileName.toString)

                onComplete(uploadFuture) {
                    case Success(_) => complete(StatusCodes.OK)
                    case Failure(_) => complete(StatusCodes.FailedDependency)
                }
            }
        }
    }
}

case class S3UploaderException(msg: String) extends Exception(msg)

def upload(file: File, key: String): Future[String] = {
    val s3Client = AmazonS3ClientBuilder.standard()
        .withCredentials(new DefaultAWSCredentialsProviderChain())
        .withRegion(Regions.EU_WEST_3)
        .build()

    val promise = Promise[String]()

    val listener = new ProgressListener() {
        override def progressChanged(progressEvent: ProgressEvent): Unit = {
            (progressEvent.getEventType: @unchecked) match {
                case ProgressEventType.TRANSFER_FAILED_EVENT => promise.failure(S3UploaderException(s"Uploading a file with a key: $key"))
                case ProgressEventType.TRANSFER_COMPLETED_EVENT |
                     ProgressEventType.TRANSFER_CANCELED_EVENT => promise.success(key)
            }
        }
    }

    val request = new PutObjectRequest("S3_BUCKET", key, file)
    request.setGeneralProgressListener(listener)

    s3Client.putObject(request)

    promise.future
}

```

当我将其更改为使用alpakka连接器时,代码看起来更好了,因为我们可以将ByteSource和alpakka Sink连接在一起。但是,这种方法无法处理这么大的负载。当我一次执行1000个请求(10 kb文件)时,只有不到10%的请求通过,其余请求失败,并出现以下异常:

  

akka.stream.alpakka.s3.impl.FailedUpload:已超出配置   max-open-requests值为[32]。这意味着请求队列   这个游泳池   (HostConnectionPoolSetup(bargain-test.s3-eu-west-3.amazonaws.com,443,ConnectionPoolSetup(ConnectionPoolSettings(4,0,5,32,1,30   秒,ClientConnectionSettings(某些(用户代理:akka-http / 10.1.3),10   秒1   分钟,512,无,WebSocketSettings(,ping,Duration.Inf,akka.http.impl.settings.WebSocketSettingsImpl $$$ Lambda $ 4787/1279590204 @ 4d809f4c),List(),ParserSettings(2048,16,64,64,8192 ,64,8388608,256,1048576,严格,RFC6265,true,Set(),完整,错误,映射(If-Range   -> 0,If-Modified-Since-> 0,If-Unmodified-Since-> 0,默认-> 12,Content-MD5-> 0,Date-> 0,If-Match-> 0,If-None-匹配-> 0,   用户代理->   32),false,true,akka.util.ConstantFun $$$ Lambda $ 4534/1539966798 @ 69c23cd4,akka.util.ConstantFun $$$ Lambda $ 4534/1539966798 @ 69c23cd4,akka.util.ConstantFun $$$ Lambda $ 4535/297570074 @ 6b426c59),无,TCP传输),新的,1   第二个),akka.http.scaladsl.HttpsConnectionContext @ 7e0f3726,akka.event.MarkerLoggingAdapter @ 74f3a78b)))   由于池当前未处理,已完全填满   请求足够快地处理传入的请求负载。请重试   稍后的请求。看到   http://doc.akka.io/docs/akka-http/current/scala/http/client-side/pool-overflow.html   有关更多信息。

以下是加特林测试摘要的外观:

  

----响应时间分布----------------------------------------   t <800毫秒0(0%)

     

800毫秒      

t> 1200毫秒90(9%)

     

失败910(91%)

     

当我同时执行100个请求时,其中一半失败。因此,仍然接近令人满意。

这是一个新代码: ```

path("uploadfile") {
    withRequestTimeout(20.seconds) {
        extractRequestContext { ctx =>
            implicit val materializer = ctx.materializer

            extractActorSystem { actorSystem =>

                fileUpload("csv") {

                    case (metadata, byteSource) =>

                        val uploadFuture = byteSource.runWith(S3Uploader.sink("s3FileKey")(actorSystem, materializer))

                        onComplete(uploadFuture) {
                            case Success(_) => complete(StatusCodes.OK)
                            case Failure(_) => complete(StatusCodes.FailedDependency)
                        }
                }            
            }
        }
    }
}

def sink(s3Key: String)(implicit as: ActorSystem, m: Materializer) = {
    val regionProvider = new AwsRegionProvider {
        def getRegion: String = Regions.EU_WEST_3.getName
    }

    val settings = new S3Settings(MemoryBufferType, None, new DefaultAWSCredentialsProviderChain(), regionProvider, false, None, ListBucketVersion2)
    val s3Client = new S3Client(settings)(as, m)

    s3Client.multipartUpload("S3_BUCKET", s3Key)
}

```

带有两个端点的完整代码可以看到here

我有几个问题。

1)这是功能吗?这就是我们所说的背压吗?

2)如果我希望这段代码的行为类似于带有临时文件的旧方法(没有失败的请求,并且所有请求都在某个时刻完成),我该怎么办?我试图为流实现一个队列(链接到下面的源),但这没什么区别。可以在here上看到该代码。

(*免责声明*我仍然是Scala新手,试图快速了解akka流并找到解决该问题的方法。很有可能此代码中有一些简单的错误。*免责声明*)

1 个答案:

答案 0 :(得分:0)

这是背压功能。

Exceeded configured max-open-requests value of [32]在配置max-open-requests中默认设置为32。 流用于处理大量数据,而不是每秒处理许多请求。

Akka开发人员必须为max-open-requests投入一些东西。他们出于某些原因选择32。他们不知道它将用于什么。可以一次发送1000个32KB文件或1000个1GB文件吗?他们不知道但是他们仍然希望确保默认情况下(可能有80%的人使用默认值)可以安全,优雅地处理应用程序。因此,他们必须限制处理能力。

您要求“立即”执行1000个操作,但是我很确定AWS不会同时发送1000个文件,而是使用了一些队列,如果您要上传的文件很多,这对您来说也是个好例子。

但是完全适合您的情况! 如果您知道自己的机器和目标服务器将处理更多的同时连接,则可以将数字更改为更高的值。

此外,对于许多HTTP调用,请使用cached host connection pool