最近在作應用配置化的事情。 應用配置化,就是將應用中一些頻繁變化的變量經過配置好比JSON串的形式存儲到配置平臺。當新業務須要增長新的枚舉時,只要在配置平臺修改對應配置,就能實時推送給應用更新,無需應用從新發布。頻繁變化的變量包括文案、分流比例、枚舉等。java
配置更新的實時推送機制,讓人很容易就想到觀察者模式。 觀察者模式的要點在於:多線程
觀察者模式的設計結構以下:
app
這裏藉助 jdk 的Observable, Observer 提供觀察者的一個極簡實現。框架
package zzz.study.patterns.observer.realconfig.simple; import java.util.Observable; public class Config<T> extends Observable { private T conf; public Config(T conf) { this.conf = conf; } public T getConf() { return conf; } public void update(T config) { this.conf = config; setChanged(); } }
package zzz.study.patterns.observer.realconfig.simple; import java.util.Observable; import java.util.Observer; public class Application implements Observer { public void update(Observable o, Object arg) { Config obj = (Config)o; Object conf = obj.getConf(); System.out.println("conf updated: " + conf); } }
package zzz.study.patterns.observer.realconfig.simple; public class ConfigRealUpdating { public static void main(String[] args) { Config config = new Config<>(5); Application app = new Application(); config.addObserver(app); config.update(6); config.notifyObservers(); } }
極簡實現提供了對觀察者模式基本思想的直接理解。但極簡實現是遠遠不夠的。它只有一個被觀察者和一個應用對象。假設有兩個應用 A,B以及兩個配置 aconf, bconf , A 關注 aconf 的變化, B 關注 bconf 的變化。怎麼辦呢? 有兩種方案:分佈式
代碼實現以下:ide
public class AConfig extends Config<String> { public AConfig(String conf) { super(conf); } } public class BConfig extends Config<Long> { public BConfig(Long conf) { super(conf); } } public abstract class AbstractApplication implements Observer { public void update(Observable o, Object arg) { Config obj = (Config)o; Object conf = obj.getConf(); handle(conf); } public abstract void handle(Object conf); } public class AApplication extends AbstractApplication { @Override public void handle(Object conf) { System.out.println("A Conf updated: " + conf); } } public class BApplication extends AbstractApplication { @Override public void handle(Object conf) { Long num = (Long)conf; if (num != null && num > 0) { String info = String.format("factor(%d) = %d", num, factor(num)); System.out.println(info); } } public Long factor(Long num) { if (num < 0) { return 0L; } if (num == 0) {return 1L; } return num * factor(num-1); } } public class ConfigRealUpdating { public static void main(String[] args) { Config aConfig = new AConfig("Haha"); Config bConfig = new BConfig(-1L); AbstractApplication aApp = new AApplication(); AbstractApplication bApp = new BApplication(); aConfig.addObserver(aApp); bConfig.addObserver(bApp); aConfig.update("I am changed"); aConfig.notifyObservers(); bConfig.update(9L); bConfig.notifyObservers(); } }
設計結構變成:
優化
停下來從新思考下,經過擴展更多子類來實現是否有些衝動?須要思考兩個問題:this
如今,思考一個更加實際的問題:假設應用A監聽配置a的變化,作HAa的處理,監聽配置b的變化,作HAb的處理,監聽配置c的變化,作HAc的處理,……;應用B監聽配置a的變化,作HBa的處理,監聽配置b的變化,作HBb的處理,監聽配置c的變化,作HBc的處理,……。 也就是說,有多個應用,同時監聽多個配置,作不一樣的處理,該怎麼辦?atom
此時,不能將觀察者列表放在 Observable 了。 實際上,jdk 裏的 Observable 擔負了兩個責任:(1) 狀態變化; (2) 通知觀察者。 如今,但願將職責(2) 移到專門負責交互的一個對象。這個對象可採用中介者模式來實現。但願達成效果:線程
如今,須要造點輪子了。 要解決這個交互,須要構造一個三元組<config, app, updateFunc>集合。因爲 java 沒有提供元組功能,所以,能夠將後二者包裝成一個對象ConfigObserver,配置與應用行爲可抽象爲一個Map[Config, List[ConfigObserver]] 。注意,這裏 Config, ConfigObserver 都須要跟集合交互,爲保證存取正確,必須覆寫 equals 和 hashCode 方法。 這裏配置和應用都採用一個ID 來標識(由於配置變動時,值已經改變了,因此不能用值做爲equals的依據)。
代碼實現以下(全部類都放在包 zzz.study.patterns.observer.realconfig.interact 下):
ID.java 封裝了 id 相關的功能
package zzz.study.patterns.observer.realconfig.interact; import java.util.concurrent.atomic.AtomicLong; /** * 封裝了ID以及根據ID來比較的功能 */ public abstract class ID { private static AtomicLong gloalId = new AtomicLong(0); // 經過id字段標識配置及應用 protected Long id; public void setId() { this.id = gloalId.addAndGet(1); } public <T> boolean equals(Object obj, Class<T> cls) { if (obj == null || ! (cls.isInstance(obj))) { return false; } return ((ID)obj).id.equals(this.id); } public int hashCode() { return id.hashCode(); } }
應用類:
package zzz.study.patterns.observer.realconfig.interact; public abstract class Application extends ID { @Override public boolean equals(Object c) { return equals(c, Application.class); } @Override public int hashCode() { return super.hashCode(); } } public class AApp extends Application { public AApp() { super.setId(); } public Object haa(Config c) { System.out.println("haa: " + c.getConf()); return c; } public Object hab(Config c) { System.out.println("hab: " + c.getConf()); return c; } public Object hac(Config c) { System.out.println("hac: " + c.getConf()); return c; } } public class BApp extends Application { public BApp() { super.setId(); } public Object hba(Config c) { System.out.println("hba: " + c.getConf()); return c; } public Object hbb(Config c) { System.out.println("hbb: " + c.getConf()); return c; } public Object hbc(Config c) { System.out.println("hbc: " + c.getConf()); return c; } }
配置類:
package zzz.study.patterns.observer.realconfig.interact; import lombok.Getter; @Getter public class Config<T> extends ID { private T conf; private ObserverMediator mediator; public Config(T conf, ObserverMediator mediator) { super.setId(); this.conf = conf; this.mediator = mediator; } public void update(T config) { this.conf = config; mediator.notifyAll(this); } @Override public boolean equals(Object c) { return equals(c, Config.class); } @Override public int hashCode() { return super.hashCode(); } }
package zzz.study.patterns.observer.realconfig.interact; import java.util.function.Function; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class ConfigObserver { private Application app; private Function<Config, Object> updateFunc; @Override public boolean equals(Object c) { if (c == null || ! (c instanceof ConfigObserver)) { return false; } ConfigObserver cmp = (ConfigObserver) c; return cmp.getApp().equals(this.getApp()); } @Override public int hashCode() { return app.hashCode(); } }
中介者:
package zzz.study.patterns.observer.realconfig.interact; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 中介者模式,管理觀察者與被觀察者的交互 * 實際應用中,可做爲一個Spring Singleton Bean來管理 */ public class ObserverMediator { private Map<Config, List<ConfigObserver>> mediator = new HashMap<>(); /** * 應用啓動時初始化 */ public void init() { } public synchronized boolean register(Config config, ConfigObserver configObserver) { List<ConfigObserver> oberverList = mediator.get(config); if (oberverList == null) { oberverList = new ArrayList<>(); } oberverList.add(configObserver); mediator.put(config, oberverList); return true; } public synchronized boolean unregister(Config config, ConfigObserver configObserver) { List<ConfigObserver> oberverList = mediator.get(config); if (oberverList == null) { return false; } oberverList.remove(configObserver); mediator.put(config, oberverList); return true; } public synchronized boolean notifyAll(Config config) { List<ConfigObserver> configObservers = mediator.get(config); configObservers.forEach( observer -> observer.getUpdateFunc().apply(config) ); return true; } }
客戶端使用:
package zzz.study.patterns.observer.realconfig.interact; import java.util.concurrent.TimeUnit; public class ConfigRealUpdating { public static void main(String[] args) { // 中介者是全局管理者,是最早存在的 ObserverMediator mediator = new ObserverMediator(); // 這一步在配置平臺實現, 能夠採用註解的方式注入到應用中,配置僅與中介者交互 Config aConfig = new Config("Haha", mediator); Config bConfig = new Config(-1L, mediator); Config cConfig = new Config(true, mediator); AApp a = new AApp(); BApp b = new BApp(); // 這一步能夠經過應用啓動時註冊到分佈式服務發現系統上來完成 mediator.register(aConfig, new ConfigObserver(a, conf -> a.haa(conf))); mediator.register(bConfig, new ConfigObserver(a, conf -> a.hab(conf))); mediator.register(cConfig, new ConfigObserver(a, conf -> a.hac(conf))); mediator.register(aConfig, new ConfigObserver(b, conf -> b.hba(conf))); mediator.register(bConfig, new ConfigObserver(b, conf -> b.hbb(conf))); mediator.register(cConfig, new ConfigObserver(b, conf -> b.hbc(conf))); // 核心: 更新與通知 aConfig.update("I am changed"); sleep(2000); bConfig.update(9L); sleep(2000); mediator.unregister(cConfig, new ConfigObserver(b, conf -> b.hbc(conf))); cConfig.update(false); sleep(2000); } private static void sleep(long millis) { try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
設計結構變爲:
設計說明:
業界開源的動態配置平臺有攜程的Apollo配置。能夠想象一下主流程:
STEP1: 在配置平臺,首先要註冊應用。這樣就創建 Application 對象;
STEP2: 在配置平臺的某個應用下,新建某個配置,就會在 Apollo 系統內將 Config 與 Application 聯繫起來;
STPE3: 當應用啓動時,經過SpringXML配置的方式, 告知 Apollo 該應用所須要的配置,實際上造成了該應用的配置訂閱; Apollo 在運行時將配置與應用關聯起來;
STEP4: 當用戶在配置平臺更改某個配置時,經過觀察者模式,就能夠將配置改動通知到鏈接上的應用;
STEP5: 應用能夠經過註冊配置的監聽器,來處理配置改動。
實際中,能夠支持更多功能,好比更靈活的配置對象類型、配置的名字空間、配置的共享、經過註解的方式實時推送更新的配置等。
本文講解了經過觀察者模式來實現配置動態更新實時推送的基本原理和實現。觀察者模式是解耦變化與關聯變化的設計結構,即:當一個對象的某個狀態變化後,須要通知關注該對象該狀態的全部對象。關注者不須要知道其餘關注者,被觀察者也不須要知道觀察者。