上篇说了Nacos服务注册与发现中心的服务注册在服务端的代码,接下来看服务提供者与Nacos服务器之间心跳机制的服务端的代码
入口URI
在介绍服务注册者注册服务的客户端代码的文章中,有讲过:服务提供者在注册服务的同时会启动一个定时任务,每5秒会发送一次心跳请求到Nacos服务端。通过客户端的源码我们可以得出,该URI为:/nacos/v1/ns/instance/beat, 请求方法为PUT。
一、InstanceController
此URI的处理类,依旧的InstanceController,我们先来看Controller层的源码:
@CanDistro
@RequestMapping(value = "/beat", method = RequestMethod.PUT)
public JSONObject beat(HttpServletRequest request) throws Exception {
// ... 省略不关心的代码
// 1
Instance instance = serviceManager.getInstance(namespaceId, serviceName, clientBeat.getCluster(), clientBeat.getIp(),
clientBeat.getPort());
if (instance == null) {
instance = new Instance();
instance.setPort(clientBeat.getPort());
instance.setIp(clientBeat.getIp());
instance.setWeight(clientBeat.getWeight());
instance.setMetadata(clientBeat.getMetadata());
instance.setClusterName(clusterName);
instance.setServiceName(serviceName);
instance.setInstanceId(instance.generateInstanceId());
instance.setEphemeral(clientBeat.isEphemeral());
serviceManager.registerInstance(namespaceId, serviceName, instance);
}
Service service = serviceManager.getService(namespaceId, serviceName);
if (service == null) {
throw new NacosException(NacosException.SERVER_ERROR, "service not found: " + serviceName + "@" + namespaceId);
}
// 2
service.processClientBeat(clientBeat);
return result;
}
我们将代码关键处标上1,2标注。
我们先看1处的代码:通过NameSpaceId,serviceName,实例的ip和端口号,尝试获取实例在服务端的信息,如果获取不到,会先进行一次服务的注册,至于服务注册的代码,我们在上篇文章已经进行过讲解了。
在标注2处,会进行真正的心跳处理。
service.processClientBeat(clientBeat);
我们再来看service.processClientBeat(clientBeat);
的代码
public void processClientBeat(final RsInfo rsInfo) {
ClientBeatProcessor clientBeatProcessor = new ClientBeatProcessor();
clientBeatProcessor.setService(this);
clientBeatProcessor.setRsInfo(rsInfo);
HealthCheckReactor.scheduleNow(clientBeatProcessor);
}
可以看到,这里创建了一个异步任务ClientBeatProcessor
,我们再来看ClientBeatProcessor
的run方法:
@Override
public void run() {
Service service = this.service;
if (Loggers.EVT_LOG.isDebugEnabled()) {
Loggers.EVT_LOG.debug("[CLIENT-BEAT] processing beat: {}", rsInfo.toString());
}
String ip = rsInfo.getIp();
String clusterName = rsInfo.getCluster();
int port = rsInfo.getPort();
Cluster cluster = service.getClusterMap().get(clusterName);
List<Instance> instances = cluster.allIPs(true);
for (Instance instance : instances) {
if (instance.getIp().equals(ip) && instance.getPort() == port) {
if (Loggers.EVT_LOG.isDebugEnabled()) {
Loggers.EVT_LOG.debug("[CLIENT-BEAT] refresh beat: {}", rsInfo.toString());
}
instance.setLastBeat(System.currentTimeMillis());
if (!instance.isMarked()) {
if (!instance.isHealthy()) {
instance.setHealthy(true);
Loggers.EVT_LOG.info("service: {} {POS} {IP-ENABLED} valid: {}:{}@{}, region: {}, msg: client beat ok",
cluster.getService().getName(), ip, port, cluster.getName(), UtilsAndCommons.LOCALHOST_SITE);
getPushService().serviceChanged(service.getNamespaceId(), this.service.getName());
}
}
}
}
}
此处可以看到,任务的主要逻辑是先获取到对应的实例列表,再遍历去获取ip和端口号相同的实例,然后更新实例的最后一次心跳时间和更新实例的健康状态,最后会进行一次服务状态变更的通过:这里是通过UDP的方式通知服务消费者,具体逻辑会在介绍服务消费者时再细说。
END
可以看到服务端心跳的代码还是相对简单清晰的,下一篇将会开始介绍服务消费者相关的逻辑以及源码。