文章目录
前言
本文对Nacos 中事件的发布和订阅进行介绍。
一、Nacos 事件发布和订阅:
在 Nacos 中,事件的发布和订阅是通过监听器和回调函数实现的。Nacos 提供了一套事件监听机制,通过注册监听器,可以监听到特定事件的发生,并在事件发生时执行相应的回调函数。具体步骤如下:
-
创建事件监听器:在应用程序中创建一个事件监听器,实现对特定事件的监听逻辑。可以通过 Nacos 提供的 SDK 或 API 注册监听器。
-
注册监听器:在 Nacos 中,将事件监听器注册到相应的服务实例或配置实例上。注册监听器时,可以指定监听的事件类型和对应的回调函数。
-
发布事件:当特定事件发生时(比如配置变更、服务状态变更等),Nacos 会触发相应的监听器,并执行注册的回调函数。
-
触发回调:注册的回调函数会被执行,进行相应的处理逻辑。可以在回调函数中编写代码来处理事件,并实现特定的业务逻辑。
-
取消监听:在不需要监听特定事件时,可以取消相应的监听器,避免不必要的资源消耗。
通过事件的发布和订阅机制,Nacos 实现了服务实例和配置实例的实时通知和数据同步。例如,当服务实例状态发生变化时,客户端可以立即收到通知并做出相应的处理;当配置内容发生变化时,客户端也可以即时更新配置信息。这样可以保证系统的实时性和一致性,提高系统的可靠性和性能。
总的来说,Nacos 中的事件发布和订阅机制通过事件监听器和回调函数实现,可以使得系统各个组件之间实时通信和数据同步,保证系统的稳定性和可靠性。
Nacos 事件发布和订阅 主要通过 NotifyCenter 进行实现;
二、事件发布:
2.1 默认事件发布工厂构建:
static {
// Internal ArrayBlockingQueue buffer size. For applications with high write throughput,
// this value needs to be increased appropriately. default value is 16384
// ringBufferSize 缓冲区大小获取
String ringBufferSizeProperty = "nacos.core.notify.ring-buffer-size";
ringBufferSize = Integer.getInteger(ringBufferSizeProperty, 16384);
// The size of the public publisher's message staging queue buffer
// shareBufferSize 缓冲区大小获取
String shareBufferSizeProperty = "nacos.core.notify.share-buffer-size";
shareBufferSize = Integer.getInteger(shareBufferSizeProperty, 1024);
final Collection<EventPublisher> publishers = NacosServiceLoader.load(EventPublisher.class);
Iterator<EventPublisher> iterator = publishers.iterator();
if (iterator.hasNext()) {
clazz = iterator.next().getClass();
} else {
// 初始化方法时 clazz 取默认的事件发布器
clazz = DefaultPublisher.class;
}
// 默认的事件发布工厂 赋值 当调用apply 方法时 会调用到改实现的方法,创建出一个默认的事件监听器
DEFAULT_PUBLISHER_FACTORY = (cls, buffer) -> {
try {
// 创建 DefaultPublisher 对象
EventPublisher publisher = clazz.newInstance();
// 执行事件发布器的 初始化方法 cls 事件类型,buffer 缓冲区大小
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);
}
2.2 事件的注册:
以 集群成员变更的事件 为例:
// 注册事件 事件类型 ,队列大小
NotifyCenter.registerToPublisher(MembersChangeEvent.class,
EnvUtil.getProperty(MEMBER_CHANGE_EVENT_QUEUE_SIZE_PROPERTY, Integer.class,
DEFAULT_MEMBER_CHANGE_EVENT_QUEUE_SIZE));
(1)事件通知中心NotifyCenter 注册事件:
/**
* eventType 事件类型
* queueMaxSize 事件发布器的队列大小
**/
public static EventPublisher registerToPublisher(final Class<? extends Event> eventType, final int queueMaxSize) {
// 注册事件发布器
return registerToPublisher(eventType, DEFAULT_PUBLISHER_FACTORY, queueMaxSize);
}
(2)注册事件发布器 registerToPublisher:
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);
}
(3)事件工厂事件发布器的构建:
public static <K, C, V, T> V computeIfAbsent(Map<K, V> target, K key, BiFunction<C, T, V> mappingFunction, C param1,
T param2) {
// 属性值判断
Objects.requireNonNull(target, "target");
Objects.requireNonNull(key, "key");
Objects.requireNonNull(mappingFunction, "mappingFunction");
Objects.requireNonNull(param1, "param1");
Objects.requireNonNull(param2, "param2");
// 调用 DEFAULT_PUBLISHER_FACTORY 的apply 方法
return target.computeIfAbsent(key, (keyInner) -> mappingFunction.apply(param1, param2));
}
事件发布器构建:
DEFAULT_PUBLISHER_FACTORY = (cls, buffer) -> {
try {
// 创建 DefaultPublisher 对象
EventPublisher publisher = clazz.newInstance();
// 执行事件发布器的 初始化方法 cls 事件类型,buffer 缓冲区大小
publisher.init(cls, buffer);
return publisher;
} catch (Throwable ex) {
LOGGER.error("Service class newInstance has error : ", ex);
throw new NacosRuntimeException(SERVER_ERROR, ex);
}
};
(4)事件发布初始化publisher.init:
默认的事件发布器 DefaultPublisher 和共享的事件发布器 DefaultSharePublisher 调用init 方法都会进入到 DefaultPublisher # init
@Override
public void init(Class<? extends Event> type, int bufferSize) {
// 守护线程标识
setDaemon(true);
// 设置事件发布器的名称 "nacos.publisher-" + 事件的类型
setName("nacos.publisher-" + type.getName());
// 赋值事件的 类型
this.eventType = type;
this.queueMaxSize = bufferSize;
if (this.queueMaxSize == -1) {
this.queueMaxSize = ringBufferSize;
}
// 设置事件发布器中队列 的大小
this.queue = new ArrayBlockingQueue<>(this.queueMaxSize);
// DefaultPublisher extends Thread 开启一个新线程 执行DefaultPublisher 的run 方法
start();
}
(5)事件发布器对于事件的接收(事件的消费):
start(); 方法开启线程 消费对应事件类型 队列中的事件;
@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
// 如果改事件类型对应的没有订阅者,则最多循环60次;
while (!shutdown && !hasSubscriber() && waitTimes > 0) {
ThreadUtils.sleep(1000L);
waitTimes--;
}
while (!shutdown) {
// BlockingQueue<Event> queue
// 从事件发布器的队列中获取 事件
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);
}
}
2.3 发布事件NotifyCenter.publishEvent:
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;
}
}
2.3.1 发布事件:
private static boolean publishEvent(final Class<? extends Event> eventType, final Event event) {
if (ClassUtils.isAssignableFrom(SlowEvent.class, eventType)) {
// 如果是慢事件 则使用共享事件发布器 发布事件
return INSTANCE.sharePublisher.publish(event);
}
// 获取topic 包名 +类名
final String topic = ClassUtils.getCanonicalName(eventType);
// 根据事件类型 获取到对应的事件处理器
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;
}
默认事件发布器DefaultPublisher/ DefaultSharePublisher 发布事件:
默认事件发布器DefaultPublisher/ DefaultSharePublisher 最终都调用 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;
}
三、事件订阅:
3.1 以 注册本机ip 变更事件为例:
NotifyCenter.registerSubscriber(new Subscriber<InetUtils.IPChangeEvent>() {
// ip 事件变更后的回调方法
@Override
public void onEvent(InetUtils.IPChangeEvent event) {
String newAddress = event.getNewIP() + ":" + port;
ServerMemberManager.this.localAddress = newAddress;
EnvUtil.setLocalAddress(localAddress);
Member self = ServerMemberManager.this.self;
self.setIp(event.getNewIP());
String oldAddress = event.getOldIP() + ":" + port;
ServerMemberManager.this.serverList.remove(oldAddress);
ServerMemberManager.this.serverList.put(newAddress, self);
ServerMemberManager.this.memberAddressInfos.remove(oldAddress);
ServerMemberManager.this.memberAddressInfos.add(newAddress);
}
@Override
public Class<? extends Event> subscribeType() {
// 定义事件的类型 IPChangeEvent extends SlowEvent
return InetUtils.IPChangeEvent.class;
}
});
3.2 事件注册步骤1:
public static void registerSubscriber(final Subscriber consumer) {
// consumer 事件回调方法,DEFAULT_PUBLISHER_FACTORY 使用默认的事件工厂
registerSubscriber(consumer, DEFAULT_PUBLISHER_FACTORY);
}
3.3 事件注册步骤2:
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);
}
3.4 注册到默认事件发布器:
private static void addSubscriber(final Subscriber consumer, Class<? extends Event> subscribeType,
EventPublisherFactory factory) {
// topic 获取,包名+类名
final String topic = ClassUtils.getCanonicalName(subscribeType);
// 类锁获取
synchronized (NotifyCenter.class) {
// MapUtils.computeIfAbsent is a unsafe method.
// 将 对应的事件 及其 消费者放入到 Map<String, EventPublisher> publisherMap = new ConcurrentHashMap<>(16)
// key : 包名+类名的topic ,value :改事件对应的事件发布器 ,subscribeType 为事件类型,ringBufferSize 事件处理器队列大小
// 如果publisherMap 没有改subscribeType 类型的事件发布器, 这里会调用 DEFAULT_PUBLISHER_FACTORY 的apply 方法
// 生成一个默认的事件处理器
MapUtil.computeIfAbsent(INSTANCE.publisherMap, topic, factory, subscribeType, ringBufferSize);
}
// 获取事件发布器
EventPublisher publisher = INSTANCE.publisherMap.get(topic);
if (publisher instanceof ShardedEventPublisher) {
// 如果是共享事件发布器,则调用ShardedEventPublisher 的 addSubscriber 方法
((ShardedEventPublisher) publisher).addSubscriber(consumer, subscribeType);
} else {
// 调用对应事件发布器的注册方法
publisher.addSubscriber(consumer);
}
}
事件定义者订阅 publisher.addSubscriber(consumer):
protected final ConcurrentHashSet<Subscriber> subscribers = new ConcurrentHashSet<>();
@Override
public void addSubscriber(Subscriber subscriber) {
// 将订阅者(消费者) 放入对应事件类型处理器的 订阅者set 集合中
subscribers.add(subscriber);
}
3.5 注册到共享事件发布器:
DefaultSharePublisher # addSubscriber:
private final Lock lock = new ReentrantLock();
@Override
public void addSubscriber(Subscriber subscriber, Class<? extends Event> subscribeType) {
// Actually, do a classification based on the slowEvent type.
// 对应的事件类型
Class<? extends SlowEvent> subSlowEventType = (Class<? extends SlowEvent>) subscribeType;
// For stop waiting subscriber, see {@link DefaultPublisher#openEventHandler}.
// DefaultPublisher 类中 protected final ConcurrentHashSet<Subscriber> subscribers = new ConcurrentHashSet<>();
// 将订阅者也存入一份到DefaultPublisher 的set 结合中
subscribers.add(subscriber);
// 获取锁
lock.lock();
try {
// 获取改事件类型的 订阅者
Set<Subscriber> sets = subMappings.get(subSlowEventType);
if (sets == null) {
// 如果集合为空,说明改事件是被消费者第一次订阅
Set<Subscriber> newSet = new ConcurrentHashSet<>();
// set 集合添加 订阅者
newSet.add(subscriber);
// 放回 事件 以及对应的事件订阅者
subMappings.put(subSlowEventType, newSet);
return;
}
sets.add(subscriber);
} finally {
// 释放锁
lock.unlock();
}
}
3.6 默认事件发布器和共享事件发布器区别:
- DefaultSharePublisher extends DefaultPublisher 共享事件发布器 继承了默认事件发布器 ,默认事件发布器有的共享事件发布器都有;
- DefaultSharePublisher 的事件发布器,每种事件都会定义一个自己的DefaultSharePublisher 对象,然后对应的消费者,也只是消费者一种事件;
- DefaultSharePublisher 的事件发布器,只要是SlowEvent 都会公用一个 DefaultSharePublisher 对象,然后对应的消费者和事件会被放入到 Map<Class<? extends SlowEvent>, Set> subMappings = new ConcurrentHashMap<>() map 集合中,其中key 是对应的慢事件 包名+类名,value 为对应改事件的消费者集合;
- 共享的事件发布器,可以对多个类型事件进行发布,而默认的一个事件发布器只针对一类事件;
- 共享的事件发布器缓冲区和队列大小是1024;
- 默认的事件类型缓冲区和队列大小是,128
四、事件消费:
在NotifyCenter static 的静态代码块中,共享事件发布器,和默认的事件发布器,都会调用到 DefaultPublisher 的 init 方法:
在 init 方法初始化完成的最后 会调用 start 方法:
@Override
public void init(Class<? extends Event> type, int bufferSize) {
setDaemon(true);
setName("nacos.publisher-" + type.getName());
this.eventType = type;
this.queueMaxSize = bufferSize;
if (this.queueMaxSize == -1) {
this.queueMaxSize = ringBufferSize;
}
this.queue = new ArrayBlockingQueue<>(this.queueMaxSize);
//DefaultPublisher extends Thread 这里会开启线程 消费队列中的事件
start();
}
在线程的run 方法中会调用接收事件的 receiveEvent 方法:
这里的 receiveEvent 会有两个实现,一个对应不是 慢事件的 默认事件发布器,一个对应共享事件的 慢事件发布器
4.1 非慢事件消费(对应默认事件发布器):
DefaultPublisher # receiveEvent
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);
}
}
DefaultPublisher # notifySubscriber
@Override
public void notifySubscriber(final Subscriber subscriber, final Event event) {
LOGGER.debug("[NotifyCenter] the {} will received by {}", event, subscriber);
// 定义一个线程去处理 事件的回调onEvent 方法
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);
}
}
}
以ip 地址变更为例,最终回调 onEvent 方法,进行对应的业务处理
NotifyCenter.registerSubscriber(new Subscriber<InetUtils.IPChangeEvent>() {
@Override
public void onEvent(InetUtils.IPChangeEvent event) {
String newAddress = event.getNewIP() + ":" + port;
ServerMemberManager.this.localAddress = newAddress;
EnvUtil.setLocalAddress(localAddress);
Member self = ServerMemberManager.this.self;
self.setIp(event.getNewIP());
String oldAddress = event.getOldIP() + ":" + port;
ServerMemberManager.this.serverList.remove(oldAddress);
ServerMemberManager.this.serverList.put(newAddress, self);
ServerMemberManager.this.memberAddressInfos.remove(oldAddress);
ServerMemberManager.this.memberAddressInfos.add(newAddress);
}
@Override
public Class<? extends Event> subscribeType() {
return InetUtils.IPChangeEvent.class;
}
});
4.1 慢事件消费(对应共享事件发布器):
DefaultSharePublisher # receiveEvent
@Override
public void receiveEvent(Event event) {
// 事件的序列号
final long currentEventSequence = event.sequence();
// get subscriber set based on the slow EventType.
final Class<? extends SlowEvent> slowEventType = (Class<? extends SlowEvent>) event.getClass();
// Get for Map, the algorithm is O(1).
// 根据类型获取慢事件的 订阅者
Set<Subscriber> subscribers = subMappings.get(slowEventType);
if (null == subscribers) {
LOGGER.debug("[NotifyCenter] No subscribers for slow event {}", slowEventType.getName());
return;
}
// Notification single event subscriber
// 遍历订阅者 调用 notifySubscriber 消费事件
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;
}
// Notify single subscriber for slow event.
notifySubscriber(subscriber, event);
}
}
DefaultPublisher # notifySubscriber
@Override
public void notifySubscriber(final Subscriber subscriber, final Event event) {
LOGGER.debug("[NotifyCenter] the {} will received by {}", event, subscriber);
// 定义一个线程去处理 事件的回调onEvent 方法
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);
}
}
}
总结
Nacos 事件发布&订阅 本身就是一个生产者和消费者的模型,Nacos 将事件分成了两类,常规事件 和 慢事件,常规事件:每种事件类型都对应自己的一个 默认事件发布器对象 队列大小为128,通过set 集合存放消费改事件的消费者 ; 慢事件:都使用共享事件发布器, 队列大小为1024,使用map 集合存放消费改事件的消费者 key: 包名+类名的 topIc, value 为set 集合存放消费改事件的消费者 ;
生产者通过NotifyCenter # publishEvent 方法将事件 放入到事件发布器的ArrayBlockingQueue 队列中;
消费者通过NotifyCenter # registerSubscriber 将消费者放入到 事件发布器的 集合中(慢事件为 Map<Class<? extends SlowEvent>, Set> subMapping ; 非慢事件为 ConcurrentHashSet subscribers )
通过事件发布器中的线程中while 循环 进行 事件的获取,最终回调 onEvent 方法完成事件的消费;