开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情
最近在阅读一些框架源码的时候,会发现越来越多的基于事件驱动的代码实现。并且在上一篇nacos注册中心源码的最后,关于服务发现功能的订阅是如何接收到实例变化的消息并触发onEvent方法只是做了简单是描述。这块源码的实现其实就涉及到了事件驱动模型的代码实现。
1-事件驱动简述
事件驱动一般包含几个要素:事件、事件发布者、订阅者。发布者和订阅者之间的事件通道一般会使用队列来实现。下面直接通过源码来看看在Nacos中是如何实现的。
2-NacosNamingService
我们在写订阅者demo的时候在一开始就先创建了一个NacosNamingService。
private void init(Properties properties) throws NacosException {
ValidatorUtils.checkInitParam(properties);
this.namespace = InitUtils.initNamespaceForNaming(properties);
InitUtils.initSerialization();
InitUtils.initWebRootContext(properties);
initLogName(properties);
this.notifierEventScope = UUID.randomUUID().toString();
//定义事件相关的要素
this.changeNotifier = new InstancesChangeNotifier(this.notifierEventScope);
NotifyCenter.registerToPublisher(InstancesChangeEvent.class, 16384);
NotifyCenter.registerSubscriber(changeNotifier);
this.serviceInfoHolder = new ServiceInfoHolder(namespace, this.notifierEventScope, properties);
this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder, properties, changeNotifier);
}
上面的代码通NotifyCenter来完成了两个注册:
1-registerToPublisher:注册事件发布者
给InstancesChangeEvent这个事件类型注册了一个EventPublisher:NamingEventPublisher。并且维护在了单列模式NotifyCenter中的publisherMap中。
private final Map<String, EventPublisher> publisherMap = new ConcurrentHashMap<>(16);
2-registerSubscriber:注册订阅者
这一步最终就是给NamingEventPublisher注册相关的订阅者:
InstancesChangeNotifier extends Subscriber<InstancesChangeEvent>
最终在其map结构中去维护,key则是订阅事件的类型InstancesChangeEvent。
public void addSubscriber(Subscriber subscriber, Class<? extends Event> subscribeType) {
subscribes.computeIfAbsent(subscribeType, inputType -> new ConcurrentHashSet<>());
subscribes.get(subscribeType).add(subscriber);
}
private final Map<Class<? extends Event>, Set<Subscriber<? extends Event>>> subscribes = new ConcurrentHashMap<>();
3-PushReceiver
在上一篇已经知道了Nacos服务端是基于PushService来通知订阅者,底层是基于UDP的推送。
那么在客户端就会有一个Receiver,就是PushReceiver。当接收到消息时会调用:
serviceInfoHolder.processServiceInfo(pushPacket.data);
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));
}
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(notifierEventScope, serviceInfo.getName(), serviceInfo.getGroupName(),
serviceInfo.getClusters(), serviceInfo.getHosts()));
DiskCache.write(serviceInfo, cacheDir);
}
return serviceInfo;
}
上面的代码中如果判断了changed则会调用NotifyCenter的publishEvent方法,并且event类型就是InstancesChangeEvent:
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 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;
}
这里通过InstancesChangeEvent类型获取的EventPublisher就是在上面注册的NamingEventPublisher,最终调用了其publish方法**.**
3-NamingEventPublisher
publish方法的实现:
this.queue = new ArrayBlockingQueue<>(bufferSize);
public boolean publish(Event event) {
checkIsStart();
//添加event到阻塞队列
boolean success = this.queue.offer(event);
if (!success) {
Loggers.EVT_LOG.warn("Unable to plug in due to interruption, synchronize sending time, event : {}", event);
handleEvent(event);
return true;
}
return true;
}
就是像阻塞队列中添加了当前的event。那么肯定有地方去消费这个队列:
NamingEventPublisher extends Thread
NamingEventPublisher本身继承了Thread,并且在其init方法中调用了start方法,那么我们可以直接在当前类中找到run方法:
public void run() {
try {
waitSubscriberForInit();
handleEvents();
} catch (Exception e) {
Loggers.EVT_LOG.error("Naming Event Publisher {}, stop to handle event due to unexpected exception: ",
this.publisherName, e);
}
}
private void handleEvents() {
while (!shutdown) {
try {
//从队列中获取event
final Event event = queue.take();
handleEvent(event);
} catch (InterruptedException e) {
Loggers.EVT_LOG.warn("Naming Event Publisher {} take event from queue failed:", this.publisherName, e);
// set the interrupted flag
Thread.currentThread().interrupt();
}
}
}
从队列中获取到event后然后就可以通知前面注册的订阅者了:
private void handleEvent(Event event) {
Class<? extends Event> eventType = event.getClass();
Set<Subscriber<? extends Event>> subscribers = subscribes.get(eventType);
if (null == subscribers) {
if (Loggers.EVT_LOG.isDebugEnabled()) {
Loggers.EVT_LOG.debug("[NotifyCenter] No subscribers for slow event {}", eventType.getName());
}
return;
}
for (Subscriber subscriber : subscribers) {
notifySubscriber(subscriber, event);
}
}
这里同样是根据event事件的类型找到对应的subscriber:InstancesChangeNotifier。而方法notifySubscriber就会调用InstancesChangeNotifier中的onEvent方法:
public void onEvent(InstancesChangeEvent event) {
String key = ServiceInfo
.getKey(NamingUtils.getGroupedName(event.getServiceName(), event.getGroupName()), event.getClusters());
ConcurrentHashSet<EventListener> eventListeners = listenerMap.get(key);
if (CollectionUtils.isEmpty(eventListeners)) {
return;
}
for (final EventListener listener : eventListeners) {
final com.alibaba.nacos.api.naming.listener.Event namingEvent = transferToNamingEvent(event);
if (listener instanceof AbstractEventListener && ((AbstractEventListener) listener).getExecutor() != null) {
((AbstractEventListener) listener).getExecutor().execute(() -> listener.onEvent(namingEvent));
} else {
listener.onEvent(namingEvent);
}
}
}
这里的EventListener就是我们一开始在demo中调用subscribe方法来添加到InstancesChangeNotifier中的listenerMap中一个监听器,所以最终就调用到了demo中我们自己实现的onEvent方法:
naming.subscribe("nacos.test.3", new AbstractEventListener() {
//EventListener onEvent is sync to handle, If process too low in onEvent, maybe block other onEvent callback.
//So you can override getExecutor() to async handle event.
@Override
public Executor getExecutor() {
return executor;
}
@Override
public void onEvent(Event event) {
System.out.println("serviceName: " + ((NamingEvent) event).getServiceName());
System.out.println("instances from event: " + ((NamingEvent) event).getInstances());
}
});
//subscribe添加的实现往InstancesChangeNotifier中加入的listener
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);
}
4-总结
到此,我们基本上已经将最简单的源码进行了闭环。上面主要涉及到的类总结如下:
InstancesChangeEvent:事件类型
NacosNamingService:注册事件相关的信息初始化
PushReceiver :接收服务端消息,基于NamingEventPublisher发布InstancesChangeEvent事件
NamingEventPublisher:发布事件者,本身是一个线程。添加发布的事件到阻塞队列,并且异步不停的轮训阻塞队列中事件交给对应的subscriber进行处理。
InstancesChangeNotifier:订阅者,根据向其注册的EventListener来调用我们自己的实现
基于这种思想,可以发现其具有可扩展,耦合性低的特点。熟悉了这一种事件的调用情况,在看源码中其他事件就可以使用相同的思路了。