3、Nacos 服务注册服务端源码分析(二)

话接上篇,我们提到了一个类NotifyCenter。首先,我们先分析一下这个类

NotifyCenter分析

这个类是放在nacos-common工程中的com.alibaba.nacos.common.notify包下面,通过工程和包名可以看出这是一个公共处理类。

在类的最开头就写了一句

private static final NotifyCenter INSTANCE = new NotifyCenter();

表明这个类是一个单例,整个进程共享。这个时候,就应该立马反应,所有的内部变量归所有线程共享,那必须考虑并发的问题。所以,在其内部变量的时候,我们看到了以下声明:

// 这两个变量因为在静态块中初始化,然后并没有改变其初始值,所以不需要特殊处理
public static int ringBufferSize;
public static int shareBufferSize;

private static final AtomicBoolean CLOSED = new AtomicBoolean(false);
private final Map<String, EventPublisher> publisherMap = new ConcurrentHashMap<>(16);

继续向下看,看到了一个静态代码块

static {
    // Internal ArrayBlockingQueue buffer size. For applications with high write throughput,
    // this value needs to be increased appropriately. default value is 16384
    // 默认的生产者的阻塞队列大小
    String ringBufferSizeProperty = "nacos.core.notify.ring-buffer-size";
    ringBufferSize = Integer.getInteger(ringBufferSizeProperty, 16384);

    // The size of the public publisher's message staging queue buffer
    // 共享生产者阻塞队列大小
    String shareBufferSizeProperty = "nacos.core.notify.share-buffer-size";
    shareBufferSize = Integer.getInteger(shareBufferSizeProperty, 1024);
    // 这里采用了SPI的扩展机制,里面包含了JDK原生的SPI扩展机制,不过Nacos对这部分的内容加了缓存
    final Collection<EventPublisher> publishers = NacosServiceLoader.load(EventPublisher.class);
    Iterator<EventPublisher> iterator = publishers.iterator();
    // 没有配置,就用默认的DefaultPublisher生产者
    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);
        }
    };

    try {
        // Create and init DefaultSharePublisher instance.
        // 共享的生产者
        INSTANCE.sharePublisher = new DefaultSharePublisher();
        INSTANCE.sharePublisher.init(SlowEvent.class, shareBufferSize);
    } catch (Throwable ex) {
        LOGGER.error("Service class newInstance has error : ", ex);
    }

    ThreadUtils.addShutdownHook(NotifyCenter::shutdown);
}

EventPublisher分析

这里面有两个EventPublisher,一个是默认的DefaultPublisher,一个是共享的DefaultSharePublisherDefaultSharePublisher是继承于DefaultPublisher,然后多了一个私有变量

private final Map<Class<? extends SlowEvent>, Set<Subscriber>> subMappings = new ConcurrentHashMap<>();

这个私有变量维护了一个Map,key是SlowEvent,value是Set<Subscriber>。也就是说其维护了一个Set,每种事件一个Set。他们共享了这个DefaultPublisher,既然定义为SlowEvent,应该是处理比较耗时或者可以慢点处理,所以都放在一个Set,可以慢慢轮训处理。

那关键的处理逻辑还是在DefaultSharePublisher

public class DefaultPublisher extends Thread implements EventPublisher

DefaultSharePublisher继承了Thread,说明其为一个线程。其私有变量有

private volatile boolean initialized = false;
private volatile boolean shutdown = false;
// 事件类型,其非集合,说明一个线程只能绑定一种事件类型
private Class<? extends Event> eventType;
// 事件的订阅者
protected final ConcurrentHashSet<Subscriber> subscribers = new ConcurrentHashSet<>();
// 阻塞队列大小
private int queueMaxSize = -1;
// 阻塞队列
private BlockingQueue<Event> queue;
// 最大的事件序号,事件每次产生都会有个事件序号
//com.alibaba.nacos.common.notify.Event#sequence
protected volatile Long lastEventSequence = -1L;
// 用于判断事件是否过期
private static final AtomicReferenceFieldUpdater<DefaultPublisher, Long> UPDATER = AtomicReferenceFieldUpdater
    .newUpdater(DefaultPublisher.class, Long.class, "lastEventSequence");

看下它的初始化方法

@Override
public void init(Class<? extends Event> type, int bufferSize) {
    // 设置后台线程,当服务关闭的时候会自动关闭
    setDaemon(true);
    // 设置线程的名称,很重要,当我们打印JVM线程的时候可以快速查找到
    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
        // 保证只启动一次,调用父类,告诉JVM去进行线程启动
        super.start();
        if (queueMaxSize == -1) {
            queueMaxSize = ringBufferSize;
        }
        initialized = true;
    }
}

既然这个是个线程,肯定有一个run方法,run方法是线程运转逻辑的核心

@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
        while (!shutdown && !hasSubscriber() && waitTimes > 0) {
            ThreadUtils.sleep(1000L);
            waitTimes--;
        }

        while (!shutdown) {
            // 如果没有事件,这里进行阻塞
            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);
    }
}

在这个线程中,执行了一个死循环,如果没有收到shutdown事件,则会一直运行在后台。

看似一直在运行,实际它会被阻塞队列阻塞。在阻塞队列的线程是不是占用CPU资源的。所以其也就是占了一部分空间和资源。不要害怕这种死循环。只有一直占用CPU资源,让CPU不断干活的死循环才可怕。

