上一篇我们介绍了配置服务的插件功能,可以通过插件进行一个很好的扩展。本篇接着上篇未分析的ConfigChangePublisher.notifyConfigChange部分的内容。

public static void notifyConfigChange(ConfigDataChangeEvent event) {
    if (PropertyUtil.isEmbeddedStorage() && !EnvUtil.getStandaloneMode()) {
        // 内部存储并且非单例就不处理了
        return;
    }
    NotifyCenter.publishEvent(event);
}

上述代码又碰到之前分析的NotifyCenter,没看过的小伙伴可以点击这里进行查看。既然走到了通知中心,可以直接查找ConfigDataChangeEventonEvent方法来查看处理逻辑。

@Autowired
// spring 自动注入
public AsyncNotifyService(ServerMemberManager memberManager) {
    this.memberManager = memberManager;
    // 注册ConfigDataChangeEvent到NotifyCenter.
    NotifyCenter.registerToPublisher(ConfigDataChangeEvent.class, NotifyCenter.ringBufferSize);
    // 注册一个处理类
    NotifyCenter.registerSubscriber(new Subscriber() {
        @Override
        public void onEvent(Event event) {
            if (event instanceof ConfigDataChangeEvent) {
                ConfigDataChangeEvent evt = (ConfigDataChangeEvent) event;
                long dumpTs = evt.lastModifiedTs;
                String dataId = evt.dataId;
                String group = evt.group;
                String tenant = evt.tenant;
                String tag = evt.tag;
                // 记录monitor
                MetricsMonitor.incrementConfigChangeCount(tenant, group, dataId);
                // 获取所有的服务节点
                Collection<Member> ipList = memberManager.allMembers();
                Queue<NotifySingleTask> httpQueue = new LinkedList<>();
                Queue<NotifySingleRpcTask> rpcQueue = new LinkedList<>();
                for (Member member : ipList) {
                    if (!MemberUtil.isSupportedLongCon(member)) {
                        httpQueue.add(new NotifySingleTask(dataId, group, tenant, tag, dumpTs, member.getAddress(),
                                                           evt.isBeta));
                    } else {
                        rpcQueue.add(
                            new NotifySingleRpcTask(dataId, group, tenant, tag, dumpTs, evt.isBeta, member));
                    }
                }
                if (!httpQueue.isEmpty()) {
                    ConfigExecutor.executeAsyncNotify(new AsyncTask(nacosAsyncRestTemplate, httpQueue));
                }
                if (!rpcQueue.isEmpty()) {
                    // 重点关注Rpc的处理
                    ConfigExecutor.executeAsyncNotify(new AsyncRpcTask(rpcQueue));
                }

            }
        }
        @Override
        public Class<? extends Event> subscribeType() {
            return ConfigDataChangeEvent.class;
        }
    });
}

AsyncNotifyService构造方法中就注册了ConfigDataChangeEvent监听处理器。而AsyncNotifyService又归属于spring进行托管,会在容器创建的时候创建这个bean。然后创建一个队列,将相关配置的其他服务节点都存放进来,然后通过线程池进行异步通知调用。

ServerMemberManager

我们先分析一下在AsyncNotifyService传入的ServerMemberManager。它是对集群中多服务节点的管理。包括获取所有的服务节点列表,是否包含了某个服务节点列表,是否为本机节点等方法。下面简单分析下是如何拿到所有节点的。

public ServerMemberManager(ServletContext servletContext) throws Exception {
    // 先创建一个支持并发,跳表的HashMap
    this.serverList = new ConcurrentSkipListMap<>();
    EnvUtil.setContextPath(servletContext.getContextPath());
    // 初始化
    init();
}

protected void init() throws NacosException {
    Loggers.CORE.info("Nacos-related cluster resource initialization");
    // 处理本机ip
    this.port = EnvUtil.getProperty(SERVER_PORT_PROPERTY, Integer.class, DEFAULT_SERVER_PORT);
    this.localAddress = InetUtils.getSelfIP() + ":" + port;
    this.self = MemberUtil.singleParse(this.localAddress);
    this.self.setExtendVal(MemberMetaDataConstants.VERSION, VersionUtils.version);
    // init abilities.
    this.self.setAbilities(initMemberAbilities());
    // 加入本地地址
    serverList.put(self.getAddress(), self);
    // 注册NodeChangeEvent publisher到NotifyManager
    registerClusterEvent();
    // 初始化一些监听器并查找配置的其他服务节点
    initAndStartLookup();
    if (serverList.isEmpty()) {
        throw new NacosException(NacosException.SERVER_ERROR, "cannot get serverlist, so exit.");
    }
    Loggers.CORE.info("The cluster resource is initialized");
}

ServerMemberManager构造方法中创建的是ConcurrentSkipListMap,ConcurrentSkipListMap可以支持更多的数据,更高的并发。后续初始化过程中,先添加了本机地址到服务列表,然后注册了集群事件监听器和初始化一些配置。

