適於互聯網的SEDA高併發架構

一、前言

    最近看了一篇博士畢業論文(Matthew David Welsh--An Architecture for Highly Concurrent, Well-Conditioned Internet Services),於是將主要思想寫了出來,旨在傳播別人的思想,這篇論文探討一種使用於Internet Services高併發的可擴展性架構,比較了傳統的模型Thread-based模型和Event-driven模型,並提出了自己的模型SEDA


二、背景


      隨着互聯網的高速發展,各種各樣的互聯網服務相繼出現,現在不再侷限於靜態頁面的訪問,如股票交易,電子商務,即時消息服務,點對點文件共享,應用託管等 等,與以前的靜態頁面發佈服務相比,這些動態的服務,每一請求需要消耗更多的CPUI/O等資源。導致管理和分發這些服務的系統日益複雜,包括Web Server, Cache, Middle-tier Application Server, Database等。

三、傳統的併發編程模型


       傳統的併發編程模型主要有兩種,一種是Thread-based concurrency, 另一種是Event-driven concurrency。下面分別介紹這兩種模型,並比較這兩種模型的優劣。


3.1 Thread-based concurrency 模型

1 Thread-based concurrency 模型,系統爲每一個請求分配一個ThreadProcess,這個ThreadProcess負責處理這個請求並把結果返回給客戶.這幅圖中邊代表控制流。


       這是一種最常見的併發編程模型,爲每一個請求分配一個線程或進程。如今的現代編程語言和操作系統對這種模型有很好的支持。

       在這種Thread-per-request模型下,如圖1所示,每一個被接受的請求被指派給一個線程或進程去處理。鎖,信號量等被用於同步Thread/Process對共享數據結構的訪問。操作系統負責調度這些處理每個請求的Thread, 來保證每個請求可以透明的分享計算機的CPUI/O等資源。

       這種Thread-per-request模型,編程起來簡單,可以將處理一個完整請求的代碼編寫在一個代碼路徑中,並且據有很好的隔離性,每一個請求的處理都被隔離在一個Thread/Process中。請求請求之間的交換隻有通過訪問共享數據結構來實現。

       但是thread-per-request也引起了一些挑戰,資源管理,可擴展性等。資源管理粒度太粗,一個系統支持的線程數是有限制的,導致可擴展性差。

2 thread-per-request模型的性能 This benchmark measures a simple threaded server that dispatches a separate thread for each concurrent request in the system. After receiving a request, each thread performs an 8 KB read from a disk file; all threads read from the same file, so the data is always in the buffer cache. Threads are pre-allocated in the server to eliminate thread startup overhead from the measurements, and requests are generated internally to negate network effects. The server is implemented in C and is running on a 4-way 500 MHz Pentium III with 2GB of memory under Linux 2.2.14. As the number of concurrent requests increases, throughput initially increases until about 8 threads are in use. Adding additional threads causes throughput to degrade substantially. Response time becomes unbounded as request queue lengths increase; for comparison, we have shown the ideal linear response time curve (note the log scale on thehorizontal axis).


       很重要的一點就是設計多線程(進程)模型是爲了共享計算機的資源。隨着線程(進程)數的上升,這個模型引起了更高的資源消耗,操作系統在這些線程(進程)之間的頻繁切換,內存交換,缺頁更加頻繁,鎖競爭更加激烈等,將急劇降低系統的性能,急劇降低系統的吞吐量和增加每個請求的響應時間。從圖2中我們可以看出,隨着線程(進程)數的增加,在線程(進程)數較低時,系統的性能是上升的(系統的吞吐量增加,每個請求的響應時間線性上身),但是到達一個臨界值,隨着線程(進程)數的增加系統的性能急劇下降(系統的吞吐量急劇下降,每個請求的響應時間急劇上升幾乎是指數上升)。更重要的是一個系統所能支持的線程(進程)數是有限的,線程(進程)數超過一個理想值後將引起系統的性能急劇下降。

       當然爲了控制系統的線程(進程)數在一個理想值,避免過多的線程(進程)導致系統性能下降,可以引入線程(進程)池。在這種方式下,所有的請求被分配給一組固定大小的線程(進程)。當所有的線程(進程)忙於處理請求時,新的請求被安放在隊列中等待處理。

    Thread-per-request模型的資源管理問題Thread-per-request模型中的一些重要信息,如每一個請求的資源消耗,Blocking I/O等信息隱藏在OSThread scheduler中,應用層面基本上不可能去合理調度每一個請求的處理,這些是被OS給剝奪了。所以服務器基本上不可能合理的去調度每一個請求,只能祈禱OS調度了。當然我們也可以去實現應用層的線程,這樣的複雜度何代價很大。


