何 鋒,曾 文,王秉鈞
酒泉衛星發射中心,甘肅 酒泉 732750
近年來隨著國際信息安全形勢問題的日益突出,各國都紛紛立足于開源軟件平臺,設計開發自主、可控的軟件系統。致力于開發跨平臺面向對象的Qt平臺自誕生以來,經過開源社區的共同努力,已集成了各式各樣的圖形組件和豐富的基礎庫,實現了對Windows、IOS、Android、Unix Like 等操作系統的支持,已成為跨平臺自主軟件首選的開發工具。OpenMP 即Open Multiprocessing[1],是基于共享內存的一種便攜式和可擴展的并行編程模型,包含編譯制導語句、運行時函數庫和環境變量三部分,由一組支持C++/Fortran 的應用程序接口(API)構成,不僅支持細粒度的for 循環展開并行,也支持SPMD[2]模式的線程級別并行。在目前的共享內存并行編程標準中,OpenMP是使用最為廣泛和便捷的開發標準之一,Qt平臺也提供了對OpenMP線程庫的支持。
實時測控數據存儲系統就是要把各類測控設備如光學、雷達、遙測、計算機等產生的實時測控數據存儲起來,供事后數據分析使用。由于實時測控數據處理的復雜性,實時測控數據存儲系統不但要應對多設備、高并發性、大數據量的數據特點,還要對異常數據有相應的處理策略,做到不缺不漏,實現高可靠性和強容錯性。隨著計算機硬件的不斷發展,目前存儲服務器大多都是單機多核環境[3],但是傳統的串行程序邏輯不能充分利用多核的優勢,計算負載大多集中在一個處理核心上,導致計算資源利用率低,同時也難以發揮存儲系統后臺RAID(Redundant Array of Independent Disks)陣列并行讀寫的優勢[4],存儲效率低,難以滿足日益增長的強實時、大流量和可擴展的實時測控數據存儲需求。需要設計實現一個規模可擴展、實時性高、并行能力強的并行實時測控數據存儲系統。
實時測控數據處理系統為了保證系統的強實時性,應對一對多的傳輸要求,大多采用UDP組播協議,來減輕網絡壓力[5]。本文設計的系統基于Qt 圖形界面使用OpenMP 實現SPMD 線程級并行。不同于事后數據處理每一個并行線程的數據是已知的持久化數據,實時測控系統并行線程的負載將對應每一個UDP Socket接收到的網絡實時測控數據。
如圖1所示,并行實時測控數據存儲系統框架包含四個模塊:進程初始化模塊、線程初始化模塊、數據解析模塊、數據持久化模塊。
進程初始化模塊負責整個系統的任務初始化。創建的任務進程P,申請并初始化用于各并行線程間進行通信的共享緩沖區。該共享緩沖區可以被各個并行子線程訪問,實現線程間數據通信。同時讀取任務配置,根據配置度量系統的靜態負載,并根據靜態負載平衡算法進行并行任務劃分。
線程初始化模塊負責初始化使用OpenMP 制導語句Fork 出的各并行子線程。考慮到存儲系統人機交互需要,本框架使用Qt 圖形界面專門設置一個GUI(Graphical User Interface)線程T0來負責處理圖形界面,完成內存占用率、多核CPU 的使用率,硬盤使用率等系統資源的監視、異常數據的顯示、刷新和人機交互任務。T1至Tn是數據存儲線程,具體個數由進程初始化模塊中任務負載的劃分情況來決定。每個數據存儲線程在Fork 之后第一步會根據網絡配置創建接收數據的socket套接字,并創建一個循環隊列緩沖區用于緩存實時數據,借助于隊列先進先出的特性保證網絡接收數據的時序性。

