Nacos源码分析04-客户端服务发现与服务订阅

1,809 阅读10分钟

本系列博客,采用官方源码版本为2.0.3

1. 什么是服务发现

服务发现模型

2. Nacos服务发现API

  我们再次回到【客户端服务注册】中探讨过的测试用例com.alibaba.nacos.client.NamingTest

public class NamingTest {
    
    @Test
    public void testServiceList() throws Exception {
        
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8848");
        properties.put(PropertyKeyConst.USERNAME, "nacos");
        properties.put(PropertyKeyConst.PASSWORD, "nacos");
        
        Instance instance = new Instance();
        instance.setIp("1.1.1.1");
        instance.setPort(800);
        instance.setWeight(2);
        Map<String, String> map = new HashMap<String, String>();
        map.put("netType", "external");
        map.put("version", "2.0");
        instance.setMetadata(map);
    
        NamingService namingService = NacosFactory.createNamingService(properties);
        namingService.registerInstance("nacos.test.1", instance);
        
        ThreadUtils.sleep(5000L);
        
        //①:获取全部nacos.test.1实例
        List<Instance> list = namingService.getAllInstances("nacos.test.1");
        
        System.out.println(list);
        
        ThreadUtils.sleep(30000L);
        //        ExpressionSelector expressionSelector = new ExpressionSelector();
        //        expressionSelector.setExpression("INSTANCE.metadata.registerSource = 'dubbo'");
        //        ListView<String> serviceList = namingService.getServicesOfServer(1, 10, expressionSelector);
        
    }
}

3. 命名服务 NamingService

3.1. 服务发现 getAllInstances()

  通过观察NamingTest,我们可以发现namingService的getAllInstances方法(用例中①处)就是服务发现的入口,内部涉及通信协议、订阅流程、本地缓存、故障转移等。

  namingService在【客户端服务注册】篇已分析过,此处简单略过。getAllInstances(String serviceName)方法会经过一系列的重载方法调用,最终调用getAllInstances(String serviceName, String groupName, List clusters, boolean subscribe)方法。

  最终方法比入口多出了几个参数,不仅有服务名称,还有分组名、集群列表、是否订阅,重载方法中的其他参数已经在各种重载方法的调用过程中设置了默认值。分组名称默认:DEFAULT_GROUOP;集群列表:默认为空数组;是否为订阅模式:订阅模式。

@Override
public List<Instance> getAllInstances(String serviceName) throws NacosException {
    // 为了提高学习效率,此处压缩了重载方法过程
    return getAllInstances(serviceName, Constants.DEFAULT_GROUP, new ArrayList<String>(), true);
}
 
@Override
public List<Instance> getAllInstances(String serviceName, String groupName, List<String> clusters,
        boolean subscribe) throws NacosException {
    ServiceInfo serviceInfo;
    String clusterString = StringUtils.join(clusters, ",");
    // 是否为订阅模式
    if (subscribe) {
        // 先从本地缓存获取服务信息
        serviceInfo = serviceInfoHolder.getServiceInfo(serviceName, groupName, clusterString);
        // 如果本地缓存不存在服务信息,则进行订阅
        if (null == serviceInfo) {
            serviceInfo = clientProxy.subscribe(serviceName, groupName, clusterString);
        }
    } else {
        // 如果未订阅服务信息,则直接从服务器进行查询
        serviceInfo = clientProxy.queryInstancesOfService(serviceName, groupName, clusterString, 0, false);
    }
    // 从服务信息中获取实例列表
    List<Instance> list;
    if (serviceInfo == null || CollectionUtils.isEmpty(list = serviceInfo.getHosts())) {
        return new ArrayList<Instance>();
    }
    return list;
}

流程图

  如果是订阅模式,则直接从本地缓存获取服务信息(ServiceInfo),然后从中获取实例列表,这是因为订阅机制会自动同步服务器实例的变化到本地。如果本地缓存中没有,那说明是首次调用,则进行订阅,在订阅完成后会获得服务信息。

  如果是非订阅模式,那就直接请求服务器端,获得服务信息。

3.2. 服务订阅 subscribe()

  服务订阅是服务发现的一种实现方式,就是在服务发现的时候执行订阅方法,触发定时任务去拉取服务端的数据。

  subscribe()和getAllInstances()一样都经过了各种重载方法,此处不再赘述,直接查看最终调用函数。