3.2 Event-driven concurrency模型

      考慮到Thread-pre-request模型的可擴展性問,人們發明了Event-driven concurrency模型。在這種模型中,服務器由一組線程/進程(一般是 one per CPU)循環處理各種來自隊列的事件(Event)OS,應用都可以產生這些事件,表示某些操作需要執行,如:network或 disk I/O 準備就緒,或者完成通知,定時器,或者應用層的自定義事件。

3 Event-driven concurrency模型

       Event-driven concurrency模型中,每一個請求在系統被表示成一個 Finite State MachineFSM,有限狀態機)。每一個FSM的狀態表示請求的一系列的操作。比如一個靜態頁面的請求可能包含這些狀態,如圖4


    1. Read request from network;


      1. Parse request (e.g., process HTTP headers);


      1. Look up disk file corresponding to request;


      1. Read file data;


      1. Format reply packet;


      1. Write reply to network.


4 Finite state machine for a simple HTTP server requestThis figure depicts a static HTTP server request as a finite state machine (FSM) as used in an event-driven system. Each state represents some aspect of request processing, and edges represent transitions between states, triggered by incoming events or the completion of the processing for a given state.


       當一個請求的事件到來,系統分配線程組中的一個線程處理這個請求的對應狀態的操作。這些對應狀態的操作一般很短,處理完後請求轉換到FSM的下一個狀態等待系統處理,這個線程又會被系統分配去處理下一個請求的這個狀態。

       Event-driven concurrency模型中實際上通過FSM來在應用層調度每一個請求,通過在請求之間切換來實現,通過使用一組過定數量的線程來處理請求的各個狀態。一個線程可以處理每個請求的一種狀態,或者是多個狀態,或者是多個線程,這就依賴於具體實現了。這種模型要求每一個狀態的操作是短暫的並且是非阻塞的,所以 Event-driven concurrency模型一般都用了非阻塞的I/O接口。Event-driven concurrency模型中的事件調度類似於OSthread 調度,這樣Event-driven concurrency模型中可以設計應用層的調度策略。

       Event-driven concurrency模型一般比Thread-per-request模型更據可擴展性,利用FSM來表示每一個請求,比Thread-per-request模型用ThreadProcess)來表示一個請求更據可擴展性,也降低了系統中的ThreadProcess)數,降低了系統消耗的資源。

      如果設計的好,Event-driven concurrency模型可以支持很高的併發度,並且隨着併發度的增加,系統的整體性能的降低在一個可以接受的範圍內,如圖5所示。這種高性能得益於Event-driven concurrency模型降低了系統中過多的線程(進程)的使用,而降低了系統的資源消耗。

      如圖5所示,隨着請求數的增加,系統的吞吐量增加,每個請求的響應時間緩慢線性上升,直到達到飽和,這時受限到計算機的物理硬件,CPU,內存等,達到飽和後沒個請求的響應時間急劇上升,因爲系統中有太多的請求等待調度。當然隨着請求數的進一步上升,系統的吞吐量也會急劇下降,這時由於系統的物理內存消耗殆盡,引起頻繁的缺頁何內存交換,TLB失效等。

5 Event-driven server throughput: This benchmark measures an event-driven version