圖1 并行實時測控數據存儲系統框架
數據解析模塊負責UDP數據包的解析工作。為了及時發現測控數據的有效性,存儲系統還需要對數據包做基本的異常性校驗工作。各個數據存儲線程響應并接收網絡數據后,解析應用層協議包頭并進行校驗,如果為異常數據則首先寫入共享通信緩沖區,再根據持久化的要求進行持久化。若為正常數據則直接根據持久化需求進行持久化操作。GUI線程T0會根據線程的同步策略去訪問共享緩沖區的異常數據并在GUI 界面更新,為用戶決策提供依據。
數據持久化模塊負責數據的持久存儲工作,目前存儲系統大多使用RAID 陣列作為后臺存儲器。用戶端會在虛擬磁盤控制器的指導下,根據控制指令完成數據庫文件的讀寫工作。多個并行線程可以同時在不同的磁盤上進行讀寫,發揮RAID 陣列并行I/O 的優勢。并行I/O 工作主要由RAID 的虛擬磁盤控制器完成,本文不再深入討論。
基于Qt 和OpenMP 的并行實時測控數據存儲系統要具備高可擴展性和強實時性,首先要解決任務劃分問題。將網絡數據按照流量大小均衡分割為一個個并行子域,然后交給各個存儲線程并行地解析處理,防止單個線程負載過重引起的延遲甚至是數據丟失。其次是線程間通信的同步問題,本系統的線程間同步任務主要是T0和某個存儲線程之間的共享緩存通信同步。某一時刻點至多關系到兩個線程,而OpenMP本身提供的線程同步機制粒度過大[6],在處理本系統通信同步時容易導致全局線程阻塞產生較大延遲,需要設計一個細粒度的線程間同步算法。最后要利用Qt基于消息和槽機制的良好人機交互型特性,還需要將Qt 的特有的事件循環機制和OpenMP 線程有效結合,在OpenMP 線程中實現事件處理循環。
并行程序的性能本身受各線程之間的同步影響較大。如果單個線程的負載過重就會使其運行速度下降,導致和其同步線程在同步點長時間阻塞等待,進而使整個并行程序的運行速度大打折扣,降低并行效率。如果單個線程相比其他線程負載過輕,該線程就會過快地完成計算任務而在同步點等待,CPU 核長時間空閑,浪費計算資源。考慮到動態遷移帶來的額外時間開銷,本系統采用靜態負載均衡思想來解決系統的任務劃分問題。
根據系統框架圖所示,抽象的數據存儲過程模型如圖2所示。測控設備將自身處理產生的測控數據以UDP數據報的方式發送進入網絡,存儲系統根據預先約定的網絡地址和端口創建socket并響應數據接收,將數據放入緩沖隊列,然后繼續響應下一個網絡數據包。處理模塊從循環緩沖隊列取出一包數據進行校驗、存盤等解析工作。如果將測控設備作為顧客源,存儲系統作為服務機構,該過程可以用排隊系統[7-8]來表示。

圖2 數據存儲過程的抽象模型
排隊系統包含三個要素:輸入過程、排隊規則、服務機構。根據網絡設備基本特點本系統的輸入過程和服務過程均可以使用負指數分布模型來刻畫,由于使用循環隊列緩沖區,有先進先出的特性,因此服務規則可以對應先來先服務(First Come First Served,FCFS)原則。這是一個典型M/M/1模型的排隊系統[9-10]。
假設某一個測控設備的發送速率為λ,網絡的傳送過程沒有數據丟包(網絡可靠性不在本系統考慮范圍),系統的服務時間即處理時間為Ts。本文主要關心的幾個參數包括:
緩沖區長度Nq,排隊系統進入穩態后,系統內長期駐留的數據包的個數,它確定在保證丟失率為可接收的數值范圍內,應為每個線程分配的緩沖隊列大小;輸入速率λ,它是線程負載的直接度量,跟緩沖區大小有直接關系,必須確保系統能處理完數據,不產生丟失;處理時間Ts,在保證基本功能的前提下,服務時間越短越好。
M/M/1系統在穩態時平均隊列的長度滿足式(1)[11]:

其中,ρ為服務臺的使用率,是平均到達率與平均服務率之比,即在相同時區內顧客到達的平均數與被服務的平均數之比,通過網絡速率λ和服務速率μ定義,如式(2):

將式(2)帶回式(1),就得到了本文關心的三個要素之間的制約關系,如式(3)所示:

