Nacos 服务下线快 会导致dubbo直接调用,报错为No route to host
原因在于容器开始销毁的时间比dubbo服务下线的时间要早。因为容器销毁时,任何连接请求都会返回Connection refused。此时如果发生dubbo调用,就会有相应的报错。
报错日志:
https://github.com/alibaba/spring-cloud-alibaba/issues/1805
一、思路
1、让新的服务先启动起来,注册到注册中心,等待客户端发现新服务。
2、把待下线的服务从注册中心下线,等待客户端刷新服务列表。
3、把待下线的服务优雅停机。
二、实现方式
基于K8S滚动升级的机制,当新的POD准备就绪之后,旧的POD会被删除。在删除旧POD之前会调用容器preStop的钩子。
1、让新的服务先启动起来,注册到注册中心,等待客户端发现新服务。
K8S容器本身有一个就绪探针配置,当就绪探针返回正常,则开始删除旧POD。
//就绪探针
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
scheme: HTTP
initialDelaySeconds: 120
timeoutSeconds: 300
periodSeconds: 35
successThreshold: 1
failureThreshold: 1
我们可以使用SpringBoot的健康检查actuator作为就绪探针。核心的配置是initialDelaySeconds,当容器创建到第一次就绪检查的延迟时间。
initialDelaySeconds=服务启动完成耗时+客户端负责均衡刷新服务列表的时间(ribbon.ServerListRefreshInterval,默认是30s)
2、把将要下线的服务从注册中心下线,等待客户端刷新服务列表。
我们要实现一个preStop钩子,把服务从注册中心下线,而且要等待ribbon.ServerListRefreshInterval秒时间,等待客户端刷新服务,把当前服务剔除。
我们用的nacos作为注册中心,AbstractAutoServiceRegistration是服务注册的抽象基类,stop方法可以从服务中心下线服务,NacosAutoServiceRegistration是实现类,所以我们只需要调用NacosAutoServiceRegistration.stop()即可下线服务。
public void stop() {
if (this.getRunning().compareAndSet(true, false) && this.isEnabled()) {
this.deregister();
if (this.shouldRegisterManagement()) {
this.deregisterManagement();
}
this.serviceRegistry.close();
}
}
自定义一个deregister的endpoint,让k8s的preStop去调用
@Endpoint(id = "deregister")
public class NacosServiceDeregisterEndpoint {
private static final Logger log = LoggerFactory.getLogger(NacosServiceDeregisterEndpoint.class);
private NacosAutoServiceRegistration serviceRegistration;
public NacosServiceDeregisterEndpoint(NacosAutoServiceRegistration serviceRegistration) {
this.serviceRegistration = serviceRegistration;
}
@ReadOperation
public Map deregister() {
log.info("开始服务下线");
serviceRegistration.stop();
log.info("完成服务下线");
int wait = 10;
log.info("等待{}s,等待客户端刷新服务列表", wait);
int num = 0;
while (true) {
try {
//等待客户端刷新服务发现列表
TimeUnit.SECONDS.sleep(1);
num++;
log.info("还需等待{}秒", wait - num);
if (num >= wait) {
break;
}
} catch (InterruptedException e) {
//ignore
log.info("InterruptedException", e);
}
}
log.info("等待结束");
Map result = new HashMap<>();
result.put("deregister", true);
return result;
}
}
别忘记了要把endpoint注册到BeanFactory中
@Configuration(proxyBeanMethods = false)
public class NacosServiceDeregisterAutoConfig {
@Bean
public NacosServiceDeregisterEndpoint deregisterEndpoint(NacosAutoServiceRegistration registration) {
return new NacosServiceDeregisterEndpoint(registration);
}
}
定义K8S的preStop钩子
lifecycle:
preStop:
httpGet:
path: /actuator/deregister
port: 8080
scheme: HTTP
3、把待下线的服务优雅停机
Spring Boot 已经支持优雅停机。
我们需要添加下面的配置,超时需要根据平时请求的耗时来定,可以稍微大一点也没关系。
server.shutdown=graceful spring.lifecycle.timeout-per-shutdown-phase=60s
最后还有一个重要的K8S配置
terminationGracePeriodSeconds要大于preStop+spring.lifecycle.timeout-per-shutdown-phase,可以设置大一点没什么关系。
//停止超时时间 terminationGracePeriodSeconds: 300
其他方案
https://blog.csdn.net/shine0181/article/details/122308860
https://my.oschina.net/u/4022819/blog/5528262