一、簡介
基於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容器內。詳細學習參考https://docs.spring.io/spring/docs/4.3.19.RELEASE/spring-framework-reference/htmlsingle/#beans-factory-nature。
參數中心系統參數的配置實現參考了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項目(注釋還是比較清晰的)
七、說明
本人技術有限,上述有錯誤的理解歡迎指出,共同交流學習,若對上述說明不了解,建議先學習一點Spring IoC設計,學習地址:https://docs.spring.io/spring/docs/4.3.19.RELEASE/spring-framework-reference/htmlsingle,若對於客戶端的設計有好的建議可以提出來,共同討論。
