Как обрабатывать исключение с помощью ask pattern и наблюдения

Как я должен обрабатывать исключение, вызванное DbActor здесь ? Я не уверен, как справиться с этим, должен ли трубить случай отказа ?

class RestActor extends Actor with ActorLogging {
  import context.dispatcher

  val dbActor = context.actorOf(Props[DbActor])
  implicit val timeout = Timeout(10 seconds)


  override val supervisorStrategy: SupervisorStrategy = {
    OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
      case x: Exception => ???
    }
  }

  def receive = {
    case GetRequest(reqCtx, id) => {

        // perform db ask
       ask(dbActor, ReadCommand(reqCtx, id)).mapTo[SomeObject] onComplete {
        case Success(obj) => { // some stuff }
        case Failure(err) => err match {
          case x: Exception => ???
        }
      }
    }
  }
}

был бы рад получить вашу мысль, спасибо заранее !

3 ответов


есть пара вопросов, которые я вижу здесь на основе вопросов в вашем примере кода:

  1. какие типы вещей я могу сделать, когда я переопределяю поведение супервизора по умолчанию в определении того, как обрабатывать исключения?

  2. при использовании ask, какие вещи я могу сделать, когда я получаю Failure результат Future что я жду?

давайте начнем с первым вопросом сначала (обычно хорошая идея). При переопределении стратегии супервизора по умолчанию можно изменить способ обработки определенных типов необработанных исключений в дочернем акторе в отношении того, что делать с этим неудачным дочерним актором. Ключевое слово в предыдущем предложении -unhandled. Для субъектов, которые выполняют запрос / ответ, вы можете фактически захотеть обработать (поймать) определенные исключения и вернуть определенные типы ответов (или не выполнить восходящее будущее, больше об этом позже), а не позволять им идти без рук. Когда происходит необработанное исключение, вы в основном теряете возможность ответить отправителю с описанием проблемы, и отправитель, вероятно, получит TimeoutException вместо того, как их Future никогда не будет завершен. Как только вы выяснили, что вы обрабатываете явно, вы можете рассмотреть все остальные исключения при определении своей пользовательской стратегии супервизора. Внутри этого блока здесь:

OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
  case x: Exception => ???
}

у вас есть шанс сопоставление типа исключения с ошибкой Directive, который определяет, как сбой будет обрабатываться с точки зрения надзора. Варианты:

  1. стоп-полностью остановить ребенка актер и не отправлять больше сообщений на него

  2. Resume-возобновить неудачный ребенок, не перезапуская его, таким образом, сохраняя его текущее внутреннее состояние

  3. Restart-аналогично resume, но в этом случае старый экземпляр выбрасывается прочь, и создается новый экземпляр, и внутреннее состояние сбрасывается (preStart)

  4. Escalate-эскалация вверх по цепочке к родителю супервизора

Итак, скажем, что учитывая SQLException вы хотели возобновить и учитывая все другие, которые вы хотите перезапустить, тогда ваш код будет выглядеть так:

OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
  case x: SQLException => Resume
  case other => Restart
}

теперь второй вопрос, который относится к тому, что делать, когда возвращает Failure ответ. В этот случай, я думаю, зависит от того, что должно было произойти в результате этого Future. Если сам актер rest отвечал за выполнение http-запроса (предположим, что httpCtx имеет complete(statusCode:Int, message:String) функция на нем), то вы можете сделать что-то вроде этого:

   ask(dbActor, ReadCommand(reqCtx, id)).mapTo[SomeObject] onComplete {
    case Success(obj) => reqCtx.complete(200, "All good!")
    case Failure(err:TimeoutException) => reqCtx.complete(500, "Request timed out")
    case Failure(ex) => reqCtx.complete(500, ex.getMessage)
  }

теперь, если другой актер вверх по течению отвечает за выполнение http-запроса, и вам нужно ответить этому актеру, вы можете сделать что-то вроде этого:

   val origin = sender
   ask(dbActor, ReadCommand(reqCtx, id)).mapTo[SomeObject] onComplete {
    case Success(obj) => origin ! someResponseObject
    case Failure(ex) => origin ! Status.Failure(ex)
  }

этот подход предполагает, что в блоке успеха вы сначала хотите массировать объект результата, прежде чем отвечать. Если вы не хотите этого делать, и вы хотите отложить обработку результатов отправителю, вы можете просто сделать:

   val origin = sender
   val fut = ask(dbActor, ReadCommand(reqCtx, id))
   fut pipeTo origin

есть активатор (http://www.typesafe.com/activator) шаблон, который показывает хороший пример связывания ask и наблюдения вместе:http://www.typesafe.com/activator/template/akka-supervision.


для более простых систем может потребоваться поймать и переслать все ошибки. Для этого я сделал эту небольшую функцию, чтобы обернуть метод receive, не беспокоясь о наблюдении:

  import akka.actor.Actor.Receive
  import akka.actor.ActorContext
  /**
   * Meant for wrapping the receive method with try/catch.
   * A failed try will result in a reply to sender with the exception.
   * @example
   *          def receive:Receive = honestly {
   *            case msg => sender ! riskyCalculation(msg)
   *          }
   *          ...
   *          (honestActor ? "some message") onComplete {
   *            case e:Throwable => ...process error
   *            case r:_ => ...process result
   *          }
   * @param receive
   * @return Actor.Receive
   *
   * @author Bijou Trouvaille
   */
    def honestly(receive: =>Receive)(implicit context: ActorContext):Receive = { case msg =>
      try receive(msg) catch { case error:Throwable => context.sender ! error }
    }

затем вы можете поместить его в файл пакета и импортировать a la akka.pattern.pipe и тому подобное. Очевидно, что это не будет иметь дело с исключениями, создаваемыми асинхронным кодом.