@Override
public void subscribe(String serviceName, String groupName, List<String> clusters, EventListener listener)
    throws NacosException {
    if (null == listener) {
        return;
    }
    String clusterString = StringUtils.join(clusters, ",");
    //注册监听事件
    changeNotifier.registerListener(groupName, serviceName, clusterString, listener);
    //订阅服务
    clientProxy.subscribe(serviceName, groupName, clusterString);
}

3.2.1. 注册监听事件

changeNotifier.registerListener(groupName, serviceName, clusterString, listener);

  服务订阅方法中会调用changeNotifier.registerListener方法来进行具体监听器注册。

  可以看出,事件的注册便是将EventListener存储在InstancesChangeNotifier的listenerMap属性当中了。listenerMap属性的实际类型是ConcurrentHashMap<String, ConcurrentHashSet>,key是服务,value是监听事件的集合。

public void registerListener(String groupName, String serviceName, String clusters, EventListener listener) {
    String key = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters);
    ConcurrentHashSet<EventListener> eventListeners = listenerMap.get(key);
    if (eventListeners == null) {
        synchronized (lock) {
            eventListeners = listenerMap.get(key);
            if (eventListeners == null) {
                eventListeners = new ConcurrentHashSet<EventListener>();
                //将EventListener缓存到listenerMap
                listenerMap.put(key, eventListeners);
            }
        }
    }
    eventListeners.add(listener);
}

4. 服务订阅

4.1. clientProxy.subscribe

  综上所述,subscribe()和getAllInstances()中都有通过clientProxy进行订阅的代码,所以Nacos服务发现与服务订阅是分不开的。想要学透服务发现,就必须清楚服务订阅的流程。

serviceInfo = clientProxy.subscribe(serviceName, groupName, clusterString);

  首先clientProxy是NamingClientProxy类的对象,对应的实现类是NamingClientProxyDelegate。

@Override
public ServiceInfo subscribe(String serviceName, String groupName, String clusters) throws NacosException {
    String serviceNameWithGroup = NamingUtils.getGroupedName(serviceName, groupName);
    String serviceKey = ServiceInfo.getKey(serviceNameWithGroup, clusters);
    // 定时调度UpdateTask
    serviceInfoUpdateService.scheduleUpdateIfAbsent(serviceName, groupName, clusters);
    // 获取缓存中的ServiceInfo
    ServiceInfo result = serviceInfoHolder.getServiceInfoMap().get(serviceKey);
    if (null == result) {
        // 如果为null,则进行订阅逻辑处理,基于gRPC协议
        result = grpcClientProxy.subscribe(serviceName, groupName, clusters);
    }
    // ServiceInfo本地缓存处理
    serviceInfoHolder.processServiceInfo(result);
    return result;
}

流程图

  订阅方法先开启定时任务,这个定时任务的主要作用就是用来定时同步服务端的实例信息,并进行本地缓存更新等操作,但是如果是首次这里将会直接返回来走下一步。

  判断本地缓存是否存在,如果本地缓存存在ServiceInfo信息,则直接返回。如果不存在,则默认采用gRPC协议进行订阅,并返回ServiceInfo。

  grpcClientProxy的subscribe订阅方法就是直接向服务器发送了一个订阅请求,并返回结果。

  最后,ServiceInfo本地缓存处理。这里会将获得的最新ServiceInfo与本地内存中的ServiceInfo进行比较,更新,发布变更时间,磁盘文件存储等操作。其实,这一步的操作,在订阅定时任务中也进行了处理。

5. 定时任务

5.1. 创建定时任务

serviceInfoUpdateService.scheduleUpdateIfAbsent(serviceName, groupName, clusters);

  订阅流程涉及内容比较多,细节比较复杂,所以我们主要学习核心部分。

流程图

  serviceInfoUpdateService中的scheduleUpdateIfAbsent函数,顾名思义就是如果不存在定时更新任务就创建。该方法包含了构建serviceKey、通过serviceKey判断重复、最后创建UpdateTask。其中最重要的addTask方法就是延迟一秒执行一个更新任务。

public void scheduleUpdateIfAbsent(String serviceName, String groupName, String clusters) {
    String serviceKey = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters);
    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);
    }
}

private synchronized ScheduledFuture<?> addTask(UpdateTask task) {
    return executor.schedule(task, DEFAULT_DELAY, TimeUnit.MILLISECONDS);
}

