我们有一个调度程序实例组,每个活动VM每秒接收大约700个请求。此调度程序位于自动扩展的负载均衡器后面。到目前为止,我们所有的VM都是regaular虚拟机,但是我们一直在研究使它们成为先发制人的可能性。
根据文档,GCP可以在any time终止抢先实例。
假设每个调度程序VM都没有状态。它接收请求,处理它并向其他机器发出HTTP请求。
在任何给定时间,每个VM将同时处理大约700个请求,同时从负载均衡器接收数据。
如果处理700请求的抢占式VM收到要终止的信号,会发生什么?
好吧,理论上应该有一个shutdown script,确保处理这些请求完成然后杀死应用程序(干净退出)。这引出了一个大问题:
如果是,那么这意味着一些请求将失败,因为一旦应用程序关闭,机器仍然启动,负载均衡器继续向机器发送请求,而不知道应用程序已经关闭。
理想情况下,这些请求将作为失败的请求返回到负载均衡器,并将请求发送到另一台计算机。但是,GCP负载平衡器不够智能,所以他们不这样做。
如果负载均衡器知道这个VM被选择用于预留终止,那么就没有什么特别需要做的了。
哪一个?
但负载均衡器是否知道我的VM正在关闭?它会继续向终止VM发送请求吗?
是的,负载均衡器将继续向实例发送请求。
您需要创建一个关闭脚本并从负载均衡器中删除您的实例。
并不是负载均衡器不够智能。负载均衡器不知道您的请求是否可以重试。该决定应由客户端/后端逻辑做出。
您的用例不是抢先实例的好例子。抢先实例将每24小时终止一次。如果您的目标是节省成本,请将长期实例定价的成本与优先定价进行比较。节省的费用不足以证明工程,测试和质量保证费用的合理性。
架构应该设计为失败,但我不会刻意选择一个会不断失败的架构。在你的情况下每24小时。您还有可能无法启动另一个实例来弥补增加的负载。并且存在所有实例将被终止的风险。
我们有类似的问题。我们几乎通过负载均衡器运行状况检查解决了这个问题(在非常高的负载条件下存在一些问题)。 Trick现在在抢占信号的10-15秒内,负载均衡器会将实例标记为不健康,因为停止向该实例发送新请求。
解:
ContextCloseEvent (Spring boot)
或Runtime.getRuntime().addShutdownHook()
在Java中捕获Preempt信号(在我的情况下,在JVM收到信号之前需要几秒钟)
@EventListener
public void onShutdown(ContextClosedEvent event) {
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);
}
//health endpoint
@RequestMapping(value = "ping", produces = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity ping() {
if(isShuttingDown()) {
log.warn("health failed - shutting down soon");
return new ResponseEntity(HttpStatus.NOT_FOUND);
}
return ResponseEntity.ok("pong");
}