Nacos源码学习系列第8篇客户端服务管理类HostReactor

144 阅读2分钟

属性说明

    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时间不一致呢?

比如服务端主动推送服务列表到客户端更新缓存

服务端主动推动逻辑下一章节解析