






摘要:隨著大型軟件系統的復雜度增加,傳統的單體架構面臨水平擴展困難、存在性能瓶頸、故障隔離不佳以及技術棧統一限制等問題,此外,單體應用構建和部署耗時較長,不利于頻繁更新,影響了軟件快速迭代與持續交付,微服務架構能夠有效解決這些問題。文章面向一種云測試平臺,采用領域驅動設計思想,設計并實現了基于Redis緩存的無狀態微服務架構,應用相應的持續集成方法對該架構進行集成和部署,顯著提升了平臺的可擴展性、故障隔離能力和性能,支持多樣化的技術棧選擇并加速了軟件的持續快速迭代和交付,取得了良好效果。
關鍵詞:單體架構;微服務架構;無狀態;持續集成;云測試
中圖分類號:TP311" 文獻標志碼:A
0 引言
近年來,微服務架構(Microservice Architecture,MSA)逐漸受到越來越多人的關注。作為一種架構模式,微服務架構提倡將單體架構的應用劃分成一組小的服務,服務之間互相協調、互相配合,為用戶提供最終價值。
云測試平臺系統是一種測試調度執行系統。該系統采用單體架構,作為測試平臺支撐大量項目使用,存在以下主要問題:(1)各模塊之間耦合性強,功能擴展不方便,須要整體修改升級;(2)故障隔離性不好,某個模塊的故障可能會導致系統整體不可用;(3)只能采用單一技術棧,無法發揮各語言在不同場景下的優勢;(4)整體構建和部署時間比較長,不利于按需頻繁部署。
為了解決以上問題,將云測試平臺系統從單體架構演進到微服務架構。與傳統的單體應用架構相比,微服務架構具有易于開發、技術棧多樣性、擴展性強、故障可隔離性、可獨立快速部署等各種優勢,適合應用于大型復雜軟件系統[1-2]。為了進一步提升系統面對大規模用戶時的性能,微服務采用無狀態設計支持多實例擴展。本文詳細介紹了云測試系統無狀態微服務架構的設計、實現以及針對微服務架構的持續集成和部署的具體方法,極大地提高了云測試平臺的可擴展性、故障隔離性和性能,加快了軟件持續快速迭代交付的能力。
1 單體應用架構和微服務架構
1.1 單體應用架構
傳統的單體應用架構是將應用程序所有功能部署為一個單一的文件或者同一個目錄下的文件合集,可以是JAR、WAR等格式,而且所有應用程序代碼都運行在相同的進程中。單體應用有如下優點:(1)為人們所熟知。現有大部分工具、應用服務器、框架和腳本都是這種應用程序。(2)IDE友好。Eclipse、IntelliJ等開發環境都是針對開發、部署、調試單個應用而設計的。(3)便于共享。單個打包文件即包含所有功能,便于在團隊之間以及不同的部署階段共享。(4)易于測試和部署。單體應用一旦部署,所有服務或特性就都可以使用,沒有額外依賴,每項測試都可以在部署完成后立刻開始。
截至目前,單體應用已經為人們提供了很好的服務,然而,不管如何模塊化,單體應用都存在以下問題:(1)模塊擴展性差。隨著系統的復雜性越來越高,在單體應用中擴展業務功能模塊越來越復雜。(2)故障隔離不佳。單體應用中一個模塊的嚴重故障會導致整個應用不可用。(3)性能提升難。單體應用很難通過水平擴展來提升關鍵模塊的性能。(4)技術棧統一限制。每個團隊成員都必須使用相同的開發語言、持久化存儲及消息系統,而且要使用類似工具,無法根據具體場景做出其他選擇。(5)單體應用構建和部署耗時較長。單體應用可能較大,構建和部署時間也相應較長,不利于頻繁部署,阻礙持續交付。
1.2 微服務架構
隨著業務需求的快速發展變化,敏捷性、靈活性、可擴展性和性能需求不斷增長,迫切需要一種更加快速高效的軟件交付方式。微服務架構就是一種可以滿足這種需求的軟件架構模式,采用多個服務間互相協作的方式構建應用,每個服務獨立運行在不同的進程中,服務與服務之間通過輕量級通信機制交互并且每個服務可以通過自動化方式獨立部署。
1.2.1 微服務架構的特點
(1)領域驅動設計[3]。應用程序功能分解通過DDD中明確定義的規則實現;每個團隊負責與一個領域或業務功能相關的全部開發。(2)單一職責。每個服務只負責該功能的一個單獨的小的部分,也是SOLID原則之一。(3)明確發布接口。每個服務都會發布一個定義明確的接口且保持不變;服務消費者只關心接口,而對于被消費的服務沒有任何運行依賴。(4)獨立部署、升級、擴展和替換。每個服務都可以單獨部署及重新部署而不影響整個系統,使得服務很容易升級擴展。(5)可以采用多種異構語言。每個服務的實現細節與其他服務無關,使得服務之間能夠解耦,團隊可以針對每個服務選擇最合適的開發語言、持久化存儲、工具和方法。
1.2.2 微服務架構的優點
(1)每個服務只須做好一件事,更加專注和簡單,易于開發、理解、擴展和維護。(2)故障隔離好,一個服務出現問題不會影響整個應用。(3)性能水平擴展性好,可以通過對服務進行無狀態設計,多實例化水平擴展來提升性能。(4)服務可以根據不同的要求選擇合適的技術來做開發,不會受限于任何技術棧。(5)局部修改容易部署,有利于持續集成和持續交付[4]。
2 基于微服務架構的云測試平臺
云測試平臺的核心是一個測試調度執行系統[5-6],對測試環境的物理資源和用例對環境的資源需求2部分進行抽象描述,通過匹配算法達到測試用例和具體執行物理環境解耦的目的。測試人員提交測試任務,系統根據測試任務中測試用例的要求進行環境資源匹配,將用例各自分發調度到合適的測試環境并行執行,匯總結果報告輸出。
2.1 單體架構
云測試系統原來的架構是經典的MasterSlave方式,一臺Master服務器對應多臺Slave,每個Slave管理一套測試環境,如圖1所示。
該架構是單體架構,作為測試平臺支撐大量項目使用時存在以下問題:(1)用戶需求不斷增加,模塊間耦合性比較強,功能擴展起來不方便,而且需要整體修改升級;(2)故障隔離性不好,某個模塊的故障可能會導致系統整體不可用,不滿足穩定性的要求;(3)資源匹配算法采用Python效率較高,但是整體系統采用Go語言,只能通過腳本調用的方式,一些團隊成員掌握其他技術棧卻無法投入開發,浪費資源。
2.2 微服務架構
為解決以上問題,本文采用微服務架構對原有云測試系統進行改造。微服務架構采用DDD領域驅動設計的基本思想把單個應用程序根據領域業務分解為多個獨立的服務,服務之間通過輕量級消息通信,形成分布式網絡系統,服務間共同協作實現系統的功能。微服務架構主要有以下優點:(1)每個服務關注內容比較少,接口清晰,容易實現高內聚低耦合,功能擴展可以在服務內增加或擴展新服務,非常方便;(2)各服務的故障相互隔離,易于實現高可用系統;(3)每個服務技術選型比較靈活,只要符合消息接口即可。
本文采用DDD領域驅動設計的思想,經過對云測試系統領域業務的分析,將原系統按領域分解為以下幾個主要的微服務:任務管理、用例管理、調度、匹配、環境管理、配置管理、產品管理、執行Slave等,通過它們的交互協作來對外提供價值。微服務架構如圖2所示。
其中,對提供最核心的測試調度執行[7],各微服務間的流程時序如圖3所示。
各微服務間具體交互過程如下:
(1)Job服務向schedule服務發送activeJob請求;
(2)Schedule收到activeJob后,通知report服務,開始創建文件夾,準備接收執行報告文件;
(3)Schedule服務通知Match服務進行用例環境匹配;
(4)Match服務接收到請求后,進行匹配并返回結果;
(5)Slave空閑時向Schedule服務請求用例;
(6)Schedule服務向Slave指派匹配的用例信息請求執行;
(7)Slave執行完用例后向Report服務上傳用例的報告文件;
(8)Report接收Slave傳過來的用例報告文件,將這些文件放到對應目錄下;
(9)Slave通知Schedule服務上傳報告到Report服務完成;
(10)Schedule服務通知Report合并用例報告;
(11)Schedule服務判斷Job服務中所有用例執行完畢,通知Report生成整個任務測試報告;
(12)Schedule服務通知Job服務完成整個任務測試。
2.3 無狀態微服務設計
隨著云測試日活測試任務數和用例數的增大,調度服務越來越成為瓶頸,主要有以下方面:
(1)調度服務已經支撐不了更大并行的測試任務調度執行,當任務達到1000時,就會有15%左右任務調度失敗,導致任務執行失敗,已經嚴重不能滿足用戶增長的需要。
(2)隨著壓力的增大,某些異常情況可能導致調度服務退出,在服務被重新拉起的時間內,系統核心調度不可用,云測試平臺的高可用性受到嚴峻挑戰。
基于上述2個主要原因,迫切須要采取相應的方法提高云測試調度服務的性能和高可用性。在對調度服務自身性能優化和異常保護后,進一步將調度服務水平擴展。云測試采用了微服務架構,調度服務是獨立的服務,只須將調度服務多實例化,負載均衡,一方面可以提升調度服務的性能,支撐更多的測試任務調度執行,另一方面提升調度服務的高可用性,當一個服務異常退出后,其他服務會繼續提供服務,不至于導致調度執行失敗。
調度服務多實例化水平擴展的關鍵是對調度服務無狀況化設計,將調度服務的狀態數據從服務中獨立出來共享存儲,如圖4所示。
在調度服務多實例架構中,無論哪個調度服務收到其他業務服務的消息,如果需要讀寫狀態,均不訪問自己的服務狀態,而是訪問共享存儲中的狀態,這樣有以下優勢:
(1)當業務需要請求調度服務處理時,可以根據負載均衡原則發給任何調度服務實例,而不須在一次業務會話中記住特定的調度服務實例,但如果調度服務存儲狀態數據,則必須保證一次業務會話中訪問相同的調度服務實例,這樣會增加系統的復雜度。
(2)當某個調度服務實例異常退出后,后續完全可以由其他調度服務實例完成相應的處理,對業務無任何影響。
為此,本文將調度中的主要狀態數據等待隊列移植到Redis中,以支持多個調度實例,如圖5所示。
當用戶提交測試任務執行時,調度執行過程如下:
(1)Job服務向調度服務請求激活測試任務,向調度實例Schd1發起;
(2)調度實例Schd1收到Active后,請求Matcher服務對用例進行預匹配;
(3)調度實例Schd1獲得預匹配結果后,將匹配用例的預匹配信息加入等待隊列并存儲到Redis中;
(4)某個Slave1空閑,向調度實例Schd1發送消息來獲取用例執行,調度實例Schd1收到請求后,訪問Redis中等待隊列中的用例預匹配信息,找到匹配的用例;
(5)調度實例Schd1發送用例信息指派Slave1執行;
(6)Slave1執行完畢后根據負載情況向調度實例Schd2發送了用例執行結果;
(7)調度實例Schd2獲得用例測試結果后,向Redis更新用例的執行結果信息;
(8)如果Job中所有用例都執行完成,則更新Job執行狀態為完成;
(9)調度實例Schd2從Redis中獲取任務的執行結果信息;
(10)調度實例Schd2將任務的執行結果信息發送給Job服務。
從上面的流程可以看到,任何一個調度實例對所有任務數據可見,如某個Slave向調度實例Schd2上報用例執行結果時,不會因為對應該Slave的任務是通過另一個調度實例Schd1激活的而無法處理。另外,某一個實例異常時,其他實例仍然可以繼續處理所有用例數據,僅對該異常實例正在處理中的數據有影響。
在上述流程中,等待隊列基于Go中的list實現,現在須要修改為在Redis中存儲(包括讀和寫),使用時須要現從Redis查詢,所有對任務、用例屬性的修改都要及時同步到Redis中,以便持久化。
3 微服務持續集成和部署
系統被分解為微服務架構后,整個云測試系統的持續集成實施方式就要跟隨架構而變。持續集成和部署的目的是協作高效開發,當代碼變動后盡快驗證并可一鍵式部署到生產環境,達到快速反饋的目的。具體實施時須要考慮微服務代碼庫和構建部署流程2個方面。
3.1 微服務代碼庫
每個微服務放在一個獨立的代碼庫中,在Jenkins中,每個Job關注一個微服務所在的倉庫。如果某個微服務代碼有變動,則觸發相應的微服務Job運行構建,生成本微服務的Docker鏡像[8]。這種方式的好處是,微服務之間互不影響,可以對某個微服務進行單獨的功能測試,多個微服務之間可以并行實施。整個系統發布時,每個微服務可以對應不同的版本號,只須構建有改動的微服務,保證所有微服務配合通過驗收測試用例即可,而不須要全部構建。
云測試的多個微服務在GitLab中被分解為獨立的倉庫,每個倉庫中有對應的腳本,可以將本微服務制作成獨立的Docker鏡像。
3.2 微服務構建部署流程
從開發人員本地驗證合入代碼到微服務Docker鏡像被部署到生產環境,須經過以下步驟[9-10]:
(1)本地在git分支上開發代碼,運行驗收用例通過;(2)合入相關微服務代碼至相應的GitLab倉庫并提交合并請求;(3)GitLab觸發對應Jenkins Job進行代碼編譯、鏡像構建;(4)Jenkins上運行驗收測試用例,通過后推送到Docker私有倉庫;(5)運行Jenkins一鍵部署Job,調用部署腳本,將鏡像部署到私有云中。
云測試系統已經基于Redis集群共享狀態實現了多實例的無狀態微服務滾動升級,因此,在替換鏡像過程中服務不會中斷,能夠做到不停服升級。
4 應用實例
4.1 系統實現
4.1.1 微服務架構實現
根據業務領域,微服務架構具體拆分為如下11項微服務,如:任務調度Scheduler、資源匹配Matcher、產品管理Product、任務管理Job、用例管理Testcase,版本管理Version等。每個服務獨立運行部署,按需單獨配置DB,服務之間基于RESTful接口通信,如圖6所示,箭頭表示服務間有依賴關系。
4.1.2 無狀態調度微服務實現
Redis是一個開源的高性能鍵值對數據庫,通過提供不同的鍵值數據類型來滿足不同場景下的存儲需求,借助高層級的接口可以勝任如存儲、隊列系統以及緩存系統等不同角色。Redis的所有內容都存儲在內存中,因此,讀寫速度較其他基于硬盤的數據庫有明顯的優勢。將數據存在內存中也有問題,比如當程序退出后內存中的數據將會丟失,因此,Redis提供了對持久化的支持,即可以將內存中的數據異步寫入硬盤,同時不影響繼續提供服務。Redis是單線程模式,而Memcache支持多線程,然而Redis在大部分情況下性能不會成為其瓶頸。如果需要復雜的數據類型或持久化等功能時,Redis將會成為Memcache很好的代替品。
Redis采用內存存儲,讀寫速度快,提供持久化支持,單線程模式共享不會沖突,且在大部分情況下性能不會成為瓶頸,因此,采用Redis作為服務狀態共享存儲,將調度服務的核心狀態數據“用例等待調度隊列”放到Redis中并對調度服務讀寫狀態的處理做相應的修改。
云測試調度服務多實例化的關鍵是采用Redis存儲狀態數據,因此,Redis的高可用性就成為關鍵。Redis高可用有2種架構模式:集群和哨兵模式。本文采用了哨兵模式。Redis 的 Sentinel 系統用于管理多個 Redis 服務器,Sentinel 會不斷地檢查主服務器和從服務器是否運作正常。當被監控的某個 Redis 服務器出現問題時,Sentinel 可以通過 API 向管理員或者其他應用程序發送通知。當一個主服務器不能正常工作時,Sentinel 會開始一次自動故障遷移操作,將失效主服務器的其中一個從服務器升級為新的主服務器,讓失效主服務器的其他從服務器改為復制新的主服務器;當客戶端試圖連接失效的主服務器時,集群也會向客戶端返回新主服務器的地址,使得集群可以使用新主服務器代替失效服務器,從而極大地提高了高可用性。
4.1.3 微服務持續集成實現
為了實現以微服務為單位的快速持續集成和部署發布,本文主要實現了以下步驟腳本:
(1)本地編譯代碼、制作鏡像的腳本。
①build_me.sh
該腳本的作用是編譯微服務代碼,檢驗其是否可以編譯通過,生成可執行文件。
②make_image.sh
該腳本的作用是制作某個微服務的Docker鏡像。首先從私有倉庫下載Docker鏡像,該鏡像預先安裝了Go語言環境。接著把微服務的目錄掛載到Go鏡像中使用build_me.sh腳本進行編譯。這樣做的好處在于,在其他機器上(比如CI機器)就不須再安裝Go語言環境。最后制作完整的Docker鏡像。
(2)合入相關微服務代碼至相應的GitLab倉庫并提交合并請求。
開發人員提交合并請求后,GitLab會觸發對應的Jenkins Job進行代碼編譯、鏡像構建。其中Jenkins Job用到的腳本有:
①make_image.sh
該腳本和本地校驗腳本是同一個腳本,Jenkins Job中引用它是為了在CI機器上生成鏡像。
②run_ut.sh
該腳本可選,可以在這里進行該微服務的單元或功能測試。
(3)運行驗收測試用例,通過后推送到Docker私有倉庫,完成生產環境部署。
①start_at.sh
該腳本的作用是啟動所有微服務鏡像,包括仿真Slave鏡像和驗收用例鏡像。讓驗收用例鏡像中的Robot向云測試系統的Web發送請求的方式對系統進行驗收測試。
②push.sh
如果驗收測試通過,可以通過此腳本將鏡像推送到Docker私有倉庫。
③depoy.sh
運行Jenkins一鍵部署Job,將生產環境鏡像替換為新版本。
4.2 效果評價
4.2.1 微服務架構實現效果
采用無狀態微服務架構實現云測試平臺系統后,取得了以下明顯效果:
(1)系統的可擴展性得到顯著增強,可以根據各種產品的測試需求增加相應的服務實現,比如針對不同的測試環境模型提供不同的資源匹配服務(MatcherXXX),針對不同的測試框架(如Robot和其他自研測試框架)開發不同的測試用例解析服務(TestcaseXXX)等。
(2)由于各個微服務都是在獨立的進程中運行,故障隔離性好,例如用例管理TestCase服務發生嚴重異常退出,僅僅無法刷新用例,但對任務管理Job服務和任務調度Scheduler服務無任何影響,因此,測試調度執行仍然能夠正常進行。
(3)各個服務采用的編程技術也可以不同,只要能滿足RESTful通信接口即可。本系統中大多數業務模塊采用Go實現,資源匹配服務Matcher涉及算法設計,則采用Python開發。
4.2.2 無狀態微服務實現效果
云測試平臺調度服務完成多實例改造后,系統調度的性能和可用性均大幅提升,本文用32C 64G的機器進行性能測試,預置條件:單任務設置50個用例,分別部署1個調度服務,2個調度服務,3個調度服務進行測試,在測試沒有基本問題后,線上部署了3個調度服務實例提供正式服務,測試和線上驗證結果如下:
(1)隨著調度服務的擴容,系統支持的任務并行調度數也會明顯增長,如表1所示。
(2)系統通過長時間穩定性測試,多實例同時出現異常退出服務的概率幾乎為0,經過長時間驗證,未發生調度服務異常退出導致的系統調度不可用情況,保證了云測試平臺核心調度能力的高可用性。
(3)Redis采用高可用方案部署,發現曾經出現過主Redis切換的情況,但是系統的Redis對調度服務仍然是可用的,未造成影響。
4.2.3 微服務持續集成實現效果
采用容器技術針對各個微服務進行持續集成和部署,極大地提高了云測試平臺持續快速迭代交付的能力。持續集成平均時間比原有時間下降了72%,集成頻率由原來的每天1次大幅度提升到平均12次左右,做到了按需集成。
5 結語
云測試平臺采用無狀態微服務架構,具有高可擴展性、故障隔離性和高性能,選擇適合的技術棧,可快速響應用戶的需求。對各個微服務進行持續集成和部署,極大地提高了云測試平臺持續快速迭代交付的能力。隨著微服務架構及持續集成技術的不斷深入應用,基于微服務的組裝式應用和相應的持續集成方法是值得進一步研究的方向,從而持續提升軟件系統的靈活性與敏捷性,提升研發效率和質量,降低成本。
參考文獻
[1]洪華軍,吳建波,冷文浩.一種基于微服務架構的業務系統設計與實現[J].計算機與數字工程,2018(1):149-154.
[2]張晶,黃小鋒.一種基于微服務的應用框架[J].計算機系統應用,2016(9):265-270.
[3]埃里克·埃文斯.領域驅動設計[M].北京:人民郵電出版社,2010.
[4]楊宇,焦麗琴.基于微服務的企業應用設計與實現[J].電子科學技術,2016(5):623-625.
[5]李喬,柯棟梁,王小林.云測試研究現狀綜述[J].計算機應用研究,2012(12):4401-4406.
[6]李喬,鄭嘯.云計算研究現狀綜述[J].計算機科學,2011(4):32-37.
[7]左利云,曹志波.云計算中調度問題研究綜述[J].計算機應用研究,2012(11):4023-4027.
[8]SAM N.Building microservices[M].California:O’Reilly Media,2014.
[9]林新黨,穆加艷.基于Jenkins的持續集成系統研究[J].雷達與對抗,2014(1):58-61.
[10]陳剛,羌鈴鈴.軟件項目開發中的持續集成研究[J].項目管理技術,2011(12):103-106.
(編輯 王雪芬)
Research on the application of stateless microservice architecture and continuous integration methods
JU" Weigang, WANG" Jia
(ZTE Corporation, Nanjing 210012, China)
Abstract: "As the complexity of largescale software systems increases, traditional monolithic architectures face challenges such as difficulty in horizontal scaling, performance bottlenecks, poor fault isolation, and limitations due to a unified technology stack. Additionally, the lengthy build and deployment processes of monolithic applications are not conducive to frequent updates, which hinders rapid iteration and continuous delivery of software. Microservice architecture can effectively address these issues. This paper focuses on a cloud testing platform and adopts the principles of domaindriven design to research, design, and implement a stateless microservice architecture based on Redis caching. It also applies appropriate continuous integration methods for the integration and deployment of this architecture. These improvements have significantly enhanced the scalability, fault isolation, and performance of the platform, supported a diversified selection of technology stacks, and accelerated the continuous and rapid iteration and delivery of software, achieving excellent results.
Key words: singleunit architecture; microservice architecture; stateless; continuous integration; cloud test