物聯網高併發編程之C10K問題原理和解決方案

C10K問題思維導圖

clipboard.png

C10K問題出現前期

大家都知道互聯網的基礎就是網絡通信,早期的互聯網可以說是一個小羣體的集合。

互聯網還不夠普及,用戶也不多。一臺服務器同時在線100個用戶估計在當時已經算是大型應用了。所以並不存在什麼C10K的難題。互聯網的爆發期應該是在www網站,瀏覽器,雅虎出現後。最早的互聯網稱之爲Web1.0,互聯網大部分的使用場景是下載一個Html頁面,用戶在瀏覽器中查看網頁上的信息。這個時期也不存在C10K問題。

Web2.0時代到來後就不同了,一方面是普及率大大提高了,用戶羣體幾何倍增長。另一方面是互聯網不再是單純的瀏覽萬維網網頁,逐漸開始進行交互,而且應用程序的邏輯也變的更復雜,從簡單的表單提交,到即時通信和在線實時互動。

C10K的問題才體現出來了。每一個用戶都必須與服務器保持TCP連接才能進行實時的數據交互。

Facebook這樣的網站同一時間的併發TCP連接可能會過億。

騰訊QQ也是有C10K問題的,只不過他們是用了UDP這種原始的包交換協議來實現的,繞開了這個難題。當然過程肯定是痛苦的。如果當時有epoll技術,他們肯定會用TCP。後來的手機QQ,微信都採用TCP協議。

C10K問題出現和本質

這時候問題就來了,最初的服務器都是基於進程/線程模型的,新到來一個TCP連接,就需要分配1個進程(或者線程)。

而進程又是操作系統最昂貴的資源,一臺機器無法創建很多進程。

如果是C10K就要創建1萬個進程,那麼操作系統是無法承受的。

如果是採用分佈式系統,維持1億用戶在線需要10萬臺服務器,成本巨大,也只有Facebook,Google,雅虎纔有財力購買如此多的服務器。這就是C10K問題的本質。

實際上當時也有異步模式,如:select/poll模型,這些技術都有一定的缺點,如selelct最大不能超過1024,poll沒有限制,但每次收到數據需要遍歷每一個連接查看哪個連接有數據請求。

C10K解決方案C10K解決方案

解決這一問題,主要思路有兩個:

  1. 一個是對於每個連接處理分配一個獨立的進程/線程;
  2. 另一個思路是用同一進程/線程來同時處理若干連接。

每個進程/線程處理一個連接

這一思路最爲直接。但是由於申請進程/線程會佔用相當可觀的系統資源,同時對於多進程/線程的管理會對系統造成壓力,因此這種方案不具備良好的可擴展性。

因此,這一思路在服務器資源還沒有富裕到足夠程度的時候,是不可行的;即便資源足夠富裕,效率也不夠高。

問題:資源佔用過多,可擴展性差

每個進程/線程同時處理多個連接(IO多路複用)

傳統思路

最簡單的方法是循環挨個處理各個連接,每個連接對應一個 socket,當所有 socket 都有數據的時候,這種方法是可行的。

但是當應用讀取某個 socket 的文件數據不 ready 的時候,整個應用會阻塞在這裏等待該文件句柄,即使別的文件句柄 ready,也無法往下處理。

思路:直接循環處理多個連接。

問題:任一文件句柄的不成功會阻塞住整個應用。

select

要解決上面阻塞的問題,思路很簡單,如果我在讀取文件句柄之前,先查下它的狀態,ready 了就進行處理,不 ready 就不進行處理,這不就解決了這個問題了嘛?

於是有了 select 方案。用一個 fd_set 結構體來告訴內核同時監控多個文件句柄,當其中有文件句柄的狀態發生指定變化(例如某句柄由不可用變爲可用)或超時,則調用返回。之後應用可以使用 FD_ISSET 來逐個查看是哪個文件句柄的狀態發生了變化。

這樣做,小規模的連接問題不大,但當連接數很多(文件句柄個數很多)的時候,逐個檢查狀態就很慢了。

因此,select 往往存在管理的句柄上限(FD_SETSIZE)。同時,在使用上,因爲只有一個字段記錄關注和發生事件,每次調用之前要重新初始化 fd_set 結構體。

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
思路:有連接請求抵達了再檢查處理。

問題:句柄上限+重複初始化+逐個排查所有文件句柄狀態效率不高。

poll

poll 主要解決 select 的前兩個問題:通過一個 pollfd 數組向內核傳遞需要關注的事件消除文件句柄上限,同時使用不同字段分別標註關注事件和發生事件,來避免重複初始化。

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
思路:設計新的數據結構提供使用效率。

問題:逐個排查所有文件句柄狀態效率不高。

epoll

既然逐個排查所有文件句柄狀態效率不高,很自然的,如果調用返回的時候只給應用提供發生了狀態變化(很可能是數據 ready)的文件句柄,進行排查的效率不就高多了麼。

epoll 採用了這種設計,適用於大規模的應用場景。

實驗表明,當文件句柄數目超過 10 之後,epoll 性能將優於 select 和 poll;當文件句柄數目達到 10K 的時候,epoll 已經超過 select 和 poll 兩個數量級。

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
思路:只返回狀態變化的文件句柄。

問題:依賴特定平臺(Linux)。

因爲Linux是互聯網企業中使用率最高的操作系統,Epoll就成爲C10K killer、高併發、高性能、異步非阻塞這些技術的代名詞了。

這些操作系統提供的功能就是爲了解決C10K問題:

  • FreeBSD推出了kqueue,
  • Linux推出了epoll
  • Windows推出了IOCP,
  • Solaris推出了/dev/poll。

這些操作系統提供的功能就是爲了解決C10K問題。

epoll技術的編程模型就是異步非阻塞回調,也可以叫做Reactor,事件驅動,事件輪循(EventLoop)。Nginx,libevent,node.js這些就是Epoll時代的產物。

select、poll、epoll具體原理詳解,

libevent

由於epoll, kqueue, IOCP每個接口都有自己的特點,程序移植非常困難,於是需要對這些接口進行封裝,以讓它們易於使用和移植,其中libevent庫就是其中之一。

跨平臺,封裝底層平臺的調用,提供統一的 API,但底層在不同平臺上自動選擇合適的調用。

按照libevent的官方網站,libevent庫提供了以下功能:

當一個文件描述符的特定事件(如可讀,可寫或出錯)發生了,或一個定時事件發生了,libevent就會自動執行用戶指定的回調函數,來處理事件。

目前,libevent已支持以下接口/dev/poll, kqueue, event ports, select, poll 和 epoll。

Libevent的內部事件機制完全是基於所使用的接口的。因此libevent非常容易移植,也使它的擴展性非常容易。

目前,libevent已在以下操作系統中編譯通過:Linux,BSD,Mac OS X,Solaris和Windows。

使用libevent庫進行開發非常簡單,也很容易在各種unix平臺上移植。

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