Scala 与Monix的扇入/扇出并发

Scala 与Monix的扇入/扇出并发,scala,monix,Scala,Monix,我正在努力学习Scala并从中获得乐趣,但我遇到了这个经典问题。这让我想起了NodeJS早期的许多嵌套回调地狱 以下是我的psuedocode程序: 获取S3存储桶列表的任务 任务1完成后,我想以十个一组的方式批处理bucket 每批: 获取每个桶的区域 筛选出不在区域中的桶 列出每个bucket中的所有对象 打印所有内容 在某一点上,我以类型结束:Task[Iterator[Task[List[Bucket]]]] 基本上: 外部任务是列出所有S3存储桶的初始步骤,而内部迭代器/task/li

我正在努力学习Scala并从中获得乐趣,但我遇到了这个经典问题。这让我想起了NodeJS早期的许多嵌套回调地狱

以下是我的psuedocode程序:

  • 获取S3存储桶列表的任务
  • 任务1完成后,我想以十个一组的方式批处理bucket
  • 每批:
  • 获取每个桶的区域
  • 筛选出不在区域中的桶
  • 列出每个bucket中的所有对象
  • 打印所有内容
  • 在某一点上,我以类型结束:
    Task[Iterator[Task[List[Bucket]]]]

    基本上:

    外部任务是列出所有S3存储桶的初始步骤,而内部迭代器/task/list则试图批处理返回列表的任务

    我希望有某种方法可以删除/展平外部任务,从而访问
    迭代器[Task[List[Bucket]]]


    当我尝试将我的处理分解为多个步骤时,深层嵌套会导致我执行许多嵌套映射。这是正确的做法还是有更好的方法来处理这种嵌套?

    在这种特殊情况下,我建议使用类似于FS2的方法,将Monix作为F:

    import cats.implicits._
    import monix.eval._, monix.execution._
    import fs2._
    
    // use your own types here
    type BucketName = String
    type BucketRegion = String
    type S3Object = String
    
    // use your own implementations as well
    val fetchS3Buckets: Task[List[BucketName]] = Task(???)
    val bucketRegion: BucketName => Task[BucketRegion] = _ => Task(???)
    val listObject: BucketName => Task[List[S3Object]] = _ => Task(???)
    
    Stream.evalSeq(fetchS3Buckets)
      .parEvalMap(10) { name =>
        // checking region, filtering and listing on batches of 10
        bucketRegion(name).flatMap {
          case "my-region" => listObject(name)
          case _           => Task.pure(List.empty)
        }
      }
      .foldMonoid // combines List[S3Object] together
      .compile.lastOrError // turns into Task with result
      .map(list => println(s"Result: $list"))
      .onErrorHandle { case error: Throwable => println(error) }
      .runToFuture // or however you handle it
    
    下面的FS2使用cats.effect.IO或Monix任务,或者任何您想要的任务,只要它提供了cats效果类型类。它构建了一个漂亮的、功能强大的DSL来设计数据流,因此您可以使用无Akka流的反应流

    这里有一个小问题,我们一次打印所有结果,如果结果超过内存的处理能力,这可能是一个坏主意-我们可以分批打印(不确定这是否是您想要的),或者将过滤和打印分开

    Stream.evalSeq(fetchS3Buckets)
      .parEvalMap(10) { name =>
        bucketRegion(name).map(name -> _)
      }
      .collect { case (name, "my-region") => name }
      .parEvalMap(10) { name =>
        listObject(name).map(list => println(s"Result: $list"))
      }
      .compile
      .drain
    

    虽然在裸Monix中这一切都不是不可能的,但FS2使此类操作更易于编写和维护,因此您应该能够更轻松地实现流程。

    在这种特殊情况下,我建议使用类似FS2的Monix作为F:

    import cats.implicits._
    import monix.eval._, monix.execution._
    import fs2._
    
    // use your own types here
    type BucketName = String
    type BucketRegion = String
    type S3Object = String
    
    // use your own implementations as well
    val fetchS3Buckets: Task[List[BucketName]] = Task(???)
    val bucketRegion: BucketName => Task[BucketRegion] = _ => Task(???)
    val listObject: BucketName => Task[List[S3Object]] = _ => Task(???)
    
    Stream.evalSeq(fetchS3Buckets)
      .parEvalMap(10) { name =>
        // checking region, filtering and listing on batches of 10
        bucketRegion(name).flatMap {
          case "my-region" => listObject(name)
          case _           => Task.pure(List.empty)
        }
      }
      .foldMonoid // combines List[S3Object] together
      .compile.lastOrError // turns into Task with result
      .map(list => println(s"Result: $list"))
      .onErrorHandle { case error: Throwable => println(error) }
      .runToFuture // or however you handle it
    
    下面的FS2使用cats.effect.IO或Monix任务,或者任何您想要的任务,只要它提供了cats效果类型类。它构建了一个漂亮的、功能强大的DSL来设计数据流,因此您可以使用无Akka流的反应流

    这里有一个小问题,我们一次打印所有结果,如果结果超过内存的处理能力,这可能是一个坏主意-我们可以分批打印(不确定这是否是您想要的),或者将过滤和打印分开

    Stream.evalSeq(fetchS3Buckets)
      .parEvalMap(10) { name =>
        bucketRegion(name).map(name -> _)
      }
      .collect { case (name, "my-region") => name }
      .parEvalMap(10) { name =>
        listObject(name).map(list => println(s"Result: $list"))
      }
      .compile
      .drain
    

    虽然在bare Monix中这些都不是不可能的,但FS2使此类操作更易于编写和维护,因此您应该能够更轻松地实现流程。

    一旦您有了迭代器[Task[X]],您就可以使用
    sequence
    将其转换为任务[Iterator[X]]。更好的是,您可能会因为
    iterator.map(f)
    而陷入这种情况,在
    f
    返回任务的地方,您可以执行
    iterator.traverse(f)
    ,这将再次为您提供任务[iterator]。然后您可以保留
    flatMapping
    所有内容。PS:我还建议使用一些流媒体解决方案,如果你不想使用fs2,你可以使用monix的obserbavles模型。当你有一个迭代器[Task[X]]时,你可以使用
    sequence
    将它变成一个任务[Iterator[X]]。更好的是,您可能会因为
    iterator.map(f)
    而陷入这种情况,在
    f
    返回任务的地方,您可以执行
    iterator.traverse(f)
    ,这将再次为您提供任务[iterator]。然后您可以保留
    flatMapping
    所有内容。PS:我还建议使用一些流媒体解决方案,如果你不想使用fs2,你可以使用monix的obserbavles模制。那要容易得多。我无法告诉你我已经为此挣扎了多久,fs2只是一件轻而易举的事!非常感谢。我很想知道你对Scala的兴趣。你在建造什么样的东西?各种各样的东西。在工作中,我会使用Akka流和活动巴士,但我也会注意FS2,因为我和Akka一样经常使用猫。最近我用FS2做了两个小PoC:和-一个是招聘任务,另一个是围绕游戏引擎的功能包装。我不认为有人会使用它们,但它们可以作为类似的示例/演示。这要容易得多。我无法告诉你我已经为此挣扎了多久,fs2只是一件轻而易举的事!非常感谢。我很想知道你对Scala的兴趣。你在建造什么样的东西?各种各样的东西。在工作中,我会使用Akka流和活动巴士,但我也会注意FS2,因为我和Akka一样经常使用猫。最近我用FS2做了两个小PoC:和-一个是招聘任务,另一个是围绕游戏引擎的功能包装。我不认为有人会使用它们,但它们可以用作类似的示例/演示。