5.2. 执行定时任务

  ServiceInfoUpdateService.UpdateTask便是定时更新任务,实现了Runnable接口。

public class UpdateTask implements Runnable {

    long lastRefTime = Long.MAX_VALUE;

    //省略一些服务的基本属性

    /**
     * 失败的情况。1:无法连接到服务器 2:serviceInfo的实例是空的
     * 最大为6
     */
    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 {
            // 判断是服务是否已暂定
            if (!changeNotifier.isSubscribed(groupName, serviceName, clusters) && !futureMap.containsKey(serviceKey)) {
                NAMING_LOGGER
                        .info("update task is stopped, service:" + groupedServiceName + ", clusters:" + clusters);
                return;
            }
            
            //获取本地缓存中的service信息
            ServiceInfo serviceObj = serviceInfoHolder.getServiceInfoMap().get(serviceKey);
            if (serviceObj == null) {
                //直接从服务的拉去最新的Service信息
                serviceObj = namingClientProxy.queryInstancesOfService(serviceName, groupName, clusters, 0, false);
                serviceInfoHolder.processServiceInfo(serviceObj);
                lastRefTime = serviceObj.getLastRefTime();
                return;
            }
            
            // 如果服务的最新更新时间小于等于缓存刷新(最后一次拉取数据的时间)时间,从注册中心重新查询
            if (serviceObj.getLastRefTime() <= lastRefTime) {
                serviceObj = namingClientProxy.queryInstancesOfService(serviceName, groupName, clusters, 0, false);
                serviceInfoHolder.processServiceInfo(serviceObj);
            }
            lastRefTime = serviceObj.getLastRefTime();
            
            //如果拉取到的服务没有实例,则记为失败
            if (CollectionUtils.isEmpty(serviceObj.getHosts())) {
                incFailCount();
                return;
            }
            
            // 下次更新缓存时间设置,默认6秒(1000ms * 6)
            // TODO multiple time can be configured.
            delayTime = serviceObj.getCacheMillis() * DEFAULT_UPDATE_CACHE_TIME_MULTIPLE;
            
            // 重置失败数量为0(可能会出现失败情况,没有ServiceInfo,连接失败)
            resetFailCount();
        } catch (Throwable e) {
            incFailCount();
            NAMING_LOGGER.warn("[NA] failed to update serviceName: " + groupedServiceName, e);
        } finally {
            executor.schedule(this, Math.min(delayTime << failCount, DEFAULT_DELAY * 60), TimeUnit.MILLISECONDS);
        }
    }

    private void incFailCount();

    private void resetFailCount();
}

流程图

6. serviceInfoHolder.processServiceInfo

serviceInfoHolder.processServiceInfo(serviceObj);

  只要Nacos客户端从服务端拉去一次服务信息,便会调用一次serviceInfoHolder.processServiceInfo方法。这个方法除了会在内存中记录最新的的ServiceInfo数据,还会比对ServiceInfo是否发生了变更。如果ServiceInfo发生了变更,便会发布一个InstancesChangeEvent事件,同时将ServiceInfo写入本地缓存。

public ServiceInfo processServiceInfo(ServiceInfo serviceInfo) {
    String serviceKey = serviceInfo.getKey();
    if (serviceKey == null) {
        return null;
    }
    ServiceInfo oldService = serviceInfoMap.get(serviceInfo.getKey());
    if (isEmptyOrErrorPush(serviceInfo)) {
        //empty or error push, just ignore
        return oldService;
    }
    // 缓存服务信息
    serviceInfoMap.put(serviceInfo.getKey(), serviceInfo);
    // 判断注册的实例信息是否已变更
    boolean changed = isChangedServiceInfo(oldService, serviceInfo);
    if (StringUtils.isBlank(serviceInfo.getJsonFromServer())) {
        serviceInfo.setJsonFromServer(JacksonUtils.toJson(serviceInfo));
    }
    // 监控服务监控缓存Map的大小
    MetricsMonitor.getServiceInfoMapSizeMonitor().set(serviceInfoMap.size());
    // 服务实例以更变
    if (changed) {
        NAMING_LOGGER.info("current ips:({}) service: {} -> {}", serviceInfo.ipCount(), serviceInfo.getKey(),
                           JacksonUtils.toJson(serviceInfo.getHosts()));
        // 添加实例变更事件,会被订阅者执行
        NotifyCenter.publishEvent(new InstancesChangeEvent(serviceInfo.getName(), serviceInfo.getGroupName(),
                                                           serviceInfo.getClusters(), serviceInfo.getHosts()));
        // 记录Service本地文件
        DiskCache.write(serviceInfo, cacheDir);
    }
    return serviceInfo;
}

