java注册实现原理_Spring Cloud Alibaba Nacos 服务注册--实现原理与源码解析

好久不见,最近工作一直被一堆事所捆绑,也没有大块的时间去看技术相关的东西,以至于部分伙伴的留言都没有及时的回复,先和大家分享一句话突然想起来的一句吧,不变的可能是这个世界,不断变化的可能是我们自己。

微服务架构在近几年变的越来越流行,这可能会成为我们的一个必备技能(本人的建议是如果现有的系统没有遇到什么运维、业务瓶颈的话不要为了单纯的使用微服务技术而去进行改造)。

在微服务的早期随着Spring Cloud的支撑,好多都使用了Spring默认的Netflix全家桶,但是现在Netflix也不再进行维护且社区活跃度也比较低,所以在注册中心部分很多团队都会在Nacos、Zookeeper、Kubernetes中进行选择,本文不对其中的优缺点进行比较和分析,直接来干货Spring Cloud Alibaba Nacos 服务如何注册。

一、服务注册初始化阶段

其实对于服务的注册基本上所有开源项目的实现思路基本一致,无非就是服务的注册、心跳维持,看下图直接找到服务注册的源码,一般这种代码我都是从AutoConfig开始看了,大家可以先大体看一下,这几个AutoConfig功能其实就是初始化红框内的其他的的非AutoConfig类。

b9d96408951b

源码结构图

项目启动以后首先会通过NacosDiscoveryAutoConfiguration根据配置文件初始化NacosDiscoveryProperties,NacosDiscoveryProperties为配置文件中配置的服务注册相关参数(如serverAddr、group等),然后紧接着初始化NacosServiceDiscovery,该类主要是通过的NacosDiscoveryProperties的namingServiceInstance属性实现获取注册中心的相关信息。最后初始化NacosDiscoveryClient对NacosServiceDiscovery进行封装。

在服务注册方面,会通过NacosServiceRegistryAutoConfiguration依次初始化NacosServiceRegistry、NacosRegistration、NacosAutoServiceRegistration,在初始化NacosServiceRegistry时会对之前的NacosDiscoveryProperties的namingServiceInstance属性进行实例化,其中服务的注册的核心代码(com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register)也在该类中。在初始化AbstractAutoServiceRegistration时候,大家会发现它的父类实现了ApplicationListener,当发布事件时候会执行onApplicationEvent中的代码(可以参考我伙伴的文章),这也是服务注册的入口。

二、服务注册阶段

在AbstractAutoServiceRegistration的onApplicationEvent方法中执行了start方法,start方法中会执行register方法,最终调用的方法就是之前说到的com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register,完成对服务进行注册。

public void register(Registration registration) {

if (StringUtils.isEmpty(registration.getServiceId())) {

log.warn("No service to register for nacos client...");

} else {

String serviceId = registration.getServiceId();

String group = this.nacosDiscoveryProperties.getGroup();

Instance instance = this.getNacosInstanceFromRegistration(registration);

try {

this.namingService.registerInstance(serviceId, group, instance);

log.info("nacos registry, {} {} {}:{} register finished", new Object[]{group, serviceId, instance.getIp(), instance.getPort()});

} catch (Exception var6) {

log.error("nacos registry, {} register failed...{},", new Object[]{serviceId, registration.toString(), var6});

ReflectionUtils.rethrowRuntimeException(var6);

}

}

}

在服务注册的代码中,最核心的是this.namingService.registerInstance(serviceId, group, instance);这一行,最终实现注册的方法是com.alibaba.nacos.client.naming.net.NamingProxy#registerService,在这个方法中通过调用nacas的api包中的reqAPI来与注册中心的服务端进行交互从而完成注册。

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {

LogUtils.NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", new Object[]{this.namespaceId, serviceName, instance});

Map params = new HashMap(9);

params.put("namespaceId", this.namespaceId);

params.put("serviceName", serviceName);

params.put("groupName", groupName);

params.put("clusterName", 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()));

this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, "POST");

}

public String callServer(String api, Map params, String body, String curServer, String method) throws NacosException {

long start = System.currentTimeMillis();

long end = 0L;

this.injectSecurityInfo(params);

List headers = this.builderHeaders();

String url;

if (!curServer.startsWith("https://") && !curServer.startsWith("http://")) {

if (!curServer.contains(":")) {

curServer = curServer + ":" + this.serverPort;

}

url = HttpClient.getPrefix() + curServer + api;

} else {

url = curServer + api;

}

HttpResult result = HttpClient.request(url, headers, params, body, "UTF-8", method);

end = System.currentTimeMillis();

MetricsMonitor.getNamingRequestMonitor(method, url, String.valueOf(result.code)).observe((double)(end - start));

if (200 == result.code) {

return result.content;

} else if (304 == result.code) {

return "";

} else {

throw new NacosException(result.code, result.content);

}

}

在reqAPI的最后部分的代码com.alibaba.nacos.client.naming.net.NamingProxy#callServer(java.lang.String, java.util.Map, java.lang.String, java.lang.String, java.lang.String)中,与服务端进行交互的过程中,会将自身的信息发送给服务端,服务端响应OK后完成注册,请求服务端的信息如下。

地址:http://192.168.***.***:8848/nacos/v1/ns/instance

