摘要:
本文是Nacos源碼學習的第一篇,基於Nacos v1.1.3版本對Nacos源碼進行學習,本片主要從exmaple的App示例入手,切入Nacos客戶端NacosNamingService的初始化過程,並分析初始化相關的類。
一.源碼下載
1.首先Fork一份Nacos源碼到自己的github賬號下面。
2.用git clone自己賬號下的github項目到本地。
3.本地創建一個用於學習的分支,方便自己對源碼進行一些標註,同時不會污染到主幹分支的源碼。
4.另外還可以創建一個開發的分支,以便進行一些源碼的修改。
5.在本地搭建一套Nacos的運行環境,以便進行運行測試和調試。
二.從何入手
1.Nacos的源碼採用的是Maven父子項目結構,其源碼結構如下圖所示,目前我們還不知道每個模塊是幹什麼用的,但是其中一個模塊我認爲是值得下手研究的,那就是example,我們可以從這裏入手開始調試和閱讀源碼。
2.com.alibaba.nacos.example.App
我們首先來看下App這個程序,如圖所示,這裏程序主要演示的是進行服務註冊和服務獲取,首先通過Properties文件將Nacos服務器信息以及命名空間進行設置,然後通過NamingFactory創建NamingService服務,最後通過NamingService服務註冊了兩個服務,服務名稱都是nacos.test.3,但是集羣名稱不同。最後通過getAllInstnaces獲取這個服務命對應的所有服務。
3. com.alibaba.nacos.api.naming.NamingFactory
很顯然,我們需要看下NamingFactroy是幹什麼的,如下圖所示,NamingFactory是一個工廠類,用於創建NamingService。
NamingFactory可以通過兩種方式獲取NamingService,我們先來看看通過Properties來創建,可以看到這裏它通過反射的方式創建了NamingService的一個實現類:com.alibaba.nacos.client.naming.NacosNamingService,這個類存在於client模塊中,這樣客戶端就能夠通過NacosNamingService來管理和註冊服務了。
4.NacosNamingService初始化
接下來看下NacosNamingService的構造方法,如下圖所示,主要看init方法,其中進行了一些init方法:
initNamespaceForNaming:用於初始命名空間,在Nacos中命名空間用於租戶粗粒度隔離,同時還可以進行環境的區別,如開發環境和測試環境等等。
initServerAddr:初始化服務器地址,其中涉及到的endpoint 等,我們後面進行討論
initWebRootContext:初始化web上下文,其支持通過阿里雲EDAS進行部署
initCacheDir:初始化緩存目錄
initLogName:從配置中獲取日誌文件
EventDispatcher:監聽事件分發,當客戶端訂閱了某個服務信息後,會以Listener的方式註冊到EventDispatcher的隊列中,當有服務變化的時候,會通知訂閱者。
NamingProxy:服務端的代理,用於客戶端與服務端的通信
BeatReactor:用於維持與服務器之間的心跳通信,上報客戶端註冊到服務端的服務信息。
HostReactor:用於客戶端服務的訂閱,以及從服務端更新服務信息
6.方法具體分析和對象的構建-1
a.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.valueOf(isUseCloudNamespaceParsing)) {
tmpNamespace = TenantUtil.getUserTenantForAns();
//從系統變量獲取ans.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;
}
});
//從環境變量獲取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;
}
});
}
//如果不是上雲環境,那麼從系統變量獲取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;
}
b.initServerAddr
這裏讓人疑惑的是,有個初始化Endpoint,參考官網給出的解釋:Endpoint是提供一種能力,讓客戶端能夠感知到Nacos服務端的擴縮容,說直白一點就是配置一個URL,通過URL可以獲取Nacos服務器信息,也就是再也不用通過在客戶端配置死服務器端地址(這點設計還是很不錯),具體看下Endpoint是怎麼初始化的
//初始化服務器地址
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;
}
c.initWebRootContext
這裏不用多說了,初始化基本的web上下文,同樣會涉及到阿里雲上雲的環境
public static void initWebRootContext() {
// support the web context with ali-yun if the app deploy by EDAS
//支持阿里雲上的webContext
final String webContext = System.getProperty(SystemPropertyKeyConst.NAMING_WEB_CONTEXT);
//生成url_base和url_instance拼接
TemplateUtils.stringNotEmptyAndThenExecute(webContext, new Runnable() {
@Override
public void run() {
UtilAndComs.WEB_CONTEXT = webContext.indexOf("/") > -1 ? webContext
: "/" + webContext;
UtilAndComs.NACOS_URL_BASE = UtilAndComs.WEB_CONTEXT + "/v1/ns";
UtilAndComs.NACOS_URL_INSTANCE = UtilAndComs.NACOS_URL_BASE + "/instance";
}
});
}
d.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;
}
}
e.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";
}
}
}
f.EventDispatcher
EventDispatcher 是一個事件分發器,其維護了一個發生了變化服務的隊列,一個對於某個服務的監聽者隊列映射,實時的將服務變化信息同步給監聽者,這樣客戶端就可以通過註冊監聽者實現在服務變化後動態進行操作。
public class EventDispatcher {
private ExecutorService executor = null;
//發生了變化的服務隊列
private BlockingQueue<ServiceInfo> changedServices = new LinkedBlockingQueue<ServiceInfo>();
//監聽者維護映射
private ConcurrentMap<String, List<EventListener>> observerMap
= new ConcurrentHashMap<String, List<EventListener>>();
public EventDispatcher() {
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;
}
});
executor.execute(new Notifier());
}
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);
}
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 List<ServiceInfo> getSubscribeServices() {
List<ServiceInfo> serviceInfos = new ArrayList<ServiceInfo>();
for (String key : observerMap.keySet()) {
serviceInfos.add(ServiceInfo.fromKey(key));
}
return serviceInfos;
}
public void serviceChanged(ServiceInfo serviceInfo) {
if (serviceInfo == null) {
return;
}
changedServices.add(serviceInfo);
}
//服務變化通知線程
private class Notifier implements Runnable {
@Override
public void run() {
while (true) {
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);
}
}
}
}
public void setExecutor(ExecutorService executor) {
ExecutorService oldExecutor = this.executor;
this.executor = executor;
oldExecutor.shutdown();
}
}
g.NamingProxy
serverProxy是客戶端與服務器端的代理,其封裝了與服務端的操作,這裏代碼太多,不具體列出,遇到具體場景再進行分析
h.BeatReactor
beatReactor是負責與服務端建立上報機制的類,對於ephemeral爲true的服務,客戶端需要通過BeatReactor週期性的進行服務的上報,告訴服務端該服務處於正常狀態,若一段時間內未進行該服務的上報,服務端會移除該服務的註冊。這裏所說的ephemeral服務是指服務信息不會在服務端持久化的服務,對於ephemeral爲false的服務,服務信息會持久化到服務端。下面是BeatReactor類的代碼.
//對於臨時服務ephemeral(在服務器段不進行持久化的服務),需要BeatReactor進行週期性的狀態上報
public class BeatReactor {
private ScheduledExecutorService executorService;
private NamingProxy serverProxy;
public final Map<String, BeatInfo> dom2Beat = new ConcurrentHashMap<String, BeatInfo>();
public BeatReactor(NamingProxy serverProxy) {
this(serverProxy, UtilAndComs.DEFAULT_CLIENT_BEAT_THREAD_COUNT);
}
public BeatReactor(NamingProxy serverProxy, int threadCount) {
this.serverProxy = serverProxy;
executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
@Override
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) {
NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
dom2Beat.put(buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort()), beatInfo);
executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}
//當移除服務的時候,會移除上報線程任務
public void removeBeatInfo(String serviceName, String ip, int port) {
NAMING_LOGGER.info("[BEAT] removing beat: {}:{}:{} from beat map.", serviceName, ip, port);
BeatInfo beatInfo = dom2Beat.remove(buildKey(serviceName, ip, port));
if (beatInfo == null) {
return;
}
beatInfo.setStopped(true);
MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}
private String buildKey(String serviceName, String ip, int port) {
return serviceName + Constants.NAMING_INSTANCE_ID_SPLITTER
+ ip + Constants.NAMING_INSTANCE_ID_SPLITTER + port;
}
//對於ephemeral服務,需要按週期上報服務信息
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);
}
}
}
i.HostReactor
當客戶端去獲取服務端上註冊的服務的時候,HostReactor就排上用場了,其維護了serviceInfoMap,當客戶端調用NacosNamingServiced的獲取服務信息方法的時候,HostReactor就把服務信息維護到serviceInfoMap當中,並通過UpdateTask能夠週期性的從服務端獲取訂閱服務的最新信息,同時HostReactor還持有pushReceiver對象,用於通過UDP協議從服務器獲取推送的信息,並更新到serviceInfoMap當中。HostReactor還持有failoverReactor對象,當服務端不可用的時候,切換到本地緩存模式,從緩存中獲取服務信息。
public class HostReactor {
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 Map<String, ServiceInfo> serviceInfoMap;
private Map<String, Object> updatingMap;
private PushReceiver pushReceiver;
private EventDispatcher eventDispatcher;
private NamingProxy serverProxy;
private FailoverReactor failoverReactor;
private String cacheDir;
private ScheduledExecutorService executor;
public HostReactor(EventDispatcher eventDispatcher, NamingProxy serverProxy, String cacheDir) {
this(eventDispatcher, serverProxy, cacheDir, false, UtilAndComs.DEFAULT_POLLING_THREAD_COUNT);
}
//構造方法
public HostReactor(EventDispatcher eventDispatcher, NamingProxy serverProxy, String cacheDir,
boolean loadCacheAtStart, int pollingThreadCount) {
//設置updater線程
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.serverProxy = serverProxy;
this.cacheDir = cacheDir;
//如果loadCacheAtStart就從本地緩存文件加載服務
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);
//初始化pushReceiver用於接收服務端推送的UDP數據
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);
}
//處理從服務端接收到的數據
public ServiceInfo processServiceJSON(String json) {
ServiceInfo serviceInfo = JSON.parseObject(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() + " -> " + JSON.toJSONString(newHosts));
}
if (remvHosts.size() > 0) {
changed = true;
NAMING_LOGGER.info("removed ips(" + remvHosts.size() + ") service: "
+ serviceInfo.getKey() + " -> " + JSON.toJSONString(remvHosts));
}
if (modHosts.size() > 0) {
changed = true;
NAMING_LOGGER.info("modified ips(" + modHosts.size() + ") service: "
+ serviceInfo.getKey() + " -> " + JSON.toJSONString(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() + " -> " + JSON
.toJSONString(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() +
" -> " + JSON.toJSONString(serviceInfo.getHosts()));
}
return serviceInfo;
}
//通過key獲取服務對象
private ServiceInfo getServiceInfo0(String serviceName, String clusters) {
//得到ServiceInfo的key
String key = ServiceInfo.getKey(serviceName, clusters);
//從serviceInfoMap中獲取service
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 JSON.parseObject(result, ServiceInfo.class);
}
return null;
}
public ServiceInfo getServiceInfo(final String serviceName, final String clusters) {
NAMING_LOGGER.debug("failover-mode: " + failoverReactor.isFailoverSwitch());
//serviceName是分組@@服務名稱,再和clusters合併,得到key
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());
}
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);
}
}
//直接從服務端更新服務
public void updateServiceNow(String serviceName, String clusters) {
ServiceInfo oldService = getServiceInfo0(serviceName, clusters);
try {
String result = serverProxy.queryList(serviceName, clusters, pushReceiver.getUDPPort(), false);
if (StringUtils.isNotEmpty(result)) {
processServiceJSON(result);
}
} catch (Exception e) {
NAMING_LOGGER.error("[NA] failed to update serviceName: " + serviceName, e);
} finally {
if (oldService != null) {
synchronized (oldService) {
oldService.notifyAll();
}
}
}
}
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);
}
}
//更新服務
public class UpdateTask implements Runnable {
long lastRefTime = Long.MAX_VALUE;
private String clusters;
private String serviceName;
public UpdateTask(String serviceName, String clusters) {
this.serviceName = serviceName;
this.clusters = clusters;
}
@Override
public void run() {
try {
ServiceInfo serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
if (serviceObj == null) {
updateServiceNow(serviceName, clusters);
executor.schedule(this, DEFAULT_DELAY, TimeUnit.MILLISECONDS);
return;
}
if (serviceObj.getLastRefTime() <= lastRefTime) {
updateServiceNow(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);
}
executor.schedule(this, serviceObj.getCacheMillis(), TimeUnit.MILLISECONDS);
lastRefTime = serviceObj.getLastRefTime();
} catch (Throwable e) {
NAMING_LOGGER.warn("[NA] failed to update serviceName: " + serviceName, e);
}
}
}
}
三.小結
本文從exmaple中的App入手,粗略分析了下NacosNamingService的初始化過程,初始化包含的類,同時分析下每個類的作用,從而瞭解了Nacos客戶端中關於服務註冊中相關的機制。接下來將進一步繼續分析其它exmaple中的程序。