分布式系統Failover測試框架的實現

f663x 9年前發布 | 146K 次閱讀 分布式 軟件測試

摘 要

Failover是指系統處理故障并恢復的過程,目前大多數分布式系統都實現了自動Failover的機制,即使發生局部失敗也能繼續提供服務。Failover測試則是通過主動注入錯誤,模擬出各種軟硬件故障,以此檢驗在失敗和恢復時系統的健壯性。

我們內部的分布式系統已經部署在上千臺普通服務器上,軟硬件故障時有發生,進行Failover測試成了系統開發的必備環節。傳統的測試方法需要人 工參與,自動化程度低,我們亟需一個高效的Failover測試框架確保系統滿足高可用的需求。于是我們實現了一個針對分布式系統的Failover測試 框架,這個框架有錯誤注入功能,同時能夠對系統進行數據驗證,還有Web頁面展示等功能。

本論文將會介紹這個分布式系統Failover測試框架的需求分析,講述我們實現此測試框架的開發動機和調研成果;然后詳細介紹這個測試系統的實現 原理,了解測試框架的基本架構與實現細節;最后介紹我們進行Failover測試的應用情況,探討Failover測試的最佳實踐以及工作總結。

通過對此測試框架的實現,大大提高了我們對Failover測試的理解和重視,我們在生產環境中應用此框架對多個分布式系統進行了Failover測試,不僅找到了系統潛在的缺陷,也極大提高了我們對系統健壯性的信心。

關鍵詞:分布式系統;Failover測試;測試框架

Abstract

Failover is the process for system to recover from failure. Nowadays most distributed systems are able to automatically failover, so partial failures hardly have an effect on their services. Failover test is used to inject failures through simulating software and hardware errors. And it helps to examine the robustness of the system.

We have deployed our distributed systems on hundreds of commodity servers. The failures of software and hardware are inevitable, so failover test becomes the necessary part of software development. The traditional methods of failover test require human intervention. But we need a more practical failover test framework to test our distributed systems. Then we implemented the failover test framework for distributed system, which can inject failure, validate data of system, display status on Web UI and so on.

This paper will introduce the needs of this failover test framework. We will talk about the motivation and our research. Then it presents the implementation in detail and analysis the architecture of the test framework. Finally we will make a conclusion of our failover test. We would love to talk more about our best practices and relative works.

After implementing this failover test framework, we know more about failover and pay more attention to failover test. We have performed failover test for some distributed systems in the production environment. As a result, some potential bugs were found because of it and we have much more faith in our distributed systems.

Keyword: Distributed System, Failover Test, Test Framework

目錄

  • 摘要
  • Abstract
  • 第一章 緒論
    • 1.1 選題背景
    • 1.2 調研情況
    • 1.3 研究內容
  • 第二章 測試框架的需求分析
    • 2.1 需求概述
    • 2.2 需求分析
    • 2.3 本章小結
  • 第三章 測試框架的架構設計
    • 3.1 框架總體架構
    • 3.2 外部模塊架構
    • 3.3 本章小結
  • 第四章 測試框架的實現原理
    • 4.1 測試框架實現概述
    • 4.2 實現策略設計模式
    • 4.3 實現系統三層架構
    • 4.4 實現參數可配置化
    • 4.5 實現故障重現功能
    • 4.6 實現數據驗證功能
    • 4.7 實現頁面展示功能
    • 4.8 實現數據統計功能
    • 4.9 實現硬件級別故障
    • 4.10 實現外圍測試工具
    • 4.11 實現通用測試模塊
    • 4.12 實現工廠設計模式
    • 4.13 本章小結
  • 第五章 測試框架的最佳實踐
    • 5.1 實踐概述
    • 5.2 實踐系統
    • 5.3 實踐問題
    • 5.4 本章小結
  • 結 論
  • 參考文獻
  • 致謝

第一章 緒論

1.1 選題背景

隨著互聯網的發展,分布式系統[1]以其低廉的成本、強大的運算能力和健壯的容錯機制逐漸成為計算機行業的焦點。分布式應用可以運行在上千臺普通服 務器上,伴隨業務增長動態擴大集群規模,但也要承受普通計算機相對較高的硬件故障率,這要求系統在發生軟硬件故障時仍能保證一定的可用性[2]。而測試程 序的這種容災能力成了分布式計算時代的另一大難題。

分布式系統Failover測試框架的實現

圖1-1 分布式系統Failover流程

分布式系統Failover測試框架的實現

圖 1-2 測試框架基本功能

Failover[3]測試也稱為故障恢復測試[4],用于驗證系統在發生故障時能否迅速恢復服務。圖1-1表示分布式系統的Failover流 程,通過一致性協議[5]保證主服務器和備服務器數據的一致,因此即使系統的主服務器發生了故障,備服務器也能馬上接替服務,局部故障也就不會影響到對外 的服務質量。當然,實現這種高可用需要開發者額外的付出,編碼過程中可能存在的缺陷也需要有全面的測試。Netflix在亞馬遜云服務出現故障后發表文章 [6]說明了Failover測試的重要性,在軟硬件故障不可避免的今天,通過人為提高軟硬件故障率能夠提前暴露系統的問題,是一種更加有效的預防措施。 相比單元測試和集成測試,Failover測試尚未得到廣泛關注,大部分分布式應用在實現功能需求后直接上線,一旦在線上發現問題再人工處理,這種沒有通 過任何測試、有損用戶體驗的做法是很不值得提倡的。由于缺少足夠的Failover測試方法和測試工具,目前國內外對這項測試工作仍不夠重視。

Failover測試框架[7]是進行Failover測試的工具,如圖1-2所示,測試框架主要是能夠提供錯誤注入的方法進行軟硬件故障的模擬, 同時也要有數據驗證和頁面展示等輔助功能,最好為測試人員和開發人員提供不同的接口來使用和拓展測試框架。由于是針對分布式系統進行測試的,此測試框架必 須足夠簡單易用,為測試人員提供友好的用戶界面,框架在實現基本功能外,不能引入其他的缺陷,最好能夠方便其他開發者拓展此框架的功能。