当阻塞队列中有数据的时候,就会立马触发获取导队列元素,开始处理。

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
    for (Subscriber subscriber : subscribers) {
        if (!subscriber.scopeMatches(event)) {
            continue;
        }

        // 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) {
        // 含有线程池,将Runable放入线程池,等待异步执行
        executor.execute(job);
    } else {
        try {
            // 直接调用方法,即同步调用
            job.run();
        } catch (Throwable e) {
            LOGGER.error("Event callback exception: ", e);
        }
    }
}

那数据阻塞队列中的数据又是从哪里来的呢?看看这个方法com.alibaba.nacos.common.notify.DefaultPublisher#publish

@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;
}

通过分析DefaultPublisher,我们对这个生产者有了比较清晰的认知。它是一个后台线程,这个线程中包含一个阻塞队列,一直在等待事件的来到。当事件到来后,立马去通知订阅了这个事件的消费者去进行消费。

重新分析NotifyCenter

EventPublisher分析理解后,我们再来回头看看NotifyCenter。这个NotifyCenter在初始化的时候就创建了一个EventPublisher工厂。这里面一般是用来生产DefaultPublisher,因为没一个DefaultPublisher是一个线程,处理的是某一类的事件,去通知对这一类事件感兴趣的订阅者。然后NotifyCenter在初始化的时候就还创建了DefaultSharePublisher,而DefaultSharePublisher是一个线程,里面维护了Map<Class<? extends SlowEvent>, Set<Subscriber>>,它是用来处理多种SlowEvent。这也就是说在Nacos的进程中可能会有者多个DefaultPublisher和一个DefaultSharePublisher线程去处理Nacos中的多种事件。

再看NotifyCenter的一些方法,就不难理解了。

先看下增加订阅者的代码,就是将其放入到EventPublisher

 public static void registerSubscriber(final Subscriber consumer) {
     // 注册订阅者
     registerSubscriber(consumer, DEFAULT_PUBLISHER_FACTORY);
 }

public static void registerSubscriber(final Subscriber consumer, final EventPublisherFactory factory) {
    // If you want to listen to multiple events, you do it separately,
    // based on subclass's subscribeTypes method return list, it can register to publisher.
    if (consumer instanceof SmartSubscriber) {
        for (Class<? extends Event> subscribeType : ((SmartSubscriber) consumer).subscribeTypes()) {
            // For case, producer: defaultSharePublisher -> consumer: smartSubscriber.
            if (ClassUtils.isAssignableFrom(SlowEvent.class, subscribeType)) {
                INSTANCE.sharePublisher.addSubscriber(consumer, subscribeType);
            } else {
                // For case, producer: defaultPublisher -> consumer: subscriber.
                addSubscriber(consumer, subscribeType, factory);
            }
        }
        return;
    }

    final Class<? extends Event> subscribeType = consumer.subscribeType();
    if (ClassUtils.isAssignableFrom(SlowEvent.class, subscribeType)) {
        INSTANCE.sharePublisher.addSubscriber(consumer, subscribeType);
        return;
    }
    // 添加订阅者
    addSubscriber(consumer, subscribeType, factory);
}


private static void addSubscriber(final Subscriber consumer, Class<? extends Event> subscribeType,
                                  EventPublisherFactory factory) {

    final String topic = ClassUtils.getCanonicalName(subscribeType);
    synchronized (NotifyCenter.class) {
        // MapUtils.computeIfAbsent is a unsafe method.
        MapUtil.computeIfAbsent(INSTANCE.publisherMap, topic, factory, subscribeType, ringBufferSize);
    }
    EventPublisher publisher = INSTANCE.publisherMap.get(topic);
    // 将订阅者放入EventPublisher中
    if (publisher instanceof ShardedEventPublisher) {
        ((ShardedEventPublisher) publisher).addSubscriber(consumer, subscribeType);
    } else {
        publisher.addSubscriber(consumer);
    }
}

再看发布事件的方法

public static boolean publishEvent(final Event event) {
    try {
        return publishEvent(event.getClass(), event);
    } catch (Throwable ex) {
        LOGGER.error("There was an exception to the message publishing : ", ex);
        return false;
    }
}

private static boolean publishEvent(final Class<? extends Event> eventType, final Event event) {
    if (ClassUtils.isAssignableFrom(SlowEvent.class, eventType)) {
        return INSTANCE.sharePublisher.publish(event);
    }

    final String topic = ClassUtils.getCanonicalName(eventType);
	// 获取对应的EventPublisher进行发布,也就是通知订阅者去处理
    EventPublisher publisher = INSTANCE.publisherMap.get(topic);
    if (publisher != null) {
        return publisher.publish(event);
    }
    if (event.isPluginEvent()) {
        return true;
    }
    LOGGER.warn("There are no [{}] publishers for this event, please register", topic);
    return false;
}

总结

本篇文章分析性了NotifyCenter类。讲解了其中重点的私有变量,特别是对EventPublisher进行了分析。我们可以将NotifyCenter看做是一个通知中心(其实翻译过来也就是通知中心)。在通知中心中有着各种事件的线程在后台等待着各种事件的到来,当事件到来之后再通知订阅者去处理。EventPublisher就是一个典型的订阅-发布模型。通过这个模型将事件和处理进行了解耦操作。在很多优秀的框架都利用订阅-发布模型。我们要将这种模型充分的去消化和理解,这样你就可以发现很多场景都是由事件驱动。在平时写代码的时候也可以多多反思是不是可以用上订阅-发布模型

后续的篇章,会讲解事件发布后,订阅者的处理逻辑。现在我们才到了通知中心,具体怎么处理的呢?敬请期待下篇的讲解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值