本文基于nacos-2.0.3版本
注册服务时,nacos服务端调用InstanceController.register()方法完成注册。
InstanceController.register()再调用InstanceOperatorClientImpl.registerInstance()注册服务,下面是registerInstance()方法代码:
public void registerInstance(String namespaceId, String serviceName, Instance instance) {
//注册服务是否是临时服务
boolean ephemeral = instance.isEphemeral();
//将注册服务的IP地址与ephemeral组合起来
String clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), ephemeral);
createIpPortClientIfAbsent(clientId);//创建定时任务,检测服务是否正常
Service service = getService(namespaceId, serviceName, ephemeral);
//向nacos服务端写入服务数据
clientOperationService.registerInstance(service, instance, clientId);
}
下面分别介绍nacos如何检测服务以及如何写入服务数据。
1、写入服务数据
根据服务是否临时服务,nacos提供了两种注册服务数据的方法:PersistentClientOperationServiceImpl和EphemeralClientOperationServiceImpl。
PersistentClientOperationServiceImpl用于注册非临时服务,使用JRaft协议,将数据写入raft集群,其原理和之前文章介绍的raft算法日志复制原理相同,本文不再介绍。
下面分析EphemeralClientOperationServiceImpl的原理。
注册临时服务调用EphemeralClientOperationServiceImpl的registerInstance()方法:
public void registerInstance(Service service, Instance instance, String clientId) {
//Service记录注册服务的group,服务名,是否临时等
//getSingleton()方法将服务数据写入一个Map中,通过nacos提供的运维页面查询服务列表时,便是从该Map获取的数据
Service singleton = ServiceManager.getInstance().getSingleton(service);
//Client记录服务的地址信息
Client client = clientManager.getClient(clientId);
//检查服务是否是临时服务,如果是,则本方法直接返回
if (!clientIsLegal(client, clientId)) {
return;
}
InstancePublishInfo instanceInfo = getPublishInfo(instance);
//发布ClientChangedEvent事件
client.addServiceInstance(singleton, instanceInfo);
client.setLastUpdatedTime();
//发布ClientRegisterServiceEvent事件
NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));
//发布InstanceMetadataEvent事件
NotifyCenter
.publishEvent(new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId(), false));
}
registerInstance()方法的最后,发布了三个事件,对应的监听器的作用都是将注册服务写入内存的Map中,供查询使用。
2、服务健康检测
注册服务时,nacos启动定时任务HealthCheckTaskInterceptWrapper来检测服务是否健康,每5s执行一次检测。
//task的实现类是ClientBeatCheckTaskV2
//ClientBeatCheckTaskV2记录了注册服务的地址、是否临时服务等
public HealthCheckTaskInterceptWrapper(NacosHealthCheckTask task) {
this.task = task;
this.interceptorChain = HealthCheckInterceptorChain.getInstance();
}
//定时任务执行run方法
@Override
public void run() {
try {
interceptorChain.doInterceptor(task);//默认有两个拦截器
} catch (Exception e) {
Loggers.SRV_LOG.info("Interceptor health check task {} failed", task.getTaskId(), e);
}
}
定时任务对需要注册的服务执行两个拦截器:HealthCheckResponsibleInterceptor和HealthCheckEnableInterceptor。这两个拦截器用于检查一些开关是否打开。
在开关都打开的情况下,接着调用task.passIntercept()方法,也就是ClientBeatCheckTaskV2.passIntercept()方法。下面看一下该方法的源码。
public void passIntercept() {
doHealthCheck();
}
public void doHealthCheck() {
try {
//查询出所有的注册服务
Collection<Service> services = client.getAllPublishedService();
for (Service each : services) {
//HealthCheckInstancePublishInfo记录当前服务是否健康
HealthCheckInstancePublishInfo instance = (HealthCheckInstancePublishInfo) client
.getInstancePublishInfo(each);
//调用拦截器
interceptorChain.doInterceptor(new InstanceBeatCheckTask(client, each, instance));
}
} catch (Exception e) {
Loggers.SRV_LOG.warn("Exception while processing client beat time out.", e);
}
}
doHealthCheck()方法里面最后也是调用了拦截器,nacos提供了三个拦截器:ServiceEnableBeatCheckInterceptor、InstanceEnableBeatCheckInterceptor和InstanceBeatCheckResponsibleInterceptor。这些拦截器主要检查服务的元数据。拦截器检查通过后,调用InstanceBeatCheckTask.passIntercept()方法:
public void passIntercept() {
for (InstanceBeatChecker each : CHECKERS) {
each.doCheck(client, service, instancePublishInfo);
}
}
上面代码里面调用了两个检查器:UnhealthyInstanceChecker和ExpiredInstanceChecker。
UnhealthyInstanceChecker
UnhealthyInstanceChecker检查是否在规定的时间内收到心跳,如果心跳超时,则直接将服务设置为不健康。
public void doCheck(Client client, Service service, HealthCheckInstancePublishInfo instance) {
//isUnhealthy()用于检查心跳是否超时
if (instance.isHealthy() && isUnhealthy(service, instance)) {
changeHealthyStatus(client, service, instance);//如果超时,直接设置服务不健康
}
}
private boolean isUnhealthy(Service service, HealthCheckInstancePublishInfo instance) {
//获取心跳超时时间,默认是15000ms
long beatTimeout = getTimeout(service, instance);
return System.currentTimeMillis() - instance.getLastHeartBeatTime() > beatTimeout;
}
private long getTimeout(Service service, InstancePublishInfo instance) {
Optional<Object> timeout = getTimeoutFromMetadata(service, instance);
if (!timeout.isPresent()) {
timeout = Optional.ofNullable(instance.getExtendDatum().get(PreservedMetadataKeys.HEART_BEAT_TIMEOUT));
}
return timeout.map(ConvertUtils::toLong).orElse(Constants.DEFAULT_HEART_BEAT_TIMEOUT);
}
private void changeHealthyStatus(Client client, Service service, HealthCheckInstancePublishInfo instance) {
instance.setHealthy(false);
//代码删减
NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service));
NotifyCenter.publishEvent(new ClientEvent.ClientChangedEvent(client));
}
ExpiredInstanceChecker
ExpiredInstanceChecker用于检查服务是否已经下线,如果下线,则将服务从服务列表中删除。
经过上面两个检测器都正常的服务,可以继续对外提供服务。