根據業務需求,我們的生產環境中已經部署了多套分布式存儲和計算系統,如HBase[8]、HDFS[9]、ZooKeeper[10]和 Chronos[11]等等。這些系統在設計時考慮到高可用性的需求,都實現了各自的Failover機制,但由于編碼出錯或者一些未知因素可能導致線上 的系統并沒有達到我們的預期,于是我們必須實現一個通用的Failover測試框架,來檢測系統故障恢復的能力并找出代碼中可能存在的Bug。

1.2 調研情況

在設計和實現我們的Failover測試框架之前,我們對國內外已經存在的測試框架進行了調研。通過調研收集資料,有助于我們分析Failover測試框架的功能特性,為我們設計和實現自己的測試框架做好準備。

參考國外開源項目的設計,我們首先找到針對HBase進行集成測試的ChaosMonkey[12]。HBase[13]是一個分布式的NoSQL 數據庫,在集群里任意一臺服務器掛了以后其他服務器可以迅速接替服務,保證系統在任何時候都是高可用的。ChaosMonkey是HBase項目的一個測 試模塊,它通過向集群隨機發送操作指令,測試HBase在這種外界干預下能否繼續正常工作,它的缺點是只停留在進程級別的錯誤注入而不能模擬硬件級別的故 障。ChaosMonkey在HBase項目中應用廣泛,每次發布新版的HBase時都必須經過嚴格的系統集成測試,其中就包括使用 ChaosMonkey來檢測系統的健壯性,這樣可以保證Failover測試像單元測試那樣持續被執行,但由于針對性強,其他分布式系統就難以復用此代 碼。另一個值得借鑒的是Netflix開源的SimianArmy[14]項目,它擁有軟硬件故障的全套解決方案,但是靈活性比較差并且不容易拓展。雖然 這個項目已經開源,但仍沒有得到外界更多的關注,粗略看了一下代碼感覺實現還是比較粗糙,硬件故障的模擬也比較簡單,難以遷移到我們龐大的服務器集群上。 當我們向HBase社區反饋[15]準備實現這個Failover測試框架時,得到了Cloudera公司Todd Lipcon的建議,他已經開源實現了一個輕量級的測試框架Gremlins[16],不過整個測試框架的架構過于簡單,故障模擬都只能發生在本地,缺乏 對整個集群的測試能力。Gremlins項目至今已經有四年沒有更新了,當初也只是為HBase數據庫設計,缺乏足夠的通用性,整個框架從現在的角度看還 是稍有些落后。

通過對比和學習這些開源項目,我們覺得目前的Failover測試框架功能不夠完善,可用性、易用性和可拓展性都不能滿足我們的需求。所以我們需要實現新的Failover測試框架,為我們的分布式系統測試提供一套完整的解決方案。

1.3 研究內容

總結前面的背景調研,我們認為Failover測試框架除了要實現錯誤注入、故障模擬功能,還必須提供數據驗證和頁面展示模塊,同時要保證可拓展性 用于其他異構的分布式系統,最好還要有數據收集、郵件報警等輔助功能。因此,我們在框架設計與實現上都花了不少功夫,而且在進行Failover測試的過 程中也遇到了不少問題,包括發現被測系統存在的漏洞和測試框架自身執行時產生的問題,本論文將會詳細敘述以上提到的各部分內容。為此,我們進行了下面的研 究工作:

  1. 研究測試框架的需求設計,為系統的實現提供理論基礎。
  2. 研究測試框架的架構設計,探討框架的解耦與系統組件。
  3. 研究具體的代碼實現,更好地理解測試框架的實現原理。
  4. 研究測試框架的實踐情況,總結生產環境中的最佳實踐

本論文的余下部分將圍繞這四個章節來展開。第二章是測試框架的需求設計,我們從功能需求和非功能需求兩個角度探討分布式系統Failover測試框 架的需求,并總結出項目開發所要實現的所有功能模塊。第三章是測試框架的架構設計,我們會從整個測試框架的總體架構入手,分析系統各組件的分層與解耦,然 后剖析一些外部模塊的系統架構,最后歸納出我們實現時所遵循的項目框架。第四章是測試框架的實現原理,也是本論文研究最重要的一部分,我們會對每個功能模 塊進行劃分,然后分別詳細地講解其中的實現細節,力求讓所有人看過本章都可以實現自己的測試框架。第五章是測試框架的最佳實踐,也就是我們對此測試框架的 使用情況,我們已經在生產環境中進行Failover測試,取得了一定的成效,這些內容都會在這章內逐一敘述。

第二章 測試框架的需求分析

2.1 需求概述

在設計一個切實可用的系統之前,我們必須明確理解我們的項目需求和最終的實現成果。和其他測試框架一樣,這個項目的用戶是測試人員和開發人員,目的是為了檢驗軟件程序中可能存在的漏洞。

因此要實現測試框架的功能需求,必須提供一個可執行的測試程序,并實現基本的測試功能。對于Failover測試,我們必須能夠模擬出各種軟硬件故 障,實現錯誤注入的功能,為了驗證分布式系統的內部運行狀態,我們在測試時也要能夠進行數據的驗證和集群健康狀況的檢測。這些都需要封裝到最終的測試框架 中,實現上述提到的功能需求。

同時也要滿足測試框架的非功能需求,在易用性、拓展性方面能夠達到測試人員的預期。目前主流測試框架的日志都是直接輸出到終端或者文件中,但 Failover測試是一個相對持久的測試過程,通過看Log了解測試結果顯然是不合理的,我們認為Web UI對測試人員是最友好的,也能極大提高測試框架的易用性。除此之外,我們還要實現框架的可拓展性,補充足夠的使用文檔,這些都是我們滿足非功能需求的必 備環節。

2.2 需求分析

針對上述的功能需求和非功能需求,我們得到了圖2-1這樣的用例圖。

分布式系統Failover測試框架的實現

圖2-1 需求用例圖

首先從測試人員的角度,最重要的是使用測試框架進行測試,其中就包括執行Failover測試,同時要能夠隨時監控測試的結果,而一旦檢測出系統的 問題后,能夠馬上重現這個測試結果。而QA和測試人員一樣,如果發現系統潛在的Bug后能馬上收到框架的報警提示。對系統開發者來說,這個測試框架能夠提 供了接口來拓展新的測試功能,其中就允許開發者拓展故障的模擬,也可以拓展框架中數據驗證和集群檢測的功能。

分布式系統Failover測試框架的實現

圖2-2 系統功能架構圖

