c10k問題
網路服務在處理數以萬計的客戶端連線時,往往出現效率底下甚至完全癱瘓,這被成為c10k問題。
(c10k = connection 10 kilo 問題)。k 表示 kilo,即 1000 比如:kilometer(千公尺), kilogram(千克)。
非阻塞i/o,最關鍵的部分是readiness notification(when ready, then notify!)和找出哪乙個socket上面發生了i/o事件。
一般我們首先會想到用select來實現。
int select(int n, fd_set *rd_fds; fd_set *wr_fds, fd_set *ex_fds, struct timeval * timeout);
其中用到了fd_set結構,而fd_set不能大於fd_setsize,預設是1024,很容易導致陣列越界。
針對fd_set的問題,*nix提供了poll函式作為select的乙個替代品:
int poll(struct pollfd *ufds, unsigned int nfds, int timeout)
第乙個引數ufds是使用者提供的乙個pollfd陣列,大小由使用者自行決定,因此避免了fd_setsize帶來的麻煩。
然而select和poll在連線數增加時,效能急劇下降。這有兩方面的原因:
《1》首先作業系統面對每次的select/poll操作,都需要重新建立乙個當前執行緒的關心事件鍊錶,並把執行緒掛在這個複雜的等待佇列上,這是相當耗時的。
《2》其次,應用軟體在select/poll返回後也需要對傳入的控制代碼鍊錶做一次迴圈掃瞄來dispatch,這也是很耗時的。這兩件事都是和併發數相關,而i/o事件的密度也和併發數相關,導致cpu佔用率和併發數近似成o(n2)的關係。
基於以上原因,*nix的hacker們開發了epoll, kqueue, /dev/poll這3套利器。epoll是linux的方案,kqueue是freebsd的方案,/dev/poll是solaris的方案。
簡單的說,這些api做了兩件事:
《1》避免了每次呼叫select/poll時kernel分析參素建立事件等結構的開銷,kernel維護乙個長期的時間關注列表,應用程式通過控制代碼修改這個鍊錶和捕獲i/p事件
《2》避免了select/poll返回後,應用程式掃瞄整個控制代碼表的開銷,kernel直接返回具體的鍊錶給應用程式。
在接觸具體api之前,先了解一下邊緣觸發(edge trigger)和條件觸發(level trigger)的概念。邊緣觸發是指每當狀態變化時發生乙個io事件,假定經過長時間的沉默後,現在來了100個位元組,這是無論邊緣觸發和條件觸發都會產生乙個read
ready notification通知應用程式可讀。應用程式在讀完來的50個位元組,然後重新呼叫api等待io事件。這時條件觸發的api會因為還有50個位元組可讀從而立即返回使用者乙個read ready notification。而邊緣觸發的api會因為可讀這個狀態沒有發生變化而陷入長期等待。
因此在使用邊緣觸發的api時,要注意每次都要讀到socket返回ewouldblock為止,
否則這個socket就算廢了。而使用條件觸發的api時,如果應用程式不需要寫就不要關注socket可寫的事件,否則會無限次的立即返回乙個
write ready nitification.
大家常用的select就是屬於條件觸發這一類,以前本人翻過長期關注socket寫事件從而cpu
100%的毛病。
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epoll_create 建立kernel中的關注事件表,相當於建立fd_set.
epoll_ctl 修改這個表,相當與fd_set等操作。
epoll_wait 完全是select/poll的公升級版,支援的事件完全一致。並且epoll同時支援邊緣觸發和條件觸發,一般來講邊緣觸發的效能要好一些。
簡單的例子:
[cpp] view plaincopystrut epoll_event ev, *events;
int kdpfd = epoll_create(100); //
建立kernel中的關注事件表,返回乙個kernel事件表的控制代碼
ev.events = epollin | epollet; //
邊緣觸發
ev.data.fd =listener;
epoll_ctl(kdpfd, epoll_ctl_add, listener, &ev); //
將事件ev加入到kernel關注的事件表中
for(;;)
setnonblocking(client);
ev.events = epollin |epollet;
ev.data.fd =client;
if(epoll_ctl(kdpfd, epoll_ctl_add, client, &ev) < 0
) }
else
do_use_fd(events[n].data.fd);
} }
C10K 問題原文
編寫連線數巨大的高負載伺服器程式時,經典的多執行緒模式和select模式都不再適用。應當拋棄它們,採用epoll,kqueue,dev poll來捕獲i o事件。最後簡要介紹了aio。網路服務在處理數以萬計的客戶端連線時,往往出現效率低下甚至完全癱瘓,這被稱為c10k問題。隨著網際網路的迅速發展,越...
C10K 問題原文
編寫連線數巨大的高負載伺服器程式時,經典的多執行緒模式和select模式都不再適用。應當拋棄它們,採用epoll,kqueue,dev poll來捕獲i o事件。最後簡要介紹了aio。網路服務在處理數以萬計的客戶端連線時,往往出現效率低下甚至完全癱瘓,這被稱為c10k問題。隨著網際網路的迅速發展,越...
Linux網路之 從 C10K 到 DPDK
c10k 和 c1000k 的首字母 c 是 client 的縮寫。c10k 就是單機同時處理 1 萬個請求 併發連線 1 萬 的問題,而 c1000k 也就是單機支援處理 100 萬個請求 併發連線 100 萬 的問題。i o 的模型,在 c10k 以前,linux 中網路處理都用同步阻塞的方式,...