Scala重试期货序列,直到它们全部完成

Scala重试期货序列,直到它们全部完成,scala,future,retry-logic,Scala,Future,Retry Logic,在scala中,您将如何编写一个函数,该函数接受一系列未来,运行它们,不断重试失败的任何一个,并返回结果 例如,签名可能是: def waitRetryAll[T](futures: Seq[Future[T]]): Future[Seq[T]] 可配置超时的额外点数,在该超时点函数失败,被调用方可以处理该情况。 如果错误案例处理程序可以接收失败的未来列表,则可获得额外点数 谢谢 基于考虑 结合 Future.sequence 它将List[Future[T]]转换为Future[Lis

在scala中,您将如何编写一个函数,该函数接受一系列未来,运行它们,不断重试失败的任何一个,并返回结果

例如,签名可能是:

  def waitRetryAll[T](futures: Seq[Future[T]]): Future[Seq[T]]
可配置超时的额外点数,在该超时点函数失败,被调用方可以处理该情况。
如果错误案例处理程序可以接收失败的未来列表,则可获得额外点数

谢谢

基于考虑

结合

Future.sequence
它将
List[Future[T]]
转换为
Future[List[T]]
。然而,
序列
具有快速失效行为,因此我们必须将我们的
未来[T]
提升到
未来[T]

把这些部分放在一起,我们可以定义

def waitRetryAll[T](futures: List[() => Future[T]]): Future[List[Either[Throwable, T]]] = {
  Future.sequence(futures.map(f => retry(f.apply())))
}
像这样使用它

val futures = List(
  () => Future(42),
  () => Future(throw new RuntimeException("boom 1")),
  () => Future(11),
  () => Future(throw new RuntimeException("boom 2"))
)

waitRetryAll(futures)
  .andThen { case v => println(v) }
哪个输出

Success(List(Right(42), Left(java.lang.RuntimeException: boom 1), Right(11), Left(java.lang.RuntimeException: boom 2)))
例如,我们可以
收集
我们的
s或
s并相应地恢复或继续处理

waitRetryAll(futures)
  .map(_.collect{ case v if v.isLeft => v })
  ...

请注意,我们必须将
List[()=>Future[T]]
而不是
List[Future[T]]
传递进来,以防止期货急于启动

据我所知,在标准库中没有用于
Future
timeout的实用程序

如何中断/取消JVM上正在进行的计算?在一般情况下,您不能,您只能在
等待
时中断
线程
,但如果它从不
等待
s?异步计算的IO库(定义取消)将IO作为一系列较小的不可中断任务执行(每个map/flatMap创建一个新步骤),如果它们接收到取消/超时,则它们将继续执行当前任务(因为它们无法停止),但不会启动下一个任务。您可以在超时时返回exception,但仍将执行最后一步,因此,如果它是一些副作用(例如,DB操作),那么它将在您已返回failure之后完成

这是非直观和棘手的,我认为这就是为什么这个行为没有添加到标准库中的原因

此外,未来仍在进行中,可能会对操作产生副作用。您不能获取类型为
Future[a]
的值并重新运行它。但是,您可以通过名称参数传递future,以便在
.recoverWith
中重新创建future

令人遗憾的是,您可以实现类似“直到LocalDateTime.now-startTime>=”这样的功能,因为我认为这是您想要的:

def retry[A](future: => Future[A], attemptsLeft: Int, timeoutTime: Instant) =
  future.recoverWith {
    case error: Throwable =>
      if (attemptsLeft <= 0 || Instant.now.isAfter(timeoutTime)) Future.failure(error)
      else retryHelper(future, attemptsLeft - 1, timeoutTime)
  }
如果要跟踪哪个future失败了,哪个future成功了:

def futureAttempt[A](future: Future[A]): Future[Either[Throwable, A]] =
  future.map(a => Right(a))).recover {
    case error: Throwable => Left(error)
  }

def retryFutures[A](list: List[() => Future[A]) = {
  val attempts: Int = ...
  val timeout: Instant = ...
  Future.sequence(list.map(future => retry(futureAttempt(future()), attempts, timeout)))
}
如果您不介意取消JVM上的未来,并且如果您有更多类似的情况,我建议使用库

如果您想使用实现重试的东西,有


如果您想在定义计算时有更好的
未来
(例如,不需要使用名称参数或空函数的东西),请尝试使用或ZIO()

。。。没有限制的重试和退避似乎是一种很好的自我表现的方式。有没有理由用
或者
而不是
尝试
?谢谢@mario galic,我可能做错了什么,但在我看来,这个测试用例不会重试失败的调用。该测试调用waitRetryAll,其函数仅在第三次尝试后返回。我的测试函数
doItEventually
(它在将来返回值或引发异常)对这些函数有效吗?@Jethro你完全正确,我的实现中有一个重大错误!谢谢你的测试。请查看我最新尝试的编辑答案。请注意,我传递的是
List[()=>Future[T]]
,而不是
List[Future[T]]
,以防止期货急于启动。在scastie@ig dev解决方案中使用它也同样有效。我使用
也没有什么特别的原因,只是为了演示一个潜在的解决方案。也许一个优势是scala猫提供了更多的好东西来使用
或者
。这可能是因为理论上,
Try
不是单子:
def retryFutures[A](list: List[() => Future[A]) = {
  val attempts: Int = ...
  val timeout: Instant = ...
  Future.sequence(list.map(future => retry(future(), attempts, timeout)))
}
def futureAttempt[A](future: Future[A]): Future[Either[Throwable, A]] =
  future.map(a => Right(a))).recover {
    case error: Throwable => Left(error)
  }

def retryFutures[A](list: List[() => Future[A]) = {
  val attempts: Int = ...
  val timeout: Instant = ...
  Future.sequence(list.map(future => retry(futureAttempt(future()), attempts, timeout)))
}