Spring Cloud Nacos NacosWatch

1、注册

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.watch.enabled",
      matchIfMissing = true)
public NacosWatch nacosWatch(NacosServiceManager nacosServiceManager,
      NacosDiscoveryProperties nacosDiscoveryProperties) {
   return new NacosWatch(nacosServiceManager, nacosDiscoveryProperties);
}

2、NacosWatch

构造器

public NacosWatch(NacosServiceManager nacosServiceManager,
      NacosDiscoveryProperties properties) {
   this.nacosServiceManager = nacosServiceManager;
   this.properties = properties;
   this.taskScheduler = getTaskScheduler();
}

构造器中创建了一个线程池

private final ThreadPoolTaskScheduler taskScheduler;
private static ThreadPoolTaskScheduler getTaskScheduler() {
    ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
    taskScheduler.setBeanName("Nacos-Watch-Task-Scheduler");
    taskScheduler.initialize();
    return taskScheduler;
}

Scheduler的名称是:Nacos-Watch-Task-Scheduler

3、启动

NacosWatch的继承关系

public class NacosWatch
      implements ApplicationEventPublisherAware, SmartLifecycle, DisposableBean

NacosWatch实现了SmartLifecycle接口,实现了spring容器的生命周期的方法。

start方法

@Override
public void start() {
    //private final AtomicBoolean running = new AtomicBoolean(false); 标识符
    //这里用了cas方法,只会有一个线程获取到执行代码的权限
   if (this.running.compareAndSet(false, true)) {
       //生成一个事件监听,这里监听的是NamingEvent
       //private Map<String, EventListener> listenerMap = new ConcurrentHashMap<>(16);	监听器缓存
       //key的格式是 spring.application.name:group
      EventListener eventListener = listenerMap.computeIfAbsent(buildKey(),
            event -> new EventListener() {
               @Override
               public void onEvent(Event event) {
                   //监听器回调  判断是否NamingEvent 事件
                  if (event instanceof NamingEvent) {
                      //从事件中获取服务列表
                     List<Instance> instances = ((NamingEvent) event)
                           .getInstances();
                      //获取当前服务
                     Optional<Instance> instanceOptional = selectCurrentInstance(
                           instances);
                      //如果存在
                     instanceOptional.ifPresent(currentInstance -> {
                        //是否需要重设matadata
                         resetIfNeeded(currentInstance);
                     });
                  }
               }
            });
		//获取NamingService
      NamingService namingService = nacosServiceManager
            .getNamingService(properties.getNacosProperties());
      try {
          //订阅
         namingService.subscribe(properties.getService(), properties.getGroup(),
               Arrays.asList(properties.getClusterName()), eventListener);
      }
      catch (Exception e) {
         log.error("namingService subscribe failed, properties:{}", properties, e);
      }
		//启动定时器,默认30秒执行一次
      this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(
            this::nacosServicesWatch, this.properties.getWatchDelay());
   }
}

重设Metadata

private void resetIfNeeded(Instance instance) {
   if (!properties.getMetadata().equals(instance.getMetadata())) {
      properties.setMetadata(instance.getMetadata());
   }
}

发布HeartbeatEvent

public void nacosServicesWatch() {
	//由上面的定时器定时发布事件	默认30秒
   // nacos doesn't support watch now , publish an event every 30 seconds.
   this.publisher.publishEvent(
         new HeartbeatEvent(this, nacosWatchIndex.getAndIncrement()));

}

4、NamingService的订阅

入参是上面构建的EventListener,即NamingEvent

//com.alibaba.nacos.client.naming.NacosNamingService#subscribe
public void subscribe(String serviceName, String groupName, List<String> clusters, EventListener listener)
        throws NacosException {
    if (null == listener) {
        return;
    }
    //获取clusterString,将所有cluster按,拼接
    String clusterString = StringUtils.join(clusters, ",");
    //注册listener
    //private InstancesChangeNotifier changeNotifier;	在init方法中构造
    changeNotifier.registerListener(groupName, serviceName, clusterString, listener);
    //订阅
    //private NamingClientProxy clientProxy;
    //NamingClientProxyDelegate	在init方法中构造
    clientProxy.subscribe(serviceName, groupName, clusterString);
}

