Nacos源码3:Nacos缓存service的索引信息

96 阅读7分钟

Nacos缓存service的索引信息: 即Nacos缓存service对应的实例ip/port。

前面Nacos的系列文章提到了NacosServer服务端,在publisherIndexes中缓存service实例的ip/port。如此操作的目的:

  • 把service实例的ip/port作为service的index索引信息
  • 便于从索引信息中更进一步为订阅端获取service实例

对于在publisherIndexes中缓存service实例的ip/port的流程,我们当时没有详细说明,只是提到后面文章会进行说明,本章我们就进行详细说明下publisherIndexes缓存service实例的ip/port逻辑。

publisherIndexes缓存service实例的ip/port逻辑是怎样的???下面就是整个流程:

缓存service实例的ip端口

一、启动NacosServer

我们先给出结论,启动NacosServer在本文中主要作用是:

  • 缓存service注册event事件的发布者
  • 同时关联event发布者和监听器(也叫订阅者)
  • event发布者从queue中获取到service注册event事件之后通知监听器
  • 这个监听器收到事件之后,对事件进行处理:缓存service实例的ip/port

整个流程走的是发布/订阅模型:

  • 这种方式特别适合事件处理
  • 还可以异步解耦

详细的下面文章中会详述说明

1.1 注册ClientAttributesFilter到Spring容器

Spring容器启动时,会自动加载NamingConfig配置类。

/**
 * Naming spring configuration.
 * Spring容器启动时,自动加载NamingConfig配置类
 * @author nkorange
 */
@Configuration
public class NamingConfig {
    ...
   
    @Bean
    public FilterRegistrationBean<ClientAttributesFilter> clientAttributesFilterRegistration() {
        FilterRegistrationBean<ClientAttributesFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(clientAttributesFilter());
        registration.addUrlPatterns(URL_PATTERNS, URL_PATTERNS_V2);
        registration.setName(CLIENT_ATTRIBUTES_FILTER);
        registration.setOrder(8);
        return registration;
    }

    /**
     * 注册ClientAttributesFilter这个bean到Spring容器
     */
    @Bean
    public ClientAttributesFilter clientAttributesFilter() {
        return new ClientAttributesFilter();
    }
}

我们从上面的配置类中看到,会注册ClientAttributesFilter这个bean到Spring容器。

1.2 ClientAttributesFilter依赖clientServiceIndexesManager

由于ClientAttributesFilter依赖ClientManager

public class ClientAttributesFilter implements Filter {
    
   ... 
    
   @Autowired
   private ClientManager clientManager;


    

ClientManager依赖clientServiceIndexesManager这个bean

@DependsOn({"clientServiceIndexesManager", "namingMetadataManager"})
@Component("clientManager")
public class ClientManagerDelegate implements ClientManager {
   ...
}

1.3 实例化ClientServiceIndexesManager

所以,接下来会从Spring容器获取clientServiceIndexesManager这个bean。

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
   @Override
   public Object getBean(String name) throws BeansException {
      // Spring会去获取clientServiceIndexesManager这个bean
      return doGetBean(name, null, null, false);
   }
}

如果单例缓存中没有这个bean,则会实例化ClientServiceIndexesManager。

@Component
public class ClientServiceIndexesManager extends SmartSubscriber {
    ...
    public ClientServiceIndexesManager() {
        NotifyCenter.registerSubscriber(this, NamingEventPublisherFactory.getInstance());
    }
}

1.4 NotifyCenter缓存事件发布者和监听器

NotifyCenter#registerSubscriber:

public static void registerSubscriber(final Subscriber consumer, final EventPublisherFactory factory) {
   ...
   addSubscriber(consumer, subscribeType, factory);
}

