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
- 缓存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中取出 && 处理事件,这个流程是怎样的???我们后文中详细说明。
- 获取事件发布者NamingEventPublisher
publisher = INSTANCE.publisherMap.get(topic);
- 事件发布者缓存监听器
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);
}
...
- 首先是获取事件的发布者NamingEventPubliser
这个存储事件发布者的publisherMap容器,在前文的NotifyCenter#addSubscriber中有初始化,所以这里获取到NamingEventPubliser事件发布者
获取NamingEventPublisher事件发布者
- 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);
...
-
取出ClientRegisterServiceEvent事件
-
然后处理事件:
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
还是贴上这个图便于理解。