最近看到nginx plus收费版里有个session draining的概念,在用nginx做反向代理,要优雅停止后端应用的话,需要先在nginx层控制请求不再发到相应的节点,但与此同时已经建立的链接则要继续保持,一直等这些链接都结束了再停止应用。
其实在免费版的tengine就能实现这个特性,在以前的这篇nginx反向代理对后端某个节点优雅下线的问题 文章里测试过后端tomcat有一个耗时很长的请求,当tengine端健康监测已发现后端节点不可用的情况下,该请求并不会被tengine中止,而会等后端响应结束后再返回给客户端。
优化关闭应用并不只是在负载均衡或反向代理层面就能完美解决的,后端的应用本身也要考虑很多细节。举一个例子,Actor怎么有序的关闭,可以参考之前分享的Governor模式,所有干活的Actor由Governor创建,在创建时就设定好优先级。
在应用关闭的时候,对master发送PoisonPill,示例代码如下:
// 在你的容器或微容器里监听到应用关闭事件时触发shutdown,最简单的情况下是在shutdownhook里
def shutdown(): Unit = {
gracefulStopActors
system.terminate
Await.result(system.whenTerminated, Duration.Inf)
springContext.close
}
private def gracefulStopActors {
master ! PoisonPill
signal.acquire //wait
}
在governor角色里,当收到Terminated
消息后顺序结束子actor
...
case Terminated(actor) if actor == observable => {
logger.info("===receive terminated message from: " + actor)
// 先顺序的杀死所有托管的子actor,并将结果发给自己
stopAll(managedActors.toList.sorted) pipeTo self
}
...
// 顺序的停止所有子actor
protected def stopAll(kids: List[OrderedActorRef]): Future[Any] = {
kids match {
case first :: Nil =>
logger.info("===graceful stop: " + first.actor)
gracefulStop(first.actor, stopTimeout).flatMap { _ =>
Future { AllDead }
}
case first :: rest =>
logger.info("===graceful stop: " + first.actor)
gracefulStop(first.actor, stopTimeout).flatMap { _ =>
stopAll(rest)
}
case Nil =>
Future { AllDead }
}
}
再举一个例子,应用里有一个线程一直轮询从redis里获取数据,你在关闭应用的时候如何优雅终止这个线程呢?下面用一段代码简单模拟这个场景;假设这个应用没有任何容器,结束就是靠收到kill信号,那么在ShutdownHook里,要阻塞的等待(一段时间)worker线程结束后再退出才算安全
public class Test {
static volatile boolean running = true;
public static void main(String[] args) {
Thread worker = new Thread() {
public void run() {
while (running) {
try {
System.out.println("i'm running.1");
Thread.sleep(1000);
System.out.println("i'm running.2");
Thread.sleep(1000);
System.out.println("i'm running.3");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
worker.start();
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
System.out.println("receive shutdown signal.");
running = false;
while(worker.isAlive()) {
System.out.println("waiting for worker thread finish.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.out.println("wroker thread finished!");
}
});
}
}