高性能、高併發、高擴展性和可讀性的網絡服務器架構:StateThreads

StateThreads是一個C的網絡程序開發庫,提供了編寫高性能、高併發、高可讀性的網絡程序的開發庫,支持UNIX-like平臺。它結合了多線程編寫並行成的簡單性,一個進程支持多個併發,支持基於事件的狀態機架構的高性能和高併發能力。

(譯註:提供了EDSM的高性能、高併發、穩定性,“多線程”形式的簡單編程方式,用setjmp和longjmp實現的一個線程模擬多線程,即用戶空間的多線程,類似於現在的協程和纖程)

1. 定義

1.1 網絡程序(Internet Applications)

網絡程序(Internet Application)(IA)是一個網絡的客戶端或者服務器程序,它接受客戶端連接,同時可能需要連接到其他服務器。在IA中,數據的到達和發送完畢經常操縱控制流,就是說IA是數據驅動的程序。對每個連接,IA做一些有限的工作,包括和peer的數據交換,peer可能是客戶端或服務器。IA典型的事務步驟是:接受連接,讀取請求,做一些有限的工作處理請求,將相應寫入peer。一個iA的例子是Web服務器,更典型的例子是代理服務器,因爲它接受客戶端連接,同時也連接到其他服務器。

我們假定IA的性能由CPU決定,而不是由網絡帶寬或磁盤IO決定,即CPU是系統瓶頸。

1.2 性能和可擴展性

IA的性能一般可以用吞吐量來評估,即每秒的事務數,或每秒的字節數(兩者可以相互轉換,給定事務的平均大小就可以)。有很多種工具可以用來測量Web程序的特定負載,譬如SPECweb96, WebStone, WebBench。儘管對擴展性沒有通用的定義,一般而言,可擴展性指系統在外部條件改變時維持它的性能的能力。對於IAs而言,外部條件指連接數(併發),或者底層硬件(CPU數目,內存等)。因此,有兩種系統的擴展性:負載能力和系統能力。

(譯註:scalability可擴展性,指條件改變了系統是否還能高效運行,譬如負載能力指併發(條件)增多時系統是否能承擔這麼多負載,系統能力指CPU等增多時是否能高效的利用多CPU達到更強的能力)

下圖描述了客戶端數目增多時系統的吞吐量的變化,藍色線條表示理想狀況。最開始時吞吐量程線性增長,這個區間系統和CPU較爲空閒。繼續增長的連接數導致系統開始飽和,吞吐量開始觸及天花板(CPU跑滿能跑到的吞吐量),在天花板之後吞吐量變爲平行線不再增長,因爲CPU能力到達了極限。在實際應用中,每個連接消耗了計算資源和內存資源,就算是空閒狀態,這些負擔都隨連接數而增長,因此,實際的IA吞吐量在某個點之後開始往下落(藍色虛線表示)。開始掉的點,不是其他的原因,而是由系統架構決定的。

我們將系統有好的負載能力,是指系統在高負載時仍能很好的工作。SPECweb99基準測試能較好的反應系統的負載能力,因爲它測量的是連接在最小流量需求時系統能支持的最大連接數(譯註:如圖中Capacity所指出的點即灰色斜線和藍色線交叉的點)。而不像SPECweb96或其他的基準測試,是以系統的吞吐量來衡量的(譯註:圖中Max throughout,即藍色線的天花板)。

系統能力指程序在增加硬件單元例如加CPU時系統的性能,換句話說,好的系統能力意味着CPU加倍時吞吐量會加倍(圖中綠色虛線)。我們假設底層操作系統也具有很好的系統能力。好的系統能力指假設程序在一個小的機器上運行很好,當有需要換到大型服務器上運行時也能獲得很高的性能。就是說,改變服務器環境時,系統不需要重寫或者費很大的勁。

(譯註:

縱座標是吞吐量,橫座標是連接數。

灰色的線(min acceptable throughout pre client)表示是客戶端的需要的吞吐量,至少這個量才流暢。

藍色表示理想狀態的server,系統能力一直沒有問題,能達到最大吞吐量,CPU跑滿能跑到的吞吐量。

藍色虛線表示實際的server,每個連接都會消耗CPU和內存,所以在某個臨界點之後吞吐量開始往下掉,這個臨界點就是系統結構決定的。好的系統架構能將臨界點往後推,穩定的支持更高的併發;差的架構在併發增加時可能系統就僵死了。

灰色虛線表示兩個測量基準,一個是SPECweb96測量的是系統最大吞吐量,一個是SPECweb99測量每個連接在最小要求流量下系統能達到的最大連接數,後者更能反應系統的負載能力,因爲它測量不同的連接的狀況下系統的負載能力。

負載能力指的是系統支撐的最大負載,圖中的橫座標上的值,對應的藍色線和灰色線交叉的點,或者是藍色線往下掉的點。

系統能力指的是增加服務器能力,如加CPU時,系統的吞吐量是否也會增加,圖中綠色線表示。好的系統能力會在CPU增加時性能更高,差的系統能力增加CPU也不會更強。

)

儘管性能和擴展性對服務器來講更重要,客戶端也必須考慮這個問題,例如性能測試工具。

1.3 併發

併發反應了系統的並行能力,分爲虛擬併發和物理併發:

虛擬併發是指操作系統同時支持很多併發的連接。

物理併發是指硬件設備,例如CPU,網卡,硬盤等,允許系統並行執行任務。

IA必須提供虛擬併發來支持用戶的併發訪問,爲了達到最大的性能,IA創建的由內核調度的編程實體數目基本上和物理併發的數量要保持一致(在一個數量級上)(譯註:有多少個CPU就用多少個進程)。內核調度的編程實體即內核執行對象(kernel execution vehicles),包括Solaris輕量級進程,IRIX內核線程。換句話說,內核執行對象應該由物理條件決定,而不是由併發決定(譯註:即進程數目應該由CPU決定,而不是由連接數決定)。

2. 現有的架構

IAs(Internet Applications)有一些常見的被廣泛使用的架構,包括基於進程的架構(Multi-Process),基於線程的架構(Multi-Threaded), 和事件驅動的狀態機架構(Event-Driven State Machine)。

2.1 基於進程的架構:MP

(譯註:Multi-Process字面意思是多進程,但事件驅動的狀態機EDSM也常用多進程,所以爲了區分,使用“基於進程的架構”,意爲每個連接一個進程的架構)

在基於進程的架構(MP)中,一個獨立的進程用來服務一個連接。一個進程從初始化到服務這個連接,直到服務完畢才服務其他連接。

用戶Session是完全獨立的,因此,在這些處理不同的連接的進程之間,完全沒有同步的必要。因爲每個進程有自己獨立的地址空間,這種架構非常強壯。若服務某個連接的進程崩潰,其他的連接不會受到任何影響。然而,爲了服務很多併發的連接,必須創建相等數量的進程。因爲進程是內核對象,實際上是最“重”的一種對象,所以至少需要再內核創建和連接數相等的進程。在大多數的系統中,當創建了上千個進程時,系統性能將大幅降低,因爲超負荷的上下文切換。也就是說,MP架構負載能力很弱,無法支持高負載(高併發)。

另一方面,MP架構有很高的系統能力(利用系統資源,穩定性,複雜度),因爲不同的進程之間沒有共享資源,因而沒有同步的負擔。

ApacheWeb服務器就是採用的MP架構。

2.2 基於線程的架構:MT

(譯註:Multi-Threaded字面意思是多線程,但側重一個線程服務一個連接的方式,用“基於線程”會更準確)

在基於線程(MT)架構中,使用多個獨立的線程,它們共享地址空間。和MP結構的進程一樣,每個線程獨立服務每個連接直到服務完畢,這個線程才用來服務其他連接。

很多現代的UNIX操作系統實現了一個多對一的模型,用來映射用戶空間的線程到系統內核對象。在這個模型中,任意多數量的用戶空間線程複用少量的內核執行對象,內核執行對象即爲虛擬處理器。當用戶空間線程調用了一個阻塞的系統調用時,內核執行對象也會在內核阻塞。如果沒有其他沒有阻塞的內核執行對象,或者有其他需要運行的用戶空間線程,一個新的內核執行對象會被自動創建,這樣就防止一個線程阻塞時其他線程都被阻塞。

由於IAs由網絡IO驅動,所有的併發連接都會阻塞在不同的地方。因此,內核執行對象的數目會接近用戶空間線程的數目,也就是連接的數目。此時,多對一的模型就退化爲一對一的模型,和MP架構一樣,內核執行對象的數目由併發決定而不是由CPU數目決定。和MP一樣,這降低了系統的負載能力。儘管這樣,由於內核線程是輕量級進程,使用了較少的資源,比內核進程要輕,MT架構比MP架構在負載能力方面稍強一些。

在MT架構中,內核線程共享了地址空間,各種同步鎖破壞了系統能力。儘管程序可以很小心的避免鎖來提高程序性能(是個複雜的任務),標準庫函數和系統調用也會對通用資源上鎖,例如,平臺提供的線程安全函數,例如內存分配函數(malloc,free等)都是用了一個全局鎖。另外一個例子是進程的文件描述表,這個表被內核線程共享,在系統調用(open,close等)時需要保護。除此之外,多核系統中需要在CPU之間維護緩存的一致,當不同的線程運行在不同的CPU上並修改同樣的數據時,嚴重降低了系統的性能。

爲了提高負載能力,產生了一些不同類型的MT架構:創建多組線程,每組線程服務一個任務,而不是一個線程服務一個連接。例如,一小組線程負責處理客戶端連接的任務,另外一組負責處理請求,其他的負責處理響應。這種架構的主要優點是它對併發和線程解耦了,不再需要同等數量的線程服務連接。儘管這樣,線程組之間必須共享任務隊列,任務隊列需要用鎖來保護(典型的生產者-消費者問題)。額外的線程同步負擔導致在多處理器系統上性能很低。也就是說,這種架構用系統能力換取了負載能力(用性能換高併發)。

當然,線程編程的噩夢,包括數據破壞,死鎖,條件競爭,也導致了任何形式的MT架構無法實用。

2.3 基於事件的狀態機架構:EDSM

在基於事件驅動的狀態機架構(EDSM)中,一個進程用來處理多個併發。Comer和Stevens[Reference 2]描述了這個架構的基礎。EDSM架構中,每次每個連接只由數據驅動一步(譯註:例如,收一個包,動作一次),因此必須複用多個併發的連接(譯註:必須複用一個進程處理多個連接),進程設計成狀態機每次收到一個時間就處理並變換到下一個狀態。

在空閒狀態時,EDSM調用select/poll/epoll等待網絡事件,當一個特殊的連接可以讀寫時,EDSM調用響應的處理函數處理,然後處理下一個連接。EDSM架構使用非阻塞的系統調用完成異步的網絡IO。關於非阻塞的IO,請參考Stevens [Reference 3]。

爲了利用硬件並行性能,可以創建多個獨立的進程,這叫均衡的多進程EDSM,例如ZeusWeb服務器[Reference 4](譯註:商業的高性能服務器)。爲了更好的利用多磁盤的IO性能,可以創建一些輔助進程,這叫非均衡的多進程EDSM,例如DruschelWeb服務器[Reference 5]。

EDSM架構可能是IAs的最佳架構,因爲併發連接完全和內核進程解耦,這種架構有很高的負載能力,它僅僅需要少量的用戶空間的資源來管理連接。

和MP架構一樣,多核的EDSM架構也有很高的系統能力(多核性能,穩定性等),因爲進程間沒有資源共享,所以沒有同步鎖的負擔。

不幸的是,EDSM架構實際上是基於線程的概念(譯註:狀態機保存的其實就是線程的棧,上次調用的位置,下次繼續從這個狀態開始執行,和線程是一樣的),所以新的EDSM系統需要從頭開始實現狀態機。實際上,EDSM架構用很複雜的方式模擬了多線程。

3. State Threads Library

StateThreads庫結合了上面所有架構的優點,它的api提供了像線程一樣的編程方式,允許一個併發在一個“線程”裏面執行,但這些線程都在一個進程裏面。底層的實現和EDSM架構類似,每個併發連接的session在單獨的內存空間。

(譯註:StateThreads提供的就是EDSM機制,只是將狀態機換成了它的“線程”(協程或纖程),這些“線程”實際上是一個進程一個線程實現但表現起來像多線程。所以StateThread的模型是EDSM的高性能和高併發,然後提供了MT的可編程性和簡單接口,簡化了EDSM的狀態機部分。)

3.1 狀態改變和調度

每個併發的session包含它自己的棧環境(棧指針,PC,CPU寄存器)和它的棧。從概念上講,一次線程上下文切換相當於進程改變它的狀態。當然除了進程之外,並沒有使用線程(譯註:它是單線程的方式模擬多線程)。和其他通用的線程庫不一樣,StateThreads庫的設計目標很明確。線程上下文切換(進程狀態改變)只會在一些函數中才會發生(IO點,或者明確的同步點)。所以,進程級別的數據不需要鎖來保護,因爲是單線程。整個程序可以自由的使用靜態變量和不可重入的函數,極大的簡化了編程和調試,從而增加了性能。這實際上是和協程(co-routine)類似,但是不需要顯式的用yield指定——線程調用阻塞的IO函數被阻塞而交出控制權是早晚的事。所有的線程(併發連接)都有同樣的優先級,所以是非搶佔式的調度,和EDSM架構類似。由於IAs是數據驅動(處理流程由網絡緩衝區大小和數據到達的次序決定),調度不是按時間切片的。

只有兩類的外部事件可以被庫的調度器處理,因爲只有這類事件能被select/poll檢測到:

1. IO事件:一個文件描述符可讀寫時。

2. 定時器時間:指定了timeout。

儘管這樣,其他類型的事件(譬如發送給進程的信號)也能被轉換成IO事件來處理。例如,信號處理函數收到信號時可以寫入pipe,因此將信號轉換成了IO事件。

爲了能更好的發揮硬件並行的性能,和EDSM架構一樣,可以創建均衡和非均衡的進程。進程管理不是庫的功能,而是留給用戶處理。

有一些通用的線程庫,實現了多對一的模型(多個用戶空間的線程,對一個內核執行對象),使用了和StateThreads庫類似的技術(非阻塞IO,事件驅動的調度器等)。譬如,GNU Portable Threads [Reference 6]。因爲他們是通用庫,所以它們和StateThreads有不同的目標。StateThreads不是通用的線程庫,而是爲少數的需要獲得高性能、高併發、高擴展性和可讀性的IAs系統而設計的。

3.2 可擴展性

StateThreads是非常輕量級的用戶空間線程,因此創建和維護用戶連接需要很少的資源。使用StateThreads的系統在高併發時能獲得很高性能。

多CPU的系統上,程序需要創建多個進程才能利用硬件的平行能力。使用獨立的進程是唯一獲取高系統能力的方式,因爲複製進程的資源是唯一的方式來避免鎖和同步這種負擔的唯一方式。創建UNIX進程一般會複製進程的資源。再次強調,EDSM架構中,併發的連接和系統對象(進程線程)沒有任何的聯繫,也就是說,StateThreads庫將大量併發複用到了少量的獨立的進程上,因此獲得很高的系統能力和負載能力。

3.3 性能

高性能是StateThreads庫的主要目標之一,它實現了一系列的系統調用,儘可能的提高線程創建和切換的速度。例如,沒有線程級別的信號屏蔽(和POSIX線程不一樣),所以線程切換時不需要保存和恢復進程的信號屏蔽字,這樣在線程切換時少了兩個系統調用。信號事件能被高效的轉換成IO事件(如上所述)。

3.4 便攜性

StateThreads庫使用了和EDSM架構同樣的基礎概念,包括非阻塞IO,文件描述符,IO複用。這些概念在大多數的UNIX平臺都通用,所以UNIX下庫的通用性很好,只有少數幾個平臺相關的特性。

3.5 State Threads 和 NSPR

StateThreads庫是從Netscape Portable Runtime library (NSPR) [Reference 7]發展來的。NSPR主要的目標是提供一個平臺無關的系統功能,包括線程,線程同步和IO。性能和可擴展性不是NSPR主要考慮的問題。StateThreads解決了性能和可擴展性問題,但是比NSPR要小很多;它僅僅包含8個源文件,卻提供了在UNIX下寫高效IAs系統的必要功能:

NSPR State Threads
Lines of code ~150,000 ~3000
Dynamic library size(debug version)
IRIX ~700 KB ~60 KB
Linux ~900 KB ~70 KB

StateThreads是一個提供了編寫IA的基礎庫,它包含以下優點:

1. 能設計出高效的IA系統,包括很高的負載能力和系統能力。

2. 簡化了編程和調試,因爲沒有同步鎖,可以使用靜態變量和不可重入函數。

它主要的限制:

1. 所有socket的IO必須要使用庫的IO函數,因爲調度器可以避免被阻塞(譯註:用操作系統的socket的IO函數自然調度器就管不了了)。

References

  1. Apache Software Foundation, http://www.apache.org.
  2. Douglas E. Comer, David L. Stevens, Internetworking With TCP/IP, Vol. III: Client-ServerProgramming And Applications, Second Edition, Ch. 8, 12.
  3. W. Richard Stevens, UNIX Network Programming, Second Edition, Vol. 1, Ch. 15.
  4. Zeus Technology Limited, http://www.zeus.co.uk.
  5. Peter Druschel, Vivek S. Pai, Willy Zwaenepoel, Flash: An Efficient and Portable Web Server. In Proceedings of the USENIX 1999 Annual Technical Conference, Monterey, CA, June 1999.
  6. GNU Portable Threads, http://www.gnu.org/software/pth/.
  7. Netscape Portable Runtime, http://www.mozilla.org/docs/refList/refNSPR/.

Other resources covering various architectural issues in IAs

  1. Dan Kegel, The C10K problem, http://www.kegel.com/c10k.html.
  2. James C. Hu, Douglas C. Schmidt, Irfan Pyarali, JAWS: Understanding High Performance Web Systems, http://www.cs.wustl.edu/~jxh/research/research.html.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章