中國民航信息網絡股份有限公司 段鍇 李永進 郝鵬 王波
傳統上通過采用select 或poll 等系統調用可以實現多個TCP 網絡連接的I/O 復用,但該方式并不支持基于System V 消息隊列的報文傳輸。有名管道技術FIFO的描述符與Socket 的描述符相類似,可以被select 系統調用支持,因此可借助FIFO 設計一種方法,達到對System V 消息隊列進行I/O 復用的目的。方法中設計地從消息隊列接收報文的處理流程,在不降低處理性能的前提下,有效解決了有名管道和消息隊列的操作不是原子操作帶來的不一致問題,實現單一網絡通信線程可以同時處理來自網絡連接和System V 消息隊列的報文。
隨著移動互聯網技術的不斷發展和航空公司數字化轉型的持續推進,航空公司運營模式也從單一型點對點航線向復合型網格化航線發展,全球運營航線網絡的規模進而呈指數級高速增長。作為對外提供國內、國際航班庫存查詢的航班計劃及庫存查詢系統(后簡稱“航班查詢系統”或“系統”),需要面對海量訪問壓力下,進行復雜實時計算的挑戰。采用基于多進程、分布式并行計算的架構,通常是解決該問題的主要思路。
對于多進程、分布式的架構來說,進程間通信是必須采用的主要技術之一。常用的進程間通信技術包括Socket 網絡套接字、System V 消息隊列、FIFO 有名管道等。其中,Socket 網絡套接字是服務器之間通信的主要技術手段,System V 消息隊列和FIFO 有名管道是服務內部進程間通信的主要手段。除此之外,當一個進程需要同時處理多個網絡連接或交互式輸入時,就需要使用到I/O 復用(I/O Multiplexing) 技術[1]。I/O 復用的機制是指,進程通過預先告知操作系統內核,使得內核一旦發現進程指定的一個或多個I/O 條件就緒時,就會通知進程進行處理,從而提升進程間通信的效率。目前支持I/O 復用的系統調用包括select、poll、epoll 等[2]。本文研究和解決的主要問題,就是利用I/O 多路復用技術去優化航班查詢系統中服務端網絡通信的效率。
航班查詢系統中服務端網絡通信的拓撲圖如圖1 所示,大量客戶端應用與服務端系統通過socket 連接進行消息交互。服務端系統由多個航班查詢應用進程、服務端網絡通信服務進程WSH(Workstation Handler)、以及請求和應答(圖中Q_Out)消息隊列組成。其中WSH(Workstation Handler)進程是專門負責服務端網絡通信的進程,一方面它需要處理多個客戶端的socket網絡連接,從中接收不同客戶端進程的請求報文并放至航班查詢進程的請求隊列中;另一方面是該進程還要處理一個本地System V 消息隊列Q_OUT,從中獲取應答報文返回客戶端。WSH 進程需要同時處理多個網絡連接和消息隊列的I/O,因此WSH 進程需要采用I/O復用技術,來提升處理系統的效率。