其中,系統的處理時間Ts是可以預知的變量,每個線程要接收的網絡數據速率λ和緩沖區隊列的長度是可變變量。
考慮到計算核和內存的代價,本系統的靜態負載均衡方法的主要步驟如下所示:
(1)預置一個緩沖隊列的大小Nq0由式(3)確定每一個線程能承受的負載流量大小λ0。
(2)根據λ0和總流量M計算出需要的計算核個數,向上取整。
(3)比較C和服務器本來可用的CPU 總核數CN。若C≤CN,則劃分方案可行,開始執行任務;若C>CN,則計算核數不足轉到步驟(4)。
(4)在Nq0基礎上增加整數增量D,即以Nq0+D的大小再次回到步驟(1),繼續試探直至找到可行的劃分方案為止。
基于排隊論的靜態負載均衡算法可以兼顧計算核心數、緩沖隊列長度和網絡流量三個要素的影響,在滿足系統實時性、可靠性的前提下尋找到最適合目前服務器能力的任務靜態劃分方案,既能保證并行效率,又能提高資源利用率。
在本系統中,存儲線程需要顯示數據時必須先將數據寫入對應共享通信緩沖區,然后由T0即GUI 線程讀取共享數據,并在界面更新。在使用單緩沖區時,必須要實現共享緩沖區的“寫后讀”和“讀后寫”的同步才能保證讀取數據的正確性[12],因此同步算法本身的實時性也是系統實時性能的瓶頸。OpenMP 本身提供的多線程同步方法為顯示柵欄同步[13],這將會強制所有線程在共享緩沖區讀寫時同步等待,但本系統中存儲線程之間并不需要協作,每次同步只有GUI線程和某一個存儲線程,使用粗粒度的柵欄同步將會大大地降低系統的實時性。本文基于互斥鎖設計了細粒度同步算法來實現本系統的共享緩沖區同步任務。如算法1 所示,以T0和存儲線程Tm的同步為例,其主要步驟包括:
(1)主進程P初始化共享緩沖區和互斥鎖隊列。根據存儲線程總數threads,主進程為每一個存儲線程申請一塊用于共享通信的緩沖區buffer[i],其中i為線程編號,0 <i<threads。同時為其定義兩把互斥鎖,W[i]用于“寫后讀”同步(下文簡稱寫鎖),保證每個共享緩沖區在寫入數據后才能被讀取;R[i]用于“讀后寫”同步(下文簡稱讀鎖),保證每個共享緩沖區的數據被讀取結束后才能再次擦寫。在任務開始時緩沖區可以用于寫但是不能讀,因此將所有線程的寫鎖初始為開鎖狀態,而所有讀鎖調用加鎖操作Lock(W[i])初始化為加鎖狀態。
(2)存儲線程Tm向共享緩沖區寫數據。線程Tm需要顯示接收的異常數據時,先申請自身對應的共享緩沖區buffer[m]的“讀后寫”即讀鎖R[m],若加鎖成功表明該緩沖區內上一次的顯示數據已經讀取結束并允許重復擦寫,那么就將本次的顯示數據寫入,并將本共享緩沖區的“寫后讀”即寫鎖W[m]解鎖,允許GUI線程T0讀取共享緩沖區的顯示數據。否則就進入阻塞狀態,等待T0完成讀取操作釋放緩沖區的讀鎖。
(3)GUI 線程T0從共享緩沖區讀取數據并更新顯示。由于各個存儲線程的共享通信緩沖區相互獨立,數據到達時間也相互獨立。因此本文使用非阻塞加鎖方法[14]Try_Lock()和循環輪詢的方式逐個試探加鎖。若加鎖成功則讀取顯示數據,否則就跳過該線程的共享通信過程,對下一個線程的共享通信緩沖區進行試探加鎖,如此反復,直至完成所有存儲線程的共享數據顯示任務。如此,GUI線程就不會因為某一個線程的共享緩沖區加鎖不成功而發生阻塞,影響后續其他線程的界面更新顯示任務。其他存儲線程和Tm的同步過程類似,并行運行。
算法1OpenMP細粒度互斥鎖同步算法
Input:線程Tm的顯示數據和線程編號m,總的存儲線程數threads
//主進程P設置共享緩沖區,初始化線程鎖
BUFFERbuffer[threads];
LOCKW[threads],R[threads];
Fo(ri=0;i Lock(W[i]); End //線程Tm Begin //向共享緩沖區m寫入顯示數據 Lock(R[m]) WriteData(buffer[m]); Unlock(W[m]) … End 西班牙“Maria Canals國際音樂比賽”將于2019年3月23日至4月4日西班牙巴塞羅那舉行。該比賽每年舉行一次,年齡限制:17至28歲。比賽一等獎獎金為25,000歐元。比賽共分為四輪,預選輪:YOUTUBE;第一輪:獨奏20分鐘;第二輪:獨奏40分鐘;第三輪:獨奏50分鐘;決賽輪:協奏曲。比賽曲目與詳情請關注網站。 //GUI線程T0 Begin //從共享緩沖區讀取存儲線程要顯示數據 While(threads) I(fTry_Lock(W[threads])) ReadData(buffer[threads]); threads??; Unlock(R[threads]) End if End while End Output:在圖形界面顯示Tm收到的異常數據 該算法能保證每次共享緩存通信過程中最多鎖住T0和另一個存儲線程,即使這兩個線程間運行速度差異較大造成加鎖阻塞,也不會引起其他線程的同步空等,保證了其他存儲線程能正常地處理實時網絡數據而不丟包,提高并行存儲系統的可靠性和實時性。 本系統使用單獨的GUI 線程來處理界面顯示和刷新任務本身可以有效地緩解圖形界面處理帶來的資源消耗壓力,但圖形界面的主要目的是實現良好的人機交互,響應用戶操作來控制其他存儲線程記錄的開始、停止等工作。基于Qt組件的圖形界面使用特有的信號與槽通信機制來完成事件處理工作[15],而OpenMP線程本身并不支持事件處理工作。 OpenMP各個并行線程復制運行同一份程序代碼,變量名稱完全一致,但都是thread loacal局部變量,互相隔離不能訪問。那么GUI 線程就不能直接和其他存儲線程實現信號和槽的跨線程連接。本文通過在主進程中設置橋接對象的方式實現OpenMP 各線程隔離對象之間信號和槽的跨線程連接。 如圖3所示,T0線程的私有對象Object_1發送的控制信號Signal_1 首先和主進程P中可訪問的共享對象Bridge 的槽函數Slot 相連接。信號觸發時,Slot 槽函數會使用Qt的emit函數發送自身定義的傳導信號Signal,而Signal信號事先和存儲線程Tm的私有對象Object_2的槽函數Slot_2 相連接。傳導信號Signal 被時間觸發后就會加入線程Tm的消息隊列,激活線程Tm的槽函數Slot_2 進行響應,并完成圖形界面要求的相關操作。如圖中虛線所示,間接地實現了OpenMP線程間私有對象之間信號和槽的鏈接。 圖3 信號和槽跨OpenMP線程橋接 當消息進入并行子線程的消息隊列之后,仍需要事件循環機制來保證消息的正常處理過程。OpenMP 線程本身并不具備處理Qt消息隊列的功能。借助于Qt本身的事件循環機制,整個OpenMP線程的事件處理流程如算法2所示。 算法2OpenMP線程事件處理機制 Input:QEvent事件隊列 //section1 #pragma omp parallel num_threads(thread_count) { QEventLoopeventLoop; //處理本線程網絡數據 //Do something //委任eventLoop處理事件循環->見section 2 eventLoop.exec(); } //section2 int QEventLoop::exec(ProcessEventsFlagsflags) { //獲取私有數據指針d Q_D(QEventLoop); … //事件循環,消息隊列空則退出循環 while(!d->exit.loadAcquire()) { //處理事件 processEvents(flags|WaitForMoreEvents|EventLoopExec); } //阻塞等待 wait_for_more_events(); Output:QEvent的操作結果 如section1 部分偽代碼所示,每個OpenMP 線程在Fork 之后創建一個 QEventLoop 事件循環對象[16],然后才開始創建Socket并處理網絡數據,在每個線程的末尾調用QEventLoop 的成員函數exec(),委托QEventLoop處理事件循環。事件循環的輪廓如section2 所示,事件首先進入線程的私有事件隊列d中。事件處理過程會循環遍歷事件隊列,并且將入棧的事件發送到它們的目標對象當中,如果隊列為空就會阻塞等待新的事件到來,如此循環實現整個線程的事件循環。 通過橋接和事件循環機制,本文將Qt 的事件循環機制嵌入OpenMP 線程,在保持OpenMP 線程級并行優勢的同時還兼顧了Qt以消息驅動的圖形界面良好的人機交互性能。 為了測試系統的性能,本文在4*28核Intel Core i7處理器,64 GB DDR3內存,配備10 Gb/s萬兆網,RAID5磁盤陣列的服務器硬件環境下進行了測試。 首先是系統的可擴展性測試。并行實時測控數據存儲系統關注的可擴展性主要指系統在網絡流量不斷變化的情況下,能否充分利用計算資源來確保系統的實時性,即響應時間維持在較低水平而不是隨流量的變化發生較大變化。當系統測試環境確定后,數據包從解析到寫入數據庫的時間即系統服務時間就已經被確定。系統的響應時間主要取決于數據在緩沖隊列中的等待時間,等待時間越短實時性就越高。本文在保證全部輸入數據都是正常數據的前提下,測試了單線程存儲系統和并行多線程存儲系統的響應時間隨系統輸入總流量的變化情況。測試結果如圖4所示,其中多線程的系統響應時間采用每個存儲線程響應時間的統計平均值。 從實驗結果可以看出,在系統流量為0 時,單線程和多線程系統與縱軸的交點即為系統的服務時間,本文的測試環境下系統的服務時間為20 ms,二者一致。隨著總流量的不斷增加,由于單線程系統只有一個讀寫線程,因此數據將會在緩沖區中累積,流量越大累積越多,排隊時間越長,平均響應時間也越來越長,甚至出現丟包影響系統的實時性。并行系統在靜態負載均衡算法的幫助下會進行任務劃分,確定使用的計算核心數,基本保證了每個線程的流量負載都維持較低水平,而且大致相同,因此平均響應時間并不會隨著系統總流量的增加而增加,而是基本保持在單核低負載時的水平。 圖4 響應時間隨系統總流量的變化情況 實時系統另一個重要指標就是系統的容錯性。并行實時測控存儲系統關注的容錯性主要指在異常數據的干擾下系統能否做出正確的處理而不影響系統的響應時間,導致系統丟包。在保證總流量保持在80 MB/s較高負載的情況下,通過不斷增加異常數據的比例,來統計系統的響應時間,測試系統的容錯性,測試結果如圖5所示。 圖5 系統響應時間隨異常數據比例的變化情況 從測試結果可以看出,在異常數據的比例較低時,對系統的響應時間影響較小。當增大到40%以上時,系統的響應時間明顯出現延遲。這主要是因為異常數據在寫入數據庫之前,需要在圖形界面顯示給用戶,而顯示過程要和GUI 線程進行同步通信。盡管本文設計了細粒度同步方法,但GUI線程要負責所有其他存儲線程的異常數據顯示任務,負載過重,就會引起個別存儲線程的等待阻塞,導致平均響應時間發生突變。 綜上實驗表明,并行實時測控存儲系統在系統流量較大,負載較高的情況下,相比現行的串行存儲系統具有更好的可擴展性、更強的實時性,能更好地發揮系統的硬件資源優勢,進一步提高系統的綜合性能。 本文通過引入M/M/1模型的排隊系統,設計了一種適合并行實時測控數據存儲系統的靜態任務劃分算法。基于互斥鎖完成了線程間的細粒度通信同步機制。借助于事件循環機制將Qt的信號與槽特性成功地嵌入OpenMP線程,保留了二者的優點。在此基礎上設計并實現了一套并行實時測控數據存儲系統框架,并進行了測試。實驗表明,該系統對現行的測控數據存儲任務而言具有更好的擴展性、實時性和一定容錯能力。后續,本文將針對系統容錯能力閾值不高的問題,采用異常數據挑點顯示、設置顯示緩沖區等多種方法來減輕GUI線程的負載,進一步提高系統的可靠性。3.3 在OpenMP線程內嵌入事件循環

4 實驗與分析


5 總結