话接上篇,我们提到了一个类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
,一个是共享的DefaultSharePublisher
,DefaultSharePublisher
是继承于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
就是一个典型的订阅-发布模型。通过这个模型将事件和处理进行了解耦操作。在很多优秀的框架都利用订阅-发布模型。我们要将这种模型充分的去消化和理解,这样你就可以发现很多场景都是由事件驱动。在平时写代码的时候也可以多多反思是不是可以用上订阅-发布模型。
后续的篇章,会讲解事件发布后,订阅者的处理逻辑。现在我们才到了通知中心,具体怎么处理的呢?敬请期待下篇的讲解。