我們整理得到了圖2-2的系統功能架構圖,我們首先要能夠實現不同級別的故障模擬,也就是說必須可以模擬軟件進程級別的錯誤,如殺掉或者重啟進程, 也要能夠模擬硬件級別的失敗,如果網絡超時、CPU負載過高等問題。其次是在注入錯誤的同時,應該有外圍的工具來進行數據驗證,而且我們覺得除了每次測試 時需要驗證數據,還必須要有一個持久的測試工具貫穿Failover測試的各個生命周期,這是檢驗系統健康狀況的標準之一,如果數據驗證出錯,說明系統存 在缺陷,需要開發人員進一步定位代碼中的問題。而且我們是設計一個測試框架,具體的故障模擬和策略選擇應該允許用戶來拓展,不同分布式系統的數據驗證方式 不同,測試框架只需要提供統一的接口,由開發者實現針對異構系統的數據驗證功能。這個框架運行所需要的所有參數都應該是可配置的,通過配置文件來指定執行 具體的操作,無需改動源代碼或者重新編譯,能夠極大地提高系統的可拓展性。而一旦發現了問題,我們應該能立刻停止測試框架的執行,然后盡量重現這個問題, 于是我們就需要提供這種Replay機制,這也是一個Failover測試框架所應該支持的,而不需要給測試人員額外的負擔。由于系統故障發生率較低,出 現問題后能夠馬上重現可以大大減少我們排查問題的時間。為了簡化框架的運行環境,我們希望將系統的運行狀態展現在Web界面中,用戶通過瀏覽器就可以訪問 而不需要安裝其他客戶端,頁面展示的內容也應該齊全方便測試人員定位問題。考慮到測試應盡可能自動化,一旦測試框架發現問題后直接給相關人員發送報警郵 件,這就省卻了測試人員無休止的定期檢查工作,大大減少了系統的運維成本。最后我們也希望這個框架是簡單易用的,暴露給用戶的接口也要足夠簡單,通過一行 命令來編譯、啟動和停止測試框架。

2.3 本章小結

本章以圖表和文字的形式描述分布式系統Failover測試框架的需求,簡要說明了功能需求和非功能需求,列舉出系統的的功能架構,目的是要實現一個功能完整、易用、并且易拓展的測試框架。通過對系統需求的描述,為后面的項目開發提供了良好的理論基礎和實現準則。

第三章 測試框架的架構設計

3.1 框架總體架構

根據上述制定的需求,我們設計出以下的系統總體框架。

分布式系統Failover測試框架的實現

圖3-1 系統總體架構圖

根據圖3-1我們可以了解到整個測試框架的總體架構。左側MachineFailover是模擬硬件故障的一個外部模塊,它內部實現了一個 Flask HTTP服務器來接受測試框架發出的錯誤注入請求,然后給測試服務器發送模擬硬件故障的命令。右側是一個數據收集和展示的模塊,它集成到我們已經開源的 Minos項目中,通過啟動一個Django服務器,收集測試框架產生的歷史數據并友好地展示出來。中間是分布式系統Failover測試框架的主體部 分,主要包括以下幾個組件,ExternalTool是一個外圍測試工具,DataValidator用于驗證系統數據的一致 性,ClusterChecker則是檢測集群的健康狀況,Task是實際執行的錯誤注入結合,包括模擬的故障Action、執行策略Policy和選擇 器ActionProvider,HttpServer則是用于展示測試框架運行信息的HTTP服務器。

在測試框架主體模塊我們采用了Policy/Task/Action三層架構。Policy表示我們運行錯誤注入的策略,例如我們可以隨機執行故障 模擬或者按順序模擬各個故障,甚至還可以Replay一組任意的故障組合。Task則是指將要執行的錯誤注入的集合,它是每次Failover測試的基本 單位,也是我們要重現故障時Replay的最小單元。最后Action代表一個實際的錯誤,也就是我們能夠模擬的各類軟硬件故障。通過對系統進行這三種粒 度的劃分,我們測試框架的架構就更加清晰了,拓展性也更強。例如我們如果想使用新的策略模式,通過繼承Policy實現特定的接口就可以了,如果我想添加 新的故障模擬,只需繼承Action這個接口就可以無隙集成到這個Failover測試框架中。

其次我們定義了測試框架的基本組件,除了故障模擬這個工具外,我們還設計了DataValidator、ClusterChecker和 ExternalTool這幾個部件。在運行Failover測試框架前我們需要指定將要執行的Actioin,而我們提供了 ActionProvider這個基本組件,目前已經實現了HBaseActionProvider、HdfsActionProvider、 EnvironmentActionProvider、FileActionProvider和ScriptActionProvider,你可以針對 HBase、HDFS這些分布式系統分別進行測試,也可以模擬出硬件環境的故障。如果有新的異構分布式系統希望進行Failover測試,只需要在這里拓 展ActionProvider就可以添加新的Action集合了。另一個基本組件DataValidator是測試框架的數據驗證工具,在 Failover測試過程中系統能夠保證數據一致性也是很重要,例如針對HBase的測試我們會通過HBaseDataValidator不斷地讀寫數據 庫來驗證數據一致性。而ClusterChecker是測試框架的另一個重要組件,每一輪Failover測試過后我們都必須進行集群健康檢測工作,如果 集群沒有達到我們的預期,證明Failover測試導致了系統的出現異常,剩下的問題就需要我們不斷地改進系統了。最后的ExternalTool是一個 在外圍持續運行的基本組件,我們可以理解為它獨立于Failover測試而一直在執行的,我們希望無論被測系統遭受怎樣的Failover,從客戶端的角 度它的行為是一致的,因此我們的測試框架支持這種數據驗證的方式。在針對HBase的Failover測試中,我們實現了 HBaseExternalDataValidator這個類,通過分配多個單獨的線程來增刪改查數據庫的內容,進而檢驗分布式系統的健壯性。

3.2 外部模塊架構

