【问题标题】:spray-can webservice graceful shutdownspray-can webservice 优雅关闭
【发布时间】:2015-04-27 18:51:54
【问题描述】:

我有基于 spray.io 的 web 服务,它作为独立的 jar 运行(我使用 sbt assembly,然后只使用 java -jar myws.jar)。它与喷雾示例中的引导程序几乎相同,如下所示:

/** Bootstrap */
object Boot extends App {
   // we need an ActorSystem to host our application in
   implicit val system = ActorSystem("my-system")

   // create and start our service actor
   val service = system.actorOf(Props[MyServiceActor], "my-ws-service")

   implicit val timeout = Timeout(10.seconds)

   CLIOptionsParser.parse(args, CLIOptionsConfig()) map { config =>
     // start a new HTTP server
     IO(Http) ? Http.Bind(service, interface = config.interface, port = config.port)
   }
}

现在我只需在后台使用java -jar my-service "$@" & 运行该过程,然后使用kill -9 pid 停止。

我想优雅地停止我的网络服务,这意味着它会完成打开的连接并拒绝新的连接。

github 上的喷雾罐页面推荐to send it an Akka PoisonPill message。理想情况下,我想从命令行启动它,尽可能简单。我想也许可以再附加一个绑定到本地主机的 HTTP 服务器实例,并有一些休息方法停止,也许诊断 web 服务。可行吗?还有什么其他选择?

更新: 我根据答案添加了我可以想象的必须工作的内容,但似乎没有,至少我从未见过我希望在标准输出或日志中看到的任何消息。实际上,我已经尝试了 HttpUnbind、PoisonPill 的变体,一个一个地尝试。有眼光的人可以看看这个吗? PS。钩子本身被成功调用,检查它。我发送给 jvm 的信号是 SIGTERM。

/* Simple reaper actor */
class Reaper(refs: ActorRef*) extends Actor {
  private val log = Logging(context.system, this)
  val watched = ArrayBuffer(refs: _*)

  refs foreach context.watch

  final def receive = {
    case Terminated(ref) =>
      watched -= ref
      log.info(s"Terminated($ref)")
      println(s"Terminated($ref)")
      if (watched.isEmpty) {
        log.info("Shutting dow the system")
        println("Shutting dow the system")
        system.shutdown()
      }
  }
}


// termination hook to gracefully shutdown the service
Runtime.getRuntime.addShutdownHook(new Thread() {
  override def run() = {
    val reaper = system.actorOf(Props(new Reaper(IO(Http), service)))
    //IO(Http) ? Http.Unbind(5.minutes)
    IO(Http) ! PoisonPill
  }
})

UPDATE2:所以,它以某种方式起作用,即 - 当 PoisonPill 被发送时,所有当前的 HTTP 连接都关闭了。但我宁愿停止接收新连接,等待 open 返回响应并关闭。

VERDICT:akka 似乎有自己的钩子,因为尽管我的钩子被执行了,但是演员被杀死并且所有连接都在没有我的动作的情况下关闭。如果有人会提供带有 JVM 关闭钩子的解决方案,那就太好了。我认为这是一个重要的问题,很遗憾它在网上没有任何好的食谱。在此期间,我将尝试使用 tcp/http 实现优雅关闭。

【问题讨论】:

  • 您可以使用Http.Unbind 消息告诉服务器停止侦听新连接并在超时后关闭打开的连接。见spray.io/documentation/1.2.1/spray-can/http-server/…。有一些休息方法是绝对可能的,请参阅这个喷雾示例(尽管它不会优雅地停止),例如:github.com/spray/spray/blob/master/examples/spray-routing/…
  • 我到处尝试,但似乎不起作用。请看更新。
  • @jrudolph 我更新了有问题的问题:钩子触发,但是 akka 系统被其他东西(可能是它自己的钩子)关闭,因此进程中的连接被关闭(并且客户端收到错误连接已经由remore关闭)。我希望打开的连接完成,然后才关闭。似乎它与 unbind 本身无关。
  • 最终我明白了我的错误在哪里,糟糕的是没有人指出发送UnboundIO(Http)本身是没有用的,应该发送给听众。
  • 是的,您需要将Http.Unbind 发送给所有侦听器,然后以阻塞方式等待所有侦听器的Unbound 响应,而无需从shutdownHook 返回,否则该过程将是在清理发生之前关闭。

