属性说明
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;
serviceInfoMap: 记录service信息 key 为 serviceName@@clusterName 类型为:
ConcurrentHashMap,
updatingMap: 记录正在更新的service信息,记录service信息 key 为 serviceName@@clusterName 类型为ConcurrentHashMap, 并发控制同一时刻同样的service只有一个线程执行请求更新操作
pushReceiver: 创建udp监听端口并监听Nacos Server 发送过来的数据
eventDispatcher: 服务变更事件监听器
cacheDir: 服务持久化目录(在服务启动时如果设置了【namingLoadCacheAtStart】属性自动总该目录加载service列表 减轻服务的压力)
executor: 1秒钟执行一次的服务更新任务执行器
futureMap: 并发控制服务更新任务的执行, 保证同一时刻同一个服务只有一个更新任务在执行。
futureMap 和 updatingMap 都是控制并发,但是场景不一样控制的操作不一样,前者时控制定时任务的执行后者控制远程服务的请求
初始化
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) {
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;
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);
}
初始化方法注意初始化上面定义的属性,其中 loadCacheAtStart:控制是否服务启动时候从缓存 文件加载服务列表。
执行服务更新任务的线程池大小默认为:cpu cores / 2
服务查询
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);
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 ServiceInfo getServiceInfo0(String serviceName, String clusters) {
String key = ServiceInfo.getKey(serviceName, clusters);
return serviceInfoMap.get(key);
}
如果本地serviceInfoMap中么有找到该服务,则调用方法 updateServiceNow(serviceName, clusters); 拉取服务并保存到本地serviceInfoMap中。并设置服务更新状态;
如果本地已经该服务且有线程在执行更新操作则等待,时间是5秒。等待updateServiceNow方法执行完毕就唤醒。
if (oldService != null) {
synchronized (oldService) {
oldService.notifyAll();
}
}
否则直接从本地serviceInfoMap中返回。同时注册定时更新任务。
updateServiceNow(服务更新)
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 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;
...
Set<Instance> modHosts = new HashSet<Instance>();
Set<Instance> newHosts = new HashSet<Instance>();
Set<Instance> remvHosts = new HashSet<Instance>();
...
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());
...
return serviceInfo;
}
通过NamingProxy请求远程服务列表信息,然后通过Json反序列化成ServiceInfo对象,校验新对象的有效的host信息是否不为空且是所有的IP, 否则还是返回老的ServiceInfo。比较新老对象的差异(统计出新增 修改 删除的host 列表)如果新老对象有变化, 则触发服务变更事件eventDispatcher.serviceChanged(serviceInfo); 同时缓存新服务到本地serviceInfoMap和文件缓存。
if (serviceInfo.getHosts() == null || !serviceInfo.validate()) {
//empty or error push, just ignore
return oldService;
}
class ServiceInfo {
...
public boolean validate() {
if (isAllIPs()) {
return true;
}
List<Instance> validHosts = new ArrayList<Instance>();
for (Instance host : hosts) {
if (!host.isHealthy()) {
continue;
}
for (int i = 0; i < host.getWeight(); i++) {
validHosts.add(host);
}
}
return true;
}
...
}
scheduleUpdateIfAbsent(服务更新定时任务)
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 synchronized ScheduledFuture<?> addTask(UpdateTask task) {
return executor.schedule(task, DEFAULT_DELAY, TimeUnit.MILLISECONDS);
}
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);
}
}
}
通过 futureMap 控制任务的并发,如果更新任务还在执行就不处理。否则添加更新任务 注意这里用了synchronize 关键字做了二次校验,是典型的synchronize 用法,这里不赘述。
添加任务以1秒中后执行
执行逻辑:
如果当前缓存中没有那么直接从远程服务拉取服务,1秒钟后再次执行更新任务
如果缓存中服务的lastRefTime小于当前的lastRefTime字段,说明缓存中服务已经过期,需要重新拉取更新。
如果缓存中服务的lastRefTime大于当前的lastRefTime字段 则 执行refreshOnly(serviceName, clusters) 方法
什么情况下缓存的数据会跟任务中的lastRefTime时间不一致呢?
比如服务端主动推送服务列表到客户端更新缓存
服务端主动推动逻辑下一章节解析