Nacos服务发现
基础配置初始化
NacosDiscoveryClientConfiguration
NacosDiscoveryProperties
初始化Nacos基础配置信息的bean,主要指yaml中配置Nacos服务相关的信息。
NacosServiceDiscovery
初始化获取Nacos服务和实例的bean,通过该bean可以获取服务的信息和实例的信息。
NacosDiscoveryClientConfiguration
NacosDiscoveryClient
初始化NacosDiscoveryClient的bean,本质上就是实现NacosServiceDiscovery的实现。该类的作用就是获取到实例信息和服务信息。
NacosWatch
从继承结构上看,NacosWatch主要实现了SmartLifecycle和ApplicationEventPublisherAware,ApplicationEventPublisherAware就是发布事件,这里主要指的就是发布HeartbeatEvent
事件上报心跳。SmartLifecycle该接口主要是作用是所有的bean都创建完成之后,可以执行自己的初始化工作,或者在退出时执行资源销毁工作。NacosWatch的start方法,主要是完成以下四件事情:
- 加入NamingEvent监听;
- 获取NamingService;
- 订阅NamingService监听事件;
- 发布HeartbeatEvent事件;
public void start() {
//加入NamingEvent监听
if (this.running.compareAndSet(false, true)) {
//更新本地的Instance
EventListener eventListener = listenerMap.computeIfAbsent(buildKey(),
event -> new EventListener() {
@Override
public void onEvent(Event event) {
if (event instanceof NamingEvent) {
List<Instance> instances = ((NamingEvent) event)
.getInstances();
Optional<Instance> instanceOptional = selectCurrentInstance(
instances);
instanceOptional.ifPresent(currentInstance -> {
resetIfNeeded(currentInstance);
});
}
}
});
//获取NamingService
NamingService namingService = nacosServiceManager
.getNamingService(properties.getNacosProperties());
try {
//订阅相关NamingService的事件
namingService.subscribe(properties.getService(), properties.getGroup(),
Arrays.asList(properties.getClusterName()), eventListener);
}
catch (Exception e) {
log.error("namingService subscribe failed, properties:{}", properties, e);
}
//发布HeartbeatEvent事件
this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(
this::nacosServicesWatch, this.properties.getWatchDelay());
}
}
NacosWatch的stop方法主要是完成两件事情:
- 释放掉监听的线程池资源;
- 取消NamingService相关的监听事件;
@Override
public void stop() {
if (this.running.compareAndSet(true, false)) {
//关闭和释放watch的线程池
if (this.watchFuture != null) {
// shutdown current user-thread,
// then the other daemon-threads will terminate automatic.
((ThreadPoolTaskScheduler) this.taskScheduler).shutdown();
this.watchFuture.cancel(true);
} EventListener eventListener = listenerMap.get(buildKey());
try {
//取消NamingService相关的订阅信息
NamingService namingService = nacosServiceManager
.getNamingService(properties.getNacosProperties());
namingService.unsubscribe(properties.getService(), properties.getGroup(),
Arrays.asList(properties.getClusterName()), eventListener);
}
catch (NacosException e) {
log.error("namingService unsubscribe failed, properties:{}", properties,
e);
}
}
}
NacosNamingService
客户端信息的初始化发生在发起调用的时候,是一种懒加载的方式,并没有在初始化完成的时候就进行,这部分我们分析Ribbon源码的时候我们具体在讲解一下。我们的重点看的是NacosNamingService,从基础配置类中的NacosServiceDiscovery的getInstances的方法调用追踪到更下层我们会发现,与服务端交互的重点的类就是NacosNamingService,NacosNamingService在初始化的时候,主要做了以下10事件
- initNamespaceForNaming:用于初始命名空间,在Nacos中命名空间用于租户粗粒度隔离,同时还可以进行环境的区别,如开发环境和测试环境等等;
- initSerialization:序列化初始化;
- initServerAddr:初始化服务器地址,其中涉及到的endpoint 等;
- initWebRootContext:初始化web上下文,其支持通过阿里云EDAS进行部署;
- initCacheDir:初始化缓存目录;
- initLogName:从配置中获取日志文件;
- EventDispatcher:监听事件分发,当客户端订阅了某个服务信息后,会以Listener的方式注册到EventDispatcher的队列中,当有服务变化的时候,会通知订阅者;
- NamingProxy:服务端的代理,用于客户端与服务端的通信;
- BeatReactor:用于维持与服务器之间的心跳通信,上报客户端注册到服务端的服务信息;
- HostReactor:用于客户端服务的订阅,以及从服务端更新服务信息;
initNamespaceForNaming
//初始化获取Namespace
public static String initNamespaceForNaming(Properties properties) {
String tmpNamespace = null; //是否使用阿里云上环境进行解析,默认为true,如果没有进行配置,
//默认使用DEFAULT_USE_CLOUD_NAMESPACE_PARSING
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();
//从系统变量获取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 = 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;
}
});
}
//如果不是上云环境,那么从系统变量获取namespace
tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
@Override
public String call() {
String namespace = System.getProperty(PropertyKeyConst.NAMESPACE);
LogUtils.NAMING_LOGGER.info("initializer namespace from System Property :" + namespace);
return namespace;
}
});
//从properties中获取namespace
if (StringUtils.isEmpty(tmpNamespace) && properties != null) {
tmpNamespace = properties.getProperty(PropertyKeyConst.NAMESPACE);
}
//获取系统默认的namespace
tmpNamespace = TemplateUtils.stringEmptyAndThenExecute(tmpNamespace, new Callable<String>() {
@Override
public String call() {
return UtilAndComs.DEFAULT_NAMESPACE_ID;
}
});
return tmpNamespace;
}
initServerAddr
//初始化服务器地址
private void initServerAddr(Properties properties) {
//从properties中获取服务器地址
serverList = properties.getProperty(PropertyKeyConst.SERVER_ADDR);
//初始化endpoint,如果有endpoint,则废弃serverList
endpoint = InitUtils.initEndpoint(properties);
if (StringUtils.isNotEmpty(endpoint)) {
serverList = "";
}
}
public static String initEndpoint(final Properties properties) {
if (properties == null) { return "";
}
// Whether to enable domain name resolution rules
//是否使用endpoint解析,默认为true,也就是:USE_ENDPOINT_PARSING_RULE_DEFAULT_VALUE
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.valueOf(isUseEndpointRuleParsing);
String endpointUrl;
//使用endpoint解析功能
if (isUseEndpointParsingRule) {
// Get the set domain name information
endpointUrl = ParamUtil.parsingEndpointRule(properties.getProperty(PropertyKeyConst.ENDPOINT));
if (StringUtils.isBlank(endpointUrl)) {
return "";
}
} else {
//不使用的化,直接通过properties文件来获取
endpointUrl = properties.getProperty(PropertyKeyConst.ENDPOINT);
} if (StringUtils.isBlank(endpointUrl)) {
return "";
} //获取endpoint的端口
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;
}
initWebRootContext
//阿里云EDAS相关的
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 = webContext.indexOf("/") > -1 ? webContext : "/" + webContext; UtilAndComs.nacosUrlBase = UtilAndComs.webContext + "/v1/ns";
UtilAndComs.nacosUrlInstance = UtilAndComs.nacosUrlBase + "/instance";
}
});
}
initCacheDir
//初始化缓存目录,用于存放从服务端获取的服务信息,如果客户端与服务端断开了连接,将会使用缓存的信息
private void initCacheDir() {
cacheDir = System.getProperty("com.alibaba.nacos.naming.cache.dir");
if (StringUtils.isEmpty(cacheDir)) {
cacheDir = System.getProperty("user.home") + "/nacos/naming/" + namespace;
}
}
initLogName
//初始化日志存放路径
private void initLogName(Properties properties) {
logName = System.getProperty(UtilAndComs.NACOS_NAMING_LOG_NAME);
if (StringUtils.isEmpty(logName)) { if (properties != null && StringUtils
.isNotEmpty(properties.getProperty(UtilAndComs.NACOS_NAMING_LOG_NAME))) {
logName = properties.getProperty(UtilAndComs.NACOS_NAMING_LOG_NAME);
} else {
logName = "naming.log";
}
}
}
EventDispatcher
EventDispatcher维护一个阻塞队列,主要存储发生改变的服务的信息,维护了一个对于服务的监听队列的映射的Map,实时的将服务变化信息同步给监听者,这样客户端就可以通过注册监听者实现在服务变化后动态进行操作。
public class EventDispatcher implements Closeable { private ExecutorService executor = null; //发生了变化的服务队列
private final BlockingQueue<ServiceInfo> changedServices = new LinkedBlockingQueue<ServiceInfo>();
//监听者维护映射
private final ConcurrentMap<String, List<EventListener>> observerMap = new ConcurrentHashMap<String, List<EventListener>>(); private volatile boolean closed = false; public EventDispatcher() { this.executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "com.alibaba.nacos.naming.client.listener");
thread.setDaemon(true); return thread;
}
}); this.executor.execute(new Notifier());
} /**
* Add listener.
*
* @param serviceInfo service info
* @param clusters clusters
* @param listener listener
*/
public void addListener(ServiceInfo serviceInfo, String clusters, EventListener listener) { NAMING_LOGGER.info("[LISTENER] adding " + serviceInfo.getName() + " with " + clusters + " to listener map");
List<EventListener> observers = Collections.synchronizedList(new ArrayList<EventListener>());
observers.add(listener); observers = observerMap.putIfAbsent(ServiceInfo.getKey(serviceInfo.getName(), clusters), observers);
if (observers != null) {
observers.add(listener);
} serviceChanged(serviceInfo);
} /**
* Remove listener.
*
* @param serviceName service name
* @param clusters clusters
* @param listener listener
*/
public void removeListener(String serviceName, String clusters, EventListener listener) { NAMING_LOGGER.info("[LISTENER] removing " + serviceName + " with " + clusters + " from listener map"); List<EventListener> observers = observerMap.get(ServiceInfo.getKey(serviceName, clusters));
if (observers != null) {
Iterator<EventListener> iter = observers.iterator();
while (iter.hasNext()) {
EventListener oldListener = iter.next();
if (oldListener.equals(listener)) {
iter.remove();
}
}
if (observers.isEmpty()) {
observerMap.remove(ServiceInfo.getKey(serviceName, clusters));
}
}
} public boolean isSubscribed(String serviceName, String clusters) {
return observerMap.containsKey(ServiceInfo.getKey(serviceName, clusters));
} public List<ServiceInfo> getSubscribeServices() {
List<ServiceInfo> serviceInfos = new ArrayList<ServiceInfo>();
for (String key : observerMap.keySet()) {
serviceInfos.add(ServiceInfo.fromKey(key));
}
return serviceInfos;
} /**
* Service changed.
*
* @param serviceInfo service info
*/
public void serviceChanged(ServiceInfo serviceInfo) {
if (serviceInfo == null) {
return;
} changedServices.add(serviceInfo);
} @Override
public void shutdown() throws NacosException {
String className = this.getClass().getName();
NAMING_LOGGER.info("{} do shutdown begin", className);
ThreadUtils.shutdownThreadPool(executor, NAMING_LOGGER);
closed = true;
NAMING_LOGGER.info("{} do shutdown stop", className);
}
//服务变化通知线程
private class Notifier implements Runnable { @Override
public void run() {
while (!closed) { ServiceInfo serviceInfo = null;
try {
//从队列取出变化消息
serviceInfo = changedServices.poll(5, TimeUnit.MINUTES);
} catch (Exception ignore) {
} if (serviceInfo == null) {
continue;
} try {
//获取监听者队列
List<EventListener> listeners = observerMap.get(serviceInfo.getKey());
//遍历监听者队列,调用其onEvent方法
if (!CollectionUtils.isEmpty(listeners)) {
for (EventListener listener : listeners) {
List<Instance> hosts = Collections.unmodifiableList(serviceInfo.getHosts());
listener.onEvent(new NamingEvent(serviceInfo.getName(), serviceInfo.getGroupName(),
serviceInfo.getClusters(), hosts));
}
} } catch (Exception e) {
NAMING_LOGGER.error("[NA] notify error for service: " + serviceInfo.getName() + ", clusters: "
+ serviceInfo.getClusters(), e);
}
}
}
}
}
NamingProxy
NamingProxy封装了与服务端的操作,代码相对比较简单,值得注意的是如果配置安全相关的内容,在初始化的时候该处会进行一个定时任务的查询,如果对安全要求比较高,可重SecurityProxy部分内容,当然服务端部分也需要重写,大部分的情况这注册中心一般暴露在内网环境下,基本上不需要重写的。
BeatReactor
BeatReactor负责将客户端的信息上报和下线,对于非持久化的内容采用周期上报内容,这部分在服务心跳的时候我们讲解过,这里不进行源码分析,大家重点关注addBeatInfo、removeBeatInfo和BeatTask的内容,相对比较简单。
HostReactor
HostReactor主要负责客户端获取服务端注册的信息的部分,主要分为三个部分:
- 客户端需要调用NacosNamingService获取服务信息方法的时候,HostReactor负责把服务信息维护本地缓存的serviceInfoMap中,并且通过UpdateTask定时更新已存在的服务;
- HostReactor内部维护PushReceiver对象,负责接收服务端通过UDP协议推送过来的服务变更的信息,并更新到本地缓存serviceInfoMap当中;
- HostReactor内部维护FailoverReactor对象,负责当服务端不可用的时候,切换到本地文件缓存模式,从本地文件的缓存中获取服务信息;
public class HostReactor implements Closeable { private static final long DEFAULT_DELAY = 1000L; private static final long UPDATE_HOLD_INTERVAL = 5000L; private final Map<String, ScheduledFuture<?>> futureMap = new HashMap<String, ScheduledFuture<?>>(); private final Map<String, ServiceInfo> serviceInfoMap; private final Map<String, Object> updatingMap;
//接收UDP服务端UDP协议
private final PushReceiver pushReceiver;
//阻塞队列的变更消息处理
private final EventDispatcher eventDispatcher;
//心跳上报
private final BeatReactor beatReactor;
//HTTP请求消息的处理
private final NamingProxy serverProxy;
//服务不可用时本地降级文件的处理模式
private final FailoverReactor failoverReactor; private final String cacheDir;
//定时更新服务消息的定时任务
private final ScheduledExecutorService executor; public HostReactor(EventDispatcher eventDispatcher, NamingProxy serverProxy, BeatReactor beatReactor,
String cacheDir) {
this(eventDispatcher, serverProxy, beatReactor, cacheDir, false, UtilAndComs.DEFAULT_POLLING_THREAD_COUNT);
} public HostReactor(EventDispatcher eventDispatcher, NamingProxy serverProxy, BeatReactor beatReactor,
String cacheDir, boolean loadCacheAtStart, int pollingThreadCount) {
// init executorService
this.executor = new ScheduledThreadPoolExecutor(pollingThreadCount, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("com.alibaba.nacos.client.naming.updater");
return thread;
}
});
this.eventDispatcher = eventDispatcher;
this.beatReactor = beatReactor;
this.serverProxy = serverProxy;
this.cacheDir = cacheDir;
if (loadCacheAtStart) {
this.serviceInfoMap = new ConcurrentHashMap<String, ServiceInfo>(DiskCache.read(this.cacheDir));
} else {
this.serviceInfoMap = new ConcurrentHashMap<String, ServiceInfo>(16);
} this.updatingMap = new ConcurrentHashMap<String, Object>();
this.failoverReactor = new FailoverReactor(this, cacheDir);
this.pushReceiver = new PushReceiver(this);
} public Map<String, ServiceInfo> getServiceInfoMap() {
return serviceInfoMap;
} public synchronized ScheduledFuture<?> addTask(UpdateTask task) {
return executor.schedule(task, DEFAULT_DELAY, TimeUnit.MILLISECONDS);
} /**
* Process service json.
*
* @param json service json
* @return service info
*/
//处理从服务端接收到的数据
public ServiceInfo processServiceJson(String json) {
ServiceInfo serviceInfo = JacksonUtils.toObj(json, ServiceInfo.class);
ServiceInfo oldService = serviceInfoMap.get(serviceInfo.getKey());
if (serviceInfo.getHosts() == null || !serviceInfo.validate()) {
//empty or error push, just ignore
return oldService;
} boolean changed = false;
//新老信息对比处理
if (oldService != null) {
//如果本地旧服务的获取时间比服务器端获取的时间新,则保留本地旧服务的时间
if (oldService.getLastRefTime() > serviceInfo.getLastRefTime()) {
NAMING_LOGGER.warn("out of date data received, old-t: " + oldService.getLastRefTime() + ", new-t: "
+ serviceInfo.getLastRefTime());
}
//用新服务信息替换serviceInfoMap
serviceInfoMap.put(serviceInfo.getKey(), serviceInfo); Map<String, Instance> oldHostMap = new HashMap<String, Instance>(oldService.getHosts().size());
for (Instance host : oldService.getHosts()) {
oldHostMap.put(host.toInetAddr(), host);
} Map<String, Instance> newHostMap = new HashMap<String, Instance>(serviceInfo.getHosts().size());
for (Instance host : serviceInfo.getHosts()) {
newHostMap.put(host.toInetAddr(), host);
} Set<Instance> modHosts = new HashSet<Instance>();
Set<Instance> newHosts = new HashSet<Instance>();
Set<Instance> remvHosts = new HashSet<Instance>(); List<Map.Entry<String, Instance>> newServiceHosts = new ArrayList<Map.Entry<String, Instance>>(
newHostMap.entrySet());
for (Map.Entry<String, Instance> entry : newServiceHosts) {
Instance host = entry.getValue();
String key = entry.getKey();
if (oldHostMap.containsKey(key) && !StringUtils
.equals(host.toString(), oldHostMap.get(key).toString())) {
modHosts.add(host);
continue;
} if (!oldHostMap.containsKey(key)) {
newHosts.add(host);
}
} for (Map.Entry<String, Instance> entry : oldHostMap.entrySet()) {
Instance host = entry.getValue();
String key = entry.getKey();
if (newHostMap.containsKey(key)) {
continue;
} if (!newHostMap.containsKey(key)) {
remvHosts.add(host);
} } if (newHosts.size() > 0) {
changed = true;
NAMING_LOGGER.info("new ips(" + newHosts.size() + ") service: " + serviceInfo.getKey() + " -> "
+ JacksonUtils.toJson(newHosts));
} if (remvHosts.size() > 0) {
changed = true;
NAMING_LOGGER.info("removed ips(" + remvHosts.size() + ") service: " + serviceInfo.getKey() + " -> "
+ JacksonUtils.toJson(remvHosts));
} if (modHosts.size() > 0) {
changed = true;
updateBeatInfo(modHosts);
NAMING_LOGGER.info("modified ips(" + modHosts.size() + ") service: " + serviceInfo.getKey() + " -> "
+ JacksonUtils.toJson(modHosts));
} serviceInfo.setJsonFromServer(json); if (newHosts.size() > 0 || remvHosts.size() > 0 || modHosts.size() > 0) {
//服务信息变更写入阻塞队列
eventDispatcher.serviceChanged(serviceInfo);
//磁盘缓存希尔
DiskCache.write(serviceInfo, cacheDir);
} } else {
//新服务的信息直接加入本地缓存
changed = true;
NAMING_LOGGER.info("init new ips(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getKey() + " -> "
+ JacksonUtils.toJson(serviceInfo.getHosts()));
serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
eventDispatcher.serviceChanged(serviceInfo);
serviceInfo.setJsonFromServer(json);
DiskCache.write(serviceInfo, cacheDir);
}
//上报数量
MetricsMonitor.getServiceInfoMapSizeMonitor().set(serviceInfoMap.size()); if (changed) {
NAMING_LOGGER.info("current ips:(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getKey() + " -> "
+ JacksonUtils.toJson(serviceInfo.getHosts()));
} return serviceInfo;
} private void updateBeatInfo(Set<Instance> modHosts) {
for (Instance instance : modHosts) {
String key = beatReactor.buildKey(instance.getServiceName(), instance.getIp(), instance.getPort());
if (beatReactor.dom2Beat.containsKey(key) && instance.isEphemeral()) {
BeatInfo beatInfo = beatReactor.buildBeatInfo(instance);
beatReactor.addBeatInfo(instance.getServiceName(), beatInfo);
}
}
} //通过key获取服务对象
private ServiceInfo getServiceInfo0(String serviceName, String clusters) {
//得到ServiceInfo的key
String key = ServiceInfo.getKey(serviceName, clusters);
//从本地缓存中获取服务信息
return serviceInfoMap.get(key);
}
//从服务器端获取Service信息,并解析为ServiceInfo对象
public ServiceInfo getServiceInfoDirectlyFromServer(final String serviceName, final String clusters)
throws NacosException {
String result = serverProxy.queryList(serviceName, clusters, 0, false);
if (StringUtils.isNotEmpty(result)) {
return JacksonUtils.toObj(result, ServiceInfo.class);
}
return null;
} public ServiceInfo getServiceInfo(final String serviceName, final String clusters) { NAMING_LOGGER.debug("failover-mode: " + failoverReactor.isFailoverSwitch()); String key = ServiceInfo.getKey(serviceName, clusters);
//是否开启本地文件缓存模式
if (failoverReactor.isFailoverSwitch()) {
return failoverReactor.getService(key);
} ServiceInfo serviceObj = getServiceInfo0(serviceName, clusters);
//从serviceInfoMap获取serviceObj,如果没有serviceObj,则新生成一个
if (null == serviceObj) {
serviceObj = new ServiceInfo(serviceName, clusters); serviceInfoMap.put(serviceObj.getKey(), serviceObj); updatingMap.put(serviceName, new Object());
updateServiceNow(serviceName, clusters);
updatingMap.remove(serviceName); } else if (updatingMap.containsKey(serviceName)) {
//如果更新列表中包含服务,则等待更新结束
if (UPDATE_HOLD_INTERVAL > 0) {
// hold a moment waiting for update finish
synchronized (serviceObj) {
try {
serviceObj.wait(UPDATE_HOLD_INTERVAL);
} catch (InterruptedException e) {
NAMING_LOGGER
.error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, e);
}
}
}
}
//添加更新调度任务
scheduleUpdateIfAbsent(serviceName, clusters); return serviceInfoMap.get(serviceObj.getKey());
}
//从服务端更新服务
private void updateServiceNow(String serviceName, String clusters) {
try {
updateService(serviceName, clusters);
} catch (NacosException e) {
NAMING_LOGGER.error("[NA] failed to update serviceName: " + serviceName, e);
}
} /**
* Schedule update if absent.
*
* @param serviceName service name
* @param clusters clusters
*/
//添加更新调度任务
public void scheduleUpdateIfAbsent(String serviceName, String clusters) {
if (futureMap.get(ServiceInfo.getKey(serviceName, clusters)) != null) {
return;
} synchronized (futureMap) {
if (futureMap.get(ServiceInfo.getKey(serviceName, clusters)) != null) {
return;
} ScheduledFuture<?> future = addTask(new UpdateTask(serviceName, clusters));
futureMap.put(ServiceInfo.getKey(serviceName, clusters), future);
}
} /**
* Update service now.
*
* @param serviceName service name
* @param clusters clusters
*/
public void updateService(String serviceName, String clusters) throws NacosException {
ServiceInfo oldService = getServiceInfo0(serviceName, clusters);
try { String result = serverProxy.queryList(serviceName, clusters, pushReceiver.getUdpPort(), false); if (StringUtils.isNotEmpty(result)) {
processServiceJson(result);
}
} finally {
if (oldService != null) {
synchronized (oldService) {
oldService.notifyAll();
}
}
}
} /**
* Refresh only.
*
* @param serviceName service name
* @param clusters cluster
*/
public void refreshOnly(String serviceName, String clusters) {
try {
serverProxy.queryList(serviceName, clusters, pushReceiver.getUdpPort(), false);
} catch (Exception e) {
NAMING_LOGGER.error("[NA] failed to update serviceName: " + serviceName, e);
}
} @Override
public void shutdown() throws NacosException {
String className = this.getClass().getName();
NAMING_LOGGER.info("{} do shutdown begin", className);
ThreadUtils.shutdownThreadPool(executor, NAMING_LOGGER);
pushReceiver.shutdown();
failoverReactor.shutdown();
NAMING_LOGGER.info("{} do shutdown stop", className);
} //定时更新已存在的服务
public class UpdateTask implements Runnable { long lastRefTime = Long.MAX_VALUE; private final String clusters; private final String serviceName; /**
* the fail situation. 1:can't connect to server 2:serviceInfo's hosts is empty
*/
private int failCount = 0; public UpdateTask(String serviceName, String clusters) {
this.serviceName = serviceName;
this.clusters = clusters;
} private void incFailCount() {
int limit = 6;
if (failCount == limit) {
return;
}
failCount++;
} private void resetFailCount() {
failCount = 0;
} @Override
public void run() {
long delayTime = DEFAULT_DELAY; try {
ServiceInfo serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters)); if (serviceObj == null) {
updateService(serviceName, clusters);
return;
} if (serviceObj.getLastRefTime() <= lastRefTime) {
updateService(serviceName, clusters);
serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
} else {
// if serviceName already updated by push, we should not override it
// since the push data may be different from pull through force push
refreshOnly(serviceName, clusters);
} lastRefTime = serviceObj.getLastRefTime(); if (!eventDispatcher.isSubscribed(serviceName, clusters) && !futureMap
.containsKey(ServiceInfo.getKey(serviceName, clusters))) {
// abort the update task
NAMING_LOGGER.info("update task is stopped, service:" + serviceName + ", clusters:" + clusters);
return;
}
if (CollectionUtils.isEmpty(serviceObj.getHosts())) {
incFailCount();
return;
}
delayTime = serviceObj.getCacheMillis();
resetFailCount();
} catch (Throwable e) {
incFailCount();
NAMING_LOGGER.warn("[NA] failed to update serviceName: " + serviceName, e);
} finally {
executor.schedule(this, Math.min(delayTime << failCount, DEFAULT_DELAY * 60), TimeUnit.MILLISECONDS);
}
}
}
}
总结
从NacosNamingService初始化的整个过程中,很重要的一点值得我们学习,就是单一职责这个理念在每个类的设计上表现的淋淋尽致。
结束
欢迎大家点点关注,点点赞,上海地区帮助团队招两个Java,其中两点比较重要:靠谱和全日制本科,正常的业务开发,整体技术栈Dubbo+GateWay网关,有意者私聊!
Nacos服务发现的更多相关文章
- SpringCloud使用Nacos服务发现实现远程调用
本文使用SpringCloud结合Nacos服务发现,Feign远程调用做一个简单的Demo. 1 Nacos 关于Nacos之前写了两篇文章关于SpringBoot对它的使用,感兴趣可以查看一下. ...
- SpringBoot使用Nacos服务发现
本文介绍SpringBoot应用使用Nacos服务发现. 上一篇文章介绍了SpringBoot使用Nacos做配置中心,本文介绍SpringBoot使用Nacos做服务发现. 1.Eureka闭源 相 ...
- SpringCloud之Nacos服务发现(十七)
一 Nacos简介 Nacos是以服务为主要服务对象的中间件,Nacos支持所有主流的服务发现.配置和管理. Nacos主要提供以下四大功能: 服务发现与服务健康检查 Nacos使服务更容易注册自己并 ...
- Spring Cloud Alibaba基础教程:Nacos服务发现与配置管理
随着微服务概念的流行,越来越多的公司采用`Spring Cloud`全家桶构建微服务系统,实现业务的快速迭代.`Spring Cloud`提供了快速构建分布式微服务常用组件,包括`Spring Clo ...
- Spring Cloud Alibaba学习笔记(2) - Nacos服务发现
1.什么是Nacos Nacos的官网对这一问题进行了详细的介绍,通俗的来说: Nacos是一个服务发现组件,同时也是一个配置服务器,它解决了两个问题: 1.服务A如何发现服务B 2.管理微服务的配置 ...
- Alibaba Nacos 服务发现组件集群部署
前面学习了单机模式下的启动,生产环境中部署nacos肯定是使用集群模式cluster保证高可用. 官方文档的集群部署推荐使用VIP+域名模式,把所有服务列表放到一个vip下面,然后挂到一个域名下面. ...
- Nacos服务发现源码解析
1.Spring服务发现的统一规范 Spring将这套规范定义在Spring Cloud Common中 discovery包下面定义了服务发现的规范 核心接口:DiscoveryClient 用于服 ...
- 配置中心Nacos(服务发现)
服务演变之路 单体应用架构 在刚开始的时候,企业的用户量.数据量规模都⽐较⼩,项⽬所有的功能模块都放在⼀个⼯程中编码.编译.打包并且部署在⼀个Tomcat容器中的架构模式就是单体应用架构,这样的架构既 ...
- Nacos 解读:服务发现客户端
Nacos是阿里巴巴的微服务开源项目,用于服务发现和配置管理,开源以来我就一直关注,在此准备以几篇文章来窥其全貌,但大段大段贴代码就没必要了,这里用自己的一些理解和总结来帮助大家理解.文章将基于截止目 ...
随机推荐
- CrashLoopBackOff的解决办法之一
问题来源 # kubectl get pods -n assembly NAME READY STATUS RESTARTS AGE alertmanager-858b7749c5-6jsfh 0/1 ...
- java面试一日一题:rabbitMQ的工作模式
问题:请讲下rabbitMQ的工作模式 分析:该问题纯属概念题,需要掌握rabbtiMQ的基础知识,同时该题也是切入MQ的一个引子: 回答要点: 主要从以下几点去考虑, 1.rabbitMQ的基本概念 ...
- MongoDB教程--配置与入门
MongoDB简介 阿里云配置MongoDB 数据库的增删查改 MongoDB 数据最重要的操作是Key-Value的映射.有了这样的映射,可以直接通过关键字去寻找想要的值.例如,通过用户的ID寻找与 ...
- 数据库MySQL二
注意拼接的时候如果为null则都为null 用if null 1.条件查询 2.按逻辑表达式筛选 3.模糊查询 还有not like 用转义字符\ #2.in 数值型的常量值都不用单引号,非数值型的都 ...
- 自动化kolla-ansible部署centos7.9+openstack-train-超融合单机架构
自动化kolla-ansible部署centos7.9+openstack-train-超融合单机架构 欢迎加QQ群:1026880196 进行交流学习 环境说明: 1. 满足一台电脑一个网卡的环 ...
- 《Python编程:从入门到实践》基础知识部分学习笔记整理
简介 此笔记为<Python编程:从入门到实践>中前 11 章的基础知识部分的学习笔记,不包含后面的项目部分. 书籍评价 从系统学习 Python 的角度,不推荐此书,个人更推荐使用< ...
- 多图详解 TCP 连接管理,太全了!!!
TCP 是一种面向连接的单播协议,在 TCP 中,并不存在多播.广播的这种行为,因为 TCP 报文段中能明确发送方和接受方的 IP 地址. 在发送数据前,相互通信的双方(即发送方和接受方)需要建立一条 ...
- Day11_58_增强for循环
增强for循环 * 语法 : for(数据类型 变量名:数组名/集合名) * 集合如果要使用增强for循环需要先使用泛型来确定元素类型,如果没有使用泛型就使用foreach,那么变量类型设置为Obje ...
- 【cypress】5. 测试本地web应用
在之前的cypress介绍里曾提到过,cypress虽然也可以测试部署好的应用,但是它最大的能力还是发挥在测试本地应用上. 本章主要内容就是关于如何测试本地web应用的概述: cypress与后台应用 ...
- lumen Rest API 起步
lumen Rest API 起步 修改项目文件 .env DB_DATABASE=<数据库名> DB_USERNAME=<数据库用户名> DB_PASSWORD=<数据 ...