4.1、InstancesChangeNotifier注册listener

//com.alibaba.nacos.client.naming.event.InstancesChangeNotifier#registerListener
public void registerListener(String groupName, String serviceName, String clusters, EventListener listener) {
    //这样的格式 group@@service@@clusters
    String key = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters);
    //从缓存中获取
    //private final Map<String, ConcurrentHashSet<EventListener>> listenerMap = new ConcurrentHashMap<String, ConcurrentHashSet<EventListener>>();
    ConcurrentHashSet<EventListener> eventListeners = listenerMap.get(key);
    //双重检查锁,将listener放入缓存
    if (eventListeners == null) {
        synchronized (lock) {
            eventListeners = listenerMap.get(key);
            if (eventListeners == null) {
                eventListeners = new ConcurrentHashSet<EventListener>();
                listenerMap.put(key, eventListeners);
            }
        }
    }
    eventListeners.add(listener);
}

4.2、NamingClientProxyDelegate 的订阅

//com.alibaba.nacos.client.naming.remote.NamingClientProxyDelegate#subscribe
public ServiceInfo subscribe(String serviceName, String groupName, String clusters) throws NacosException {
    //group@@service
    String serviceNameWithGroup = NamingUtils.getGroupedName(serviceName, groupName);
    //group@@service@@clusters
    String serviceKey = ServiceInfo.getKey(serviceNameWithGroup, clusters);
    //服务信息修改
    serviceInfoUpdateService.scheduleUpdateIfAbsent(serviceName, groupName, clusters);
    //判断ServiceInfo中的serviceInfoMap有没有
    ServiceInfo result = serviceInfoHolder.getServiceInfoMap().get(serviceKey);
    //没有,grpc的订阅ServiceInfo,并返回
    if (null == result) {
        result = grpcClientProxy.subscribe(serviceName, groupName, clusters);
    }
    //处理服务
    serviceInfoHolder.processServiceInfo(result);
    return result;
}
4.2.1、服务信息修改
//com.alibaba.nacos.client.naming.core.ServiceInfoUpdateService#scheduleUpdateIfAbsent
public void scheduleUpdateIfAbsent(String serviceName, String groupName, String clusters) {
    //group@@service@@clusters
    String serviceKey = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters);
    //缓存,如果有,代表已经添加了task,不用重复添加
    //private final Map<String, ScheduledFuture<?>> futureMap = new HashMap<String, ScheduledFuture<?>>();
    if (futureMap.get(serviceKey) != null) {
        return;
    }
    //加锁
    synchronized (futureMap) {
        if (futureMap.get(serviceKey) != null) {
            return;
        }
        //添加定时任务  UpdateTask
        ScheduledFuture<?> future = addTask(new UpdateTask(serviceName, groupName, clusters));
        //放入缓存
        futureMap.put(serviceKey, future);
    }
}

添加UpdateTask