圖1 服務端網絡通信的進程WSH (Workstation Handler)Fig.1 The process of server network communication WSH (Workstation Handler)
常見的多網絡連接I/O 復用技術,主要是通過調用select 或poll 等系統服務實現,但是由于System V 消息隊列的標識符不屬于描述符標識,因此不能在消息隊列Q_OUT 上采用select 或poll 等調用。其他I/O 復用方案有采用多線程技術,即使用多個線程分別去處理Q_OUT 隊列和網絡Socket 的報文。但是多線程方案存在以下缺點:
(1)多線程的編程實現難度高,調試復雜;(2)多線程的可靠性較差,一個線程掛掉將導致整個進程掛掉;(3)多線程比單線程有額外的資源開銷要求(例如內存和多核CPU),在某些特定用戶的環境下(單核CPU 和內存受限),多線程方案不能被采用。
針對上述問題,本文提出了一種用于System V 消息隊列的I/O 復用技術方案,以使得WSH 進程在不使用多線程技術的前提下,可以同時處理來自網絡連接和System V 消息隊列的報文,該方案也可以用于其他有類似服務端處理網絡通信需求的系統,具體方案分為以下5個步驟。
WSH 進程創建有名管道FIFO 和消息隊列Q_OUT。
系統調用mkfifo 用于創建有名管道FIFO:
int mkfifo(const char *path, mode_t mode); //其中path 是基于配置文件的配置,例如path=/opt/app/config/todewsh.fifo。
系統調用msgget 用于創建消息隊列Q_OUT:
int msgget(key_t key, int msgflg); //其 中key是基于配置文件的配置,例如ipckey=372539。
由于FIFO 是半雙工的,不能打開來既讀又寫,另外還基于FIFO 的特性,如果FIFO 當前沒有被打開來寫的話,以阻塞方式打開FIFO 只讀的操作會被阻塞。由于WSH 進程啟動時并不能保證已有航班查詢進程打開有名管道FIFO 來寫,所以WSH 進程必須以非阻塞的方式打開有名管道FIFO 來讀:
int fiforead = open(“/opt/app/config/todewsh.fifo”,O_RDONLY|O_NONBLOCK)。
WSH 進程將FIFO 的描述符標識fiforead 放入可讀描述字集合,同時也將客戶端的網絡Socket 加入相應描述字集合,然后調用select 進行I/O 復用。如果有客戶端發送請求報文時,WSH 進程會被select 調用觸發去接收數據,然后把請求報文發送至航班查詢進程。
航班查詢進程調用封裝好的API(應用程序編程接口) Server_NetPutMsg 實現將應答報文放入Q_OUT 隊列,同時打開并將事件報文放入有名管道todewsh.fifo。Server_NetPutMsg 函數首先調用msgsnd 系統調用,將應答報文放入消息隊列Q_OUT,如果放入成功,再以只寫的方式打開todewsh.fifo,為了防止航班查詢進程被阻塞,仍然要以非阻塞的方式打開:int fifowrite=open(“/opt/app/config/todewsh.fifo”,O_WRONLY|O_NONBLOCK);打開以后再調用系統調用write 往管道todewsh.fifo 寫入一個字符“T”當作事件報文。
由于Server_NetPutMsg 函數放消息隊列Q_OUT 和打開以及放管道todewsh.fifo 事件是三個獨立的API,非原子操作,所以存在放Q_OUT 成功、放管道事件失敗的可能性,包括WSH 進程從Q_OUT 讀取應答報文和從管道讀取事件也是不同的API,同樣存在一致性問題。當產生不一致狀態時,如果WSH 進程只是基于select 的事件進行處理的話,勢必造成Q_OUT 的數據延遲到下一次管道事件發送成功時才能被取到,這對實時性要求很高的在線系統來說是不允許的,因此需要增加下列操作來避免不一致狀態的產生。
(1)首先判斷上次從Q_OUT 取到的應答個數是否超過10 個,如果超過,則Q_OUT 仍有待處理數據的可能性很大,因此select 調用不設置超時;如果沒超時,則select 調用設置超時為1ms。(2)基于以上的超時設置進行select 調用。(3)select 返回后判斷管道是否有事件,如果有則執行第(4)步,沒有則執行第(5)步。(4)調用read 方法讀取管道事件,如果沒讀到事件則執行第(7)步;如果讀到事件,則繼續調用msgrcv 方法讀取Q_OUT 隊列數據。如果沒讀到繼續從第(4)步重新開始,如果讀到則執行第(6)步。(5)如果管道沒有事件或者select 超時,為了避免管道事件和Q_OUT 隊列數據的不一致造成延誤隊列數據發送,則調用msgrcv 方法讀取Q_OUT 隊列數據。如果沒讀到數據執行第(7)步,如果讀到則調用read 方法嘗試讀取管道事件,不論管道有無事件,繼續執行步驟(6)。(6)將計數器Rcv_Qout_Num 加1,并將數據發送客戶端,然后判斷Rcv_Qout_Num 是否大于10 個,如果大于10,繼續執行第(7)步,如果不大于10 則繼續從第(3)步重新開始。(7)繼續基于select 的返回處理網絡事件,以讀取客戶端的請求數據,這塊邏輯與普通網絡I/O 復用處理相同,不再贅述。處理完畢后繼續從第(1)步循環處理。
WSH 進程被select 調用觸發,按照上述流程讀取管道的事件和消息隊列Q_OUT 中的應答報文。航班查詢進程放入管道todewsh.fifo 一個字節的事件后,WSH 進程就會被select 調用觸發,進而首先從管道中讀取一個字符,再從消息對列Q_OUT 讀取應答報文,然后將數據發送給客戶端。利用上述處理流程和方法,WSH 進程就可以借用有名管道,實現多個網絡連接和消息隊列的I/O 復用處理。
中國民航旅客服務系統為海內外60 多家航空公司提供庫存管理和運營服務,目前是世界第三大航空旅游分銷系統提供商和全球最大的結算數據處理中心[3]。旅客服務系統依托于信息技術,是高并發實時交易系統,具有旅客服務主業務鏈條長、相關參與方數量多、安全風險等級高的特點,涉及從航空產品的頂層設計到末端接觸點服務的全流程和全生命周期[4]。航班計劃及庫存查詢系統,是中國民航旅客服務系統中的重要核心子系統之一,該系統的查詢訪問量峰值超過10000 筆每秒。通過本文的方法優化,全面提升了整個系統的響應時間,平均響應時間由300ms 縮短至150ms,查詢請求處理效率提升100%。
經過航班查詢系統的實際檢驗,相對于多線程方案,本文提出的I/O 復用方案具有以下幾個優點:(1)復用了select 的處理模型,沒有對普通socket 處理流程帶來復雜變化,同時具備很好的處理性能;(2)保持了單進程、單線程的特點,適用范圍廣泛;(3)有名管道FIFO和System V IPC 隊列均是操作系統提供,訪問它們的接口也都是系統調用,保證了程序的可靠性。
引用
[1] STEVENS,Richard W.UNIX Network Programming,Interprocess Communications[J].CODES'93:IEEE/ACM International Workshop on Computer-Aided Hardware-Software Codesign,1998,11(11):225-228.
[2] Richard W Stevens,Bill Fenner,Andrew M Rudoff.UNIX網絡編程卷1 套接字聯網API 第3版[M].北京:人民郵電出版社,2019.
[3] 崔志雄.中國航信數字化轉型進行時[J].企業管理,2020(6):105-107.
[4] 梁海峰,黃愷,杜建國.民航旅客服務系統交易模型及關鍵技術研究[J].電子測試,2017(22):80-83.