劉怡然,謝鵬飛,鄧雪峰
(1.山西農(nóng)業(yè)大學(xué) 信息科學(xué)與工程學(xué)院,山西 太谷 030801;2.北京微夢(mèng)創(chuàng)科網(wǎng)絡(luò)技術(shù)有限公司,北京 100080)
隨著軟件模塊化技術(shù)的發(fā)展和軟件編程工具的日益豐富,各種編程框架簡化了系統(tǒng)的開發(fā)過程,通過對(duì)現(xiàn)有的框架進(jìn)行配置可以構(gòu)建出軟件的基本結(jié)構(gòu),把軟件開發(fā)者從復(fù)雜的環(huán)境配置中解放出來.動(dòng)態(tài)配置技術(shù)支持軟件的在線演化,能實(shí)現(xiàn)多樣的服務(wù)質(zhì)量(quality of service,QoS)控制,是目前許多軟件系統(tǒng)和構(gòu)件都支持的一種配置方式,在分布式系統(tǒng)和實(shí)時(shí)系統(tǒng)配置中尤為重要[1].然而,動(dòng)態(tài)配置的加載流程在使服務(wù)更加靈活的同時(shí),也存在配置錯(cuò)誤導(dǎo)致系統(tǒng)崩潰的風(fēng)險(xiǎn).修改配置文件可能會(huì)引發(fā)一系列潛在的問題,在測試階段無法被發(fā)現(xiàn),一段時(shí)間后可能會(huì)因?yàn)榫W(wǎng)絡(luò)環(huán)境的改變導(dǎo)致系統(tǒng)的崩潰[2].另一方面,一些框架沒有回退(fallback)機(jī)制,一旦配置的數(shù)據(jù)類型出了問題,會(huì)導(dǎo)致服務(wù)不可用.即使框架支持了回退機(jī)制,軟件的容錯(cuò)機(jī)制會(huì)使配置修改后不生效,也并不符合用戶的期望.基于此,文中擬提出一種模型驅(qū)動(dòng)的動(dòng)態(tài)配置校驗(yàn)方法,解決軟件動(dòng)態(tài)重配過程中配置格式錯(cuò)誤導(dǎo)致的問題.
為了給用戶提供不間斷的服務(wù),動(dòng)態(tài)地進(jìn)行資源分配,許多當(dāng)前流行的分布式系統(tǒng)框架都提供了配置管理支持,用于管理應(yīng)用程序的外部屬性配置,如Spring Cloud Config、Spring Cloud Consul、Apollo和Nacos等[3].國內(nèi)外學(xué)者一直對(duì)分布式系統(tǒng)的配置管理問題保持關(guān)注,配置管理主要包含權(quán)限管控、灰度發(fā)布、版本管理、格式校驗(yàn)和安全配置等流程[4].如M.ZNIGA-PRIETO等[5]提出了一種動(dòng)態(tài)和增量架構(gòu)的云服務(wù)重配方法,該方法允許開發(fā)人員將新服務(wù)指定為軟件增量,并實(shí)現(xiàn)了生成服務(wù)集成邏輯,作為服務(wù)部署和重構(gòu)架構(gòu)腳本的工具.尤其針對(duì)分布式框架的配置格式校驗(yàn),S.KIKUCHI等[6]開發(fā)了一種利用UML/OCL驗(yàn)證從現(xiàn)有基礎(chǔ)設(shè)施的配置信息中提取參數(shù)配置策略的方法.這些方法實(shí)現(xiàn)了配置的動(dòng)態(tài)管理,但缺少對(duì)屬性數(shù)據(jù)類型的正確性校驗(yàn),在數(shù)據(jù)類型配置錯(cuò)誤時(shí)會(huì)產(chǎn)生報(bào)錯(cuò).
為了確保服務(wù)能夠正常執(zhí)行并滿足其預(yù)期目標(biāo),其屬性必須在正式執(zhí)行之前進(jìn)行驗(yàn)證.文中擬基于有限狀態(tài)機(jī)實(shí)現(xiàn)配置格式校驗(yàn)邏輯,將配置提取出來,通過對(duì)屬性數(shù)據(jù)類型的轉(zhuǎn)換驗(yàn)證其正確性,以期使用戶修改的配置正確.
當(dāng)前主流分布式框架常見的配置文件格式主要有.properties、.json、.xml、.yml等,如Spring Cloud支持采用.properties和.yml格式進(jìn)行配置,Hadoop和Spark支持采用.xml格式進(jìn)行配置.通過研究當(dāng)前主流分布式框架的環(huán)境配置過程,設(shè)計(jì)了一種分布式框架配置校驗(yàn)方法.
配置文件格式錯(cuò)誤會(huì)導(dǎo)致客戶端解析配置失敗引起生產(chǎn)故障,對(duì)配置的格式校驗(yàn)?zāi)軌蛴行Х乐谷藶殄e(cuò)誤操作的發(fā)生,是配置管理中非常核心的功能.為了實(shí)現(xiàn)配置文件的格式校驗(yàn)和數(shù)據(jù)類型校驗(yàn),系統(tǒng)的主要功能包括以下3個(gè)方面:① 配置文件解析,即解析各種類型的配置文件,將配置內(nèi)容提取出來,轉(zhuǎn)換成容易處理的鍵值類型;② 配置屬性解析,即對(duì)抽取出的key進(jìn)行解析,獲取目標(biāo)數(shù)據(jù)類型;③ 數(shù)據(jù)類型校驗(yàn),即校驗(yàn)并轉(zhuǎn)換value的數(shù)據(jù)類型.
因此,該校驗(yàn)方法主要功能由配置文件解析器、配置屬性解析器和數(shù)據(jù)類型校驗(yàn)器3個(gè)部分完成,其流程見圖1.
圖1 總體設(shè)計(jì)圖
為了獲取正確的目標(biāo)數(shù)據(jù)類型,對(duì)常見數(shù)據(jù)類型的表示進(jìn)行統(tǒng)一約定,如表1所示.約定原則如下:① 對(duì)簡單數(shù)據(jù)類型如String、int、Double等,使用類型的首字母替代,如果有沖突,用該數(shù)據(jù)類型的第2個(gè)字母替代;② 對(duì)于容器數(shù)據(jù)類型如List、Map、Set等,使用2個(gè)首字母分別標(biāo)志容器的開始和閉合;③ 支持容器數(shù)據(jù)類型的嵌套.
表1 常見數(shù)據(jù)類型的表示舉例
有限狀態(tài)機(jī)又稱有限狀態(tài)自動(dòng)機(jī)(finite-state automation,F(xiàn)SA),簡稱狀態(tài)機(jī),是表示有限個(gè)數(shù)的狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動(dòng)作等行為的數(shù)學(xué)計(jì)算模型[7].它可以用來對(duì)對(duì)象行為建模,其作用主要是描述對(duì)象在它的生命周期內(nèi)所經(jīng)歷的狀態(tài)序列,以及如何響應(yīng)來自外界的各種事件[8].有限狀態(tài)機(jī)模型由一個(gè)5元組構(gòu)成,記作M=(S,E,f,S0,Z),其中:S表示狀態(tài);E表示條件(輸入);f表示狀態(tài)轉(zhuǎn)換函數(shù);S0是S的一個(gè)子集,表示初態(tài);Z表示次態(tài)(終態(tài))[9].構(gòu)建有限狀態(tài)自動(dòng)機(jī)的5個(gè)元組,并將狀態(tài)和事件進(jìn)行解耦,主要步驟如下:
1)確定狀態(tài).文中定義了Start、SetStart、SetEle等9種狀態(tài),由于一次只解析一個(gè)數(shù)據(jù)類型,容器閉合就代表著解析結(jié)束,所以沒有對(duì)各個(gè)容器設(shè)置結(jié)束狀態(tài).End為結(jié)束狀態(tài).9種狀態(tài)代表的含義見表2.
表2 狀態(tài)名稱及其代表的含義
2)拆分事件.事件是掃描到的每一個(gè)字符,由于字符種類較多,而int和Double、String和Long的處理非常相似,文中將事件類型抽象為原始類型元素(PRIMITIVE_ELE)、包裝類型元素(WRAPPED_ELE)、Map、List和Set類型5個(gè)事件,每個(gè)事件代表的含義見表3.
表3 事件名稱及其代表的含義
3)繪制狀態(tài)轉(zhuǎn)換圖(表).狀態(tài)轉(zhuǎn)換圖(表)能夠清晰地表示各個(gè)狀態(tài)和事件之間的轉(zhuǎn)換關(guān)系.用于數(shù)據(jù)類型校驗(yàn)的狀態(tài)轉(zhuǎn)換圖見圖2.
圖2 狀態(tài)轉(zhuǎn)換圖
借助棧結(jié)構(gòu)后進(jìn)先出的特性,基于有限狀態(tài)機(jī)設(shè)計(jì)了數(shù)據(jù)類型校驗(yàn)器校驗(yàn)的總體流程,如圖3所示.
圖3 數(shù)據(jù)類型校驗(yàn)器處理流程圖
Spring Cloud是一個(gè)完整的微服務(wù)解決方案,它提供分布式情況下的各種解決方案合集,其中的Spring Cloud Config為分布式系統(tǒng)中的外部化配置提供服務(wù)器和客戶端支持[10-11].文中基于有限狀態(tài)機(jī)在Spring Cloud Config框架中實(shí)現(xiàn)了所設(shè)計(jì)的配置校驗(yàn)方法.
在Spring Cloud框架下實(shí)現(xiàn)配置校驗(yàn)方法的總體思路如下:在服務(wù)外通過 ApplicationContext 類重新啟動(dòng)了新的Java進(jìn)程,并在新進(jìn)程里實(shí)例化構(gòu)造出來的Java類;如果類實(shí)例化過程中出現(xiàn)問題,即可說明配置是有問題的,捕獲新進(jìn)程的報(bào)錯(cuò)可以識(shí)別錯(cuò)誤信息.然而,把各個(gè)字段解析完成后放到類模板中時(shí),所生成的Java類需要?jiǎng)討B(tài)編譯成字節(jié)碼.JavaCompiler的默認(rèn)實(shí)現(xiàn)都是通過文件進(jìn)行的,輸入和輸出都在內(nèi)存中進(jìn)行,所以需要修改JavaCompiler的實(shí)現(xiàn),以實(shí)現(xiàn)不中斷的服務(wù).
2.1.1Spring Cloud的動(dòng)態(tài)配置流程
Spring Cloud進(jìn)行動(dòng)態(tài)配置時(shí),先將動(dòng)態(tài)配置通過@Value注入到一個(gè)動(dòng)態(tài)配置Bean,并將這個(gè)Bean用注解標(biāo)記為@RefreshScope.在配置變更后,這些動(dòng)態(tài)配置Bean會(huì)被統(tǒng)一銷毀,之后Spring Cloud的ContextRefresher會(huì)將變更后的配置作為一個(gè)新的Spring Environment加載到ApplicationContext對(duì)象中,由于Scoped Bean都是懶加載(Lazy Init)的,它們會(huì)在下一次使用時(shí)被使用新的Environment重新創(chuàng)建.
2.1.2動(dòng)態(tài)編譯和Spring Bean實(shí)例化
JavaCompiler通過JavaFileManager管理輸入和輸出文件,使用時(shí)通過getTask()方法提交一個(gè)異步CompilationTask進(jìn)行代碼編譯,代碼編譯時(shí),JavaCompiler通過getCharContent()從傳入的compilation-Units獲取到 .java 文件內(nèi)容,把編譯后的結(jié)果調(diào)用CompiledByteCode的openOutputStream()方法寫到CompiledByteCode對(duì)象里.
JavaCompiler的默認(rèn)實(shí)現(xiàn)都是通過文件進(jìn)行的,為了避免不必要的文件讀寫,對(duì)JavaCompiler進(jìn)行了改進(jìn),讓編譯過程的輸入和輸出都在內(nèi)存進(jìn)行,如圖4所示.JavaCompiler、JavaFileManager、Java-FileObject(Input/Output)等分別使用委托模式實(shí)現(xiàn).
因?yàn)樾戮幾g的字節(jié)碼是懶加載的,所以要顯示獲取.要讓Spring能夠加載到這些編譯好的字節(jié)碼,需要類加載器的配合,可以新加一個(gè)ClassLoader來實(shí)現(xiàn),如圖4所示.
圖4 對(duì)JavaCompiler的改進(jìn)
為了轉(zhuǎn)變解析狀態(tài)并將結(jié)構(gòu)結(jié)果保存起來,將事件整體抽象為一個(gè)事件處理器接口.該接口的定義如下:
public interface StateHandler{
/**
* @param event要處理的事件
* @param states系統(tǒng)整體狀態(tài)
* @param result解析的結(jié)果
*/
void handle(Event event,Stack
}
將狀態(tài)機(jī)的各個(gè)要素都抽出來之后,再分別完善每個(gè)StateHandler的處理邏輯.為了處理容器的開啟和閉合,可以考慮使用棧來保存預(yù)期的下一個(gè)字符類型,再對(duì)比棧頂字符類型和當(dāng)前處理字符,決定解析的結(jié)果.還要注意類型嵌套的情況下,內(nèi)層嵌套的容器作為外層容器的元素被解析完成時(shí),需要修改外層容器的預(yù)期字符.有別于對(duì)Set容器和List容器的處理,Map容器還要處理它的左右元素.同時(shí)還不能忘記處理各種異常,如未知字符、容器內(nèi)是原始類型、容器未正確閉合等.例如,MapLeftHandler的處理流程見圖5.
圖5 MapLeftHandler處理流程圖
為了證明該方法的有效性,基于Spring Cloud Config框架對(duì)配置校驗(yàn)功能進(jìn)行測試,設(shè)計(jì)了各種數(shù)據(jù)類型的測試用例,幾種典型的測試用例見表4.目標(biāo)數(shù)據(jù)類型指的是配置文件要求的數(shù)據(jù)類型.
表4 測試用例示例
由表4可見,在實(shí)現(xiàn)了該配置校驗(yàn)的系統(tǒng)中,如果配置文件所有屬性數(shù)據(jù)類型正確,則配置文件可以正常生效,不提示錯(cuò)誤;如果存在錯(cuò)誤,則會(huì)產(chǎn)生錯(cuò)誤提示.可以檢測的錯(cuò)誤類型包括簡單數(shù)據(jù)類型不匹配、數(shù)據(jù)溢出、容器類型未閉合、容器類型內(nèi)數(shù)據(jù)類型錯(cuò)誤、容器類型嵌套錯(cuò)誤等.
1)文中提出了一種分布式框架動(dòng)態(tài)配置屬性數(shù)據(jù)類型校驗(yàn)方法,提供了一種比較清晰的配置屬性數(shù)據(jù)類型校驗(yàn)邏輯,能夠通過動(dòng)態(tài)編譯實(shí)現(xiàn)對(duì)配置文件數(shù)據(jù)類型的校驗(yàn),為實(shí)現(xiàn)框架配置管理的校驗(yàn)功能提供了參考.
2)由于基于狀態(tài)機(jī)的實(shí)現(xiàn)邏輯拆分清晰,在添加或刪除一種狀態(tài)時(shí)比較方便,使系統(tǒng)有較高的可維護(hù)性.相比常見的IF-ELSE結(jié)構(gòu),實(shí)現(xiàn)了代碼解耦.
3)改進(jìn)了Java編譯器的實(shí)現(xiàn),避免了不必要的文件讀寫,提高了執(zhí)行效率.
4)在實(shí)際開發(fā)環(huán)境中使用該分布式框架配置校驗(yàn)器,能夠解決實(shí)際配置過程中可能出現(xiàn)的配置校驗(yàn)問題,具有較高的實(shí)用性.