Nacos客户端namespace初始化及注册流程
-
- NamingService
- NacosNamingService
-
- ValidatorUtils.checkInitParam(properties)
- InitUtils.initNamespaceForNaming(properties)
- InitUtils.initSerialization()
- initServerAddr(properties)
- InitUtils.initWebRootContext()
- initCacheDir()
- initLogName(properties)
- new EventDispatcher()
- new NamingProxy(this.namespace, this.endpoint, this.serverList, properties)
- new BeatReactor(this.serverProxy, initClientBeatThreadCount(properties))
- new HostReactor(this.eventDispatcher, this.serverProxy, beatReactor, this.cacheDir, isLoadCacheAtStart(properties), initPollingThreadCount(properties))
- registerInstance
很多人在学习开源的时候,无从下手,代码那么多,从哪个地方开始呢?我们学习nacos,首先去到nocas的github源码的地方链接: https://github.com/alibaba/nacos下载源码到我们的idea,打开example项目,
进入APP,可以看到如下代码:
public static void main(String[] args) throws NacosException {
Properties properties = new Properties();
properties.setProperty("serverAddr", "21.34.53.5:8848,21.34.53.6:8848");
properties.setProperty("namespace", "quickStart");
NamingService naming = NamingFactory.createNamingService(properties);
naming.registerInstance("nacos.test.3", "11.11.11.11", 8888, "TEST1");
naming.registerInstance("nacos.test.3", "2.2.2.2", 9999, "DEFAULT");
System.out.println(naming.getAllInstances("nacos.test.3"));
}
这里我们可以看到,这里构建了一个NamingService实例,同时设置了我们的nacos服务端的地址和端口,设置namespace。
我们进入createNamingService方法
NamingService
/**
* Create a new naming service.
*
* @param properties naming service properties
* @return new naming service
* @throws NacosException nacos exception
*/
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 e) {
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
}
}
这里通过反射创建了一个NamingService实例,实际的实现类是在api项目里面的NacosNamingService,随后我们进入NacosNamingService看看
NacosNamingService
1.先看里面的属性
namespace 名字空间
endpoint 服务管理服务端地址管理服务器地址,获取服务管理服务端地址(当 nacos server 集群需要扩缩容时,客户端需要有一种能力能够及时感知到集群发生变化,及时感知到集群的变化是通过 endpoint 来实现的。也即客户端会定时的向endpoint发送请求来更新客户端内存中的集群列表。)
serverList 服务管理服务端地址,可直接配置,或从endpoint获取
cacheDir 调用服务信息本地文件缓存地址
logName 暂未使用
HostReactor 客户端关心的服务的实例信息,推拉模式的更新,failover服务实例信息读写管理
BeatReactor 本地实例信息心跳
EventDispatcher 服务信息变更监听回调处理
NamingProxy 服务管理服务端地址列表更新管理,接口调用负载均衡,失败重试
/**
* Each Naming service should have different namespace.
* 名字空间
*/
private String namespace;
/**
* 当 nacos server 集群需要扩缩容时,客户端需要有一种能力能够及时感知到集群发生变化。
* 及时感知到集群的变化是通过 endpoint 来实现的。也即客户端会定时的向 endpoint 发送请求来更新客户端内存中的集群列表。
* 服务管理服务端地址管理服务器地址,获取服务管理服务端地址
*/
private String endpoint;
/**
* 服务管理服务端地址管理服务器地址,获取服务管理服务端地址
*/
private String serverList;
/**
* 服务管理服务端地址管理服务器地址,获取服务管理服务端地址
*/
private String cacheDir;
private String logName;
/**
* 客户端关心的服务的实例信息,推拉模式的更新,failover服务实例信息读写管理
*/
private HostReactor hostReactor;
/**
* 本地实例信息心跳
*/
private BeatReactor beatReactor;
/**
* 服务信息变更监听回调处理
*/
private EventDispatcher eventDispatcher;
/**
* 服务管理服务端地址列表更新管理,接口调用负载均衡,失败重试
*/
private NamingProxy serverProxy;
了解了相关字段的意思我们来看看构造方法
public NacosNamingService(Properties properties) throws NacosException {
init(properties);
}
这里其实就是执行init初始化方法
private void init(Properties properties) throws NacosException {
ValidatorUtils.checkInitParam(properties); //检查contextPath格式 可为空
this.namespace = InitUtils.initNamespaceForNaming(properties); //初始化命名空间
//子类实现类中的静态代码串中已经向Jackson进行了注册,但是由于classloader的原因,只有当 该子类被使用的时候,才会加载该类。
// 这可能会导致Jackson先进性反序列化,再注册子类,从而导致 反序列化失败。
//所以这里将NoneSelector、ExpressionSelector这两个类进行注册或者销毁
InitUtils.initSerialization();
//这里进行nacos服务端地址初始化
//这里面会涉及到是否启用endpoint
initServerAddr(properties);
//如果应用由EDAS部署,则支持阿里云的web上下文
InitUtils.initWebRootContext();
//这里初始化本地缓存的路径及存放的registerInstance的内容
initCacheDir();
//初始化LogName,未设置用naming.log
initLogName(properties);
/**
*初始化ExecutorService线程池,创建名字为com.alibaba.nacos.naming.client.listener的daemon线程Notifier
* EventDispatcher中有一个LinkedBlockingQueue队列,放的是ServiceInfo
* EventDispatcher中有ConcurrentMap<String, List<EventListener>>放入的是EventListener
*Notifier中run方法解析
* 先去队列中弹出队顶元素(poll方法)
* 如果为空进行下一次循环
* 如果不为空则去ConcurrentMap取listeners
* 去除listener去监听NamingEvent
*
*/
this.eventDispatcher = new EventDispatcher();
/**
* 初始化服务代理,用户名密码服务地址及initRefreshTask任务的线程池,创建com.alibaba.nacos.client.naming.updater名字的daemon线程
*/
this.serverProxy = new NamingProxy(this.namespace, this.endpoint, this.serverList, properties);
/**
* initClientBeatThreadCount(properties):Runtime.getRuntime().availableProcessors()返回到Java虚拟机的可用的处理器数量
* 创建一个此案城池com.alibaba.nacos.naming.beat.sender的daemon线程
*/
this.beatReactor = new BeatReactor(this.serverProxy, initClientBeatThreadCount(properties));
/**
* 同上
*/
this.hostReactor = new HostReactor(this.eventDispatcher, this.serverProxy, beatReactor, this.cacheDir,
isLoadCacheAtStart(properties), initPollingThreadCount(properties));
}
innit方法里面在初始化各个模块,具体的步骤是
1.检查contextPath格式
2.将NoneSelector、ExpressionSelector这两个类进行注册或者销毁
3.nacos服务端地址初始化
4.如果应用由EDAS部署,则支持阿里云的web上下文
5.这里初始化本地缓存
6.初始化LogName
7.初始化服务信息变更监听回调处理
8.初始化服务管理服务端地址列表更新管理,接口调用负载均衡,失败重试
9.初始化本地实例信息心跳
10.初始化客户端关心的服务的实例信息
说明:7-10都会初始化线程池,创建daemon线程
总的来说,init方法为我们初始化各种本地信息,下面来看具体初始化方法
ValidatorUtils.checkInitParam(properties)
public static final String CONTEXT_PATH = "contextPath";
private static final Pattern CONTEXT_PATH_MATCH = Pattern.compile("(\\/)\\1+");
public static void checkInitParam(Properties properties) throws NacosException {
checkContextPath(properties.getProperty(PropertyKeyConst.CONTEXT_PATH));
}
/**
* Check context path.
*
* @param contextPath context path
*/
public static void checkContextPath(String contextPath) {
if (contextPath == null) {
return;
}
Matcher matcher = CONTEXT_PATH_MATCH.matcher(contextPath);
if (matcher.find()) {
throw new IllegalArgumentException("Illegal url path expression");
}
}
这里的代码比较简单,只是检查了一下contextPath
InitUtils.initNamespaceForNaming(properties)
/**
* Add a difference to the name naming. This method simply initializes the namespace for Naming. Config
* initialization is not the same, so it cannot be reused directly.
*
* 为名称命名添加差异。此方法简单地初始化命名空间以进行命名。配置初始化不一样,所以不能直接重用。
*
* @param properties properties
* @return namespace
*/
public static String initNamespaceForNaming(Properties properties) {
String tmpNamespace = null;
String isUseCloudNamespaceParsing = properties.getProperty(PropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,
System.getProperty(SystemPropertyKeyConst.IS_USE_CLOUD_NAMESPACE_PARSING,
String.valueOf(Constants.DEFAULT_USE_CLOUD_NAMESPACE_PARSING)));//默认是true
System.out.println("isUseCloudNamespaceParsing:" + isUseCloudNamespaceParsing);
if (Boolean.parseBoolean(isUseCloudNamespaceParsing)) {
tmpNamespace = TenantUtil.getUserTenantForAns();//这里是ans,据说是注册中心,未设置tenant.id和ans.namespace 返回为空
/**
* 这里检查是否为空,如果不为空发返回tmpNamespace,如果为空执行Callable.call()方法,
* call()方法里面去取ans.namespace属性,返回namespace
*/
tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
@Override
public String call() {
String namespace = System.getProperty(SystemPropertyKeyConst.ANS_NAMESPACE);
LogUtils.NAMING_LOGGER.info("initializer namespace from System Property :" + namespace);
return namespace;
}
});
/**
* 这里检查是否为空,如果不为空发返回tmpNamespace,如果为空执行Callable.call()方法,
* call()方法里面去取ALIBABA_ALIWARE_NAMESPACE环境变量
*/
tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
@Override
public String call() {
String namespace = System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_NAMESPACE);
LogUtils.NAMING_LOGGER.info("initializer namespace from System Environment :" + namespace);
return namespace;
}
});
}
/**
* 这里检查是否为空,如果不为空发返回tmpNamespace,如果为空执行Callable.call()方法,
* call()方法里面去取NAMESPACE属性
*/
tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
@Override
public String call() {
String namespace = System.getProperty(PropertyKeyConst.NAMESPACE);
LogUtils.NAMING_LOGGER