在系統總體框架設計出來后,我們考慮到必須實現模擬硬件級別故障的需求。在我們的生產環境如Linux操作系統中,要實現諸如網絡異常、磁盤不可 用、CPU使用率過高等異常是可以用過軟件來模擬的,前提是我們已經安裝了特定的軟件并且配置好,更重要的是調用這些軟件需要Root權限。調研部分提到 的SimianArmy和Gremlins項目對硬件故障的模擬都是在本地或者SSH登陸到服務器上以Root權限執行命令的,而我們的線上環境為了保證 系統安全,在服務器外層做了一個門神系統,所有遠程操作命令都不允許被直接執行。針對這種特殊環境,我們設計出一個更為通用的 MachineFailover系統,這個系統只暴露了一些安全的HTTP請求接口,只有授權的用戶才能在遠程發出請求,由這個HTTP 服務器來統一處理,并通過Salt或者SSH的形式對測試服務器進行實際的錯誤注入工作。對于所有我們已經能夠模擬的硬件故障,我們也整理成文[17], 將我們選好的軟件直接部署到被測服務器上就可以了。

作為一個高度自動化的測試框架,我們最初并沒有把太多精力關注到界面設計上,后來逐漸才意識到頁面展示的重要性。在Failover測試框架的運行 過程中,我們需要知道測試框架當前的運行狀態,還有歷史上已經運行了什么Action,這些信息都需要一個載體展現給測試人員。于是我們參考HBase HMaster的設計,測試框架運行時會啟動一個內嵌的HTTP服務器,通過服務器容器和模板引擎生成一個動態的Web頁面,同時也會推送JMX指標,任 何人通過服務器提供的IP和端口都可以在瀏覽器上直接查看框架的配置和運行情況。

分布式系統Failover測試框架的實現

圖3-2 數據展示模塊架構圖

由于內嵌的HTTP服務器只能展示當前測試框架的運行情況,包括測試框架運行的配置信息,在生產環境中我們還需要將這些數據持久化保存到數據庫中, 進行后期的統計和分析。因此我們采取由Collector來拉取數據的架構模式,如圖3-2所示,Failover測試框架運行時通過JXM上報自身的 Metrics,這些Metrics是由當前進程運行信息組成的JSON數據,這些指標暴露在特定的端口上。我們利用了我們另一個開源系統 Minos[18],使用項目中的Django這個Web框架,通過一個Collector進程拉取這些指標,持久化到MySQL數據庫中,最后在Web 頁面展示測試框架長期的運行情況。

雖然有了不錯的Web UI供我們觀察,但實際發生測試失敗的次數畢竟很少,我們需要一種報警機制而不是由開發者定期去考察系統狀態。這是一種非常適合Notification 而不是Polling的場景,我們需要實現郵件報警功能,只有在測試不通過的情況才給我們開發者發報警郵件,這樣就省去了大量后期運維的工作。利用公司搭 建的郵件服務器和Python腳本,很容易就實現這個功能了。

最后在我們提供給測試人員使用的接口上,我們希望用戶使用簡單方便,但又必須提供靈活的可配置能力。所以我們生成最終的可執行文件必須是直接可用 的,至于選擇哪種Policy和Aciton、針對那個集群進行測試,這些都通過與HBase、HDFS一樣的自描述的XML文件來配置。我們會自動生成 一份默認的配置文件,測試人員直接修改里面的配置項就可以進行各種Failover測試了。當然如果你想要測試HBase或者HDFS系統,你還必須提供 hbase-site.xml和hdfs-site.xml來指定要測試集群的配置。而針對開發人員的接口設計上,我們設計了純虛基類這樣的接口,開發者 使用Java最基本的類繼承就可以拓展原有的功能,這種模式沒有改變原有框架的開發架構,也不會給開發者增加額外的學習負擔。我們作為內部開發者,已經為 這個分布式系統Failover測試框架添加了對HBase、HDFS系統的支持,無論是ActionProvider、DataValidator還是 ClusterChecker都有針對這兩個系統的測試組件。

3.3 本章小結

本章從總體介紹了分布式系統Failover測試框架的基本架構,介紹了系統的分層以及各層之間的關系。同時通過模塊的劃分將系統解耦,對外部各模塊進行了詳細的架構分析,對后面實現整個測試框架提供更清晰的思路。

第四章 測試框架的實現原理

4.1 測試框架實現概述

整個分布式系統Failover測試框架的實現遵循我們的需求分析和架構設計,每一個模塊都必須滿足原來的功能需求和非功能需求,同時項目開發時要 有足夠的單元測試來覆蓋我們的實現代碼。為了讓開發者更好地使用和拓展這個Failover測試框架,基礎框架的代碼注釋也應該是足夠清晰的,每個函數都 要有用法解釋和參數說明,代碼質量的高低也會影響后續的維護工作。下面將會詳細講解針對分布式系統的Failover測試框架的實現要點。

在開發語言上我們選擇與開源界的Hadoop生態相同的Java,同樣也使用Maven[19]來構建項目。這樣做的好處是代碼結構符合業界規范, 項目依賴管理也容易,是Java項目開發的標準模式。根據Maven的規范,我們需要在pom.xml里面聲明此項目的依賴,包括用于單元測試的 JUnit、Mockito,用于序列化的Json-lib和測試框架功能時所依賴的HBase項目。由于Java本身就是跨平臺的,各主流操作系統如 Linux、Mac OS和Windows都有對應的Java虛擬機,Maven構建工具也是基于Java實現的,所以此分布式系統Failover測試框架并沒有平臺限制, 在上述的操作系統都可以進行開發和部署運行。

分布式系統Failover測試框架的實現

圖4-1 系統流程圖

根據圖4-1我們可以清晰理解分布式系統Failover測試框架的運行原理。首先是從配置文件Failover.xml中讀取配置項,然后啟動內 嵌的HTTP服務器來展示框架的運行信息,接著就開始運行ExternalTool這個外圍測試工具,然后進入Failover測試的主循環。在主循環中 首先啟動DataValidator開始驗證數據,然后根據前面的配置分別項構建ActionProvider、Policy和Task對象,然后從中選 擇Action來執行故障模擬。如果系統經過一輪Failover測試都沒有出現異常,也就是通過了數據驗證和集群的健康監測,就會更新測試指標然后等待 下一次測試,否則會通過Collector收集指標進行郵件報警,測試框架回收系統資源并退出。

4.2 實現策略設計模式

