03、SpringCloud Alibaba - Nacos-NacosNamingService初始化流程
1、 NacosServiceManager的创建;
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
public class NacosServiceAutoConfiguration {
@Bean
public NacosServiceManager nacosServiceManager() {
return new NacosServiceManager();
}
}
2、 NacosWatch的创建;
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureBefore({
SimpleDiscoveryClientAutoConfiguration.class,
CommonsClientAutoConfiguration.class })
@AutoConfigureAfter(NacosDiscoveryAutoConfiguration.class)
public class NacosDiscoveryClientConfiguration {
@Bean
public DiscoveryClient nacosDiscoveryClient(
NacosServiceDiscovery nacosServiceDiscovery) {
return new NacosDiscoveryClient(nacosServiceDiscovery);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.watch.enabled",
matchIfMissing = true)
public NacosWatch nacosWatch(NacosServiceManager nacosServiceManager,
NacosDiscoveryProperties nacosDiscoveryProperties,
ObjectProvider<ThreadPoolTaskScheduler> taskExecutorObjectProvider) {
return new NacosWatch(nacosServiceManager, nacosDiscoveryProperties,
taskExecutorObjectProvider);
}
}
3、 在NacosWatch的start方法中会通过nacosServiceManager创建NacosNamingService;
NamingService namingService = nacosServiceManager
.getNamingService(properties.getNacosProperties());
public NamingService getNamingService(Properties properties) {
if (Objects.isNull(this.namingService)) {
//构建 NamingService
buildNamingService(properties);
}
return namingService;
}
4、 buildNamingService();
private NamingService buildNamingService(Properties properties) {
if (Objects.isNull(namingService)) {
synchronized (NacosServiceManager.class) {
if (Objects.isNull(namingService)) {
namingService = createNewNamingService(properties);
}
}
}
return namingService;
}
5、 createNewNamingService();
private NamingService createNewNamingService(Properties properties) {
try {
return createNamingService(properties);
}
catch (NacosException e) {
throw new RuntimeException(e);
}
}
public static NamingService createNamingService(Properties properties) throws NacosException {
return NamingFactory.createNamingService(properties);
}
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);
}
}
可以看出,这里是通过反射的方式调用 com.alibaba.nacos.client.naming.NacosNamingService 的构造方法
5、 NacosNamingService的构造方法;
public NacosNamingService(Properties properties) throws NacosException {
init(properties);
}
6、 init();
private void init(Properties properties) throws NacosException {
ValidatorUtils.checkInitParam(properties);
// 初始化命名空间
this.namespace = InitUtils.initNamespaceForNaming(properties);
InitUtils.initSerialization();
// 初始化Nacos注册中心服务地址
initServerAddr(properties);
// 初始化web上下文
InitUtils.initWebRootContext(properties);
// 初始化缓存目录
initCacheDir();
// 初始化日志文件
initLogName(properties);
//服务端的代理,用于客户端与服务端之间的通信
this.serverProxy = new NamingProxy(this.namespace, this.endpoint, this.serverList, properties);
//用于客户端与服务器之间的心跳通信
this.beatReactor = new BeatReactor(this.serverProxy, initClientBeatThreadCount(properties));
//用于客户端服务的订阅,从服务端更新服务信息
this.hostReactor = new HostReactor(this.serverProxy, beatReactor, this.cacheDir, isLoadCacheAtStart(properties),
isPushEmptyProtect(properties), initPollingThreadCount(properties));
}
7、 initNamespaceForNaming();
public static String initNamespaceForNaming(Properties properties) {
String tmpNamespace = null;
//获取环境变量 isUseCloudNamespaceParsing 或 nacos.use.cloud.namespace.parsing 或 默认值true
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)));
if (Boolean.parseBoolean(isUseCloudNamespaceParsing)) {
tmpNamespace = TenantUtil.getUserTenantForAns();
tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
@Override
public String call() {
//获取环境变量 ans.namespace
String namespace = System.getProperty(SystemPropertyKeyConst.ANS_NAMESPACE);
LogUtils.NAMING_LOGGER.info("initializer namespace from System Property :" + namespace);
return namespace;
}
});
tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
@Override
public String call() {
//获取环境变量 ALIBABA_ALIWARE_NAMESPACE
String namespace = System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_NAMESPACE);
LogUtils.NAMING_LOGGER.info("initializer namespace from System Environment :" + namespace);
return namespace;
}
});
}
tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
@Override
public String call() {
//获取环境变量 namespace
String namespace = System.getProperty(PropertyKeyConst.NAMESPACE);
LogUtils.NAMING_LOGGER.info("initializer namespace from System Property :" + namespace);
return namespace;
}
});
if (StringUtils.isEmpty(tmpNamespace) && properties != null) {
tmpNamespace = properties.getProperty(PropertyKeyConst.NAMESPACE);
}
tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
@Override
public String call() {
//获取默认值 public
return UtilAndComs.DEFAULT_NAMESPACE_ID;
}
});
return tmpNamespace;
}
8、 initServerAddr();
private void initServerAddr(Properties properties) {
serverList = properties.getProperty(PropertyKeyConst.SERVER_ADDR);
endpoint = InitUtils.initEndpoint(properties);
//endpoint存在时,serverList 设置为空
if (StringUtils.isNotEmpty(endpoint)) {
serverList = "";
}
}
public static String initEndpoint(final Properties properties) {
if (properties == null) {
return "";
}
// Whether to enable domain name resolution rules
//获取环境变量 isUseEndpointParsingRule 或 nacos.use.endpoint.parsing.rule 或 默认值true
String isUseEndpointRuleParsing = properties.getProperty(PropertyKeyConst.IS_USE_ENDPOINT_PARSING_RULE,
System.getProperty(SystemPropertyKeyConst.IS_USE_ENDPOINT_PARSING_RULE,
String.valueOf(ParamUtil.USE_ENDPOINT_PARSING_RULE_DEFAULT_VALUE)));
boolean isUseEndpointParsingRule = Boolean.parseBoolean(isUseEndpointRuleParsing);
String endpointUrl;
if (isUseEndpointParsingRule) {
// Get the set domain name information
//获取配置 endpoint
endpointUrl = ParamUtil.parsingEndpointRule(properties.getProperty(PropertyKeyConst.ENDPOINT));
if (StringUtils.isBlank(endpointUrl)) {
return "";
}
} else {
endpointUrl = properties.getProperty(PropertyKeyConst.ENDPOINT);
}
if (StringUtils.isBlank(endpointUrl)) {
return "";
}
//获取 ALIBABA_ALIWARE_ENDPOINT_PORT 或 endpointPort 或默认值8080
String endpointPort = TemplateUtils
.stringEmptyAndThenExecute(System.getenv(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_PORT),
new Callable<String>() {
@Override
public String call() {
return properties.getProperty(PropertyKeyConst.ENDPOINT_PORT);
}
});
endpointPort = TemplateUtils.stringEmptyAndThenExecute(endpointPort, new Callable<String>() {
@Override
public String call() {
return "8080";
}
});
return endpointUrl + ":" + endpointPort;
}
9、 initWebRootContext();
初始化nacos服务端访问路径信息
public static void initWebRootContext(Properties properties) {
final String webContext = properties.getProperty(PropertyKeyConst.CONTEXT_PATH);
TemplateUtils.stringNotEmptyAndThenExecute(webContext, new Runnable() {
@Override
public void run() {
UtilAndComs.webContext = ContextPathUtil.normalizeContextPath(webContext);
UtilAndComs.nacosUrlBase = UtilAndComs.webContext + "/v1/ns";
UtilAndComs.nacosUrlInstance = UtilAndComs.nacosUrlBase + "/instance";
}
});
initWebRootContext();
}
/**
* Init web root context.
*/
@Deprecated
public static void initWebRootContext() {
// support the web context with ali-yun if the app deploy by EDAS
final String webContext = System.getProperty(SystemPropertyKeyConst.NAMING_WEB_CONTEXT);
TemplateUtils.stringNotEmptyAndThenExecute(webContext, new Runnable() {
@Override
public void run() {
UtilAndComs.webContext = ContextPathUtil.normalizeContextPath(webContext);
UtilAndComs.nacosUrlBase = UtilAndComs.webContext + "/v1/ns";
UtilAndComs.nacosUrlInstance = UtilAndComs.nacosUrlBase + "/instance";
}
});
}
10、 NamingProxy;
1、构造方法
public NamingProxy(String namespaceId, String endpoint, String serverList, Properties properties) {
this.securityProxy = new SecurityProxy(properties, nacosRestTemplate);
this.properties = properties;
this.setServerPort(DEFAULT_SERVER_PORT);
this.namespaceId = namespaceId;
this.endpoint = endpoint;
//重试次数
this.maxRetry = ConvertUtils.toInt(properties.getProperty(PropertyKeyConst.NAMING_REQUEST_DOMAIN_RETRY_COUNT,
String.valueOf(UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT)));
if (StringUtils.isNotEmpty(serverList)) {
this.serverList = Arrays.asList(serverList.split(","));
if (this.serverList.size() == 1) {
this.nacosDomain = serverList;
}
}
//初始化定时刷新任务
this.initRefreshTask();
}
private void initRefreshTask() {
//线程池
this.executorService = new ScheduledThreadPoolExecutor(2, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.naming.updater");
t.setDaemon(true);
return t;
}
});
//定时任务 refreshSrvIfNeed
this.executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
refreshSrvIfNeed();
}
}, 0, vipSrvRefInterMillis, TimeUnit.MILLISECONDS);
//定时任务 login
this.executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
securityProxy.login(getServerList());
}
}, 0, securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);
refreshSrvIfNeed();
this.securityProxy.login(getServerList());
}
2、refreshSrvIfNeed( )
private void refreshSrvIfNeed() {
try {
//校验服务端列表serverList是否为空
if (!CollectionUtils.isEmpty(serverList)) {
NAMING_LOGGER.debug("server list provided by user: " + serverList);
return;
}
//校验刷新时间间隔
if (System.currentTimeMillis() - lastSrvRefTime < vipSrvRefInterMillis) {
return;
}
//调用远程接口获取服务端列表 "http://" + endpoint + "/nacos/serverlist"
List<String> list = getServerListFromEndpoint();
if (CollectionUtils.isEmpty(list)) {
throw new Exception("Can not acquire Nacos list");
}
if (!CollectionUtils.isEqualCollection(list, serversFromEndpoint)) {
NAMING_LOGGER.info("[SERVER-LIST] server list is updated: " + list);
}
//更新服务列表serversFromEndpoint
serversFromEndpoint = list;
//更新最新刷新时间
lastSrvRefTime = System.currentTimeMillis();
} catch (Throwable e) {
NAMING_LOGGER.warn("failed to update server list", e);
}
}
3、login( )
public boolean login(List<String> servers) {
try {
//校验刷新时间间隔
if ((System.currentTimeMillis() - lastRefreshTime) < TimeUnit.SECONDS
.toMillis(tokenTtl - tokenRefreshWindow)) {
return true;
}
//尝试登录nacos服务列表
for (String server : servers) {
if (login(server)) {
lastRefreshTime = System.currentTimeMillis();
return true;
}
}
} catch (Throwable ignore) {
}
return false;
}
11、 BeatReactor;
客户端需要通过BeatReactor周期性的进行服务的上报,以便让nacos服务端能够实时感知服务的状态。如果一段时间内未进行上报,nacos服务端会移除该服务的注册。
12、 HostReactor;
HostReactor把服务信息维护到serviceInfoMap中。