上一篇我们介绍了配置服务的插件功能,可以通过插件进行一个很好的扩展。本篇接着上篇未分析的ConfigChangePublisher.notifyConfigChange
部分的内容。
public static void notifyConfigChange(ConfigDataChangeEvent event) {
if (PropertyUtil.isEmbeddedStorage() && !EnvUtil.getStandaloneMode()) {
// 内部存储并且非单例就不处理了
return;
}
NotifyCenter.publishEvent(event);
}
上述代码又碰到之前分析的NotifyCenter
,没看过的小伙伴可以点击这里进行查看。既然走到了通知中心,可以直接查找ConfigDataChangeEvent
的onEvent
方法来查看处理逻辑。
@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
主要执行两个重要逻辑:
- 本机直接执行
dump
操作 - 其他的服务节点判断是否支持长连接,非长连接,即
http
的模式,异步发送http
请求,长连接则发送grpc
请求
总结
本篇主要介绍了AsyncNotifyService
的主要逻辑和对集群中服务节点的获取方式。但是在AsyncRpcTask
主要执行两个重要逻辑dump
和服务节点间的消息同步。这个下篇再分析。