流程图

7. 通知体系

  整体服务订阅的事件机制还是比较复杂的,因为用到了事件的形式,逻辑比较绕,并且其中还有守护线程,死循环,阻塞队列等。

  需要重点理解NotifyCenter对事件发布者、事件订阅者和事件之间关系的维护,而这一关系的维护的入口就位于NacosNamingService的init方法当中。

7.1. 发布通知

NotifyCenter发布通知流程

  NotifyCenter中进行事件发布时,会根据InstancesChangeEvent事件类型获得对应的CanonicalName。将CanonicalName作为key,从NotifyCenter.publisherMap中获取对应的事件发布者(EventPublisher)。最后EventPublisher将InstancesChangeEvent事件进行发布。

private static boolean publishEvent(final Class<? extends Event> eventType, final Event event) {
    if (ClassUtils.isAssignableFrom(SlowEvent.class, eventType)) {
        return INSTANCE.sharePublisher.publish(event);
    }
	
    // 根据InstancesChangeEvent事件类型,获得对应的CanonicalName
    final String topic = ClassUtils.getCanonicalName(eventType);

    // 将CanonicalName作为Key,从NotifyCenter#publisherMap中获取对应的事件发布者(EventPublisher)
    EventPublisher publisher = INSTANCE.publisherMap.get(topic);
    if (publisher != null) {
        // 事件发布者publisher发布事件(InstancesChangeEvent)
        return publisher.publish(event);
    }
    LOGGER.warn("There are no [{}] publishers for this event, please register", topic);
    return false;
}

7.2. 通知中心的初始化

  观察NotifyCenter的源码可以发现,INSTANCE.publisherMap中的EventPublisher是需要提前从外部注册进来。

public static EventPublisher registerToPublisher(final Class<? extends Event> eventType, final int queueMaxSize) {
    return registerToPublisher(eventType, DEFAULT_PUBLISHER_FACTORY, queueMaxSize);
}

  NacosNamingService在初始化过程中完成了InstancesChangeEvent对应的EventPublisher的注册。

private void init(Properties properties) throws NacosException {
    //........
    
    //初始化变更事件通知器
    this.changeNotifier = new InstancesChangeNotifier();
    NotifyCenter.registerToPublisher(InstancesChangeEvent.class, 16384);
    NotifyCenter.registerSubscriber(changeNotifier);
    
    //.....
}

  外部通常会调用registerToPublisher(final Class<? extends Event> eventType, final int queueMaxSize) 函数,registerToPublisher方法就会发现默认采用了DEFAULT_PUBLISHER_FACTORY(默认发布者工厂)来进行构建。

public static EventPublisher registerToPublisher(final Class<? extends Event> eventType,
        final EventPublisherFactory factory, final int queueMaxSize) {
    if (ClassUtils.isAssignableFrom(SlowEvent.class, eventType)) {
        return INSTANCE.sharePublisher;
    }
    
    final String topic = ClassUtils.getCanonicalName(eventType);
    synchronized (NotifyCenter.class) {
        // MapUtils.computeIfAbsent is a unsafe method.
        MapUtil.computeIfAbsent(INSTANCE.publisherMap, topic, factory, eventType, queueMaxSize);
    }
    return INSTANCE.publisherMap.get(topic);
}

7.3. DefaultPublisher

  在NotifyCenter中静态代码块,会发现DEFAULT_PUBLISHER_FACTORY默认构建的EventPublisher为DefaultPublisher的实例。

static {
    //省略大片代码
    
    if (iterator.hasNext()) {
        clazz = iterator.next().getClass();
    } else {
        clazz = DefaultPublisher.class;
    }
        
    DEFAULT_PUBLISHER_FACTORY = (cls, buffer) -> {
        try {
            EventPublisher publisher = clazz.newInstance();
            publisher.init(cls, buffer);
            return publisher;
        } catch (Throwable ex) {
            LOGGER.error("Service class newInstance has error : ", ex);
            throw new NacosRuntimeException(SERVER_ERROR, ex);
        }
    };
}

  DefaultPublisher类继承自Thread,也就是说它是一个线程类,同时,它又实现了EventPublisher,同时是一个发布者。查看init初始化方法,从这里我们可以看出当DefaultPublisher被初始化时,是以守护线程的方式运作的,并初始化了一个阻塞队列。