//com.alibaba.nacos.client.naming.core.ServiceInfoUpdateService#addTask
//private final ScheduledExecutorService executor;
//名称为:com.alibaba.nacos.client.naming.updater
//延迟一秒
private synchronized ScheduledFuture<?> addTask(UpdateTask task) {
    return executor.schedule(task, DEFAULT_DELAY, TimeUnit.MILLISECONDS);
}
public ServiceInfoUpdateService(Properties properties, ServiceInfoHolder serviceInfoHolder,
            NamingClientProxy namingClientProxy, InstancesChangeNotifier changeNotifier) {
        this.executor = new ScheduledThreadPoolExecutor(initPollingThreadCount(properties),
                new NameThreadFactory("com.alibaba.nacos.client.naming.updater"));
    this.serviceInfoHolder = serviceInfoHolder;
    this.namingClientProxy = namingClientProxy;
    this.changeNotifier = changeNotifier;
}
4.2.2、grpc的 订阅
//com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy#subscribe
public ServiceInfo subscribe(String serviceName, String groupName, String clusters) throws NacosException {
    //redo中放入ServiceInfo	redo标志为false
    redoService.cacheSubscriberForRedo(serviceName, groupName, clusters);
    //返回订阅结果
    return doSubscribe(serviceName, groupName, clusters);
}
//com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy#doSubscribe
public ServiceInfo doSubscribe(String serviceName, String groupName, String clusters) throws NacosException {
    //构建SubscribeServiceRequest
    SubscribeServiceRequest request = new SubscribeServiceRequest(namespaceId, groupName, serviceName, clusters,
            true);
    //获取请求结果
    SubscribeServiceResponse response = requestToServer(request, SubscribeServiceResponse.class);
    //将上面的redo的标志设置为true
    redoService.subscriberRegistered(serviceName, groupName, clusters);
    //返回订阅结果
    return response.getServiceInfo();
}
4.2.3、处理服务
//com.alibaba.nacos.client.naming.cache.ServiceInfoHolder#processServiceInfo
public ServiceInfo processServiceInfo(ServiceInfo serviceInfo) {
    String serviceKey = serviceInfo.getKey();
    if (serviceKey == null) {
        return null;
    }
    //获取当前保存的服务信息
    ServiceInfo oldService = serviceInfoMap.get(serviceInfo.getKey());
    //订阅获得的serviceInfo为空,或者获取到的健康的实例数量是空,直接返回
    if (isEmptyOrErrorPush(serviceInfo)) {
        //empty or error push, just ignore
        return oldService;
    }
    //将新的服务信息覆盖旧的服务信息
    serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
    //是否服务有变更
    boolean changed = isChangedServiceInfo(oldService, serviceInfo);
    //如果当前保存的JsonFromServer为空,直接保存为新的实例的JsonFromServer
    if (StringUtils.isBlank(serviceInfo.getJsonFromServer())) {
        serviceInfo.setJsonFromServer(JacksonUtils.toJson(serviceInfo));
    }
    //监控信息
    MetricsMonitor.getServiceInfoMapSizeMonitor().set(serviceInfoMap.size());
    //服务有变更  发布InstancesChangeEvent 事件,并且刷新磁盘缓存
    if (changed) {
        NAMING_LOGGER.info("current ips:(" + serviceInfo.ipCount() + ") service: " + serviceInfo.getKey() + " -> "
                + JacksonUtils.toJson(serviceInfo.getHosts()));
        NotifyCenter.publishEvent(new InstancesChangeEvent(serviceInfo.getName(), serviceInfo.getGroupName(),
                serviceInfo.getClusters(), serviceInfo.getHosts()));
        DiskCache.write(serviceInfo, cacheDir);
    }
    return serviceInfo;
}

判断服务信息是否有变更

