簡體   English   中英

Scala 重試期貨序列,直到它們全部完成

[英]Scala retry sequence of futures until they all complete

在 scala 中,您將如何編寫一個函數,該函數接受一個 Futures 序列,運行它們,不斷重試失敗的任何一個,並返回結果?

例如,簽名可能是:

  def waitRetryAll[T](futures: Seq[Future[T]]): Future[Seq[T]]

可配置超時的獎勵積分,此時函數失敗並且被調用者可以處理這種情況。
如果該錯誤案例處理程序可以接收失敗的期貨列表,則獎勵積分。

謝謝!

基於重試返回未來的函數考慮

def retry[T](expr: => Future[T], n: Int = 3): Future[Either[Throwable, T]] = {
  Future
    .unit
    .flatMap(_ => expr).map(v => Right(v))
    .recoverWith {
      case _ if n > 1 => retry(expr, n - 1)
      case e => Future.failed(e).recover{case e => Left(e)}
    }
}

結合

Future.sequence

它將List[Future[T]]Future[List[T]] 然而sequence具有快速失敗的行為,因此我們不得不將我們的Future[T]提升到Future[Either[Throwable, 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)))

我們可以collect我們的Left s 或Right s 並相應地恢復或繼續處理,例如

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

請注意我們如何必須傳入List[() => Future[T]]而不是List[Future[T]]以防止期貨急於啟動。

據我所知,在標准庫中沒有用於Future超時的實用程序。

您將如何中斷/取消 JVM 上正在進行的計算? 在一般情況下,你不能,你只能在wait時中斷Thread但如果它從不wait s? 用於異步計算的 IO 庫(定義取消)將 IO 作為一系列較小的不可中斷任務(每個 map/flatMap 創建一個新步驟)執行,如果它們收到取消/超時,則它們將繼續執行當前任務(因為它們無法停止它) ) 但他們不會開始下一個。 您可以在超時時返回異常,但仍然會執行最后一步,因此如果它是一些副作用(例如數據庫操作),它將在您已經返回失敗后完成。

這是不直觀和棘手的,我認為這就是為什么沒有將這種行為添加到標准庫中的原因。

此外,未來正在進行中,可能會產生副作用。 您不能采用Future[A]類型的值並重新運行它。 但是,您可以將未來作為按名稱參數傳遞,以便在.recoverWith您可以重新創建未來。

令人難過的是,您可以實施諸如“重試直到 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.sequence來創建結果列表:

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)))
}

如果您不擔心取消 JVM 上的期貨,並且如果您有更多類似的情況,我建議您使用庫。

如果你想使用一些為你實現重試的東西,那么cats-retry

如果你想在定義計算方面有比Future更好的東西(例如不需要你使用按名稱參數或空函數的東西)試試Monix或 ZIO ( https://zio.dev/ )

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM