1. 程式人生 > >從半同步-半非同步模式談伺服器的設計

從半同步-半非同步模式談伺服器的設計

半同步-半非同步模式,最早應該是由ACE的作者提出,原文在這裡.

簡而言之,所謂的半同步半非同步模式分為三個組成模組:同步處理模組,佇列模組,非同步處理模組.三個模組之間的互動關係如圖:


(注:上圖出自這裡)
幾個模組的之間的互動為:非同步模組接收可能會非同步到來的各種事件(I/O,訊號等),然後將它們放入佇列中,而同步模組一般只有一種動作,就是不停的從佇列中取出訊息進行處理.

半同步-半非同步模式的出現是為了給伺服器的功能進行劃分,儘可能將的可能阻塞的操作放在同步模組中,這樣不會影響到非同步模組的處理.
舉個例子說明.
假設現在有一個伺服器,在接收完客戶端請求之後會去資料庫查詢,這個查詢可能會很慢.這時,如果還是採用的把接收客戶端的連線和處理客戶端的請求(在這裡這個處理就是查詢資料庫)放在一個模組中來處理,很可能將會有很多連線的處理響應非常慢.
此時,考慮使用半同步半非同步的模式,開一個程序,使用多路複用IO(如epoll/select)等監聽客戶端的連線,接收到新的連線請求之後就將這些請求存放到通過某種IPC方式實現的訊息佇列中,同時,還有N個處理程序,它們所做的工作就是不停的從訊息佇列中取出訊息進行處理.這樣的劃分,將接收客戶端請求和處理客戶端請求劃分為不同的模組,相互之間的通過IPC進行通訊,將對彼此功能的影響限制到最小.

然後,不是每種請求下都適合使用半同步半非同步模式的.

我之前深入閱讀過ligty的程式碼,它的設計是monitor+worker多程序 + 多路複用IO + 狀態機的架構.也就是說,每個worker程序負責接收客戶端連線和處理客戶端連線的全過程,每個過程都會記錄一個狀態,比如現在在接收包頭,如果這次的接收不是因為連線關閉的原因導致的接收錯誤,那麼就將這個客戶端的fd放入多路複用IO中,等待著下一次根據這次儲存的狀態進入狀態機中進行處理.

簡單的說,在ligty中,一個worker子程序全權負責了接收和處理的全過程,並沒有按照上面半同步半非同步的劃分來設計.

再後來,我大概看過一些nginx的程式碼,細節之處可能不一樣,但是就伺服器總體的架構而言,是與ligty的設計差不多的.

這兩個伺服器是目前比較快的web伺服器了,沒有采用多麼複雜的模式.

那麼為什麼對於web伺服器而言,不需要使用半同步半非同步也可以達到非常高的效率呢?我想,這與伺服器的業務有關.對於web伺服器而言,大部分的時間都花在了IO處理上,比如監聽伺服器埠,接收客戶端連線,根據客戶端的請求傳送文字檔案內容到客戶端去,這裡的操作,基本上沒有太可能會造成阻塞的地方,也就是說,處理完成一個客戶端請求的全過程對web伺服器而言是非常快的.

所以,要回答這個問題,需要看具體的業務需求.打個比方,如果處理一個客戶端請求需要10s,那麼完全有一個模組全部處理不是一個很好的設計;反之,如果處理一個請求只需要10ms,而程序/執行緒間的切換就需要1s了,還將模組進行劃分就不必了.

另外,回到半同步半非同步模式的具體實現上,可以使用執行緒或者程序,而佇列層則可以使用不同的IPC方式,有很多關於多執行緒多程序孰優孰劣的爭論,由於我沒有太多多執行緒的程式設計經驗,也就不在這裡進一步說明了.