如何在定制的akka​​ Sink内处理未来?

时间:2017-08-05 07:42:26

标签: scala akka future akka-stream

我正在尝试实施定制的Akka Sink,但我无法找到一种方法来处理它内部的未来。

class EventSink(...) {

  val in: Inlet[EventEnvelope2] = Inlet("EventSink")
  override val shape: SinkShape[EventEnvelope2] = SinkShape(in)

  override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = {
    new GraphStageLogic(shape) {

      // This requests one element at the Sink startup.
      override def preStart(): Unit = pull(in)

      setHandler(in, new InHandler {
        override def onPush(): Unit = {
          val future = handle(grab(in))
          Await.ready(future, Duration.Inf)
          /*
          future.onComplete {
            case Success(_) =>
              logger.info("pulling next events")
              pull(in)
            case Failure(failure) =>
              logger.error(failure.getMessage, failure)
              throw failure
          }*/
          pull(in)
        }
      })
    }
  }

  private def handle(envelope: EventEnvelope2): Future[Unit] = {
    val EventEnvelope2(query.Sequence(offset), _/*persistenceId*/, _/*sequenceNr*/, event) = envelope
    ...
    db.run(statements.transactionally)
  }
}

此刻我必须阻止未来,这看起来不太好。我注释掉的非阻塞只适用于第一个事件。有人可以帮忙吗?

已更新感谢@ViktorKlang。它似乎现在正在运作。

override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = 
{
    new GraphStageLogic(shape) {
      val callback = getAsyncCallback[Try[Unit]] {
        case Success(_) =>
          //completeStage()
          pull(in)
        case Failure(error) =>
          failStage(error)
      }

      // This requests one element at the Sink startup.
      override def preStart(): Unit = {
        pull(in)
      }

      setHandler(in, new InHandler {
        override def onPush(): Unit = {
          val future = handle(grab(in))
          future.onComplete { result =>
            callback.invoke(result)
          }
        }
      })
    }
  }

我正在尝试实现与ReadJournal.eventsByTag连接的Rational DB事件接收器。所以这是一个连续的流,除非出现错误,否则永远不会结束 - 这就是我想要的。我的方法是否正确?

还有两个问题:

  1. 除非我手动调用completeStage或failStage,否则GraphStage是否永远不会结束?

  2. 在preStart方法之外声明回调是正确还是正常?在这种情况下,我是否可以在preStart中调用pull(in)?

  3. 谢谢, 程

1 个答案:

答案 0 :(得分:0)

避免自定义阶段

一般情况下,您应该尝试使用库SourceFlowSink的给定方法来消耗所有可能性。自定义阶段几乎不需要,并且使代码难以维护。

写你的"自定义"使用标准方法的阶段

根据您问题的示例代码的详细信息,我没有看到您开始使用自定义Sink的原因。

根据您的handle方法,您可以稍微修改它以执行您在问题中指定的日志记录:

val loggedHandle : (EventEnvelope2) => Future[Unit] =
  handle(_) transform {
    case Success(_)       => {
      logger.info("pulling next events")
      Success(Unit)
    }
    case Failure(failure) => {
      logger.error(failure.getMessage, failure)
      Failure(failure)
    }
  }

然后只需使用Sink.foreachParallel来处理信封:

val createEventEnvelope2Sink : (Int) => Sink[EventEnvelope2, Future[Done]] = 
  (parallelism) =>
    Sink[EventEnvelope2].foreachParallel(parallelism)(handle _)

现在,即使您希望将每个EventEnvelope2发送到数据库,也可以使用1进行并行操作:

val inOrderDBInsertSink : Sink[EventEnvelope2, Future[Done]] =
  createEventEnvelope2Sink(1)

此外,如果数据库抛出异常,您仍可在foreachParallel完成时获取它:

val someEnvelopeSource : Source[EventEnvelope2, _] = ???

someEnvelopeSource
  .to(createEventEnvelope2Sink(1))
  .run()
  .andThen {
    case Failure(throwable) => { /*deal with db exception*/ }
    case Success(_)         => { /*all inserts succeeded*/  }
  }