http://blog.chinaunix.net/uid-20761674-id-75056.htmlphp
一.C10K的問題
C10K的問題在上個世紀90年代就被提出來了。大概的意思是當用戶數超過1萬時,不少設計不良好的網絡服務程序性能都將急劇降低、甚至癱瘓。而且,這個問題並不能經過升級硬件設備解決,是操做系統固有的問題,也就是說,若是你的服務器最高能支撐1000個併發,儘管你升級了計算能力高一倍的 cpu,內存再翻一番,硬盤轉速在快一倍,也沒法支撐2000個併發。
經典的網絡編程模型有4個:
1. Serve one client with each thread/process, and use blocking I/O。即對每一個客戶都使用不一樣的線程或進程進行服務,在每一個線程或進程中使用阻塞I/O。這是小程序和java經常使用的策略,對於交互式的應用也是常見的選擇,這種策略很能難知足高性能程序的需求,好處是實現極其簡單,容易實現複雜的交互邏輯。咱們經常使用的Apache、ftpd等都是這種工做。
2. Serve many clients with single thread, and use nonblocking I/O and readiness notification。即對全部的客戶使用單一一個線程或進程進行服務,在這個線程或進程裏,採用異步IO的策略。這是經典模型,優勢在於實現較簡單,方便移植,也能提供足夠的性能;缺點在於沒法充分利用多CPU的資源。
3. Serve many clients with each thread, and use nonblocking I/O and readiness notification 對經典模型2的簡單改進,仍然採用異步IO的策略,但對全部的客戶使用多個線程或進程進行服務。缺點是容易在多線程併發上出bug,甚至某些OS不支持多線程進行readiness notification
4. Serve many clients with each thread, and use asynchronous I/O 在有AI/O支持的OS上,能提供至關高的性能。不過AI/O編程模型和經典模型差異至關大,基本上很難寫出一個框架同時支持AI/O和經典模型。這個模型主要是用於window平臺上。
在linux上開發高性能的網絡應用,只能選着第二、3種方式。考慮到複雜性,咱們每每只採用第2種。下面就討論一下第二種模型。
html
咱們知道,實現異步IO通常是採用select 或poll來實現。Select 定義以下:java
int select(int n, fd_set *rd_fds, fd_set *wr_fds, fd_set *ex_fds, struct timeval *timeout);
Poll 的接口以下:
int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
然而 Select 和Poll 在鏈接數增長時,性能急劇降低。這有兩方面的緣由:首先操做系統面對每次的select/poll 操做,都須要從新創建一個當前線程的關心事件列表,並把線程掛在這個複雜的等待隊列上,這是至關耗時的。其次,應用軟件在select/poll 返回後也須要對傳入的句柄列表作一次掃描來判斷哪些句柄是可用的,這也是很耗時的。這兩件事都是和併發數相關,而I/O 事件的密度也和併發數相關,致使CPU 佔用率和併發數近似成O(n2)的關係。
由於以上的緣由,Unix 上開發了性能更高的epoll, kqueue, /dev/poll 這3個程序接口來解決上述問題。其中epoll 是linux 的方案,kqueue 是freebsd 的方案,/dev/poll 是最古老的Solaris 的方案,使用難度依次遞增。
簡單的說,這些api 作了兩件事:
1. 避免了每次調用select/poll 時kernel 分析參數創建事件等待結構的開銷,kernel 維護一個長期的事件關注列表,應用程序經過句柄修改這個列表和捕獲I/O 事件。
2. 避免了select/poll 返回後,應用程序掃描整個句柄表的開銷,Kernel 直接返回具體的事件列表給應用程序。
二. libevent庫
因爲epoll, kqueue, /dev/poll每一個接口都有本身的特色,程序移植很是困難,因而須要對這些接口進行封裝,以讓它們易於使用和移植,其中libevent庫就是其中之一。
按照libevent的官方網站,libevent庫提供瞭如下功能:當一個文件描述符的特定事件(如可讀,可寫或出錯)發生了,或一個定時事件發生了,libevent就會自動執行用戶指定的回調函數,來處理事件。目前,libevent已支持如下接口/dev/poll, kqueue(2), event ports, select(2), poll(2) 和 epoll(4)。Libevent的內部事件機制徹底是基於所使用的接口的。所以libevent很是容易移植,也使它的擴展性很是容易。目前,libevent已在如下操做系統中編譯經過:Linux,BSD,Mac OS X,Solaris和Windows。
使用libevent庫進行開發很是簡單,也很容易在各類unix平臺上移植。一個簡單的使用libevent庫的程序以下:
python
三.libevent庫的應用
Go2代理是一個大流量的代理應用,月流量近TB。其中圖片、flash、zip文件佔總流量的絕大部分。爲了減小流量成本,須要將部分進行分流。開始時,使用了傳統的php代理來分流,但Go2併發訪問極大,多進程架構的php沒法承受,在虛擬主機vps上啓動數秒後就當即癱瘓。後改用 python的twisted網絡架構,採用了twisted的異步tcp通信功能。運行一段時間後,發現twisted的異步dns穩定性不太好,常常發生系統級的崩潰。最後,通過分析比較,決定採用libevent庫來作Go2 的分流代理應用。
Libevent庫支持異步socket,支持異步dns,並自己還帶了個簡單的http 服務器。Go2 的分流代理應用就是使用了libevent庫的以上三個功能。
一、簡單的http 服務器:實現的分類代理的用戶端的輸入,輸出管理。
二、異步socket,實現了高併發性的用戶接入,和高併發性的目的服務器訪問。
三、異步dns,解決了dns查詢時的併發性和高效性。
下面簡單介紹一下Go2 的分流代理程序的主要流程。源代碼請參考http://bbs.mycbc.cn/upload/bbs/viewthread.php?tid=601&extra=page%3D1
A. 主程序流程
linux
一、使用event_init()初始化libevent環境;
二、使用evhttp_new()生成http服務實例,並使用evhttp_bind_socket聆聽指定的端口;
三、使用evhttp_set_cb設置http服務實例的url回調函數。
四、進入libevent的事件循環。每當有用戶請求url,libevent將自動調用url所對應的回調函數。
B. 用戶請求處理流程:
編程
一、Libevent監控到用戶url訪問請求,libevent調用自定義的回調函數。
二、對用戶最終訪問的url進行base64解碼操做,獲取到用戶訪問的目的URL。
三、檢查目的URL是否合法,非法,則結束。
四、URL合法,檢查是否有本地緩存,有,直接從本地緩存獲取數據,返回,並結束。
五、沒有本地緩存,則經過libevent的異步dns查詢api 對目的域名進行解釋,並設置解釋完成後的回調函數。Api原型:evdns_resolve_ipv4(host, 0, http_dns_cb, req),其中host是域名, http_dns_cb是解釋完成後的回調函數。
六、在回調函數http_dns_cb中,完成如下操做:
a) 使用libevent異步socket鏈接到指定的服務器,並經過監控socket是否可寫來判斷鏈接是否成功。經過設置監聽socket寫事件來完成這個功能:event_set (&req->proxyev, socket, EV_WRITE, my_connect_cb, req ),其中參數EV_WRITE告訴libevent,只監控socket的可寫事件;my_connect_cb是事件發生後的回調函數。
b) 在my_connect_cb函數中,經過監控socket的可讀事件,來獲取服務器返回的數據:event_set(&req->proxyev, socket, EV_READ, my_read_cb, req ),其中參數EV_READ是告訴libevent只監控socket的可讀事件;my_read_cb是當可讀事件發生後的回調函數。
c) my_read_cb回調函數則是核心的數據處理函數。負責從服務器中獲取數據,並將數據返回給用戶。
七、將圖片緩存到本地,結束。
Go2 的分流代理在實際應用中,每分鐘能處理3000個請求,月流量近TB,而應用則是跑在一臺低端的vps上。
參考文檔:
一、C10K問題---epoll簡介,搜狗實驗室, http://www.sogou.com/labs/report/1-1.pdf
二、The C10K problem(英文),http://www.kegel.com/c10k.html
三、libevent 官方網站,http://www.monkey.org/~provos/libevent/小程序
http://www.cnblogs.com/fll/archive/2008/05/17/1201540.htmlapi