- k8s配置就绪探针,readinessProbe
readinessProbe:
httpGet:
path: /actuator/health
port: 3900
scheme: HTTP
# initialDelaySeconds: 20 ## 指定的这个秒以后才执行探测
timeoutSeconds: 3
periodSeconds: 5
successThreshold: 1
failureThreshold: 3
- k8s配置滚动更新策略、优雅关闭策略
terminationGracePeriodSeconds: 90
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
minReadySeconds: 3
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
- 开启健康检查、开启Nacos手动注销微服务端点开关
management:
health:
mail:
enabled: false
elasticsearch:
enabled: false
endpoints:
# jmx:
# exposure:
# include: health,info
web:
exposure:
include: "*"
exclude: env,beans
endpoint:
serviceregistry:
enabled: true
health:
enabled: true
show-details: always
- k8s配置生命周期钩子
lifecycle:
preStop:
httpGet:
path: /actuator/xdownRegister
port: 3900
scheme: HTTP
# exec:
# command: ["sleep", "60"]
/actuator/xdownRegister为自定义端点,配合Nacos内置端点serviceregistry使用
- 微服务通用项,配置自定义端点xdownRegister,主要包含三个文件:GracefulShutdownEndPoint、GracefulShutdownListener、GracefulShutdownProcessor。字如其名
import lombok.RequiredArgsConstructor;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
@Endpoint(id = "xdownRegister")
@RequiredArgsConstructor
public class GracefulShutdownEndPoint {
private final GracefulShutdownProcessor gracefulShutdownProcessor;
/**
* 访问路径:GET http://容器组IP:端口号/actuator/xdownRegister
*/
@ReadOperation
public Map<String, Object> getInfo() throws InterruptedException {
Map<String, Object> info = new HashMap<>();
gracefulShutdownProcessor.deregister(false);
info.put("message", "service shutdown ok!");
return info;
}
}
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@RequiredArgsConstructor
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {
private final Environment env;
private final GracefulShutdownProcessor gracefulShutdownProcessor;
@Override
public void onApplicationEvent(ContextClosedEvent event) {
if (!"prod".equals(env.getActiveProfiles()[0])) {
return;
}
try {
gracefulShutdownProcessor.deregister(true);
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
}
}
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
@RequiredArgsConstructor
public class GracefulShutdownProcessor {
private final RestTemplate restTemplate;
public static final String INSTANCE_DOWN_X_URL = "http://%s:%s/actuator/serviceregistry?status=DOWN";
@Value("${spring.application.name}")
private String applicationName;
@Value("${server.port}")
private Integer port;
public void deregister(boolean handleOthers) throws InterruptedException {
InetAddress ipAddr = null;
try {
ipAddr = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
log.error(e.getMessage(), e);
}
if (Objects.isNull(ipAddr)) {
return;
}
log.info(String.format("开始注销nacos实例 (%s - %s:%d)", applicationName, ipAddr.getHostAddress(), port));
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Content-Type", "application/vnd.spring-boot.actuator.v2+json;charset=UTF-8");
post(String.format(INSTANCE_DOWN_X_URL, ipAddr.getHostAddress(), port), new HashMap<>(), httpHeaders);
log.info("注销nacos实例success.");
if (handleOthers) {
TimeUnit.SECONDS.sleep(45);
}
}
private void post(String url, Object data, HttpHeaders requestHeaders) {
HttpEntity<Object> requestEntity = new HttpEntity<>(data, requestHeaders);
restTemplate.postForObject(url, requestEntity, String.class);
}
}
总结
- 整体实现的逻辑:
- K8S Pod接收到关闭指令,进入生命周期钩子,立即调用/actuator/xdownRegister,将当前微服务从Nacos注册中心手动取消注册,防止再有其他流量打到当前服务;
- 之后,系统监听ContextClosedEvent事件,进入优雅停机GracefulShutdownProcessor处理器,内部休眠45秒,使服务延迟关闭,可继续处理未完成的任务