策略設計模式[20]是指定義一個算法的序列,如前面提到分布式系統Failover測試框架的流程圖,我們封裝好系統的各個組件,通過接口讓它們 來交互,而在測試框架實際運行過程中根據用戶配置隨意替換。在此Failover測試框架中,各個組件如ActionProvider、Policy、 DataValidator、ClusterChecker、ExternalTool等都封轉成Java中的抽象類,調用者只需要知道接口就可以進行交 互了。事實上我們已經實現了針對HBase、HDFS的各組件的子類,如HBaseActionProvider和 HdfsActionProvider,用戶在運行框架時只需指定這些子類即可替代原來的接口。由于不是每個分布式系統都需要ExternalTool這 樣的組件,我們靈活地實現了NullExternalTool等各種空組件,缺省時不會對系統做任何操作,這樣既保證了框架的可拓展性也不會為開發人員增 加額外的負擔。

分布式系統Failover測試框架的實現

圖4-2 ActionProvider類圖

我們可以看到圖4-2這個ActionProvider的類圖,基類提供了虛函數getActionClassName()與外界交互,但這個函數 必須由子類來實現。針對不同分布式系統我們實現了不同的子類,每個子類重寫的這個函數將會返回針對這個分布式系統的可執行的故障模擬類型。這樣我們只需在 系統運行前配置好要使用的ActionProvider,就可以自動使用這個ActionProvider所提供的錯誤注入功能了。

同理,在FailoverFramework.java中,runFramework()里面用到的框架組件也都只是他們的純虛父類,由于已經定義 好接口,所以組件之間可以正常地交互,而實際上的交互邏輯都由子類來實現的。所有框架用到的ActionProvider、DataValidator、 ClusterChecker、Policy和ExternalTool都是虛類對象的引用,只有在運行時反射出用戶指定的子類對象,最終執行用戶所期待 的故障模擬或者數據驗證行為。

4.3 實現系統三層架構

在分布式系統Failover測試框架中,Policy表示選擇和執行Action的策略,它從ActionProvider給定的故障集合中選擇 Action,制定出相應的策略,然后生成一個特定的Task來執行。所以如果想要拓展Policy的話,只需集成Policy基類并重載 selectActionsToCreateTask()這個函數就可以了。而Task則維護了一次Failover測試相關的Action集合和這次 Task執行的時間戳,每個Task可以包含一個或多個Action。其中的Action就是一次故障模擬的具體實現,為了支持多個Action同時執 行,我們實現的Action都繼承了Java的Runnable接口,而且加入的Action執行結果的時間的統計,我們要拓展新的錯誤注入只需要繼承這 個類即可。我們在實現這三層架構時,由于Policy和Action都是可拓展的,這里充分利用了Java里面虛類的概念,代碼依賴時只需要提供交互的接 口,只有在真正運行時才會用實現類來代替這個基類。目前我們已經實現了RandomActionPolicy、OneByOneActionPolicy 和ReplayActionPolicy這三種策略,這種清晰的架構劃分為我們拓展自定義的Policy時提供了便捷的方式。

我們可以簡單看看RandomActionPolicy.java的源代碼,首先是繼承了Policy基類并重寫了 selectActionsToCreateTask()這個虛函數。第一步根據整個actionSet隨機選取一定數量的Action,然后利用選出來 的Action集合創建一個最基本的Task。實際上運行Action的邏輯都在Task這個類里面,根據給定的Action類名反射出Action對 象,然后為每一個Action對象創建新的線程來執行。當然在執行完所有的Action后,這個Task的信息也會記錄到本地的一個Log文件中,這個文 件能夠被后面將會提到的ReplayActionPolicy用來重現故障了。

4.4 實現參數可配置化

前面提到我們實現的Policy目前有三個,而實現的全部Action也有三十個之多,具體使用那些系統參數來運行這個Failover測試,這些 都需要通過配置文件來指定的。通過系統地調研,我們認為自解析的XML文件十分適合作為我們分布式系統Failover測試框架的配置文件。即使用戶在不 了解測試框架的具體實現時,他們也能通過修改配置項達到自己的測試目的,不過這需要我們在系統讀取配置項時做更多的工作。我們在測試框架中大量地采用了工 廠設計模式,同時利用Java的反射機制,只有在運行時才生成確定要使用的類。例如我們可以指定RandomActionPolicy、 HBaseActionProvider和HBaseDataValidator,這幾個類的使用都不是硬編碼到測試框架中,而是由測試框架去讀取配置文 件,當讀到用于指定的類名時直接反射出這個類的對象,從用戶的角度也不需要知道這個類具體的實現,只要配置好相應的參數就可以了。這是一種開放性的系統架 構,允許其他人拓展我們測試框架的任意一個組件,如由開發人員自定義Policy或者ExternalTool,然后直接在配置文件里指定自定義的類名即 可,無須修改框架的任意一行代碼,做到可插拔、易拓展。

我們可以看一下Failover.xml這個配置文件,里面有多個配置項的標簽,標簽的Key表示需要配置的項,Value表示這個配置項的值。可 以看出,我們幾乎可以配置這個框架的所有組件,如ActionProvider、DataValidator、ClusterChecker、 Policy和ExternalTool的類名,還有針對ReployActionPolicy所要Replay的Log文件名和Timestamp等 等。所有的可選值都列舉在配置文件中,測試人員無需閱讀框架源代碼就可以快速使用框架的所有功能,通過任意文本編輯器即可修改配置文件,無須依賴額外的客 戶端來實現此功能。

4.5 實現故障重現功能

我們調研了目前主流的Failover測試框架,大多都實現了基本的錯誤注入功能,但是一旦框架測試出系統的Bug時,它們大都無法重現這個問題, 因此很可能由于無法重現而忽略掉這個系統潛在的問題了。而我們的測試框架在設計時就考慮到這一點,我們將運行一次Failover測試所有的Action 都抽象成一個Task,每個Task執行結束后都會將自身信息持久化到本地的文件中。當執行完其中一個Task后發現被測系統存在問題,我們可以迅速從本 地文件中找到這個Task的信息,然后通過選用ReplayActionPolicy馬上Replay這個故障。Replay功能也允許我們針對特定的 Task進行不同頻度的測試,這種靈活的執行方式大大提高了故障重現的概率,也讓我們在發現和定位問題時更加有把握了。

分布式系統Failover測試框架的實現

圖4-3 Policy類圖

