基於ZooKeeper,Spring設計實現的引數系統
一、簡介
基於ZooKeeper服務端、ZooKeeper Java客戶端以及Spring框架設計的用於系統內部進行引數維護的系統。
二、設計背景
在我們日常開發的系統內部,開發過程中最常見的一項工作便是常用引數的維護,從我學習Java以來,引數的配置多樣化,最常見的方式是properties配置檔案或者是xml配置檔案,高深點的用法是JMX MBean進行引數管理以及資料庫引數配置。我們對現有的引數配置方式進行分析,詳見下表:
配置方式 | 常見問題 | 備註 |
---|---|---|
程式碼內字元竄字面量配置 | 每次引數的修改都需要重新編譯 | |
properties配置檔案/xml配置檔案 | 普通用法雖然將引數從程式碼內抽離出來,但是無法隨時更新生效 | Spring提供的ReloadableResourceBundleMessageSource工具類可以實現熱載入Properties檔案,將引數配置檔案從程式碼分離可以做到不停機不重啟做引數維護,並被程式載入,但是仍需系統重新從檔案資源內獲取新的引數值 |
JMX引數配置 | 標準MBEAN是有侵入性的,他要管理的物件是符合JAVA BEAN規範的物件。但是要作為標準MBEAN而被管理,就需要實現一個介面。這個介面的名稱必須是類名加上MBean。 | Spring支援將普通Bean通過配置MBeanExporter生成MBean |
資料庫引數配置 | 需要從資料庫內載入引數,每次引數的使用需要連線資料庫進行資料庫查詢。後來出現了程式碼快取資料庫引數,在一次使用後將引數資訊放置快取內,但是這種做法無法感知資料庫引數的變化。 | 程式碼快取資料庫引數方式,可以新增程式碼設計用於重新整理快取引數,從庫內重新讀取放置快取內,也不失為一種方便的引數管理。 |
基於上述各類引數配置分析,一番思考設想,設計出如下結構的[引數中心繫統](詳細設計連結),設計說明檢視下一節:
三、系統設計說明
引數中心繫統,顧名思義,主要是將引數集中化,在實際開發中,一個業務的實現需要幾個甚至數十個模組聯合完成,每個模組都需要進行引數的更新維護,一個模組的引數更新設計缺陷,在進行引數維護時,就可能導致某個業務的中斷,故需要將多引數管理統一化管理;統一化的引數管理方式,便可能涉及到了引數資料的統一儲存,統一之後便出現了效能瓶頸需求,不然所有雞蛋裝一個籃子裡,一出問題全部碎掉;在集中化後,各個模組有自己的引數,有些引數可能僅限單個系統訪問,便需要安全的引數訪問方式;在引數使用過程中,常見的功能之一便是引數的實時維護;在專案投產過程中,經常因為引數配置問題比如配置錯誤等情況導致業務中斷,故需要一個引數檢查表來確認引數的正確性;引數管理整合後,需要方便的操作來實現管理功能。概括一下,引數中心繫統需要滿足以下技術需求:
- 多系統、多模式、安全、動態維護的引數配置
- 個性化話引數配置(普通字元竄,JSON字元竄,陣列竄)
- 低侵入
- 快捷的引數匯入匯出功能
- 便捷的管理方式
- 上線引數檢查表,用於上線時各類引數的檢查,防止出錯
- 高可用
- 安全控制
根據上述分析,設計之初,思考如何實現多系統多模式的引數儲存,雖然一直知道ZooKeeper這個東西,但從未詳細瞭解過,偶然機會大致學習了一下ZooKeeper,發現ZooKeeper的各類機制與引數中心繫統設計相吻合,比如:多系統多模式的引數儲存與ZooKeeper的目錄型儲存方式相似,檢視下面圖3-1展示;引數的安全方式認證與ZooKeeper的ACL機制吻合;引數實時維護可以借鑑ZooKeeper的Watcher機制;引數中心繫統的高效能高可用設計與ZooKeeper的叢集方式相吻合。這樣下來,引數中心繫統最大的問題引數儲存模組服務端得到了完美的解決。接下來的便是基於ZooKeeper設計出對應的客戶端,管理端。
圖3-1 基於ZooKeeper的引數儲存
Java應用端常用的技術之一便是Spring框架,也符合低侵入的設計原則,在使用Spring開發過程中,常用的功能之一便是使用${}引用properties配置檔案內的引數,如此方便的引數配置方式,我決定使用類似的方式,配置方式為zk{}(zk表示ZooKeeper引數),故客戶端的設計是基於Spring的設計。
四、系統技術組合
ZooKeeper叢集 + ZooKeeper Java客戶端 + Spring BeanFactoryPostProcessor擴充套件點 + JSON字元竄解析 + Spring SpEL表示式
五、設計實現(重點)
根據上述設計說明等資訊,最後得出這樣一個系統,基於ZooKeeper引數儲存,Spring客戶端使用zk{}進行引數配置的引數中心繫統。
- 服務端
服務端設計如3-1圖所示(在實際開發過程中可能稍有變動)
- 客戶端(重點)
在進行引數中心繫統客戶端實現之前,我們先了解一點Spring框架的基礎知識。
在Spring框架開發過程中,最常用的配置是<bean/>標籤的使用,在工作中,最常聽的一種說法是,在xml裡配置上就可以使用bean了,就有物件了,這種理解潛在一層含義xml直接配置成了java bean,而實際上,Spring中bean的定義最終表現為BeanDefinition物件,個人理解為xml實際配置的是bean的說明資訊,Spring將這些說明資訊轉換為了BeanDefinition物件,再由BeanDefinition生成了我們最終看到的bean,實際流程為[xml配置->BeanDefinition->Bean]而一般開發者不知道BeanDefinition,故理解含義成了[xml配置->Bean],中間缺少了重要的環節。在BeanDefinition到Bean這個過程中,Spring由BeanFactory生成實際的bean,在實際bean產生前,Spring提供了BeanFactoryPostProcessor擴充套件點(類似還有BeanPostProcessor),通過該擴充套件點可以獲取到配置的BeanDefinition資訊,用於自定義擴充套件對bean定義變更修改,實現自由控制。
引數中心繫統引數的配置實現參考了Spring的${}引數配置,我們對${}的實現做簡單學習。${}的實現便使用了Spring擴充套件點BeanFactoryPostProcessor,開發中常見配置如下:
上圖中採用了context:property-placeholder標籤配置,根據Spring context的xsd說明檔案,我們知道了property-placeholder對應的實際類為org.springframework.context.support.PropertySourcesPlaceholderConfigurer,context:property-placeholder配置實際為在spring容器內註冊一個擴充套件點,實現${}表示式的解析。實現類圖以及呼叫流程大致如下,再詳細過程檢視原始碼,(有句話叫做師傅領進門,修行在個人):
引數中心繫統客戶端的實現程式碼與上述實現類似,不同的是properties配置檔案變成了ZooKeeper引數儲存,${}變成了zk{}。
客戶端專案名稱:itwatertop-pczk-client
- 管理端
基於H5的管理頁面設計,詳細情況還未設想(先實現了主要的服務端客戶端,管理端暫時可以使用ZooKeeper的客戶端)。
六、客戶端設計原始碼
客戶端程式結構如下:
檔案說明:
- BaseLoader.java 資料載入基類
- ZookeeperDataLoader.java Java ZooKeeper客戶端實現資料載入,引數更新回撥。
- PlaceholderMsg.java zk{zkexp} zk配置表示式解析結果,以及使用該表示式的bean屬性獲取SpEL表示式。
- ParamCenterStore.java 對ZooKeeper引數服務端獲取的引數資訊做快取,並且將對應的使用該引數的Bean屬性表示式統計,方便ZooKeeper引數變更時回撥使用。
- PczkConstants.java 系統內常量字元竄整合。
- PczkStringValueResolver.java 表示式解析統一介面
- PczkPropertyPlaceholderConfiguer.java 實現Spring擴充套件點BeanFactoryPostProcessor,通過該擴充套件點對BeanDefinition配置元資訊做解析以及變更。
- PropertyPlaceholderHelper.java 具體實現zk{}表示式解析規則。
- PczkBeanDefinitionVisitor.java 對Spring IoC容器內的BeanDefinition屬性配置資訊做解析,主要結合PczkPropertyPlaceholderConfiguer.java使用。
- test目錄下包含一部分測試程式碼,可以自行檢視
客戶端程式碼實現簡介:
根據上述需求,客戶端程式碼需要具備的能力包括:
- 與ZooKeeper伺服器的連通,並獲取引數資訊
- Spring xml中zk{}表示式的解析
- 多樣化的引數配置,支援字元竄,物件,陣列
- ZooKeeper引數變更回撥,維護引數資訊
針對上面4種要求,在開發時使用以下解決辦法:
- 使用ZooKeeper Java客戶端;
- 結合Spring的擴充套件點BeanFactoryPostProcessor;
- 在ZooKeeper服務端節點內設定資料時設定字元竄/JSON物件字元竄或者是JSON陣列字元竄,客戶端使用資料時分別配置為zk{param},zk{param.key},zk{param[i]},通過對zk{}表示式解析判斷ZooKeeper引數格式,若為JSON字元竄使用FastJSON對字元竄做解析,訪問對應屬性值;
- 針對引數變更回撥,在解析zk{}表示式時拼接出了可以訪問到對應bean屬性的SpEL表示式,通過SpEL表示式訪問bean屬性呼叫setter方法,因此屬性操作也受到SpEL表示式的限制。在個別情況下,由於引數的變更可能需要別的一下操作處理,比如重新建立連線,這個可以自行擴充套件程式碼,比如比較上一次的值和當前值是否一致,不一致做出新的操作(現在也在設想怎麼可以自動識別進行額外操作)。
客戶端詳細實現原始碼下載:訪問GitHub專案(註釋還是比較清晰的)
七、說明