public class DefaultPublisher extends Thread implements EventPublisher {
    
    @Override
    public void init(Class<? extends Event> type, int bufferSize) {
        // 守护线程
        setDaemon(true);
        // 设置线程名字
        setName("nacos.publisher-" + type.getName());
        this.eventType = type;
        this.queueMaxSize = bufferSize;
        // 阻塞队列初始化
        this.queue = new ArrayBlockingQueue<>(bufferSize);
        start();
    }
    
    @Override
    public synchronized void start() {
        if (!initialized) {
            // start just called once
            super.start();
            if (queueMaxSize == -1) {
                queueMaxSize = ringBufferSize;
            }
            initialized = true;
        }
    }
    
}

  run()方法直接调用openEventHandler()方法。openEventHandler里面写了两个死循环。第一个死循环可以理解为延时效果,也就是说线程启动时最大延时60秒,在这60秒中每隔1秒判断一下当前线程是否关闭,是否有订阅者,是否超过60秒。如果满足一个条件,就可以提前跳出死循环。而第二个死循环才是真正的业务逻辑处理,会从阻塞队列中取出一个事件,然后通过receiveEvent方法进行执行。

@Override
public void run() {
    openEventHandler();
}

void openEventHandler() {
    try {

        // This variable is defined to resolve the problem which message overstock in the queue.
        int waitTimes = 60;
        // To ensure that messages are not lost, enable EventHandler when
        // waiting for the first Subscriber to register
        
        for (; ; ) {
            if (shutdown || hasSubscriber() || waitTimes <= 0) {
                break;
            }
            ThreadUtils.sleep(1000L);
            waitTimes--;
        }
		
        // 死循环不断的从队列中取出Event,并通知订阅者Subscriber执行Event
        for (; ; ) {
            if (shutdown) {
                break;
            }
            // 从队列中取出Event
            final Event event = queue.take();
            receiveEvent(event);
            UPDATER.compareAndSet(this, lastEventSequence, Math.max(lastEventSequence, event.sequence()));
        }
    } catch (Throwable ex) {
        LOGGER.error("Event listener exception : ", ex);
    }
}

  值得注意的是,当DefaultPublisher的发布事件方法被调用了publish往阻塞队列中存入事件失败时,会直接调用receiveEvent方法,可以理解为直接消费消息。

@Override
public boolean publish(Event event) {
    checkIsStart();
    // 向队列中插入事件元素
    boolean success = this.queue.offer(event);
    // 判断是否成功插入
    if (!success) {
        // "由于中断,无法插入同步发送时间"
        LOGGER.warn("Unable to plug in due to interruption, synchronize sending time, event : {}", event);
        // 失败直接消费消息
        receiveEvent(event);
        return true;
    }
    return true;
}

  最后再来看receiveEvent方法的实现:这里其实就是遍历DefaultPublisher的subscribers,然后执行通知订阅者的方法。

void receiveEvent(Event event) {
    final long currentEventSequence = event.sequence();

    if (!hasSubscriber()) {
        LOGGER.warn("[NotifyCenter] the {} is lost, because there is no subscriber.", event);
        return;
    }

    // Notification single event listener
    // 通知订阅者执行Event
    for (Subscriber subscriber : subscribers) {
        // Whether to ignore expiration events
        if (subscriber.ignoreExpireEvent() && lastEventSequence > currentEventSequence) {
            LOGGER.debug("[NotifyCenter] the {} is unacceptable to this subscriber, because had expire",
                         event.getClass());
            continue;
        }

        // Because unifying smartSubscriber and subscriber, so here need to think of compatibility.
        // Remove original judge part of codes.
        notifySubscriber(subscriber, event);
    }
}

@Override
public void notifySubscriber(final Subscriber subscriber, final Event event) {

    LOGGER.debug("[NotifyCenter] the {} will received by {}", event, subscriber);
	//执行订阅者事件
    final Runnable job = () -> subscriber.onEvent(event);
    // 执行者
    final Executor executor = subscriber.executor();

    if (executor != null) {
        executor.execute(job);
    } else {
        try {
            job.run();
        } catch (Throwable e) {
            LOGGER.error("Event callback exception: ", e);
        }
    }
}

7.4. 综上所述

ServiceInfoHolder中通过NotifyCenter发布了InstancesChangeEvent事件