registerClusterEvent

private void registerClusterEvent() {
    // 注册节点变动通知事件
    NotifyCenter.registerToPublisher(MembersChangeEvent.class,
                                     EnvUtil.getProperty(MEMBER_CHANGE_EVENT_QUEUE_SIZE_PROPERTY, Integer.class,
                                                         DEFAULT_MEMBER_CHANGE_EVENT_QUEUE_SIZE));
    // 注册一个ip变动监听器,可以动态更新本机ip
    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;
        }
    });
}

registerClusterEvent主要注册了一个事件发布者和处理了ip的变动。可支持动态更新ip

initAndStartLookup

private void initAndStartLookup() throws NacosException {
    // 获取到MemberLookup
    this.lookup = LookupFactory.createLookUp(this);
    isUseAddressServer = this.lookup.useAddressServer();
    // 对文件进行监控
    this.lookup.start();
}

初始化过程中创建了一个查找功能。然后对地址进行监控更新。这里以经常使用的cluster.file为例来分析。

public static MemberLookup createLookUp(ServerMemberManager memberManager) throws NacosException {
    if (!EnvUtil.getStandaloneMode()) {
        // 非单机情况下,选择一种查找方式
        String lookupType = EnvUtil.getProperty(LOOKUP_MODE_TYPE);
        LookupType type = chooseLookup(lookupType);
        LOOK_UP = find(type);
        currentLookupType = type;
    } else {
        LOOK_UP = new StandaloneMemberLookup();
    }
    LOOK_UP.injectMemberManager(memberManager);
    Loggers.CLUSTER.info("Current addressing mode selection : {}", LOOK_UP.getClass().getSimpleName());
    return LOOK_UP;
}

private static LookupType chooseLookup(String lookupType) {
    if (StringUtils.isNotBlank(lookupType)) {
        // 配置了直接用配置的
        LookupType type = LookupType.sourceOf(lookupType);
        if (Objects.nonNull(type)) {
            return type;
        }
    }
    // 优先判断cluster.conf是否存在,存在就用文件模式
    File file = new File(EnvUtil.getClusterConfFilePath());
    if (file.exists() || StringUtils.isNotBlank(EnvUtil.getMemberList())) {
        return LookupType.FILE_CONFIG;
    }
    // 非文件就用配置文件application.properties的配置地址
    return LookupType.ADDRESS_SERVER;
}

在上述代码中,Nacos针对集群模式情况下,会寻找配置的集群地址,一共有两种方式,分别是使用cluster.conf文件模式配置,另一种是application.properties配置nacos.member.list。这里使用有个优先级,如果配置了使用某种方式,就以配置为准,否则优先用cluster.conf文件,以上都没有则使用application.properties

继续分析this.lookup.start()

@Override
public void start() throws NacosException {
    if (start.compareAndSet(false, true)) {
        // 未启动则启动
        doStart();
    }
}
 @Override
public void doStart() throws NacosException {
    // 从磁盘中读取数据
    readClusterConfFromDisk();
    // Use the inotify mechanism to monitor file changes and automatically
    // trigger the reading of cluster.conf
    try {
        // 监控文件的变动,自动修改集群中的服务节点成员
        WatchFileCenter.registerWatcher(EnvUtil.getConfPath(), watcher);
    } catch (Throwable e) {
        Loggers.CLUSTER.error("An exception occurred in the launch file monitor : {}", e.getMessage());
    }
}

start方法中。就是读取了cluster.conf的文件内容,但是它还监控了文件的变化。而文件的变化监控又是如何做的呢?

public static synchronized boolean registerWatcher(final String paths, FileWatcher watcher) throws NacosException {
    checkState();
    if (NOW_WATCH_JOB_CNT == MAX_WATCH_FILE_JOB) {
        return false;
    }
    WatchDirJob job = MANAGER.get(paths);
    if (job == null) {
        job = new WatchDirJob(paths);
        // job线程异步处理
        job.start();
        MANAGER.put(paths, job);
        NOW_WATCH_JOB_CNT++;
    }
    job.addSubscribe(watcher);
    return true;
}

public void run() {
    // 一直循环监控
    while (watch && !this.isInterrupted()) {
        try {
            final WatchKey watchKey = watchService.take();
            final List<WatchEvent<?>> events = watchKey.pollEvents();
            watchKey.reset();
            if (callBackExecutor.isShutdown()) {
                return;
            }
            if (events.isEmpty()) {
                continue;
            }
            // 文件监控线程池进行处理
            callBackExecutor.execute(() -> {
                for (WatchEvent<?> event : events) {
                    WatchEvent.Kind<?> kind = event.kind();
                    // Since the OS's event cache may be overflow, a backstop is needed
                    if (StandardWatchEventKinds.OVERFLOW.equals(kind)) {
                        eventOverflow();
                    } else {
                        // 处理监控文件时间逻辑
                        eventProcess(event.context());
                    }
                }
            });
        } catch (InterruptedException | ClosedWatchServiceException ignore) {
            Thread.interrupted();
        } catch (Throwable ex) {
            LOGGER.error("An exception occurred during file listening : ", ex);
        }
    }
}

