在S3存储桶之间并行移动文件

时间:2019-06-10 00:12:20

标签: multithreading scala amazon-web-services asynchronous amazon-s3

我有两个使用不同帐户的S3存储桶。我想将文件从存储桶A移到存储桶B,并且我想尽快执行此操作。为此,我认为我将使用Scala,异步请求和并行处理来尽可能快地移动文件。

为此,我必须调用listObjects命令(该命令返回其自身的未来,然后对于该命令返回的每个对象,我必须依次运行getObject和putObject。因此,listObjects应该生成几个getObject期货,当这些期货解析时,应该在它们之后放置putObject期货。

我一直试图以这种方式实现这一目标:

def moveData(listObjects: Future[ListObjectV2Response]) = {
  listObjects.isCompleted {
    case Success(objListResp) =>
      val getAndPut = objListResp.objects()
                                 .map(obj => getObject(obj.key))
                                 .map(obj => putObject(obj.key))
      moveData(ListObjects(objlistResp.continuationToken())
    case Failure(error) => error.printStackTrace()
}

我已经尝试了这种方式的大约6种不同方法。我始终对以下现象感到困惑:

  1. .isCompleted具有Unit响应类型,而我无法使用递归函数。
  2. 我经常需要传递各种值,例如listObjects对PutObject请求的响应。这导致将3个以上的静态值作为期货传递到.map链下。
  3. listObjects是一个单一的未来,但是调用.contents返回一个可迭代的对象,必须将其转换为许多不同的未来。结合(2)会导致对mapflatMap的一些非常棘手的使用,其中当实际响应类型为{{1时,scala希望map / flatmap类似于Iterator[S3Object] => Future[NotInferred] }}

如何解决此问题?有没有更好的办法?

编辑:

有成千上万个文件,其中许多文件的大小为几GB。总的来说,要复制的数据以十亿兆字节为单位。我对源存储桶及其所在的帐户具有极其有限的访问权限。除Get和List操作外,我将无法执行任何其他操作。

2 个答案:

答案 0 :(得分:2)

如果两个存储桶位于不同的地区,则可以使用Amazon S3 Cross-Region Replication

如果它们位于相同区域,则复制对象的“最快”方法是:

  • 创建 Amazon S3事件以触发AWS Lambda函数
  • Lambda函数将接收触发事件的对象的存储桶和密钥
  • Lambda函数应使用CopyObject()命令将对象复制到另一个存储桶

此方法的优点是无需列出存储桶中的对象,因为将为每个新对象触发Lambda函数。

答案 1 :(得分:0)

尽管遇到了并发问题导致的错误,但我设法编写了一个在技术上可以运行的极其粗糙的函数。在这里张贴以供后代使用,但我不喜欢这个答案。


  def streamData(response: Future[ListObjectsV2Response]): Future[ListObjectsV2Response] = {
    var continuationToken: String = ""
    val operationChain = response.map((res: ListObjectsV2Response) => {
      println("LISTED - " + res.maxKeys())
      continuationToken = res.nextContinuationToken()
      res.contents().asScala.toList
    }).map((objs: List[S3Object]) => {
        for {
          obj <- objs
          fileName = obj.key().split("/").last
          getObjectRequest = GetObjectRequest.builder().bucket(BucketA).key(obj.key()).build()
          writeFilePath = Paths.get("./" + fileName)
          future = BucketAS3.getObject(getObjectRequest, writeFilePath).toScala
        } yield {
          (future, Future(obj.key()), Future(writeFilePath))
        }
    }).map((futureSeq: Seq[(Future[GetObjectResponse], Future[String], Future[Path])]) =>
      futureSeq.map((futureTuple: (Future[GetObjectResponse], Future[String], Future[Path])) => {
        for {
          getObjResp <- futureTuple._1
          key <- futureTuple._2
          writeFilePath <- futureTuple._3
        } yield {
          println("DOWNLOADED - " + key)
          val putObjectRequest = PutObjectRequest.builder()
            .bucket(bucketB).key("ability_dump/" + key).build()
          (BucketBS3.putObject(putObjectRequest, writeFilePath).toScala, Future(key), Future(writeFilePath))
        }
      })
    ).map((futuresSeq: Seq[Future[(Future[PutObjectResponse], Future[String], Future[Path])]]) => {
      futuresSeq.map((futures: Future[(Future[PutObjectResponse], Future[String], Future[Path])]) => {
        for {
          f <- futures
          putObjResp <- f._1
          key <- f._2
          writeFilePath <- f._3
        } yield {
          println("UPLOADED - " + key)
          val writeFile = new File(writeFilePath.toString)
          if (writeFile.exists) {
            writeFile.delete()
          }
          println("DELETED  - " + writeFilePath.toString)
        }
      })
    })
    streamData(BucketAS3.listObjectsV2(abilityListRequestBuilder.continuationToken(continuationToken).build()).toScala)
  }