Nacos服务注册与发现中心之服务注册
本文只介绍nacos的服务注册与发现中心功能中的服务提供者,在启动时与nacos服务器的交互,使用的是SpringCloud(Hoxton.SR12)集成Nacos1.0,其他集成方式不作介绍。
入口类及具体方法
服务注册的入口类为:AbstractAutoServiceRegistration.java
此类的类图为:
- 此类实现了ApplicationListener接口,用于监听WebServerInitializedEvent事件,该事件是web容器在准备完毕后发送的事件,关于此事件的介绍为:
* Event to be published when the {@link WebServer} is ready. Useful for obtaining the
* local port of a running server.
大概意思为:该事件是在web容器准备好(此处理解为初始化完毕,具体怎么算初始化完毕还没有找到相关定义,但是起码是已经完成了端口的监听)发布,用于获取运行中的服务端的本地端口
- AbstractAutoServiceRegistration类监听WebServerInitializedEvent事件的代码为:
public void onApplicationEvent(WebServerInitializedEvent event) {
bind(event);
}
- AbstractAutoServiceRegistration.bind(WebServerInitializedEvent event) 方法为:
public void bind(WebServerInitializedEvent event) {
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableWebServerApplicationContext) {
if ("management".equals(((ConfigurableWebServerApplicationContext) context)
.getServerNamespace())) {
return;
}
}
this.port.compareAndSet(0, event.getWebServer().getPort());
this.start();
}
可以看出这个方法主要是设置了port变量的值为web容器监听的端口的值,而具体的逻辑在start()方法上。
- AbstractAutoServiceRegistration.start() 方法:
public void start() {
... //去掉不关心的代码
if (!this.running.get()) {
this.context.publishEvent(
new InstancePreRegisteredEvent(this, getRegistration()));
register();
if (shouldRegisterManagement()) {
registerManagement();
}
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, getConfiguration()));
this.running.compareAndSet(false, true);
}
}
此处我们看到最终会调用register()方法进行服务的注册,而接下来就要说到Nacos提供的NacosAutoServiceRegistration类。
NacosAutoServiceRegistration
先来看看NacosAutoServiceRegistration的类图
可以看到,此类是继承了AbstractAutoServiceRegistration类。翻看代码发现他重写了AbstractAutoServiceRegistration的register()方法:
@Override
protected void register() {
if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
log.debug("Registration disabled.");
return;
}
if (this.registration.getPort() < 0) {
this.registration.setPort(getPort().get());
}
super.register();
}
可以看到,在这里做了一些开关性的判断,设置了使用中的端口号,最终会调用AbstractAutoServiceRegistration的register()方法。
- AbstractAutoServiceRegisration.register()方法:
/**
* Register the local service with the {@link ServiceRegistry}.
*/
protected void register() {
this.serviceRegistry.register(getRegistration());
}
该方法会调用serviceRegistry变量的register方法,而按照惯例这个serviceRegistry为Nacos提供的NacosServiceRegistry而getRegistration()方法返回的为NacosServiceRegistration。
- 我们来看NacosServiceRegistry类的register(Registration registration)方法:
@Override
public void register(Registration registration) {
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
String serviceId = registration.getServiceId();
// 构建服务注册者的信息
Instance instance = getNacosInstanceFromRegistration(registration);
try {
// 此处为注册逻辑
namingService.registerInstance(serviceId, instance);
log.info("nacos registry, {} {}:{} register finished", serviceId,
instance.getIp(), instance.getPort());
}
catch (Exception e) {
log.error("nacos registry, {} register failed...{},", serviceId,
registration.toString(), e);
}
}
此处会先构造一个Instance类: Instance instance = getNacosInstanceFromRegistration(registration);
,这个类包括了需要注册的服务的各种信息,我们看看他的代码:
private Instance getNacosInstanceFromRegistration(Registration registration) {
Instance instance = new Instance();
instance.setIp(registration.getHost());
instance.setPort(registration.getPort());
instance.setWeight(nacosDiscoveryProperties.getWeight());
instance.setClusterName(nacosDiscoveryProperties.getClusterName());
instance.setMetadata(registration.getMetadata());
return instance;
}
然后我们主要看namingService.registerInstance(serviceId, instance);
这一段代码,按照惯例此处的NamingService为NacosNamingService
- 我们再看NacosNamingService.registerInstance(String serviceName, Instance instance)方法:
@Override
public void registerInstance(String serviceName, Instance instance) throws NacosException {
registerInstance(serviceName, Constants.DEFAULT_GROUP, instance);
}
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
if (instance.isEphemeral()) {
BeatInfo beatInfo = new BeatInfo();
beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
beatInfo.setIp(instance.getIp());
beatInfo.setPort(instance.getPort());
beatInfo.setCluster(instance.getClusterName());
beatInfo.setWeight(instance.getWeight());
beatInfo.setMetadata(instance.getMetadata());
beatInfo.setScheduled(false);
long instanceInterval = instance.getInstanceHeartBeatInterval();
beatInfo.setPeriod(instanceInterval == 0 ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval);
beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
}
serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}
此处可以看到通过判断:instance.isEphemeral()
,然后会进行一个心跳的操作,instance.isEphemeral()
表示的是该instance是否为临时的instance,nacos服务端根据此标记会有不同的操作,这个后续再补充,我们只要知道这个值默认为true先。
这里补充一下,当nacos服务提供者注册成功后,会有一个心跳机制来向nacos服务端表明我这个服务的存活。
- 接下来我们来看一下这段代码:
long instanceInterval = instance.getInstanceHeartBeatInterval();
,这里表示的是心跳的间隔,那么这个间隔是多少呢?我们来看instance.getInstanceHeartBeatInterval()
方法:
public long getInstanceHeartBeatInterval() {
return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_INTERVAL, Constants.DEFAULT_HEART_BEAT_INTERVAL);
}
这段代码的意思为:从配置获取信息,获取不到则取默认值,这里我们一般不进行设置,取默认值:
public static final long DEFAULT_HEART_BEAT_INTERVAL = TimeUnit.SECONDS.toMillis(5);
从此处可以看出,默认的心跳间隔时间为5秒,但是这个间隔是会受到nacos服务端的控制,这个后面补充。
- 接下来我们来看
registerInstance
方法的:beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
在这里启动心跳,来看看BeatReactor.addBeatInfo
方法:
public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
dom2Beat.put(buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort()), beatInfo);
// 主要看这一段代码
executorService.schedule(new BeatTask(beatInfo), 0, TimeUnit.MILLISECONDS);
MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}
我们主要看这行代码:executorService.schedule(new BeatTask(beatInfo), 0, TimeUnit.MILLISECONDS);
,在这里构建了一个BeatTask
类,并且使用线程池跑了个延迟0秒的逻辑(即立即执行),既然是放在线程池跑的类,那么这个类就是实现了Runnable接口的,我们来看这个类的代码:
class BeatTask implements Runnable {
BeatInfo beatInfo;
public BeatTask(BeatInfo beatInfo) {
this.beatInfo = beatInfo;
}
@Override
public void run() {
if (beatInfo.isStopped()) {
return;
}
long result = serverProxy.sendBeat(beatInfo);
long nextTime = result > 0 ? result : beatInfo.getPeriod();
executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
}
}
此处,会立即发送一次心跳,从心跳获取到的结果result
,并且result
不小于0的情况下,将result
作为下一次心跳的时间,所以这里就是上文说的nacos服务端可以影响服务注册者心跳时间的机制了我们进一步看long result = serverProxy.sendBeat(beatInfo);
这行代码,serverProxy.sendBeat(beatInfo);
的代码为:
public long sendBeat(BeatInfo beatInfo) {
try {
if (NAMING_LOGGER.isDebugEnabled()) {
NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}", namespaceId, beatInfo.toString());
}
Map<String, String> params = new HashMap<String, String>(4);
params.put("beat", JSON.toJSONString(beatInfo));
params.put(CommonParams.NAMESPACE_ID, namespaceId);
params.put(CommonParams.SERVICE_NAME, beatInfo.getServiceName());
String result = reqAPI(UtilAndComs.NACOS_URL_BASE + "/instance/beat", params, HttpMethod.PUT);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject != null) {
return jsonObject.getLong("clientBeatInterval");
}
} catch (Exception e) {
NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: " + JSON.toJSONString(beatInfo), e);
}
return 0L;
}
可以看到这里是发送了一个Http的请求,Http请求的uri为:/nacos/v1/ns/instance/beat
;接下来,我们去到nacos服务端,查看处理该请求的代码:
@CanDistro
@RequestMapping(value = "/beat", method = RequestMethod.PUT)
public JSONObject beat(HttpServletRequest request) throws Exception {
JSONObject result = new JSONObject();
result.put("clientBeatInterval", switchDomain.getClientBeatInterval());
....
return result;
}
由于本文讲解的是Nacos服务注册端的流程,关于Nacos服务端的代码暂时不做详细讲解,因此此处我们把无关代码删除,主要看的是会影响服务注册端下一次心跳时间的switchDomain.getClientBeatInterval()
,点击查看该行代码的详情,可以看到它的取值为:
private long clientBeatInterval = TimeUnit.SECONDS.toMillis(5);
可以看到返回的时间也是5秒~
- 看完心跳机制后,我们回到
NacosNamingService.registerInstance()
方法,我们来看该方法的最后一行:serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
,该行代码会进行最后的服务注册调用
来看具体代码:
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}",
namespaceId, serviceName, instance);
final Map<String, String> params = new HashMap<String, String>(8);
params.put(CommonParams.NAMESPACE_ID, namespaceId);
params.put(CommonParams.SERVICE_NAME, serviceName);
params.put(CommonParams.GROUP_NAME, groupName);
params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
params.put("ip", instance.getIp());
params.put("port", String.valueOf(instance.getPort()));
params.put("weight", String.valueOf(instance.getWeight()));
params.put("enable", String.valueOf(instance.isEnabled()));
params.put("healthy", String.valueOf(instance.isHealthy()));
params.put("ephemeral", String.valueOf(instance.isEphemeral()));
params.put("metadata", JSON.toJSONString(instance.getMetadata()));
reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);
}
可以看到这里组装了一个Map,往里面塞了服务的ip,port等信息,最终调用一个HTTP请求,向Nacos服务端进行注册
至于Nacos服务端的代码我们后续篇章补充