NotifyCenter中进行事件发布,发布的核心逻辑是:

  • 根据InstancesChangeEvent事件类型,获得对应的CanonicalName
  • 将CanonicalName作为Key,从NotifyCenter.publisherMap中获取对应的事件发布者(EventPublisher)
  • EventPublisher将InstancesChangeEvent事件进行发布

InstancesChangeEvent事件发布:

  • 通过EventPublisher的实现类DefaultPublisher进行InstancesChangeEvent事件发布
  • DefaultPublisher本身以守护线程的方式运作,在执行业务逻辑前,先判断该线程是否启动
  • 如果启动,则将事件添加到BlockingQueue中,队列默认大小为16384
  • 添加到BlockingQueue成功,则整个发布过程完成
  • 如果添加失败,则直接调用DefaultPublisher.receiveEvent方法,接收事件并通知订阅者
  • 通知订阅者时创建一个Runnable对象,执行订阅者的Event
  • Event事件便是执行订阅时传入的事件

如果添加到BlockingQueue成功,则走另外一个业务逻辑:

  • DefaultPublisher初始化时会创建一个阻塞(BlockingQueue)队列,并标记线程启动
  • DefaultPublisher本身是一个Thread,当执行super.start方法时,会调用它的run方法
  • run方法的核心业务逻辑是通过openEventHandler方法处理的
  • openEventHandler方法通过两个for循环,从阻塞队列中获取时间信息
  • 第一个for循环用于让线程启动时在60s内检查执行条件
  • 第二个for循环为死循环,从阻塞队列中获取Event,并调用DefaultPublisher#receiveEvent方法,接收事件并通知订阅者
  • Event事件便是执行订阅时传入的事件

8. 服务端推送ServiceInfo

8.1. 处理Grpc长连接

  如何消费Grpc推送的消息,这要从NamingGrpcClientProxy开始说起。NamingGrpcClientProxy构造函数中会初始化rpcClient属性,并在随后的start方法中注册服务器请求处理器NamingPushRequestHandler。

private final RpcClient rpcClient;

public NamingGrpcClientProxy(String namespaceId, SecurityProxy securityProxy, ServerListFactory serverListFactory,
            Properties properties, ServiceInfoHolder serviceInfoHolder) throws NacosException {
        super(securityProxy, properties);
        this.namespaceId = namespaceId;
        this.uuid = UUID.randomUUID().toString();
        this.requestTimeout = Long.parseLong(properties.getProperty(CommonParams.NAMING_REQUEST_TIMEOUT, "-1"));
        Map<String, String> labels = new HashMap<String, String>();
        labels.put(RemoteConstants.LABEL_SOURCE, RemoteConstants.LABEL_SOURCE_SDK);
        labels.put(RemoteConstants.LABEL_MODULE, RemoteConstants.LABEL_MODULE_NAMING);
        this.rpcClient = RpcClientFactory.createClient(uuid, ConnectionType.GRPC, labels);
        this.redoService = new NamingGrpcRedoService(this);
        start(serverListFactory, serviceInfoHolder);
    }
    
private void start(ServerListFactory serverListFactory, ServiceInfoHolder serviceInfoHolder) throws NacosException {
    rpcClient.serverListFactory(serverListFactory);
    rpcClient.registerConnectionListener(redoService);
    rpcClient.registerServerRequestHandler(new NamingPushRequestHandler(serviceInfoHolder));
    rpcClient.start();
    NotifyCenter.registerSubscriber(this);
}

  NamingPushRequestHandler便是处理服务端推送的处理。

public class NamingPushRequestHandler implements ServerRequestHandler {
    
    private final ServiceInfoHolder serviceInfoHolder;
    
    public NamingPushRequestHandler(ServiceInfoHolder serviceInfoHolder) {
        this.serviceInfoHolder = serviceInfoHolder;
    }
    
    @Override
    public Response requestReply(Request request) {
        if (request instanceof NotifySubscriberRequest) {
            NotifySubscriberRequest notifyResponse = (NotifySubscriberRequest) request;
            serviceInfoHolder.processServiceInfo(notifyResponse.getServiceInfo());
            return new NotifySubscriberResponse();
        }
        return null;
    }
}

8.2. 处理UDP推送

  UDP的推送在Nacos 2.x中弃用了。核心代码在PushReceiver.run()中,有兴趣的话可以看看,此处不再赘述。