private void eventProcess(Object context) {
    // 构造文件变动事件
    final FileChangeEvent fileChangeEvent = FileChangeEvent.builder().paths(paths).context(context).build();
    final String str = String.valueOf(context);
    for (final FileWatcher watcher : watchers) {
        if (watcher.interest(str)) {
            // 构造触发事件
            Runnable job = () -> watcher.onChange(fileChangeEvent);
            Executor executor = watcher.executor();
            if (executor == null) {
                try {
                    // 直接执行
                    job.run();
                } catch (Throwable ex) {
                    LOGGER.error("File change event callback error : ", ex);
                }
            } else {
                executor.execute(job);
            }
        }
    }
}

这个文件的监控的是后台有个线程,不断的去获取最新的状态,如果变动了文件,第一时间去通知其他对本事件感兴趣的订阅者。

AsyncRpcTask

获取到服务列表后,通过线程池调用异步任务AsyncRpcTask

class AsyncRpcTask implements Runnable {
    private Queue<NotifySingleRpcTask> queue;
    public AsyncRpcTask(Queue<NotifySingleRpcTask> queue) {
        // 构造方法放入rpcTask的队列
        this.queue = queue;
    }
    @Override
    public void run() {
        while (!queue.isEmpty()) {
            NotifySingleRpcTask task = queue.poll();
            // 构造配置变动的请求
            ConfigChangeClusterSyncRequest syncRequest = new ConfigChangeClusterSyncRequest();
            syncRequest.setDataId(task.getDataId());
            syncRequest.setGroup(task.getGroup());
            syncRequest.setBeta(task.isBeta);
            syncRequest.setLastModified(task.getLastModified());
            syncRequest.setTag(task.tag);
            syncRequest.setTenant(task.getTenant());
            Member member = task.member;、
            // 如果是当前节点,直接调用dumpService执行dump操作
            if (memberManager.getSelf().equals(member)) {
                if (syncRequest.isBeta()) {
                    dumpService.dump(syncRequest.getDataId(), syncRequest.getGroup(), syncRequest.getTenant(),
                                     syncRequest.getLastModified(), NetUtils.localIP(), true);
                } else {
                    dumpService.dump(syncRequest.getDataId(), syncRequest.getGroup(), syncRequest.getTenant(),
                                     syncRequest.getTag(), syncRequest.getLastModified(), NetUtils.localIP());
                }
                continue;
            }

            if (memberManager.hasMember(member.getAddress())) {
                // 启动健康检查,有IP未被监控,直接放入通知队列,否则通知
                boolean unHealthNeedDelay = memberManager.isUnHealth(member.getAddress());
                if (unHealthNeedDelay) {
                    // 目标 IP 运行状况不健康,然后将其放入通知列表中
                    ConfigTraceService.logNotifyEvent(task.getDataId(), task.getGroup(), task.getTenant(), null,
                                                      task.getLastModified(), InetUtils.getSelfIP(), ConfigTraceService.NOTIFY_EVENT_UNHEALTH,
                                                      0, member.getAddress());
                    // 获取延迟时间并将失败计数设置为任务
                    asyncTaskExecute(task);
                } else {
                    // 判断是否支持长连接,长连接用rpc,非长连接用http
                    if (!MemberUtil.isSupportedLongCon(member)) {
                        // 异步执行http请求
                        asyncTaskExecute(
                            new NotifySingleTask(task.getDataId(), task.getGroup(), task.getTenant(), task.tag,
                                                 task.getLastModified(), member.getAddress(), task.isBeta));
                    } else {
                        try {
                            // rpc请求
                            configClusterRpcClientProxy
                                .syncConfigChange(member, syncRequest, new AsyncRpcNotifyCallBack(task));
                        } catch (Exception e) {
                            MetricsMonitor.getConfigNotifyException().increment();
                            asyncTaskExecute(task);
                        }
                    }

                }
            } else {
                //No nothig if  member has offline.
            }

        }
    }
}

AsyncRpcTask主要执行两个重要逻辑:

  1. 本机直接执行dump操作
  2. 其他的服务节点判断是否支持长连接,非长连接,即http的模式,异步发送http请求,长连接则发送grpc请求

总结

本篇主要介绍了AsyncNotifyService的主要逻辑和对集群中服务节点的获取方式。但是在AsyncRpcTask主要执行两个重要逻辑dump和服务节点间的消息同步。这个下篇再分析。