Dubbo配置設計
- 配置分類
- 配置格式
- 配置加載
- 可編程配置
- 配置缺省值
- 配置一致性
- 配置覆蓋
- 配置繼承
- 配置向後兼容
配置分類
首先,配置的用途是有多種的,大致可以分為:
- 環境配置,比如:連接數,超時等配置。
- 描述配置,比如:服務接口描述,服務版本等。
- 擴展配置,比如:協議擴展,策略擴展等。
配置格式
通常環境配置,用 properties 配置會比較方便,因為都是一些離散的簡單值,用 key-value 配置可以減少配置的學習成本。
而描述配置,通常信息比較多,甚至有層次關系,用 xml 配置會比較方便,因為樹結構的配置表現力更強。如果非常復雜,也可以考自定義 DSL 做為配置。有時候這類配置也可以用 Annotation 代替, 因為這些配置和業務邏輯相關,放在代碼裏也是合理的。
另外擴展配置,可能不盡相同。如果只是策略接口實現類替換,可以考慮 properties 等結構。如果有復雜的生命周期管理,可能需要 XML 等配置。有時候擴展會通過註冊接口的方式提供。
配置加載
對於環境配置,在 java 世界裏,比較常規的做法,是在 classpath 下約定一個以項目為名稱的 properties 配置,比如:log4j.properties,velocity.properties等。產品在初始化時,自動從 classpath 下加載該配置。我們平臺的很多項目也使用類似策略,如:dubbo.properties,comsat.xml 等。這樣有它的優勢,就是基於約定,簡化了用戶對配置加載過程的幹預。但同樣有它的缺點,當 classpath 存在同樣的配置時,可能誤加載,以及在 ClassLoader 隔離時,可能找不到配置,並且,當用戶希望將配置放到統一的目錄時,不太方便。
Dubbo 新版本去掉了 dubbo.properties,因為該約定經常造成配置沖突。
而對於描述配置,因為要參與業務邏輯,通常會嵌到應用的生命周期管理中。現在使用 spring 的項目越來越多,直接使用 spring 配置的比較普遍,而且 spring 允許自定義 schema,配置簡化後很方便。當然,也有它的缺點,就是強依賴 spring,可以提編程接口做了配套方案。
在 Dubbo 即存在描述配置,也有環境配置。一部分用 spring 的 schame 配置加載,一部分從 classpath 掃描 properties 配置加載。用戶感覺非常不便,所以在新版本中進行了合並,統一放到 spring 的 schame 配置加載,也增加了配置的靈活性。
擴展配置,通常對配置的聚合要求比較高。因為產品需要發現第三方實現,將其加入產品內部。在 java 世界裏,通常是約定在每個 jar 包下放一個指定文件加載,比如:eclipse 的 plugin.xml,struts2 的 struts-plugin.xml 等,這類配置可以考慮 java 標準的服務發現機制,即在 jar 包的 META-INF/services 下放置接口類全名文件,內容為每行一個實現類類名,就像 jdk 中的加密算法擴展,腳本引擎擴展,新的 JDBC 驅動等,都是采用這種方式。參見:ServiceProvider 規範。
Dubbo 舊版本通過約定在每個 jar 包下,放置名為 dubbo-context.xml 的 spring 配置進行擴展與集成,新版本改成用 jdk 自帶的 META-INF/services 方式,去掉過多的 spring 依賴。
可編程配置
配置的可編程性是非常必要的,不管你以何種方式加載配置文件,都應該提供一個編程的配置方式,允許用戶不使用配置文件,直接用代碼完成配置過程。因為一個產品,尤其是組件類產品,通常需要和其它產品協作使用,當用戶集成你的產品時,可能需要適配配置方式。
Dubbo 新版本提供了與 xml 配置一對一的配置類,如:ServiceConfig 對應
<dubbo:service />
,並且屬性也一對一,這樣有利於文件配置與編程配置的一致性理解,減少學習成本。配置缺省值
配置的缺省值,通常是設置一個常規環境的合理值,這樣可以減少用戶的配置量。通常建議以線上環境為參考值,開發環境可以通過修改配置適應。缺省值的設置,最好在最外層的配置加載就做處理。程序底層如果發現配置不正確,就應該直接報錯,容錯在最外層做。如果在程序底層使用時,發現配置值不合理,就填一個缺省值,很容易掩蓋表面問題,而引發更深層次的問題。並且配置的中間傳遞層,很可能並不知道底層使用了一個缺省值,一些中間的檢測條件就可能失效。Dubbo 就出現過這樣的問題,中間層用“地址”做為緩存 Key, 而底層,給“地址”加了一個缺省端口號,導致不加端口號的“地址”和加了缺省端口的“地址”並沒有使用相同的緩存。
配置一致性
配置總會隱含一些風格或潛規則,應盡可能保持其一致性。比如:很多功能都有開關,然後有一個配置值:
- 是否使用註冊中心,註冊中心地址。
- 是否允許重試,重試次數。
你可以約定:
- 每個都是先配置一個 boolean 類型的開關,再配置一個值。
- 用一個無效值代表關閉,N/A地址,0重試次數等。
不管選哪種方式,所有配置項,都應保持同一風格,Dubbo 選的是第二種。相似的還有,超時時間,重試時間,定時器間隔時間。如果一個單位是秒,另一個單位是毫秒(C3P0的配置項就是這樣),配置人員會瘋掉。
配置覆蓋
提供配置時,要同時考慮開發人員,測試人員,配管人員,系統管理員。測試人員是不能修改代碼的,而測試的環境很可能較為復雜,需要為測試人員留一些“後門”,可以在外圍修改配置項。就像 spring 的 PropertyPlaceholderConfigurer 配置,支持
SYSTEM_PROPERTIES_MODE_OVERRIDE
,可以通過 JVM 的 -D 參數,或者像 hosts 一樣約定一個覆蓋配置文件,在程序外部,修改部分配置,便於測試。Dubbo 支持通過 JVM 參數
-Dcom.xxx.XxxService=dubbo://10.1.1.1:1234
直接使遠程服務調用繞過註冊中心,進行點對點測試。還有一種情況,開發人員增加配置時,都會按線上的部署情況做配置,如:<dubbo:registry address="${dubbo.registry.address}" />
因為線上只有一個註冊中心,這樣的配置是沒有問題的,而測試環境可能有兩個註冊中心,測試人員不可能去修改配置,改為:<dubbo:registry address="${dubbo.registry.address1}" />
,<dubbo:registry address="${dubbo.registry.address2}" />
,所以這個地方,Dubbo 支持在 ${dubbo.registry.address} 的值中,通過豎號分隔多個註冊中心地址,用於表示多註冊中心地址。配置繼承
配置也存在“重復代碼”,也存在“泛化與精化”的問題。比如:Dubbo 的超時時間設置,每個服務,每個方法,都應該可以設置超時時間。但很多服務不關心超時,如果要求每個方法都配置,是不現實的。所以 Dubbo 采用了方法超時繼承服務超時,服務超時再繼承缺省超時,沒配置時,一層層向上查找。
另外,Dubbo 舊版本所有的超時時間,重試次數,負載均衡策略等都只能在服務消費方配置。但實際使用過程中發現,服務提供方比消費方更清楚,但這些配置項是在消費方執行時才用到的。新版本,就加入了在服務提供方也能配這些參數,通過註冊中心傳遞到消費方, 做為參考值,如果消費方沒有配置,就以提供方的配置為準,相當於消費方繼承了提供方的建議配置值。而註冊中心在傳遞配置時,也可以在中途修改配置,這樣就達到了治理的目的,繼承關系相當於:服務消費者 --> 註冊中心 --> 服務提供者
配置向後兼容
向前兼容很好辦,你只要保證配置只增不減,就基本上能保證向前兼容。但向後兼容,也是要註意的,要為後續加入新的配置項做好準備。如果配置出現一個特殊配置,就應該為這個“特殊”情況約定一個兼容規則,因為這個特殊情況,很有可能在以後還會發生。比如:有一個配置文件是保存“服務=地址”映射關系的,其中有一行特殊,保存的是“註冊中心=地址”。現在程序加載時,約定“註冊中心”這個Key是特殊的,做特別處理,其它的都是“服務”。然而,新版本發現,要加一項“監控中心=地址”,這時,舊版本的程序會把“監控中心”做為“服務”處理,因為舊代碼是不能改的,兼容性就很會很麻煩。如果先前約定“特殊標識+XXX”為特殊處理,後續就會方便很多。
Dubbo配置設計