中國(guó)民航信息網(wǎng)絡(luò)股份有限公司 段鍇 李永進(jìn) 郝鵬 王波
傳統(tǒng)上通過(guò)采用select 或poll 等系統(tǒng)調(diào)用可以實(shí)現(xiàn)多個(gè)TCP 網(wǎng)絡(luò)連接的I/O 復(fù)用,但該方式并不支持基于System V 消息隊(duì)列的報(bào)文傳輸。有名管道技術(shù)FIFO的描述符與Socket 的描述符相類似,可以被select 系統(tǒng)調(diào)用支持,因此可借助FIFO 設(shè)計(jì)一種方法,達(dá)到對(duì)System V 消息隊(duì)列進(jìn)行I/O 復(fù)用的目的。方法中設(shè)計(jì)地從消息隊(duì)列接收?qǐng)?bào)文的處理流程,在不降低處理性能的前提下,有效解決了有名管道和消息隊(duì)列的操作不是原子操作帶來(lái)的不一致問(wèn)題,實(shí)現(xiàn)單一網(wǎng)絡(luò)通信線程可以同時(shí)處理來(lái)自網(wǎng)絡(luò)連接和System V 消息隊(duì)列的報(bào)文。
隨著移動(dòng)互聯(lián)網(wǎng)技術(shù)的不斷發(fā)展和航空公司數(shù)字化轉(zhuǎn)型的持續(xù)推進(jìn),航空公司運(yùn)營(yíng)模式也從單一型點(diǎn)對(duì)點(diǎn)航線向復(fù)合型網(wǎng)格化航線發(fā)展,全球運(yùn)營(yíng)航線網(wǎng)絡(luò)的規(guī)模進(jìn)而呈指數(shù)級(jí)高速增長(zhǎng)。作為對(duì)外提供國(guó)內(nèi)、國(guó)際航班庫(kù)存查詢的航班計(jì)劃及庫(kù)存查詢系統(tǒng)(后簡(jiǎn)稱“航班查詢系統(tǒng)”或“系統(tǒng)”),需要面對(duì)海量訪問(wèn)壓力下,進(jìn)行復(fù)雜實(shí)時(shí)計(jì)算的挑戰(zhàn)。采用基于多進(jìn)程、分布式并行計(jì)算的架構(gòu),通常是解決該問(wèn)題的主要思路。
對(duì)于多進(jìn)程、分布式的架構(gòu)來(lái)說(shuō),進(jìn)程間通信是必須采用的主要技術(shù)之一。常用的進(jìn)程間通信技術(shù)包括Socket 網(wǎng)絡(luò)套接字、System V 消息隊(duì)列、FIFO 有名管道等。其中,Socket 網(wǎng)絡(luò)套接字是服務(wù)器之間通信的主要技術(shù)手段,System V 消息隊(duì)列和FIFO 有名管道是服務(wù)內(nèi)部進(jìn)程間通信的主要手段。除此之外,當(dāng)一個(gè)進(jìn)程需要同時(shí)處理多個(gè)網(wǎng)絡(luò)連接或交互式輸入時(shí),就需要使用到I/O 復(fù)用(I/O Multiplexing) 技術(shù)[1]。I/O 復(fù)用的機(jī)制是指,進(jìn)程通過(guò)預(yù)先告知操作系統(tǒng)內(nèi)核,使得內(nèi)核一旦發(fā)現(xiàn)進(jìn)程指定的一個(gè)或多個(gè)I/O 條件就緒時(shí),就會(huì)通知進(jìn)程進(jìn)行處理,從而提升進(jìn)程間通信的效率。目前支持I/O 復(fù)用的系統(tǒng)調(diào)用包括select、poll、epoll 等[2]。本文研究和解決的主要問(wèn)題,就是利用I/O 多路復(fù)用技術(shù)去優(yōu)化航班查詢系統(tǒng)中服務(wù)端網(wǎng)絡(luò)通信的效率。
航班查詢系統(tǒng)中服務(wù)端網(wǎng)絡(luò)通信的拓?fù)鋱D如圖1 所示,大量客戶端應(yīng)用與服務(wù)端系統(tǒng)通過(guò)socket 連接進(jìn)行消息交互。服務(wù)端系統(tǒng)由多個(gè)航班查詢應(yīng)用進(jìn)程、服務(wù)端網(wǎng)絡(luò)通信服務(wù)進(jìn)程WSH(Workstation Handler)、以及請(qǐng)求和應(yīng)答(圖中Q_Out)消息隊(duì)列組成。其中WSH(Workstation Handler)進(jìn)程是專門負(fù)責(zé)服務(wù)端網(wǎng)絡(luò)通信的進(jìn)程,一方面它需要處理多個(gè)客戶端的socket網(wǎng)絡(luò)連接,從中接收不同客戶端進(jìn)程的請(qǐng)求報(bào)文并放至航班查詢進(jìn)程的請(qǐng)求隊(duì)列中;另一方面是該進(jìn)程還要處理一個(gè)本地System V 消息隊(duì)列Q_OUT,從中獲取應(yīng)答報(bào)文返回客戶端。WSH 進(jìn)程需要同時(shí)處理多個(gè)網(wǎng)絡(luò)連接和消息隊(duì)列的I/O,因此WSH 進(jìn)程需要采用I/O復(fù)用技術(shù),來(lái)提升處理系統(tǒng)的效率。
圖1 服務(wù)端網(wǎng)絡(luò)通信的進(jìn)程WSH (Workstation Handler)Fig.1 The process of server network communication WSH (Workstation Handler)
常見(jiàn)的多網(wǎng)絡(luò)連接I/O 復(fù)用技術(shù),主要是通過(guò)調(diào)用select 或poll 等系統(tǒng)服務(wù)實(shí)現(xiàn),但是由于System V 消息隊(duì)列的標(biāo)識(shí)符不屬于描述符標(biāo)識(shí),因此不能在消息隊(duì)列Q_OUT 上采用select 或poll 等調(diào)用。其他I/O 復(fù)用方案有采用多線程技術(shù),即使用多個(gè)線程分別去處理Q_OUT 隊(duì)列和網(wǎng)絡(luò)Socket 的報(bào)文。但是多線程方案存在以下缺點(diǎn):
(1)多線程的編程實(shí)現(xiàn)難度高,調(diào)試復(fù)雜;(2)多線程的可靠性較差,一個(gè)線程掛掉將導(dǎo)致整個(gè)進(jìn)程掛掉;(3)多線程比單線程有額外的資源開(kāi)銷要求(例如內(nèi)存和多核CPU),在某些特定用戶的環(huán)境下(單核CPU 和內(nèi)存受限),多線程方案不能被采用。
針對(duì)上述問(wèn)題,本文提出了一種用于System V 消息隊(duì)列的I/O 復(fù)用技術(shù)方案,以使得WSH 進(jìn)程在不使用多線程技術(shù)的前提下,可以同時(shí)處理來(lái)自網(wǎng)絡(luò)連接和System V 消息隊(duì)列的報(bào)文,該方案也可以用于其他有類似服務(wù)端處理網(wǎng)絡(luò)通信需求的系統(tǒng),具體方案分為以下5個(gè)步驟。
WSH 進(jìn)程創(chuàng)建有名管道FIFO 和消息隊(duì)列Q_OUT。
系統(tǒng)調(diào)用mkfifo 用于創(chuàng)建有名管道FIFO:
int mkfifo(const char *path, mode_t mode); //其中path 是基于配置文件的配置,例如path=/opt/app/config/todewsh.fifo。
系統(tǒng)調(diào)用msgget 用于創(chuàng)建消息隊(duì)列Q_OUT:
int msgget(key_t key, int msgflg); //其 中key是基于配置文件的配置,例如ipckey=372539。
由于FIFO 是半雙工的,不能打開(kāi)來(lái)既讀又寫,另外還基于FIFO 的特性,如果FIFO 當(dāng)前沒(méi)有被打開(kāi)來(lái)寫的話,以阻塞方式打開(kāi)FIFO 只讀的操作會(huì)被阻塞。由于WSH 進(jìn)程啟動(dòng)時(shí)并不能保證已有航班查詢進(jìn)程打開(kāi)有名管道FIFO 來(lái)寫,所以WSH 進(jìn)程必須以非阻塞的方式打開(kāi)有名管道FIFO 來(lái)讀:
int fiforead = open(“/opt/app/config/todewsh.fifo”,O_RDONLY|O_NONBLOCK)。
WSH 進(jìn)程將FIFO 的描述符標(biāo)識(shí)fiforead 放入可讀描述字集合,同時(shí)也將客戶端的網(wǎng)絡(luò)Socket 加入相應(yīng)描述字集合,然后調(diào)用select 進(jìn)行I/O 復(fù)用。如果有客戶端發(fā)送請(qǐng)求報(bào)文時(shí),WSH 進(jìn)程會(huì)被select 調(diào)用觸發(fā)去接收數(shù)據(jù),然后把請(qǐng)求報(bào)文發(fā)送至航班查詢進(jìn)程。
航班查詢進(jìn)程調(diào)用封裝好的API(應(yīng)用程序編程接口) Server_NetPutMsg 實(shí)現(xiàn)將應(yīng)答報(bào)文放入Q_OUT 隊(duì)列,同時(shí)打開(kāi)并將事件報(bào)文放入有名管道todewsh.fifo。Server_NetPutMsg 函數(shù)首先調(diào)用msgsnd 系統(tǒng)調(diào)用,將應(yīng)答報(bào)文放入消息隊(duì)列Q_OUT,如果放入成功,再以只寫的方式打開(kāi)todewsh.fifo,為了防止航班查詢進(jìn)程被阻塞,仍然要以非阻塞的方式打開(kāi):int fifowrite=open(“/opt/app/config/todewsh.fifo”,O_WRONLY|O_NONBLOCK);打開(kāi)以后再調(diào)用系統(tǒng)調(diào)用write 往管道todewsh.fifo 寫入一個(gè)字符“T”當(dāng)作事件報(bào)文。
由于Server_NetPutMsg 函數(shù)放消息隊(duì)列Q_OUT 和打開(kāi)以及放管道todewsh.fifo 事件是三個(gè)獨(dú)立的API,非原子操作,所以存在放Q_OUT 成功、放管道事件失敗的可能性,包括WSH 進(jìn)程從Q_OUT 讀取應(yīng)答報(bào)文和從管道讀取事件也是不同的API,同樣存在一致性問(wèn)題。當(dāng)產(chǎn)生不一致?tīng)顟B(tài)時(shí),如果WSH 進(jìn)程只是基于select 的事件進(jìn)行處理的話,勢(shì)必造成Q_OUT 的數(shù)據(jù)延遲到下一次管道事件發(fā)送成功時(shí)才能被取到,這對(duì)實(shí)時(shí)性要求很高的在線系統(tǒng)來(lái)說(shuō)是不允許的,因此需要增加下列操作來(lái)避免不一致?tīng)顟B(tài)的產(chǎn)生。
(1)首先判斷上次從Q_OUT 取到的應(yīng)答個(gè)數(shù)是否超過(guò)10 個(gè),如果超過(guò),則Q_OUT 仍有待處理數(shù)據(jù)的可能性很大,因此select 調(diào)用不設(shè)置超時(shí);如果沒(méi)超時(shí),則select 調(diào)用設(shè)置超時(shí)為1ms。(2)基于以上的超時(shí)設(shè)置進(jìn)行select 調(diào)用。(3)select 返回后判斷管道是否有事件,如果有則執(zhí)行第(4)步,沒(méi)有則執(zhí)行第(5)步。(4)調(diào)用read 方法讀取管道事件,如果沒(méi)讀到事件則執(zhí)行第(7)步;如果讀到事件,則繼續(xù)調(diào)用msgrcv 方法讀取Q_OUT 隊(duì)列數(shù)據(jù)。如果沒(méi)讀到繼續(xù)從第(4)步重新開(kāi)始,如果讀到則執(zhí)行第(6)步。(5)如果管道沒(méi)有事件或者select 超時(shí),為了避免管道事件和Q_OUT 隊(duì)列數(shù)據(jù)的不一致造成延誤隊(duì)列數(shù)據(jù)發(fā)送,則調(diào)用msgrcv 方法讀取Q_OUT 隊(duì)列數(shù)據(jù)。如果沒(méi)讀到數(shù)據(jù)執(zhí)行第(7)步,如果讀到則調(diào)用read 方法嘗試讀取管道事件,不論管道有無(wú)事件,繼續(xù)執(zhí)行步驟(6)。(6)將計(jì)數(shù)器Rcv_Qout_Num 加1,并將數(shù)據(jù)發(fā)送客戶端,然后判斷Rcv_Qout_Num 是否大于10 個(gè),如果大于10,繼續(xù)執(zhí)行第(7)步,如果不大于10 則繼續(xù)從第(3)步重新開(kāi)始。(7)繼續(xù)基于select 的返回處理網(wǎng)絡(luò)事件,以讀取客戶端的請(qǐng)求數(shù)據(jù),這塊邏輯與普通網(wǎng)絡(luò)I/O 復(fù)用處理相同,不再贅述。處理完畢后繼續(xù)從第(1)步循環(huán)處理。
WSH 進(jìn)程被select 調(diào)用觸發(fā),按照上述流程讀取管道的事件和消息隊(duì)列Q_OUT 中的應(yīng)答報(bào)文。航班查詢進(jìn)程放入管道todewsh.fifo 一個(gè)字節(jié)的事件后,WSH 進(jìn)程就會(huì)被select 調(diào)用觸發(fā),進(jìn)而首先從管道中讀取一個(gè)字符,再?gòu)南?duì)列Q_OUT 讀取應(yīng)答報(bào)文,然后將數(shù)據(jù)發(fā)送給客戶端。利用上述處理流程和方法,WSH 進(jìn)程就可以借用有名管道,實(shí)現(xiàn)多個(gè)網(wǎng)絡(luò)連接和消息隊(duì)列的I/O 復(fù)用處理。
中國(guó)民航旅客服務(wù)系統(tǒng)為海內(nèi)外60 多家航空公司提供庫(kù)存管理和運(yùn)營(yíng)服務(wù),目前是世界第三大航空旅游分銷系統(tǒng)提供商和全球最大的結(jié)算數(shù)據(jù)處理中心[3]。旅客服務(wù)系統(tǒng)依托于信息技術(shù),是高并發(fā)實(shí)時(shí)交易系統(tǒng),具有旅客服務(wù)主業(yè)務(wù)鏈條長(zhǎng)、相關(guān)參與方數(shù)量多、安全風(fēng)險(xiǎn)等級(jí)高的特點(diǎn),涉及從航空產(chǎn)品的頂層設(shè)計(jì)到末端接觸點(diǎn)服務(wù)的全流程和全生命周期[4]。航班計(jì)劃及庫(kù)存查詢系統(tǒng),是中國(guó)民航旅客服務(wù)系統(tǒng)中的重要核心子系統(tǒng)之一,該系統(tǒng)的查詢?cè)L問(wèn)量峰值超過(guò)10000 筆每秒。通過(guò)本文的方法優(yōu)化,全面提升了整個(gè)系統(tǒng)的響應(yīng)時(shí)間,平均響應(yīng)時(shí)間由300ms 縮短至150ms,查詢請(qǐng)求處理效率提升100%。
經(jīng)過(guò)航班查詢系統(tǒng)的實(shí)際檢驗(yàn),相對(duì)于多線程方案,本文提出的I/O 復(fù)用方案具有以下幾個(gè)優(yōu)點(diǎn):(1)復(fù)用了select 的處理模型,沒(méi)有對(duì)普通socket 處理流程帶來(lái)復(fù)雜變化,同時(shí)具備很好的處理性能;(2)保持了單進(jìn)程、單線程的特點(diǎn),適用范圍廣泛;(3)有名管道FIFO和System V IPC 隊(duì)列均是操作系統(tǒng)提供,訪問(wèn)它們的接口也都是系統(tǒng)調(diào)用,保證了程序的可靠性。
引用
[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網(wǎng)絡(luò)編程卷1 套接字聯(lián)網(wǎng)API 第3版[M].北京:人民郵電出版社,2019.
[3] 崔志雄.中國(guó)航信數(shù)字化轉(zhuǎn)型進(jìn)行時(shí)[J].企業(yè)管理,2020(6):105-107.
[4] 梁海峰,黃愷,杜建國(guó).民航旅客服務(wù)系統(tǒng)交易模型及關(guān)鍵技術(shù)研究[J].電子測(cè)試,2017(22):80-83.