阿里碼神告訴你,為什麼Dubbo配置要那麼設計?

2019-10-31     程式設計師聖經
作者:梁飛 
來源:http://pt.alibaba-inc.com/wp/experience_1301/code-detail.html


Dubbo現在的設計是完全無侵入,也就是使用者只依賴於配置契約, 經過多個版本的發展,為了滿足各種需求場景,配置越來越多, 為了保持兼容,配置只增不減,裡面潛伏著各種風格,約定,規則, 新版本也將配置做了一次調整,去掉了dubbo.properties,改為全spring配置, 將想到的一些記在這,備忘。

# 配置分類

首先,配置的用途是有多種的,大致可以分為:

(1) 環境配置,比如:連接數,超時等配置。

(2) 描述配置,比如:服務接口描述,服務版本等。

(3) 擴展配置,比如:協議擴展,策略擴展等。

# 配置格式

(1) 通常環境配置,用properties配置會比較方便, 因為都是一些離散的簡單值,用key-value配置可以減少配置的學習成本。

(2) 而描述配置,通常信息比較多,甚至有層次關係, 用xml配置會比較方便,因為樹結構的配置表現力更強, 如果非常複雜,也可以考自定義DSL做為配置, 有時候這類配置也可以用Annotation代替, 因為這些配置和業務邏輯相關,放在代碼里也是合理的。

(3) 另外擴展配置,可能不盡相同, 如果只是策略接口實現類替換,可以考慮properties等結構, 如果有複雜的生命周期管理,可能需要XML等配置, 有時候擴展會通過註冊接口的方式提供。

# 配置加載

(1) 對於環境配置, 在java世界裡,比較常規的做法, 是在classpath下約定一個以項目為名稱的properties配置。 比如:log4j.properties,velocity.properties等, 產品在初始化時,自動從classpath下加載該配置, 我們平台的很多項目也使用類似策略, 如:dubbo.properties,comsat.xml等, 這樣有它的優勢,就是基於約定,簡化了用戶對配置加載過程的干預, 但同樣有它的缺點,當classpath存在同樣的配置時,可能誤加載, 以及在ClassLoader隔離時,可能找不到配置, 並且,當用戶希望將配置放到統一的目錄時,不太方便。


Dubbo新版本去掉了dubbo.properties,因為該約定經常造成配置衝突。

(2) 而對於描述配置, 因為要參與業務邏輯,通常會嵌到應用的生命周期管理中, 現在使用spring的項目越來越多,直接使用spring配置的比較普遍, 而且spring允許自定義schema,配置簡化後很方便, 當然,也有它的缺點,就是強依賴spring, 可以提編程接口做了配套方案。

在Dubbo即存在描述配置,也有環境配置, 一部分用spring的schame配置加載,一部分從classpath掃描properties配置加載, 用戶感覺非常不便,所以在新版本中進行了合併, 統一放到spring的schame配置加載,也增加了配置的靈活性。

(3) 擴展配置,通常對配置的聚合要求比較高, 因為產品需要發現第三方實現,將其加入產品內部, 在java世里,通常是約定在每個jar包下放一個指定文件加載, 比如:eclipse的plugin.xml,struts2的struts-plugin.xml等, 這類配置可以考慮java標準的服務發現機制, 即在jar包的META-INF/services下放置接口類全名文件,內容為每行一個實現類類名, 就像jdk中的加密算法擴展,腳本引擎擴展,新的JDBC驅動等,都是採用這種方式,

Dubbo舊版本通過約定在每個jar包下, 放置名為dubbo-context.xml的spring配置進行擴展與集成, 新版本改成用jdk自帶的META-INF/services方式, 去掉過多的spring依賴。

# 可編程配置

配置的可編程性是非常必要的,不管你以何種方式加載配置文件, 都應該提供一個編程的配置方式,允許用戶不使用配置文件,直接用代碼完成配置過程, 因為一個產品,尤其是組件類產品,通常需要和其它產品協作使用, 當用戶集成你的產品時,可能需要適配配置方式。

Dubbo新版本提供了與xml配置一對一的配置類

如:ServiceConfig對應,並且屬性也一對一, 這樣有利於文件配置與編程配置的一致性理解,減少學習成本。

# 配置預設值

配置的預設值,通常是設置一個常規環境的合理值,這樣可以減少用戶的配置量, 通常建議以線上環境為參考值,開發環境可以通過修改配置適應, 預設值的設置,最好在最外層的配置加載就做處理, 程序底層如果發現配置不正確,就應該直接報錯,容錯在最外層做, 如果在程序底層使用時,發現配置值不合理,就填一個預設值, 很容易掩蓋表面問題,而引發更深層次的問題, 並且配置的中間傳遞層,很可能並不知道底層使用了一個預設值, 一些中間的檢測條件就可能失效, Dubbo就出現過這樣的問題,中間層用「地址」做為緩存Key, 而底層,給「地址」加了一個預設埠號, 導致不加埠號的「地址」和加了預設埠的「地址」並沒有使用相同的緩存。

# 配置一致性

配置總會隱含一些風格或潛規則,應儘可能保持其一致性,

比如:很多功能都有開關,然後有一個配置值:

(1) 是否使用註冊中心,註冊中心地址。

(2) 是否允許重試,重試次數。

你可以約定:

(1) 每個都是先配置一個boolean類型的開關,再配置一個值。

(2) 用一個無效值代表關閉,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支持在${dubbo.registry.address}的值中, 通過豎號分隔多個註冊中心地址,用於表示多註冊中心地址。

# 配置繼承

配置也存在「重複代碼」,也存在「泛化與精化」的問題,

比如:Dubbo的超時時間設置,每個服務,每個方法,都應該可以設置超時時間, 但很多服務不關心超時,如果要求每個方法都配置,是不現實的, 所以Dubbo採用了,方法超時繼承服務超時,服務超時再繼承預設超時,沒配置時,一層層向上查找。

另外,Dubbo舊版本所有的超時時間,重試次數,負載均衡策略等都只能在服務消費方配置, 但實際使用過程中發現,服務提供方比消費方更清楚,但這些配置項是在消費方執行時才用到的,

新版本,就加入了在服務提供方也能配這些參數,通過註冊中心傳遞到消費方, 做為參考值,如果消費方沒有配置,就以提供方的配置為準,相當於消費方繼承了提供方的建議配置值, 而註冊中心在傳遞配置時,也可以在中途修改配置,這樣就達到了治理的目的,繼承關係相當於: 服務消費者 --> 註冊中心 --> 服務提供者

# 配置向後兼容

向前兼容很好辦,你只要保證配置只增不減,就基本上能保證向前兼容, 但向後兼容,也是要注意的,要為後續加入新的配置項做好準備, 如果配置出現一個特殊配置,就應該為這個「特殊」情況約定一個兼容規則, 因為這個特殊情況,很有可能在以後還會發生,

比如:有一個配置文件是保存「服務=地址」映射關係的, 其中有一行特殊,保存的是「註冊中心=地址」, 現在程序加載時,約定「註冊中心」這個Key是特殊的, 做特別處理,其它的都是「服務」, 然而,新版本發現,要加一項「監控中心=地址」, 這時,舊版本的程序會把「監控中心」做為「服務」處理, 因為舊代碼是不能改的,兼容性就很會很麻煩, 如果先前約定「特殊標識+XXX」為特殊處理,後續就會方便很多。

文章來源: https://twgreatdaily.com/zh-tw/vTKMJG4BMH2_cNUgR8Ib.html