如圖4-3,我們的Replay功能是通過拓展Policy來實現的。要使用Replay功能,需要在Failover.xml指定 replay.file.name和replay.timestamp,同時提供這兩個配置項才可以指向唯一的Task。Policy基類本身要求提供一 個selectActionToCreateTask()這樣的方法,普通的RandomActionPolicy和 OneByOneActionPolicy都是直接從ActionProvider中選擇Action然后直接返回。但 ReplayActionPolicy相對復雜,首先它內部有兩個成員變量replayFileName和replayTimestamp,表明要 Replay的Log文件名和指定Task對應的時間戳。通過這兩個變量可以指定唯一的Task,然后通過反序列化這個文件的內容,就可以得到一組 Action信息并進行Replay了。

4.6 實現數據驗證功能

數據驗證功能也是當前主流的Failover測試框架所缺少的一個重要組件,一味地對系統進行錯誤注入是沒有意義的,我們還需要知道系統在 Failover過程中能否保持預期的服務質量。因此,很多人在進行錯誤注入的同時,會手動啟動一些客戶端來請求被測的集群,這樣做的弊端是給測試人員增 添了額外的運維工作。而且在客戶端訪問失敗后,測試框架不能及時通知測試人員,于是我們討論決定將這部分功能集成到我們的分布式系統Failover測試 框架中。不同系統的數據驗證方式是不同的,我們將這種功能抽象成DataValidator的接口,如我們專門為HBase和HDFS實現的 HBaseDataValidator、HdfsDataValidator工具,在集群進行Failover測試時從客戶端的角度不斷讀寫數據庫,理論 上這種Failover測試對客戶端應該是透明的,如果出現數據一致性問題就可以確定系統存在正確性的Bug了。

我們針對HBase實現了一個HBaseDataValidator工具,用戶可以針對各自的分布式系統實現自己的驗證工具,只需要實現基類的 startValidator()、stopValidator()和isDataConsistent()這三個虛函數即可。而 HBaseDataValidator也實現了這三個函數,在startValidator()里面啟動了三個線程分別寫數據庫、讀數據和刪除數據庫中的 數據,同時也會在內存中保留這些讀寫記錄。stopValidator()時結束線程并回收資源。當外界調用isDataConsistent()時返回 驗證結果,由于我們內存中有所有增刪改的記錄,所有每次讀數據庫時都會將讀到的值也內存的作比較,如果不一致證明HBase數據庫返回的數據時不一致的。 其他系統可以參考這種模式,實現針對各自分布式系統的數據驗證工具。

4.7 實現頁面展示功能

以下是分布式系統Failover測試框架的頁面展示模塊截圖。

分布式系統Failover測試框架的實現

圖4-4 頁面展示模塊截圖

我們在運行Failover測試框架時,通過服務器指定的IP和端口就可以訪問這次Failover測試的配置以及運行情況,這有賴于我們框架實現 的內嵌HTTP服務器。我們參考了HBase中InfoServer的實現,通過創建一個新的Jetty服務器來接受瀏覽器的請求,然后使用 Servlet獲取動態內容,最后經過Jamon模板引擎生成靜態的HTML頁面。只要Failover測試框架在運行,內嵌的服務器就可以提供服務,任 何人都可以通過瀏覽器查看這次Failover測試的配置和跑過的Task情況,解放了測試人員運維時不斷登錄到服務器查Log的麻煩,界面友好的Web UI也是作為一個成熟框架很重要的一部分。

圖4-4是最終的頁面展示效果圖,這個網頁是一個相對靜態的頁面,數據沒有持久化到數據庫中,都是由Failover測試框架內的Servlet傳 到HTML模板里的。整個頁面都使用Bootstrap這個框架進行美化,因此只需要少量的代碼就可以做出整潔優雅的效果,甚至不需要手動寫CSS和 JavaScript代碼。

實際上頁面展示的服務器代碼比較晦澀,我們使用了Jamon模板,實現了FailoverStatusTemplate.jamon,通過 Failover測試框架的主程序把系統配置的Configuration對象和Task對象傳進來,然后在頁面上展示這些動態信息。我們通過HTML基 本語法搭建整個頁面的結構,動態內容使用Jamon拓展的標簽即可獲得。用戶在Failover測試框架運行起來后,只需用瀏覽器打開指定的IP和端口就 可以看到這個框架運行的詳細信息了。

4.8 實現數據統計功能

分布式系統Failover測試框架的實現

圖4-5 數據統計模塊截圖

前面提到Failover測試過程中框架會啟動內嵌的HTTP服務器,同時它也會通過標準的JMX格式吐出系統當前運行的Task信息,在服務器 IP和端口后面加上“/jmx”就可以看到了。在實際的生產環境中,我們通過分布式系統Failover測試框架吐出的系統Metrics,利用自己實現 的Collector模塊來收集這些信息,然后持久化到我們的MySQL數據庫中,最后利用Django來做數據分析以及統計功能。如圖4-5就是我們數 據統計模塊的截圖,測試框架的數據從數據庫中取出后,使用HighChart這個畫圖框架畫出柱狀圖,其余的數據按照一個小時、一天、一周、一個月和一年 的時間跨度進行分類統計。而且每個數據都可以點進去查看這段時間內Failover測試框架的運行情況。

我們的測試框架只實現了Metrics的Push,而由外部系統實現Pull,這樣充分解耦了這兩個不同模塊的功能,甚至允許開發者實現自己的數據處理和分析工作,當然我們也提供了一套開源的收集與展示的解決方案。

同時我們在收集到框架測出的系統異常時,利用公司提供的郵件服務器,通過編寫簡單的Python腳本就可以向我們開發者發送郵件報警了。郵件報警功 能使用Python的smtplib模塊很容易就可以實現了,通過這個庫創建一個SMTP對象,根據用戶名密碼登陸后,直接調用sendmail()就可 以發送郵件了。不過由于這個依賴于內部的服務器支持,需要給定用戶名和密碼,這里也就不再詳述了。這個功能雖然簡單不過也省去了我們后續大量的運維時間和 成本。

4.9 實現硬件級別故障

以下是分布式系統Failover測試框架的硬件故障模擬架構圖。

分布式系統Failover測試框架的實現

圖4-6 硬件故障模塊架構圖

圖4-6表示了我們實現硬件故障模塊的基礎架構,這部分功能比較獨立,我們稱之為MachineFailover項目。