NotifyCenter#addSubscriber,我们看看这里的流程:

 private static void addSubscriber(final Subscriber consumer, Class<? extends Event> subscribeType,
            EventPublisherFactory factory) {
        
        final String topic = ClassUtils.getCanonicalName(subscribeType);
        ...
        // 1. 缓存事件发布者
        MapUtil.computeIfAbsent(INSTANCE.publisherMap, topic, factory, subscribeType, ringBufferSize);
        ...
        // 2. 获取事件发布者
        EventPublisher publisher = INSTANCE.publisherMap.get(topic);
        ...
        // 3. 事件发布者缓存监听器
        if (publisher instanceof ShardedEventPublisher) {
            ((ShardedEventPublisher) publisher).addSubscriber(consumer, subscribeType);

注意:这里的factory是NamingEventPublisherFactory

  1. 缓存NamingEventPublisher事件发布者
public static <K, C, V, T> V computeIfAbsent(Map<K, V> target, K key, BiFunction<C, T, V> mappingFunction, C param1,
            T param2) {
        ...
        return target.computeIfAbsent(key, (keyInner) -> mappingFunction.apply(param1, param2));

mappingFunction.apply实际上就是执行NamingEventPublisherFactory的apply方法,这是使用的工厂模式,由此生产NamingEventPublisher事件发布者并返回。

public class NamingEventPublisherFactory implements EventPublisherFactory {
   
   /**
    * 由NamingEventPublisherFactory工厂,生产NamingEventPublisher事件发布者并返回
    */
   @Override
   public EventPublisher apply(final Class<? extends Event> eventType, final Integer maxQueueSize) {
        // Like ClientEvent$ClientChangeEvent cache by ClientEvent
        Class<? extends Event> cachedEventType =
                eventType.isMemberClass() ? (Class<? extends Event>) eventType.getEnclosingClass() : eventType;
        return publisher.computeIfAbsent(cachedEventType, eventClass -> {
            // 实例化事件发布者
            NamingEventPublisher result = new NamingEventPublisher();
            // 初始化并启动发布者线程
            result.init(eventClass, maxQueueSize);
            return result;
        });
    }
}

1.1 实例化NamingEventPublisher事件发布者,此事件发布者实际上是一个Thread线程。

1.2 然后执行NamingEventPublisher#init: 执行super.start()来启动事件发布者线程。

启动这个事件发布者线程的目的:

  • 从queue中获取service注册事件
  • 然后通知订阅者处理事件,(即缓存service实例的ip/port信息)

这个后面会详细说明。

public class NamingEventPublisher extends Thread implements ShardedEventPublisher {
   @Override
   public void init(Class<? extends Event> type, int bufferSize) {
        this.queueMaxSize = bufferSize;
        this.queue = new ArrayBlockingQueue<>(bufferSize);
        this.publisherName = type.getSimpleName();
        super.setName(THREAD_NAME + this.publisherName);
        super.setDaemon(true);
        // 启动事件发布者线程
        super.start();
        initialized = true;
   }
}

执行super.start(),即NamingEventPublisher事件发布者线程运行。

public class NamingEventPublisher extends Thread implements ShardedEventPublisher {
   ...
    // 线程运行
    @Override
    public void run() {
       ...
       // 处理事件
       handleEvents();
    }
}
 private void handleEvents() {
        while (!shutdown) {
           // 从队列中取出事件
           final Event event = queue.take();
           // 然后处理事件
           handleEvent(event);
        }
 }

我们看到这是一个循环:

  • 不断的从queue队列中取出event事件
  • 然后处理事件

我们想想:队列中的事件从哪里来???我们推测:

注意:这里只是我们的推测,真实的情况不见得是这样。

  • 当启动服务提供者时,会发送注册服务事件到NacosServer端
  • NacosServer端启动把事件推到queue队列中

event事件从queue中取出 && 处理事件,这个流程是怎样的???我们后文中详细说明。

  1. 获取事件发布者NamingEventPublisher
publisher = INSTANCE.publisherMap.get(topic);
  1. 事件发布者缓存监听器
 publisher.addSubscriber(consumer, subscribeType);
@Override
public void addSubscriber(Subscriber subscriber, Class<? extends Event> subscribeType) {
    subscribes.computeIfAbsent(subscribeType, inputType -> new ConcurrentHashSet<>()).add(subscriber);
}

监听器缓存到map与事件对应,这个监听器指的是ClientServiceIndexesManager,这个事件指的是ClientRegisterServerEvent

监听器缓存到map与事件对应

二、service提供者启动发送服务注册请求

启动服务提供者时,发送注册服务请求。这个流程之前的文章中有说明,这里不再详述。我们回头看看《Nacos服务注册基本原理》发现,服务提供者启动时的核心流程只是调用了NacosServer的接口来发送注册请求,我们接着往下看。

三、 NaocsServer接收服务注册请求

    @PostMapping
    ...
    public String register(HttpServletRequest request) throws Exception {
        ...
        final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        ...
        final Instance instance = HttpRequestInstanceBuilder.newBuilder()
                .setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build();
        getInstanceOperator().registerInstance(namespaceId, serviceName, instance);
        ...

3.1 发布ClientRegisterServiceEvent事件

NacosServer在注册服务实例的时候,发布ClientRegisterServiceEvent事件

    @Override
    public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
        ...
        // 返回一个单例的service对象
        Service singleton = ServiceManager.getInstance().getSingleton(service);
        ...
        // 发布service注册事件
        NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));
        ...

这个singleton封装了service提供者的相关信息,例如serviceName等等。如下图

singleton封装了服务提供者相关信息

我们看到这个provider正是我们的服务提供者的applicationName,如下图

provider正是我们的服务提供者的applicationName

clientId正是服务提供者的ip和端口信息,如下

clientId正是服务提供者的ip和端口信息

然后就是发布service注册事件:

private static boolean publishEvent(final Class<? extends Event> eventType, final Event event) {
        ...
        // 根据事件获取发布者NamingEventPubliser
        final String topic = ClassUtils.getCanonicalName(eventType);
        EventPublisher publisher = INSTANCE.publisherMap.get(topic);
        // NamingEventPubliser发布事件
        if (publisher != null) {
            return publisher.publish(event);
        }
        ...
  1. 首先是获取事件的发布者NamingEventPubliser

这个存储事件发布者的publisherMap容器,在前文的NotifyCenter#addSubscriber中有初始化,所以这里获取到NamingEventPubliser事件发布者

获取NamingEventPublisher事件发布者

  1. NamingEventPubliser发布事件
    @Override
    public boolean publish(Event event) {
        ...
        // 把ClientRegisterServiceEvent事件推到queue队列中
        boolean success = this.queue.offer(event);

把ClientRegisterServiceEvent事件推到queue队列中

把ClientRegisterServiceEvent事件推到queue队列中

3.2 处理ClientRegisterServiceEvent事件

既然queue中有了service注册事件,那么回到我们之前提到的NamingEventPublisher事件发布者线程运行:循环处理流程

private void handleEvents() {
        while (!shutdown) {
            try {
                // 取出ClientRegisterServiceEvent事件
                final Event event = queue.take();
                // 处理事件
                handleEvent(event);
                ...
  1. 取出ClientRegisterServiceEvent事件

  2. 然后处理事件:

private void handleEvent(Event event) {
    Class<? extends Event> eventType = event.getClass();
    // 1. 根据事件类型,取出订阅者
    Set<Subscriber<? extends Event>> subscribers = subscribes.get(eventType);
    ...
    for (Subscriber subscriber : subscribers) {
        // 2. 执行任务
        notifySubscriber(subscriber, event);
    }
}

获取到订阅者为ClientServiceIndexesManager

2.1 根据ClientRegisterServiceEvent事件类型获取到订阅者为ClientServiceIndexesManager

在前文中的NotifyCenter#addSubscriber流程中,ClientRegisterServiceEvent->ClientServiceIndexesManager这个订阅者的映射关系有保存到subscribes中。所以这里是可以根据事件获取到订阅者的。

2.2 执行任务

public void notifySubscriber(Subscriber subscriber, Event event) {
   ...
   // 定义线程执行的任务:调用订阅者处理事件
   final Runnable job = () -> subscriber.onEvent(event);
   ...
   // 运行任务
   job.run();
private void handleClientOperation(ClientOperationEvent event) {
        Service service = event.getService();
        String clientId = event.getClientId();
        if (event instanceof ClientOperationEvent.ClientRegisterServiceEvent) {
            addPublisherIndexes(service, clientId);
         ...

缓存service对应实例的ip/port信息

   private void addPublisherIndexes(Service service, String clientId) {
        publisherIndexes.computeIfAbsent(service, key -> new ConcurrentHashSet<>()).add(clientId);

缓存service的index索引:即对应的实例ip端口。

至此,我们缓存service实例的ip/port整体流程就介绍完了。最后总结

四、最后总结

1.首先NacosServer启动主要做了这些事情:

  • 初始化event发布者&&同时启动发布者线程一直从queue中监听获取service注册事件并通知订阅者
  • 并为发布者添加订阅者
  • 订阅者收到事件处理:缓存service实例ip/port

2.启动service提供者主要是发送service注册请求给NacosServer

3.NacosServer接收服务注册请求主要做了这些事情:

  • 注册服务
  • 注册完服务之后,发布ClientRegisterServiceEvent事件到queue
  • 发布者线程在初始化发布者的时候启动了运行,会一直监听从queue中获取事件,然后通知订阅者
  • 订阅者处理事件:缓存service实例的ip/port

还是贴上这个图便于理解。