标签: web-services scala akka spray


【解决方案1】:

当我尝试使用 SIGTERM 和 JVM 钩子时,我需要阻止钩子线程退出,直到我的关闭序列完成,我根本不知道该怎么做(我对 akka 有点缺乏经验,所以也许我错过了一些明显的解决方案)。

我最后所做的只是在 localhost 上再附加一个 HTTP 侦听器,它具有启动关闭的方法(而且它恰好对其他任务很方便,例如获取服务器状态、触发应用程序中的事件等)。

这可能看起来像这样(我怀疑这可能包含不必要的操作,因此欢迎改进):

在引导中

implicit val system = ActorSystem(...)

// create and start external service actor
val service = system.actorOf(Props[MyWebServiceActor], "my-web-service")

// create internal service to manage application
val controlService = system.actorOf(Props[ControlServiceActor], "control-service")

implicit val timeout = Timeout(10.seconds)

// start a new HTTP server for external service (notifying control of HTTP listener)
IO(Http).tell(Http.Bind(service, interface = config.interface, port = config.port), controlService)

// start internal server looking at localhost
IO(Http) ? Http.Bind(controlService, interface = "127.0.0.1", port = config.controlPort)

还有控制服务本身:

class ControlServiceActor extends Actor with HttpService with ActorLogging {
  def actorRefFactory = context
  implicit val system = context.system

  /* Listener of main service */
  var listener: ActorRef = _

  def receive = {
    // this is reply from IO.Http when HTTP listener is bound
    case Http.Bound(_) =>
      listener = sender()
      context.become(mainContext)
  }

  // http api for graceful stop
  val mainContext = runRoute {
    path("stop") {
      get {
        parameter('timeout.as[Int] ? 60) { timeout =>
          complete {
            // unbind makes listener to reject new connections
            context.become(shuttingDownContext)
            log.warning(s"Stopping application within $timeout seconds...")
            context.watch(listener)
            listener ! Http.Unbind(timeout.seconds)

            "Stopping..."
          }
        }
      }
    }
  }

  // Shutdown sequence
  val shuttingDownContext = ({
    // when unbound HTTP listener not accepting connections
    case Http.Unbound =>
      log.info("Webservice unbound, waiting for active connections to complete")

    // when HTTP listener terminated after unbound it has been processed all requests
    case Terminated(ref) if ref == listener =>
      log.info("Webservice finished, exiting")
      system.shutdown()
  }: Actor.Receive) orElse runRoute(complete("Shutdown in progress"))
}

【讨论】:

  • 开发环境的好解决方案!但是对于生产,您通常需要一些可以优雅地进入您的关闭代码的东西,即使在例如服务器重新启动(特别是如果您需要关闭操作以保持环境的其余部分快乐,尽可能多)
  • 没错,但我还没有找到更好的解决方案。
  • 小错字——不应该是// Shutdown sequence吗?
  • @dmitry 除了Http.unbind 之外发送PoisonPill 是一个好主意,考虑到spray 的自述文件页面说用PoisonPill 关闭它? github.com/spray/spray-can#shutting-down-1
  • @EdgeCaseBerg 我记得我试过 PoisonPill,它以某种直接的方式杀死了演员,关闭连接但不等待完成。
【解决方案2】:

kill -9 将立即销毁进程。您可以使用 SIGTERM (kill -15) 在 jvm 中触发关闭挂钩。

Runtime.getRuntime.addShutdownHook(new Thread() {
  def run() = {
    //here you can send PoisonPill
  }
})

【讨论】:

  • 拜托,您能否详细说明一下在 SIGTERM 的情况下,关机序列的外观如何?比如说JVM会跑钩子,它怎么知道需要的进程已经完成了?
  • 类似here 描述的东西(因为喷雾基于akka)
  • 你可以发送一些消息给喷演员,并使用become改变行为(停止接收连接)。
  • 这一切似乎都没有用,因为在 SIGTERM 上,所有东西都被其他东西杀死了,而钩子也没用。或者我做错了什么。
  • 它可以工作,但钩子必须以某种方式阻止执行,并等到所有必要的步骤都完成 - 例如一些演员关闭,或者一些序列结束。它变得不平凡,我在网上发现没有什么非常有帮助的。我放弃了尝试,并在接受的答案中继续解决。我个人希望用 SIGTERM 实现变体,但是......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-26
  • 2014-02-05
  • 2019-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多