背景
我们有一个调度器实例组,每个活动虚拟机每秒接收大约700个请求。此调度程序位于自动缩放的负载均衡器后面。到目前为止,我们所有的虚拟机都是所谓的虚拟机,但我们一直在研究让它们先发制人的可能性。
抢占式实例的问题
根据文档,GCP可以在任何时候终止抢占实例。
让我们假设每个调度器VM都不持有任何状态。它接收一个请求,处理它,并向其他机器发出HTTP请求。
在任何给定的时间,每个VM将同时处理大约700个请求,同时从负载均衡器接收数据。
问题
如果我的抢占式VM(处理700个请求(收到要终止的信号,会发生什么?
理论上,应该有一个关闭脚本,确保处理这些请求完成,然后杀死应用程序(干净退出(。这就引出了一个大问题:
- 但是负载均衡器知道我的虚拟机正在关闭吗?它会继续向终止的VM发送请求吗
注意事项
如果是,那么这意味着一些请求将失败,因为一旦应用程序关闭,机器仍在运行,负载均衡器在不知道应用程序已经关闭的情况下继续向机器发送请求。
理想情况下,这些请求将作为失败的请求返回到负载均衡器,并将请求发送到另一台机器。然而,GCP负载均衡器不够聪明,无法做到这一点,因此他们没有做到。
如果负载均衡器不知何故知道这个VM被选择用于抢先终止,那么就不需要做任何特殊的事情。
是哪一个?
但是负载均衡器知道我的VM正在关闭吗?会吗继续向终止的VM发送请求?
是的,负载平衡器将继续向实例发送请求。
您将需要创建一个关闭脚本,并从负载均衡器中删除您的实例。
这并不是说负载均衡器不够聪明。负载平衡器不知道是否可以重试您的请求。该决定应由客户端/后端逻辑做出。
您的用例不是抢占式实例的好例子。先发制人的实例将每24小时终止一次。如果您的目标是节省成本,请将长期实例定价的成本与优先定价进行比较。节省的费用不足以证明工程、测试和QA成本的合理性。
体系结构应该是为失败而设计的,但我不会故意选择一个会不断失败的体系结构。在您的情况下,每24小时一次。还有一种风险是,您将无法启动另一个实例来弥补增加的负载。还有一种风险是,您的所有实例都将被终止。
我们也遇到过类似的问题。我们几乎已经通过负载平衡器健康检查解决了这个问题(在非常高的负载条件下有一些问题(。技巧是在抢占信号的10-15秒内,负载均衡器会将实例标记为不健康,并停止向该实例发送新请求。
解决方案:
- 负载平衡器每3秒检查一次实例的运行状况,并在第三次运行状况检查失败后将实例标记为不健康。因此,负载均衡器在大约10秒内标记实例并停止发送新请求
- Java中使用
ContextCloseEvent (Spring boot)
或Runtime.getRuntime().addShutdownHook()
的Trap Preempt信号(在我的例子中,JVM收到信号需要几秒钟( - 将健康检查设置为失败,即健康检查端点将开始返回404
- 在关机块中睡眠15-25秒,以便进行和完成新请求
-
释放资源并进行关闭日志记录。
@EventListener public void onShutdown(ContextClosedEvent event) {
}//运行状况终结点@RequestMapping(value="ping",products=MediaType.TEXT_PLAIN_value(公共响应实体ping(({if(isShuttingDown((({log.warn("运行状况失败-即将关闭"(;返回新的ResponseEntity(HttpStatus.NOT_FOUND(;}return ResponseEntity.ok("pong"(;}log.warn("shutdown event received {}", event.getSource().toString()); log.warn("/ping will respond 404, Main thread will sleep for 20 seconds to allow pending tasks to complete"); isShuttingDown = true; try { Thread.sleep(SLEEP_BEFORE_SHUTDOWN_MILLIS); } catch (InterruptedException e) { log.error("sleep before shutdown interrupted", e); } log.warn("Shutting down now, daemon threads will continue work"); releaseResources(); log.info("{} {} on {} stopped.", NAME, VERSION, HOSTNAME);