在使用K8S rolling update的时候,我同时使用JMeter不间断call API,就有少部分请求抛出了这个错误
1 2 3 4 5 6 7
| { "timestamp":"2019-12-02T07:33:47.120+0000", "status":500, "error":"Internal Server Error", "message":"No message available", "path":"/api/anon/article/TW1Tq/feed" }
|
上面的异常是SpringBoot的BasicErrorController抛出的带有错误,HTTP状态和异常消息的详细信息的JSON响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package org.springframework.boot.autoconfigure.web.servlet.error;
@Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController {
@RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); return new ResponseEntity<>(body, status); }
}
|
这个错误显然是因为服务更新中,容器服务被直接停止,部分请求仍被分发到终止的容器,导致服务出现500错误,这部分错误请求数据占用比较少。由于是部分请求会产生服务器错误的情况,考虑使用优雅的终止方式,将错误请求降到最低,直至滚动更新不影响用户。
kubernetes终止pod过程如下
- kubernetes停止将新的连接路由到需要终止的pod。已建立的连接将保持不变并保持打开状态。
- 假设容器将开始停止,Kubernetes将TERM信号发送到Pod中每个容器的根进程。它发送的这个信号无法配置。
- 等待pod的TerminalGracePeriodSeconds中指定的时间段(默认为30秒),如果此时容器仍在运行,它将发送KILL终止容器
由于在更新过程中,我们使用jmeter一直发起请求,所以有部分请求仍会发送到需要delete的pod,很明显在30s内它处理不完这么多的请求,而当过了30s之后,发送KILL命令就导致有些请求失败了。
那么能不能当我们收到通知的时候就不接收新的请求了呢?然后再把正在处理的请求处理完成之后在关闭我们的应用。
由于我们使用的内嵌的tomcat,所以只需要实现对应的接口就行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| import org.apache.catalina.connector.Connector; import org.apache.tomcat.util.threads.ThreadPoolExecutor; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent;
@Slf4j public class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private static final int TIMEOUT = 30;
private volatile Connector connector;
@Override public void customize(Connector connector) { this.connector = connector; }
@Override public void onApplicationEvent(ContextClosedEvent event) { this.connector.pause(); Executor executor = this.connector.getProtocolHandler().getExecutor(); if (executor instanceof ThreadPoolExecutor) { try {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(TIMEOUT, TimeUnit.SECONDS)) {
log.warn("Tomcat thread pool did not shut down gracefully within " + TIMEOUT + " seconds. Proceeding with forceful shutdown");
threadPoolExecutor.shutdownNow();
if (!threadPoolExecutor.awaitTermination(TIMEOUT, TimeUnit.SECONDS)) { log.error("Tomcat thread pool did not terminate");
} } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } }
}
@Configuration public class ShutdownConfiguration {
@Bean public GracefulShutdown gracefulShutdown() { return new GracefulShutdown(); }
@Bean public ConfigurableServletWebServerFactory webServerFactory(final GracefulShutdown gracefulShutdown) { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); factory.addConnectorCustomizers(gracefulShutdown); return factory; } }
|
通过上面的设置我们就能实现所说的效果。同时即使在滚动更新的时候,一直有请求不断的call api,也不会出现报错的问题了。
这里不断call api,类似于一个高并发请求(现在不说点高并发都不好意思说自己是写代码的了,狗头)