private boolean isChangedServiceInfo(ServiceInfo oldService, ServiceInfo newService) {
    //当前服务信息为空,直接返回true,因为上面方法中有判断新获取的服务信息是否为空
    if (null == oldService) {
        NAMING_LOGGER.info("init new ips(" + newService.ipCount() + ") service: " + newService.getKey() + " -> "
                + JacksonUtils.toJson(newService.getHosts()));
        return true;
    }
    if (oldService.getLastRefTime() > newService.getLastRefTime()) {
        NAMING_LOGGER
                .warn("out of date data received, old-t: " + oldService.getLastRefTime() + ", new-t: " + newService
                        .getLastRefTime());
    }
    boolean changed = false;
    //将当前的服务信息组装成map
    Map<String, Instance> oldHostMap = new HashMap<String, Instance>(oldService.getHosts().size());
    for (Instance host : oldService.getHosts()) {
        oldHostMap.put(host.toInetAddr(), host);
    }
    //将新获得的服务信息组装成map
    Map<String, Instance> newHostMap = new HashMap<String, Instance>(newService.getHosts().size());
    for (Instance host : newService.getHosts()) {
        newHostMap.put(host.toInetAddr(), host);
    }
    //声明 修改的实例,新的服务实例,移除的服务实例
    Set<Instance> modHosts = new HashSet<Instance>();
    Set<Instance> newHosts = new HashSet<Instance>();
    Set<Instance> remvHosts = new HashSet<Instance>();
    //将新获得的服务实例信息做成一个entry list
    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();
        //Instance 的toString方法是重写过的,如果当前服务实例和新获得的服务实例不一样,
        //将该服务实例信息放入 modHosts
        if (oldHostMap.containsKey(key) && !StringUtils.equals(host.toString(), oldHostMap.get(key).toString())) {
            modHosts.add(host);
            continue;
        }
        //如果当前保存的服务实例信息中没有新获得的服务实例,将新获得的服务实例放入 newHosts
        if (!oldHostMap.containsKey(key)) {
            newHosts.add(host);
        }
    }
    //遍历当前保存的服务实例信息map
    for (Map.Entry<String, Instance> entry : oldHostMap.entrySet()) {
        Instance host = entry.getValue();
        String key = entry.getKey();
        //如果新的服务实例map中没有当前保存的服务实例信息map中的实例,将其放入 remvHosts
        if (newHostMap.containsKey(key)) {
            continue;
        }
        
        if (!newHostMap.containsKey(key)) {
            remvHosts.add(host);
        }
        
    }
    //如果 newHosts 中有元素,代表服务实例发生了变更
    if (newHosts.size() > 0) {
        changed = true;
        NAMING_LOGGER
                .info("new ips(" + newHosts.size() + ") service: " + newService.getKey() + " -> " + JacksonUtils
                        .toJson(newHosts));
    }
    //如果 remvHosts 中有元素,代表服务实例发生了变更
    if (remvHosts.size() > 0) {
        changed = true;
        NAMING_LOGGER.info("removed ips(" + remvHosts.size() + ") service: " + newService.getKey() + " -> "
                + JacksonUtils.toJson(remvHosts));
    }
    //如果 modHosts 中有元素,代表服务实例发生了变更
    if (modHosts.size() > 0) {
        changed = true;
        NAMING_LOGGER.info("modified ips(" + modHosts.size() + ") service: " + newService.getKey() + " -> "
                + JacksonUtils.toJson(modHosts));
    }
    return changed;
}

5、UpdateTask

上面 4.2.1 讲到添加了一个UpdateTask 到 ScheduledFuture,然后延迟一秒钟。下面看看这个task做了什么。

UpdateTask 是ServiceInfoUpdateService的内部类,实现了Runnable接口,是一个线程。

public class UpdateTask implements Runnable {
    long lastRefTime = Long.MAX_VALUE;
    private final String serviceName;
    private final String groupName;
    private final String clusters;
    private final String groupedServiceName;
    private final String serviceKey;
    //1:连不上服务端,2:获取到的服务实例是空
    private int failCount = 0;
	//构造器,初始化参数
    public UpdateTask(String serviceName, String groupName, String clusters) {
        this.serviceName = serviceName;
        this.groupName = groupName;
        this.clusters = clusters;
        this.groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
        this.serviceKey = ServiceInfo.getKey(groupedServiceName, clusters);
    }

    @Override
    public void run() {
        //保存一个延迟时间
        long delayTime = DEFAULT_DELAY;

        try {
            //当前没有订阅 并且 没有执行过updateTask
            if (!changeNotifier.isSubscribed(groupName, serviceName, clusters) && !futureMap.containsKey(serviceKey)) {
                NAMING_LOGGER
                    .info("update task is stopped, service:" + groupedServiceName + ", clusters:" + clusters);
                return;
            }
			//获取当前保存的服务实例信息
            ServiceInfo serviceObj = serviceInfoHolder.getServiceInfoMap().get(serviceKey);
            //如果没有服务实例信息
            if (serviceObj == null) {
                //从服务端查询实例信息
                serviceObj = namingClientProxy.queryInstancesOfService(serviceName, groupName, clusters, 0, false);
                //处理服务信息
                serviceInfoHolder.processServiceInfo(serviceObj);
                //保存lastRefTime
                lastRefTime = serviceObj.getLastRefTime();
                return;
            }
			//如果当前保存的实例信息的 LastRefTime <= UpdateTask 缓存的 lastRefTime
            //说明应该从新拉取
            if (serviceObj.getLastRefTime() <= lastRefTime) {
                serviceObj = namingClientProxy.queryInstancesOfService(serviceName, groupName, clusters, 0, false);
                serviceInfoHolder.processServiceInfo(serviceObj);
            }
            //保存最新的 lastRefTime
            lastRefTime = serviceObj.getLastRefTime();
            //如果当前获取到的  增加失败次数 最大6次
            if (CollectionUtils.isEmpty(serviceObj.getHosts())) {
                incFailCount();
                return;
            }
            //拉取成功,重设 delayTime	delayTime * 6,重设失败次数
            // TODO multiple time can be configured.
            delayTime = serviceObj.getCacheMillis() * DEFAULT_UPDATE_CACHE_TIME_MULTIPLE;
            resetFailCount();
        } catch (Throwable e) {
            incFailCount();
            NAMING_LOGGER.warn("[NA] failed to update serviceName: " + groupedServiceName, e);
        } finally {
            //重设延迟时间,delayTime 左移 失败次数位 与 60秒的较小值
            //如果拉取成功,delayTime 是 6s,failCount 是0
            //如果拉取失败,delayTime 是 1s,failCount 是失败次数,相当于做了一个衰减重试
            //当失败次数达到6次的时候,左移结果是64s,这时会选择60秒作为重试的延迟时间
            executor.schedule(this, Math.min(delayTime << failCount, DEFAULT_DELAY * 60), TimeUnit.MILLISECONDS);
        }
    }

