在Akka Streams中的非线性图形恢复后如何工作监督策略

时间:2018-12-12 07:58:43

标签: scala akka akka-stream

我使用恢复方法来捕获Akka Streams中的错误或异常。它适用于线性图,但不适用于非线性图(例如Broadcast,Zip)。 Graph永远带有扇入或扇出等待失败端口的原因,因此Akka Streams挂断了。 该解决方案在https://blog.softwaremill.com/akka-streams-pitfalls-to-avoid-part-2-f93e60746c58的第9节中进行了描述。

该帖子使用Try monad并在Flow中捕获Exception。这样可行。但是,我使用恢复方法,因为我有很多流程,并且想在一处捕获错误。

我准备以下示例,但不起作用...

Source(1 to 10)
  .via(graph)
  .withAttributes(ActorAttributes.supervisionStrategy(Supervision.resumingDecider))
  .runForeach(println)

private def dangerFlow: Flow[Int, Try[String], NotUsed] = {
  Flow[Int].map(a => if (a == 5) throw new Exception("5 is invalid") else a.toString).map(str => Try(str)).recover {
    case e => Failure[String](e)
  }
}

private def safeFlow: Flow[Int, String, NotUsed] = Flow[Int].map( "hello" +_)

def graph = Flow.fromGraph(GraphDSL.create() { implicit b =>
  import GraphDSL.Implicits._
  val bcast = b.add(Broadcast[Int](2))
  val zip = b.add(Zip[Try[String], String])

  bcast.out(0) ~> dangerFlow ~> zip.in0
  bcast.out(1) ~> safeFlow ~> zip.in1

  FlowShape(bcast.in, zip.out)
})

结果:

(Success(1),hello1)
(Success(2),hello2)
(Success(3),hello3)
(Success(4),hello4)

我期望:

(Success(1),hello1)
(Success(2),hello2)
(Success(3),hello3)
(Success(4),hello4)
(Failure(java.lang.Exception: 5 is invalid),hello5)
(Success(6),hello6)
(Success(7),hello7)
(Success(8),hello8)
(Success(9),hello9)
(Success(10),hello10)

请告诉我任何解决方案。谢谢。

1 个答案:

答案 0 :(得分:1)

首先,让我们添加一些打印语句以更清楚地了解发生了什么:在流完成时出现一个...

val stream =
  Source(1 to 10)
    .via(graph)
    .withAttributes(ActorAttributes.supervisionStrategy(Supervision.resumingDecider))
    .runForeach(println)

// ...

stream.onComplete { _ =>
  println("Done!") // <---
  system.terminate()
}

...以及recover块中的另一个:

private def dangerFlow: Flow[Int, Try[String], NotUsed] = {
  Flow[Int]
    .map(a => if (a == 5) throw new Exception("5 is invalid") else a.toString)
    .map(str => Try(str))
    .recover {
      case e =>
        println("Recovering...") // <---
        Failure[String](e)
    }
}

运行流的输出是...

(Success(1),hello1)
(Success(2),hello2)
(Success(3),hello3)
(Success(4),hello4)
// no "Recovering..." or "Done!"

...显示未调用recover方法,并且流永远不会完成。流死锁的原因与blog描述的原因相同:

  
      
  • [{dangerFlow]失败,并且不向Zip发射元素。然后,它从broadcast恢复要求下一个元素。但是,要使broadcast发出元素,必须从所有输出中发出需求信号。

  •   
  • Zip仅接收一个元素(来自safeFlow),并永远等待第二个元素。 Zip仅在两个输入都具有值时发出。

  •   

恢复监管策略是未调用recover的原因。删除该策略...

val stream =
  Source(1 to 10)
    .via(graph)
    //.withAttributes(ActorAttributes.supervisionStrategy(Supervision.resumingDecider))
    .runForeach(println)

...然后再次运行流将产生以下输出:

(Success(1),hello1)
(Success(2),hello2)
(Success(3),hello3)
(Success(4),hello4)
Recovering...
(Failure(java.lang.Exception: 5 is invalid),hello5)
Done!

现在调用recover,并且流完成,但是流被截断了。这是因为recover完成了流:

  

recover允许您发出最终元素,然后在上游故障时完成流。

要获得所需的行为,必须使用Try,如下所示:

private def dangerFlow: Flow[Int, Try[String], NotUsed] = {
  Flow[Int].map(a => if (a == 5) Failure(new Exception("5 is invalid")) else Try(a.toString))
}

使用以上Flow运行流会产生以下结果:

(Success(1),hello1)
(Success(2),hello2)
(Success(3),hello3)
(Success(4),hello4)
(Failure(java.lang.Exception: 5 is invalid),hello5)
(Success(6),hello6)
(Success(7),hello7)
(Success(8),hello8)
(Success(9),hello9)
(Success(10),hello10)
Done!