在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)
將觀察者被和被觀察主要功能抽象成接口。