of the server from Figure 3. In this case, the server uses a single thread to process tasks, whereeach task reads 8 KB from a single disk file. Although the filesystem interface provided by the operating system used here (Linux 2.2.14) is blocking, because the disk data is always in the cache,this benchmark estimates the best possible performance from a nonblocking disk I/O layer. As thefigure shows, throughput remains constant as the load is increased to a very large number of tasks(note the change in the horizontal axis scale from Figure 3), and response time is linear (note thelog scale on the horizontal axis).



      Event-driven concurrency模型能很好的支持高併發,但是同時也對開發者提出了更高的要求,一個很重要的就是要求事件處理代碼短小精幹,儘可能的執行非阻塞操作,避免拖延或阻塞事件處理線程,來保證每一個請求的公平性。

      Event-driven concurrency模型的關鍵是設計一個高效公平的事件調度器,同時考慮高效和公平,這裏就有很多要考慮的,如請求的優先級,事件的優先級,事件處理的順序,資源的消耗等。考慮到請求的優先級,事件的優先級,事件的資源消耗都於具體的應用相關,所以Event-driven concurrency模型的通用性必然不理想。



四、SEDA併發模型

4.1 SEDA整體介紹

         SEDA將應用分通過事件隊列鏈接的網狀Stage,每個Stage由一個線程池,一個業務相關的事件處理器(Event Handler),一個事件輸入隊列和一個多個資源控制器組成。如圖6所示。

圖 6 Staged event-driven (SEDA) HTTP server: This is a structural representation of Haboob, the SEDA-based Web server, described in detail in Chapter 6. The application is composed as a set of stages separated by queues. Edges represent the flow of events between stages. Each stage can be independently managed, and stages can be run in sequence or in parallel, or a combination of the two. The use of event queues allows each stage to be individually load-conditioned, for example, by performing admission control on its event queue. For simplicity, some event paths and stages have been elided from this figure.



SEDA的幾點重要設計:


       Efficient, event-driven concurrency: SEDA併發模型依賴於Event-driven concurrency 模型來支持高併發。利用一組線程來處理每個請求,而不是每一個請求一個線程,利用 Nonblocking I/O來避免資源的阻塞。


       Dynamic thread pooling: 爲了避免設計事件調度和達到非阻塞操作的要求,SEDA利用一系列線程組(Thread pool每一個事件處理器利用一個動態的線程池。這樣就可以利用OS的線程調度來調度事件的處理,並且可以允許事件處理代碼阻塞一小段時間,因爲有多個線程(一個動態線程池)可以處理一個事件。


       Structured queues for code modularity and load management: 通過事件隊列將一個應用分割成一系列的Stage,可以讓應用開發者只關注具體的業務邏輯(事件處理器),然後通過隊列將各個State組裝成應用,也可以動態的添加何卸載Stage。這樣可以獨立開發每一個Stage

       應用也可以在每個Stage中控制每一個請求的執行,如執行路徑的改變,或終止請求等。每一個Stage可以更具自身的狀態來控制請求的執行,如某一個Stage的負載太高,可能會拒絕處理這個請求。當然也可以讓資源管理與控制在更細的粒度,沒一個Stage都可以有自己的資源管理宇控制。


Self-tuning resource management:



4.2 Stage

        StageSEDA中的核心基本單元,每一個Stage是一個自包含的應用組件,包含一個事件處理器(Event Handler),一個輸入事件隊列(incoming event queue),一個線程池(thread pool)和一個或多個資源控制器(Controller)。資源控制器負責資源消耗的管理,調度,線程分配和回收等。Stage的線程從事件輸入隊列批量取出事件,並調用事件處理器代碼,事件處理器處理每一個事件,並輸出0個或多個事件到其他的Stage的事件輸入隊列。如圖7所示。

圖 7 A SEDA Stage: A stage consists of an incoming event queue, a thread pool, and an application-supplied event handler. The stage’s operation is managed by a set of controllers, which dynamically adjust resource allocations and scheduling.



五、對比

六、總結

七、demo

        1.一個簡單的通用框架destiny

        2.一個簡單的基於通用框架destiny的服務程序luck

        3.客戶端luckclient的

文章來源:http://blog.chinaunix.net/uid-22939760-id-186334.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章