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/healthport: 8080scheme: HTTPinitialDelaySeconds: 120timeoutSeconds: 300periodSeconds: 35successThreshold: 1failureThreshold: 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;}@ReadOperationpublic 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) {//ignorelog.info("InterruptedException", e);}}log.info("等待结束");Map result = new HashMap<>();result.put("deregister", true);return result;}}
别忘记了要把endpoint注册到BeanFactory中
@Configuration(proxyBeanMethods = false)public class NacosServiceDeregisterAutoConfig {@Beanpublic NacosServiceDeregisterEndpoint deregisterEndpoint(NacosAutoServiceRegistration registration) {return new NacosServiceDeregisterEndpoint(registration);}}
定义K8S的preStop钩子
lifecycle:preStop:httpGet:path: /actuator/deregisterport: 8080scheme: 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