内容:{app=nacos-provider, metadata={"preserved.register.source":"SPRING_CLOUD"}, ip=192.168.***.***, weight=1.0, ephemeral=true, serviceName=DEFAULT_GROUP@@nacos-provider, encoding=UTF-8, groupName=DEFAULT_GROUP, namespaceId=public, port=8081, enable=true, healthy=true, clusterName=DEFAULT}

三、心跳的维持

服务注册到注册中心服务端后,服务端与客户端会一直保持一个心跳,来对服务的可用性进行维护,在之前的部分我们有说过NacosServiceRegistry类中会对namingService进行实例化,同样也是利用nacos的api包通过反射来进行初始化。

public static NamingService createNamingService(Properties properties) throws NacosException {

try {

Class> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");

Constructor constructor = driverImplClass.getConstructor(Properties.class);

NamingService vendorImpl = (NamingService)constructor.newInstance(properties);

return vendorImpl;

} catch (Throwable var4) {

throw new NacosException(-400, var4);

}

}

在对NacosNamingService进行初始化的过程中,我们会在构造函数中发现他还会初始化com.alibaba.nacos.client.naming.NacosNamingService#beatReactor,而BeatReactor中存在一个线程池,我们在服务注册之前(com.alibaba.nacos.client.naming.NacosNamingService#registerInstance(java.lang.String, java.lang.String, com.alibaba.nacos.api.naming.pojo.Instance)方法)就会向这个线程池中发布Task。

public BeatReactor(NamingProxy serverProxy, int threadCount) {

this.lightBeatEnabled = false;

this.dom2Beat = new ConcurrentHashMap();

this.serverProxy = serverProxy;

this.executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {

public Thread newThread(Runnable r) {

Thread thread = new Thread(r);

thread.setDaemon(true);

thread.setName("com.alibaba.nacos.naming.beat.sender");

return thread;

}

});

}

public void addBeatInfo(String serviceName, BeatInfo beatInfo) {

LogUtils.NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);

String key = this.buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());

BeatInfo existBeat = null;

if ((existBeat = (BeatInfo)this.dom2Beat.remove(key)) != null) {

existBeat.setStopped(true);

}

this.dom2Beat.put(key, beatInfo);

this.executorService.schedule(new BeatReactor.BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);

MetricsMonitor.getDom2BeatSizeMonitor().set((double)this.dom2Beat.size());

}

下面我们在看一下这个定时任务中的内容,其实与服务的注册原理相似,都是与服务端进行一个交互,如果服务端响应该服务不可用,则重新进行注册,如果依旧可用(或已经重新注册)则继续将新的Task(BeatTask)放入到线程池中,从而不断的与服务端维持心跳。

地址:http://192.168.***.***:8848/nacos/v1/ns/instance/beat

内容:{app=nacos-provider, namespaceId=public, port=8081, clusterName=DEFAULT, ip=192.168.***.***, serviceName=DEFAULT_GROUP@@nacos-provider, encoding=UTF-8}

class BeatTask implements Runnable {

BeatInfo beatInfo;

public BeatTask(BeatInfo beatInfo) {

this.beatInfo = beatInfo;

}

public void run() {

if (!this.beatInfo.isStopped()) {

long nextTime = this.beatInfo.getPeriod();

try {

JSONObject result = BeatReactor.this.serverProxy.sendBeat(this.beatInfo, BeatReactor.this.lightBeatEnabled);

long interval = (long)result.getIntValue("clientBeatInterval");

boolean lightBeatEnabled = false;

if (result.containsKey("lightBeatEnabled")) {

lightBeatEnabled = result.getBooleanValue("lightBeatEnabled");

}

BeatReactor.this.lightBeatEnabled = lightBeatEnabled;

if (interval > 0L) {

nextTime = interval;

}

int code = 10200;

if (result.containsKey("code")) {

code = result.getIntValue("code");

}

if (code == 20404) {

Instance instance = new Instance();

instance.setPort(this.beatInfo.getPort());

instance.setIp(this.beatInfo.getIp());

instance.setWeight(this.beatInfo.getWeight());

instance.setMetadata(this.beatInfo.getMetadata());

instance.setClusterName(this.beatInfo.getCluster());

instance.setServiceName(this.beatInfo.getServiceName());

instance.setInstanceId(instance.getInstanceId());

instance.setEphemeral(true);

try {

BeatReactor.this.serverProxy.registerService(this.beatInfo.getServiceName(), NamingUtils.getGroupName(this.beatInfo.getServiceName()), instance);

} catch (Exception var10) {

}

}

} catch (NacosException var11) {

LogUtils.NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}", new Object[]{JSON.toJSONString(this.beatInfo), var11.getErrCode(), var11.getErrMsg()});

}

BeatReactor.this.executorService.schedule(BeatReactor.this.new BeatTask(this.beatInfo), nextTime, TimeUnit.MILLISECONDS);

}

}

}

小结

上文介绍了Spring Cloud Alibaba Nacos 服务注册的实现原理与源码解析,相信大家应该大体了解了服务是如何注册到Nacos中,但是可能大家接下来还有疑惑,我们在服务调用的时候,是从哪里获取的所要调用的服务可用列表,以及这个列表是如何与服务端共同维护的,请听下回分解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值