MachineFailover提供一個HTTP的接口,Failover測試框架可以通過這個接口發送來錯誤注入命令。接受客戶端請求的是一個用 Python實現的Flask服務器,這個服務器接收到請求后就開始執行腳本,這個腳本會向真正的被測服務器發送模擬故障的命令。至于為何不直接向被測服 務器發送命令呢,原因是我們運行Failover測試框架的機器與被測服務器可能沒有建立Root信任關系,因此沒有權限直接調用故障模擬的腳本。其次是 基于安全考慮,我們使用統一的控制服務器來接受錯誤注入請求有利于我們定位問題,同時減低程序或者用戶誤操作的可能。

硬件級別的故障模擬是我們框架設計就考慮的一個要點,由于這與底層的服務器相關,而且不同公司的服務器運行環境也各不相同,我們實現的 MachineFailover就要解決這類問題。這個系統包含了模擬CPU、內存、磁盤、網絡等幾乎硬件故障的軟件和工具,可以很方便地部署到我們的服 務器上,而且它還實現了一個作為控制中心的HTTP服務器,為不是直接SSH的環境提供了更多訪問接口。分布式Failover測試框架本身包含了硬件級 別的錯誤注入,而實際上它需要依賴于服務端提供的故障模擬服務,我們在測試框架中保留了對這種服務的訪問接口,只要服務器使用 MachineFailover或者自定義的故障模擬服務,我們就可以輕易地從客戶端進行硬件級別的Failover測試了。

MachineFailover的HTTP服務器是一個Flask程序,只是通過接受不同URL的請求來調用不同的腳本。而真正執行故障模擬的是一 系列Shell腳本,這些腳本的執行需要提前配置好環境,具體的配置因不同操作系統而異。因為模擬磁盤損壞、網絡異常、內存不足等錯誤的方法都不一樣,我 們就做成統一的腳本,所有命令都通過這個腳本發出,部署到服務器時也只需要把這個程序拷貝過去就可以了。

4.10 實現外圍測試工具

雖然我們每次Failover測試時都會執行框架提供的數據驗證工具DataValidator,但在兩次測試的間隙和一些不可預知的情況下,我們 還是需要一個全局的工具來保證系統的穩定,這就是外圍測試工具ExternalTool的價值。ExternalTool會在框架啟動之初就被執行,只有 在整個Failover測試框架停止前才會結束驗證,在針對HBase集群的測試中,我們實現的HBaseExternalDataValidator就 重用了HBaseDataValidator,因為我們必須保證在整個Failvoer測試包括兩次測試的間隙都確保數據的一致性。外圍測試工具也可以實 現其他的驗證功能,我們把這個部件集成到測試框架中,開發者可以像Policy那樣輕易地拓展成自己需要的工具。

我們通過拓展ExternalTool實現一個針對HBase的外圍驗證工具,當然如果有不同需求也可以實現不同的邏輯。如果不需要這個功能,可以使用缺省的NullExternalTool,這個程序不會做額外的數據驗證工作,當然也不會影響到原有框架的執行。

4.11 實現通用測試模塊

在實現完針對HBase和HDFS的Failover測試后,我們還想利用這個框架已經做好的數據展示、郵件報警等功能,完成對ZooKeeper 和我們內部實現的Chronos的Failover測試,發現還要為不同的系統編寫Action子類,編譯后才能執行。而且我們發現大部分系統的Fail 命令和Recover命令都可以在命令行中執行,于是我們萌生了實現ScriptAction的意愿。

ScriptAction是一個通用的Action,只需要將你要執行的命令放到一個腳本中,在運行Failover框架前修改配置文件指定調用這 個腳本,這樣你無須修改測試框架源代碼就可以執行動態的命令了。通過這個拓展,我們在沒有修改框架一行代碼的情況下,實現了對ZooKeeper和 Chronos系統的Failover測試,運行情況也相當讓人滿意。

這個類的具體實現是ScriptAction.java,它通過Failover.xml這個配置文件獲得要執行的腳本名字,然后使用Java的 Runtime類來執行。和其他Action一樣,這個Action也要有一個類型名為Script,而且必須實現perform()這個虛函數,而一旦 外部腳本執行出錯,它也會立刻拋出異常表示這個Action執行出錯了。

4.12 實現工廠設計模式

分布式系統Failover測試框架的實現

圖4-7 DataValidator類圖

工廠設計模式[21]是指通過定義一個接口來創建對象,這些子類對象都由統一的工廠類來初始化。我們在測試框架中大量的運用了工廠設計模式,實現了 ActionProviderFactory、ClusterCheckerFactory、DataValidatorFactory、 PolicyFactory和ExternalToolFactory這些工廠類,由這些工廠類來創建各個子類對象。這樣做的好處是所有子類的初始化都由 同一個接口來完成,而且初始化這些對象的參數可以統一處理,我們是通過配置文件傳進去的,這樣用戶要設置也十分簡單。

如圖4-7是DataValidator這個類的類圖,可以看到基類DataValidator的三個方法startValidator()、 stopValidator()和isDataConsistent()都是虛函數,需要子類去實現。我們首先實現了NullDataValidator 這個子類,顧名思義這個類并不會執行實際的數據驗證工作,當我們的系統不需要數據驗證時使用它就可以了,整個測試框架的結構依然不變。可以看到我們也實現 了HBaseDataValidator和HdfsDataValidator,這兩個類是針對HBase和HDFS進行數據驗證的,如果我們正想要測這 兩個系統就可以直接使用這兩個類了。

我們也實現了DataValidatorFactory這個工廠類,看一下ActionProvider.java這個文件的源代碼,只有一個被定 義為static的createDataValidator(String)函數,這個函數只需要接收一個DataValidator子類的類名,就可以 通過Java的放射機制創建出一個DataValidator對象,實現了不同參數創建不同對象的功能。

4.13 本章小結

本章將分布式系統Failover測試框架的分解成12個功能模塊,然后分別對各個功能模塊進行了詳細的介紹。通過畫圖和代碼剖析的方式,力求讓讀者對整個Failover測試框架有更清晰的認識,也能從中了解到該系統開發時的實現細節。

第五章 測試框架的最佳實踐

5.1 實踐概述

在Failover測試框架的開發過程以及開發完成后,我們已經針對HBase、HDFS、ZooKeeper以及Chronos系統進行了Failover測試。后面將會分別介紹它們的測試情況。