    private void incFailCount() {
        int limit = 6;
        if (failCount == limit) {
            return;
        }
        failCount++;
    }

    private void resetFailCount() {
        failCount = 0;
    }
}
//com.alibaba.nacos.client.naming.remote.NamingClientProxyDelegate#queryInstancesOfService
public ServiceInfo queryInstancesOfService(String serviceName, String groupName, String clusters, int udpPort,
        boolean healthyOnly) throws NacosException {
    return grpcClientProxy.queryInstancesOfService(serviceName, groupName, clusters, udpPort, healthyOnly);
}
//com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy#queryInstancesOfService
public ServiceInfo queryInstancesOfService(String serviceName, String groupName, String clusters, int udpPort,
        boolean healthyOnly) throws NacosException {
    ServiceQueryRequest request = new ServiceQueryRequest(namespaceId, serviceName, groupName);
    request.setCluster(clusters);
    request.setHealthyOnly(healthyOnly);
    request.setUdpPort(udpPort);
    //请求服务端获取实例信息
    QueryServiceResponse response = requestToServer(request, QueryServiceResponse.class);
    return response.getServiceInfo();
}

5 和 4.2 中 都有serviceInfoHolder.processServiceInfo(serviceObj),感觉只是为了防漏。

6、InstancesChangeEvent的监听

//com.alibaba.nacos.client.naming.event.InstancesChangeNotifier#onEvent
public void onEvent(InstancesChangeEvent event) {
    String key = ServiceInfo
            .getKey(NamingUtils.getGroupedName(event.getServiceName(), event.getGroupName()), event.getClusters());
    ConcurrentHashSet<EventListener> eventListeners = listenerMap.get(key);
    if (CollectionUtils.isEmpty(eventListeners)) {
        return;
    }
    for (final EventListener listener : eventListeners) {
        final com.alibaba.nacos.api.naming.listener.Event namingEvent = transferToNamingEvent(event);
        if (listener instanceof AbstractEventListener && ((AbstractEventListener) listener).getExecutor() != null) {
            ((AbstractEventListener) listener).getExecutor().execute(() -> listener.onEvent(namingEvent));
        } else {
            listener.onEvent(namingEvent);
        }
    }
}
private com.alibaba.nacos.api.naming.listener.Event transferToNamingEvent(
        InstancesChangeEvent instancesChangeEvent) {
    return new NamingEvent(instancesChangeEvent.getServiceName(), instancesChangeEvent.getGroupName(),
            instancesChangeEvent.getClusters(), instancesChangeEvent.getHosts());
}

将InstancesChangeEvent 事件包装成 NamingEvent,NacosWatch中start方法中包装的NamingEvent的监听就会收到事件。从而更改NacosWatch#properties中Metadata的值。

7、HeartbeatEvent

该事件会在gateway中监听。

org.springframework.cloud.gateway.route.RouteRefreshListener#onApplicationEvent

cloud.dubbo中也会监听。

com.alibaba.cloud.dubbo.autoconfigure.DubboServiceDiscoveryAutoConfiguration#onHeartbeatEvent

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值