在P1-简单注册中心实现和P2-Nacos解析中,我们分别实现了简单的注册中心并总结了Nacos的一些设计。
本篇继续看Nacos源码,了解一下Nacos中的设计模式。
目录
- Nacos 观察者模式 Observer Pattern
- 观察者模式总结
Nacos 观察者模式 Observer Pattern
模式定义:观察者模式是一种行为型模式,定义对象间一对多依赖,确保当一个对象变化时,其依赖对象也会被通知并自动更新。
在配置管理中,客户端 (ConfigService) 注册监听器 (Listener) 来监听特定 dataId 和 group 的配置变更。当 Nacos Server 上的配置发生变化时,会通知所有监听该配置的客户端。
在服务发现中,客户端 (NamingService) 订阅 (subscribe) 特定服务名。当该服务的实例列表(上线、下线、状态变更)发生变化时,Nacos Server 会将最新的服务实例列表推送给所有订阅的客户端。
这种推送机制和探测机制不一样,是不区分服务实例是持久化(persistent)还是临时(ephemeral)实例的,都会触发通知。
下面是简单的模拟:
// 观察者接口 (Nacos 客户端 Listener)
public interface ConfigObserver {void onConfigChange(String dataId, String group, String newContent);
}
// 被观察者 (Nacos 配置中心)
public class ConfigSubject {private Map<String, List<ConfigObserver>> observers = new HashMap<>();private Map<String, String> configStore = new HashMap<>(); // 模拟配置存储private String getConfigKey(String dataId, String group) {return dataId + "@" + group;}// 注册观察者 (客户端添加 Listener)public void addObserver(String dataId, String group, ConfigObserver observer) {String key = getConfigKey(dataId, group);observers.computeIfAbsent(key, k -> new ArrayList<>()).add(observer);System.out.println("Observer added for: " + key);// 首次添加时,可以考虑推送当前配置String currentContent = configStore.getOrDefault(key, null);if (currentContent != null) {observer.onConfigChange(dataId, group, currentContent);}}// 移除观察者public void removeObserver(String dataId, String group, ConfigObserver observer) {String key = getConfigKey(dataId, group);List<ConfigObserver> observerList = observers.get(key);if (observerList != null) {observerList.remove(observer);System.out.println("Observer removed for: " + key);}}// 发布配置 (模拟配置变更)public void publishConfig(String dataId, String group, String content) {String key = getConfigKey(dataId, group);System.out.println("Publishing config for " + key + ": " + content);configStore.put(key, content);notifyObservers(dataId, group, content);}// 通知观察者private void notifyObservers(String dataId, String group, String newContent) {String key = getConfigKey(dataId, group);List<ConfigObserver> observerList = observers.get(key);if (observerList != null && !observerList.isEmpty()) {System.out.println("Notifying " + observerList.size() + " observers for " + key);// 创建副本以防并发修改List<ConfigObserver> observersToNotify = new ArrayList<>(observerList);for (ConfigObserver observer : observersToNotify) {try {observer.onConfigChange(dataId, group, newContent);} catch (Exception e) {System.err.println("Error notifying observer: " + e.getMessage());}}}}
}
// 具体观察者实现 (客户端应用中的监听器)
public class MyConfigListener implements ConfigObserver {private String listenerName;public MyConfigListener(String name) {this.listenerName = name;}@Overridepublic void onConfigChange(String dataId, String group, String newContent) {System.out.println("[" + listenerName + "] Received config change: DataId=" + dataId + ", Group=" + group + ", Content=" + newContent);// 在这里处理配置变更逻辑,例如更新应用内部状态}
}
// --- Demo Main ---
public class ObserverDemo {public static void main(String[] args) {ConfigSubject configCenter = new ConfigSubject();MyConfigListener listener1 = new MyConfigListener("App1-Listener");MyConfigListener listener2 = new MyConfigListener("App2-Listener");// App1 和 App2 都监听同一个配置configCenter.addObserver("app.properties", "DEFAULT_GROUP", listener1);configCenter.addObserver("app.properties", "DEFAULT_GROUP", listener2);System.out.println("\n--- Publishing first config ---");configCenter.publishConfig("app.properties", "DEFAULT_GROUP", "database.url=jdbc:mysql://localhost:3306/mydb");System.out.println("\n--- Publishing updated config ---");configCenter.publishConfig("app.properties", "DEFAULT_GROUP", "database.url=jdbc:mysql://remote:3306/prod_db");System.out.println("\n--- App1 stops listening ---");configCenter.removeObserver("app.properties", "DEFAULT_GROUP", listener1);System.out.println("\n--- Publishing final config ---");configCenter.publishConfig("app.properties", "DEFAULT_GROUP", "database.url=jdbc:mysql://new_remote:3306/final_db");}
}
Nacos源码(用的是nacos2.1.1版本源码,可以在release中下载)位置如下:
-
配置监听
客户端接口: com.alibaba.nacos.api.config.ConfigService#addListener
客户端监听器接口: com.alibaba.nacos.api.config.listener.Listener
客户端内部实现: com.alibaba.nacos.client.config.NacosConfigService, com.alibaba.nacos.client.config.impl.ClientWorker (处理长轮询和回调)
服务端通知逻辑: com.alibaba.nacos.config.server.service.LongPollingService, com.alibaba.nacos.config.server.utils.ConfigExecutor (处理配置变更事件和推送) -
服务订阅
客户端接口: com.alibaba.nacos.api.naming.NamingService#subscribe
客户端监听器接口: com.alibaba.nacos.api.naming.listener.EventListener
客户端内部实现: com.alibaba.nacos.client.naming.NacosNamingService, com.alibaba.nacos.client.naming.core.ServiceInfoUpdater
服务端推送逻辑: com.alibaba.nacos.naming.push.PushService, com.alibaba.nacos.naming.core.Service (管理服务实例变更并触发推送)
观察者模式总结
通过Nacos的案例,我们可以简单总结出观察者模式的特点,如下图:
其中,被观察的通知动作可以是异步业可以是同步,观察者收到通知后,可以决定是否告知已接收还是或是不告知。
上面的类图只是用于简单理解。出于单一职责原则与低耦合、可扩展设计等考虑,观察者模式可以做出如下抽象:
被观察者(主题 Subject)、观察者(Observer)
将观察者被和被观察主要功能抽象成接口。