spray-can webservice正常关机

时间:2014-07-14 07:08:14

标签: web-services scala akka spray

我有基于 spray.io 的webservice,它作为独立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上的Spray-can页面推荐to send it an Akka PoisonPill message。理想情况下,我想从命令行启动它,尽可能简单。我想可能只附加一个绑定到localhost的HTTP服务器实例,并有一些休息方法停止,并可能诊断web服务。这可行吗?还有什么其他选择?

更新: 我根据答案添加了我能想象的工作,但似乎没有,至少我从未见过我期望在stdout或log中看到的任何消息。实际上,我已经尝试过变体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连接都被关闭了。但我宁愿停止接收新的连接,等待打开以返回响应并关闭。

VERDICT :似乎akka有自己的钩子,因为尽管我的钩子被执行,但是演员们被杀死了所有连接都被关闭了而没有我的行动。如果有人会提供JVM关闭钩子的解决方案,那将是很棒的。我认为这是一个重要的问题,很遗憾,它没有任何好的在线食谱。与此同时,我将尝试使用tcp / http实现正常关闭。

2 个答案:

答案 0 :(得分:6)

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

我最终做的是将另一个HTTP侦听器仅附加到localhost,它具有启动关闭的方法(也恰好可以方便其他任务,例如获取服务器状态,触发应用程序中的事件等)。

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

在bootstrap中

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

答案 1 :(得分:1)

kill -9会立即销毁进程。你可以使用SIGTERM(kill -15)来触发jvm中的关闭挂钩。

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