在易用性方面,這個測試框架在不斷迭代的開發過程中已足夠完善,只需要通過一個簡單的配置文件就可以利用起全部的測試功能。而且只要首次配置好測試 環境,后續的頁面展示、指標收集以及歷史數據統計都可以持續進行,無須人為參與,框架運行信息通過Web UI展示,測試人員無須安裝額外的客戶端軟件即可查看。

在實用性方面,Failover基本滿足了我們的需求,通過不斷地執行錯誤注入并且進行數據驗證,很好地考驗了分布式系統的容災能力。尤其在硬件故 障的模擬上,如磁盤填滿和機器重啟等故障經常使系統崩潰或者異常,要求我們改進被測系統的架構來滿足這種容災要求。針對Chronos的Failover 測試我們每十分鐘跑一次,隨即殺掉一個Chronos進程或者它所依賴的ZooKeeper進程,從客戶端的角度會有一小段不可用時間但仍能恢復服務,經 過連續一個星期的測試沒有發現正確性問題,這也大大提高了我們使用Chronos的信心。

5.2 實踐系統

以下是我們對各分布式系統進行Failover測試的對比圖。

分布式系統Failover測試框架的實現

圖5-1 分布式系統對比圖

HBase是根據BigTable[22]一個開源的分布式數據庫,也是我們框架設計時考慮到最重要的被測系統。它本身包含基于ZooKeeper 的HA(高可用性)機制,在軟硬件故障發生后進行Failover,理論上不會影響數據的一致性,但線上的HBase集群卻屢次由于系統的Bug導致丟數 據的情況。因此,利用Failover測試框架測試HBase可能潛在的問題,更有助于我們完善這個系統。在進行強制重啟服務器這種硬件故障 時,HBase系統中RegionServer不能被自動拉起這種情況多次出現了,我們通過Log日志找到了直接的原因,修改代碼后也修復了這個Bug, 通過這次Failover測試也提高了HBase的健壯性。

HDFS[23]是一個分布式的文件系統,它也有自身的HA機制保證部分服務器掛了不會影響整個集群,而且通過多備份的形式也可以保證數據不會丟 失。同樣利用Failover測試框架來測試HDFS的這些特性,也有利于找的系統潛在的問題。目前我們只是對HDFS文件系統進行簡單的創建文件、修改 文件和刪除文件這些操作,雖然還沒有找到系統的漏洞,但是在這種持久、頻繁的壓力下系統能保持穩定,讓我們對線上的集群也充滿信心。

ZooKeeper[24]是一個分布式的協同服務,它通過提供簡單的原語讓其他應用很容易就可以實現一致性的服務,而它本身實現了Fast Paxos協議,任何一臺機器掛了也不會影響自身的服務。我們使用Failover測試框架不斷啟停ZooKeeper進程,經過外部系統的測試可以檢驗 ZooKeeper是否能維持這種保證。我們對ZooKeeper進行了簡單的測試,不斷啟停單臺ZooKeeper服務器,即使部分服務器不能提供服 務,整個集群還是能選出Master,同樣我們也覺得線上的集群可以承受這樣的故障發生。

Chronos是一個提供全局嚴格單調遞增Timestamp的服務,它依賴ZooKeeper[25]實現主被切換和服務端的Failover, 保證在同時啟動多臺服務器的情況下,任意一臺服務器掛了都能迅速恢復服務。實際上除了服務器宕機,CPU占用率過高、磁盤損壞和網絡不可用等因素都可能造 成系統問題,我們通過Failover測試框架模擬了這些故障,經過測試后可以判斷Chronos確實能在Failover時保證高可用。

5.3 實踐問題

不過在實踐過程中我們也發現了一些問題,在對HBase很長的一段時間進行Failover測試,都沒有郵件報告系統的問題。實際上HBase曾經 在幾十分鐘內都是不可用的,雖然沒有正確性問題但這并不是我們預期的行為,我們發現我們對HBase集群健康檢測的條件設置比較寬松,即使幾十分鐘不可用 也沒有郵件報警,后續通過對框架的改進可以解決這個問題。還有一個問題就是Failover測試框架運行很長一段時間后自行推出了,通過日志看出框架本身 存在內存濫用而且沒有釋放相關資源,這個問題也希望在后續的迭代開發中解決。

5.4 本章小結

本章講述了我們使用此Failover測試框架的最佳實踐,從易用性和實用性方面進行總結。同時在實踐過程中對幾個分布式系統分別進行 Failover測試,能夠發現系統存在的缺陷,這些測試也極大提高了對我們系統的信心。不過測試過程中也找到了框架本身存在的問題,需要我們在此測試框 架的開發中繼續完善。

結論

分布式系統Failover測試框架可以說是隨著互聯網的發展應運而生的,大數據的存儲與計算給傳統計算機架構帶來了革新,采用廉價的普通計算機組 合分布式集群成為新的解決方案,在越來越重視用戶體驗和服務質量的今天,實現高可用的分布式系統是最基本的要求,同時Failover測試也成為了系統測 試中不可或缺的一環。

而我們的Failover測試框架正迎合這種趨勢,為已有的分布式存儲和計算平臺提供軟硬層級別的Failover測試,而且框架保證良好的可拓展 性和易用性,能夠輕易滿足的各類軟件的測試需求,也以其開放性可以兼容新的分布式系統。通過這樣一個系統的開發, 不僅滿足我們對分布式系統的測試需求,也希望能帶動軟件測試行業與互聯網發展共同前行。

在Failover測試框架的實現也應用過程中,我們也加深了對Failover測試的理解,更加重視Failover測試,在拓展原有測試框架功能的同時,將它應用到我們已有的分布式系統中,并且找到了系統的漏洞,也大大提高了我們對這些系統的信心。

參考文獻

致謝

感謝李紅老師在我撰寫論文時給予的幫助和支持,提醒論文的完成進度和格式要求。沒有這些督促也沒有這篇論文的誕生,本論文的正文結構和排版規范也離不開李紅老師帶領的畢業生小組中其他同學的幫助。

感謝小米云存儲團隊在此項目初期的討論和分析工作,在小米寬松的工作環境下可以最終完成這個項目。感謝部門Leader的任務安排,也感謝其他同事的交流與合作。

來自:https://github.com/tobegit3hub/note/blob/master/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9FFailover%E6%B5%8B%E8%AF%95%E6%A1%86%E6%9E%B6%E7%9A%84%E5%AE%9E%E7%8E%B0.md

 本文由用戶 f663x 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!