Nacos源码(六):客户端服务发现源码分析

发布时间 2023-12-08 17:03:21作者: 无虑的小猪

1、客户端服务发现源码入口

  在Nacos源码(二):客户端服务注册源码分析中,在nacos-2.2.0源码包中提供的nacos-example的NamingExample示例中,可以发现客户端的服务发现是在NamingService的getAllInstances方法中完成的。

0

  NamingService中的getAllInstances有许多重载的额方法,可以满足各种业务场景下获取实例信息。

0

  最终都会执行 getAllInstances(String serviceName, String groupName, List clusters, boolean subscribe) 方法。

 1 /**
 2  * 获取服务实例列表
 3  */
 4 public List<Instance> getAllInstances(String serviceName, String groupName, List<String> clusters,
 5         boolean subscribe) throws NacosException {
 6     ServiceInfo serviceInfo;
 7     String clusterString = StringUtils.join(clusters, ",");
 8     // 是否是订阅模式
 9     if (subscribe) {
10         // 先从客户端缓存获取服务信息
11         serviceInfo = serviceInfoHolder.getServiceInfo(serviceName, groupName, clusterString);
12         if (null == serviceInfo || !clientProxy.isSubscribed(serviceName, groupName, clusterString)) {
13             //  若本地缓存不存在服务信息,则进行订阅
14             serviceInfo = clientProxy.subscribe(serviceName, groupName, clusterString);
15         }
16     } else {
17         // 若未订阅服务信息,直接从Nacos服务器进行查询
18         serviceInfo = clientProxy.queryInstancesOfService(serviceName, groupName, clusterString, 0, false);
19     }
20     //  从服务信息中获取实例列表
21     List<Instance> list;
22     if (serviceInfo == null || CollectionUtils.isEmpty(list = serviceInfo.getHosts())) {
23         return new ArrayList<>();
24     }
25     // 返回实例列表
26     return list;
27 }

2、客户端的服务发现主要步骤

2.1、获取服务信息serviceInfo

  若客户端是订阅模式,则直接从客户端本地缓存中获取服务信息;若本地缓存中没有,则为首次调用,先进行订阅,在订阅完成后获取服务信息。

  若客户端是非订阅模式,直接请求Nacos服务端,获取服务信息。

2.2、从服务信息中获取实例列表

3、本地缓存获取服务信息

  从服务信息包装类中获取本地缓存的服务信息,ServiceInfoHolder#getServiceInfo(...)

// 本地缓存服务信息
private final ConcurrentMap<String, ServiceInfo> serviceInfoMap;

/**
 * 从客户端本地缓存中获取服务信息
 */
public ServiceInfo getServiceInfo(final String serviceName, final String groupName, final String clusters) {
    String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
    // 获取key clusters不为空时:key = groupedServiceName@@clusters;为空时,key = groupedServiceName
    String key = ServiceInfo.getKey(groupedServiceName, clusters);
    // 故障转移处理
    if (failoverReactor.isFailoverSwitch()) {
        return failoverReactor.getService(key);
    }
    // 根据key获取本地缓存的服务信息ServiceInfo
    return serviceInfoMap.get(key);
}

  ServiceInfoHolder 维护一个本地缓存 serviceInfoMap,根据指定的key获取缓存中的服务信息返回。

  涉及客户端的故障转移处理,在后续进行分析。

4、订阅并返回服务信息

  本地缓存中没有服务实例信息,执行订阅,获取返回的服务信息。

 

 1 /**
 2  * 执行订阅,并返回实例信息
 3  */
 4 @Override
 5 public ServiceInfo subscribe(String serviceName, String groupName, String clusters) throws NacosException {
 6     // serviceNameWithGroup = groupName@@serviceName
 7     String serviceNameWithGroup = NamingUtils.getGroupedName(serviceName, groupName);
 8     // serviceKey = serviceNameWithGroup@@clusters || serviceNameWithGroup
 9     String serviceKey = ServiceInfo.getKey(serviceNameWithGroup, clusters);
10     // 定时调度 服务信息更新服务任务 UpdateTask
11     serviceInfoUpdateService.scheduleUpdateIfAbsent(serviceName, groupName, clusters);
12     // 获取服务信息
13     ServiceInfo result = serviceInfoHolder.getServiceInfoMap().get(serviceKey);
14     if (null == result || !isSubscribed(serviceName, groupName, clusters)) {
15         // 若服务实例信息为null,基于gRPC协议进行订阅逻辑处理,并获取Nacos服务端返回的服务信息
16         result = grpcClientProxy.subscribe(serviceName, groupName, clusters);
17     }
18     // 客户端本地缓存从Nacos服务端获取的服务实例信息
19     serviceInfoHolder.processServiceInfo(result);
20     return result;
21 }

4.1、服务信息更新任务UpdateTask

  若客户端已订阅,执行服务信息更新任务 ServiceInfoUpdateService#UpdateTask,UpdateTask是ServiceInfoUpdateService的内部类,更新服务信息实际上是发起请求获取Nacos服务端的服务信息,再缓存到客户端本地。

0

  若首次执行,则不会执行此定时任务,需要先完成订阅,才能进行本地缓存与服务端的ServiceInfo信息同步。

0

4.2、订阅

  再次从本地缓存中获取服务信息,若还是无法获取到,基于gRPC协议进行订阅逻辑处理,并获取Nacos服务端返回的服务信息,将服务信息缓存到客户端。

  NamingGrpcClientProxy#subscribe 详情如下:

0

5、客户端服务发现流程

0
  1、非订阅模式下,直接从Nacos服务端获取ServiceInfo
  2、订阅模式下,则直接从客户端本地缓存中获取服务信息;若本地缓存中没有,则为首次调用,先进行订阅,在订阅完成后获取服务信息。
  3、订阅方法先开启定时任务UpdateTask,用来定时同步服务端的实例信息,若服务实例发生变化,进行本地缓存更新等操作。
  4、判断本地缓存是否存在,若本地缓存存在ServiceInfo信息,则直接返回。如果不存在,则默认采用gRPC协议进行订阅,grpcClientProxy的subscribe直接向服务器发送了一个订阅请求,并返回结果ServiceInfo。
  5、ServiceInfo本地缓存处理,获得服务端最新的ServiceInfo与本地内存中的ServiceInfo进行比较,更新,发布变更时间,磁盘文件存储。