前言
最近公司的一个新业务线集成了K8s集成部署多个项目,项目使用Nacos作为注册中心,版本是1.4.x,服务间通过Feign调用通信,最近发现在服务在重新部署的期间,会存在应用间接口调用超时的情况,此处记录下问题的排查和解决。
过程
通过追踪Nacos源码及查阅相关资料初步排查原因有如下几点
NacosAutoServiceRegistration继承了Spring Cloud中的AbstractAutoServiceRegistration类重写了deregister方法进行服务下线,由于hook机制,Spring容器停止的时候会触发该方法调用,但此处存在调用到Nacos Server端及Server端把下线消息通知到订阅者的延迟;
Nacos默认心跳超时15s后才会标记为不健康
被调用服务其实已经在停止过程中不再接收请求,调用方本地还缓存着该服务的实例地址,NacosDiscoveryClient实现了spring cloud中的DiscoveryClient类,跟踪其getInstances方法发现去服务端拉取提供方列表的定时任务默认10s才会执行一次
解决
从上面的排查中,我们最容易想到的就是缩短服务下线感知的延时,比如缩短客户端拉取提供者服务的定时任务周期,把10s改成1,2秒,这样确实有些效果,但不能完全解决问题。用户量访问较频繁的接口很容易就出现问题,要想做到平滑更新,用户无感知,需要保证以下两点
在该服务停机之前,已经从注册中心下线
下线的信息已全部同步到各订阅者
此时很容易想到是否能在服务停机之前将其从服务端下线。从上面的源码中可以看到NacosAutoServiceRegistration这个类正好能为我们所用。只要在服务停止前调用一下,等一段时间后再停止服务即可,与运维沟通了解到K8s中存在postStart 和 preStop 处理函数
可以在停止容器前做一些操作,另一方面项目里有使用到Spirngboot Actuator 做JVM监控及健康检查,就顺便增加了一个自定义端点来处理服务下线,代码如下
@Slf4j
@Component
@Endpoint(id = "shutdownGraceFul")
public class ServiceShutDownEndpoint {
@Resource
private NacosAutoServiceRegistration serviceRegistration;
@Resource
private ApplicationContext context;
/** 下线服务后关闭应用前等待的时间(秒) */
@Value("${stopService.waitTime:120}")
private int waitTime;
@WriteOperation
public Map<String, Object> shutdownGraceFul() {
log.info("开始服务下线");
serviceRegistration.stop();
log.info("完成服务下线");
log.info("等待{}s, 关闭应用", waitTime);
try {
TimeUnit.SECONDS.sleep(waitTime);
} catch (InterruptedException e) {
log.info("interrupted!", e);
}
log.info("Closing application...");
SpringApplication.exit(context);
Map<String, Object> result = new HashMap<>();
result.put("shutdownGraceFul", true);
return result;
}
}
k8s中yml配置如下
等待的时间可根据实际情况调整,不能太短,得保证下掉应用后Nacos服务端能把节点下线消息完全同步到所有调用方
总结
看了下Nacos2.x版本以后使用Grpc和Rsocket替代了之前大部分的Http请求和基于Udp协议的请求,极大的缩短了延迟,但试了下还是存在此问题,小伙伴可针对自己项目使用的版本进行测试。
评论区