如何理解高性能服務器的高性能、高併發?之二_風聞
蓝海大脑GPU服务器-水冷服务器、大数据一体机、图数据一体机01-12 18:59
高性能服務器到底是如何實現的?
當你在閲讀文章的時候,有沒有想過,服務器是怎麼把這篇文章發送給你的呢?説起來很簡單不就是一個用户請求嗎?服務器根據請求從數據庫中撈出這篇文章,然後通過網絡發回去嗎。其實有點複雜服務器端到底是如何並行處理成千上萬個用户請求的呢?這裏面又涉及到哪些技術呢?一、多進程歷史上最早出現也是最簡單的一種並行處理多個請求的方法就是利用多進程。比如在Linux世界中,可以使用fork、exec等系統調用創建多個進程,可以在父進程中接收用户的連接請求,然後創建子進程去處理用户請求。
1、多進程並行處理的優點
1)編程簡單,非常容易理解;
2)由於各個進程的地址空間是相互隔離的,因此一個進程崩潰後並不會影響其它進程;
3)充分利用多核資源。
2、多進程並行處理的缺點1)各個進程地址空間相互隔離,這一優點也會變成缺點,那就是進程間要想通信就會變得比較困難,你需要藉助進程間通信機制,想一想你現在知道哪些進程間通信機制,然後讓你用代碼實現呢?顯然,進程間通信編程相對複雜,而且性能也是一大問題;
2)創建進程開銷是比線程要大的,頻繁的創建銷燬進程無疑會加重系統負擔。
二、多線程由於線程共享進程地址空間,因此線程間通信天然不需要藉助任何通信機制,直接讀取內存就好了。線程創建銷燬的開銷也變小了,要知道線程就像寄居蟹一樣,房子(地址空間)都是進程的,自己只是一個租客,因此非常的輕量級,創建銷燬的開銷也非常小。我們可以為每個請求創建一個線程,即使一個線程因執行I/O操作——比如讀取數據庫等——被阻塞暫停運行也不會影響到其它線程。
由於線程共享進程地址空間,這在為線程間通信帶來便利的同時也帶來了無盡的麻煩。正是由於線程間共享地址空間,因此一個線程崩潰會導致整個進程崩潰退出,同時線程間通信簡直太簡單了,簡單到線程間通信只需要直接讀取內存就可以了,也簡單到出現問題也極其容易,死鎖、線程間的同步互斥、等等,這些極容易產生bug,無數程序員寶貴的時間就有相當一部分用來解決多線程帶來的無盡問題。
雖然線程也有缺點,但是相比多進程來説,線程更有優勢,但想單純的利用多線程就能解決高併發問題也是不切實際的。因為雖然線程創建開銷相比進程小,但依然也是有開銷的,對於動輒數萬數十萬的鏈接的高併發服務器來説,創建數萬個線程會有性能問題,這包括內存佔用、線程間切換,也就是調度的開銷。三、事件驅動:Event Loop到目前為止,提到“並行”二字就會想到進程、線程。但是並行編程只能依賴這兩項技術嗎?並不是這樣的!還有另一項並行技術廣泛應用在GUI編程以及服務器編程中,這就是近幾年非常流行的事件驅動編程:event-based concurrency。大家不要覺得這是一項很難懂的技術,實際上事件驅動編程原理上非常簡單。這一技術需要兩種原料:1)event;
2)處理event的函數,這一函數通常被稱為event handler;
由於對於網絡通信服務器來説,處理一個用户請求時大部分時間其實都用在了I/O操作上,像數據庫讀寫、文件讀寫、網絡讀寫等。當一個請求到來,簡單處理之後可能就需要查詢數據庫等I/O操作,我們知道I/O是非常慢的,當發起I/O後我們大可以不用等待該I/O操作完成就可以繼續處理接下來的用户請求。所以一個event loop可以同時處理多個請求。
四、事件來源:IO多路複用IO多路複用技術通過一次監控多個文件描述,當某個“文件”(實際可能是im網絡通信中socket)可讀或者可寫的時候我們就能同時處理多個文件描述符啦。這樣IO多路複用技術就成了event loop的原材料供應商,源源不斷的給我們提供各種event,這樣關於event來源的問題就解決了。
五、問題:阻塞式IO當我們進行IO操作,比如讀取文件時,如果文件沒有讀取完成,那麼我們的程序(線程)會被阻塞而暫停執行,這在多線程中不是問題,因為操作系統還可以調度其它線程。但是在單線程的event loop中是有問題的,原因就在於當我們在event loop中執行阻塞式IO操作時整個線程(event loop)會被暫停運行,這時操作系統將沒有其它線程可以調度,因為系統中只有一個event loop在處理用户請求,這樣當event loop線程被阻塞暫停運行時所有用户請求都沒有辦法被處理。你能想象當服務器在處理其它用户請求讀取數據庫導致你的請求被暫停嗎?
因此:在基於事件驅動編程時有一條注意事項,那就是不允許發起阻塞式IO。有的同學可能會問,如果不能發起阻塞式IO的話,那麼該怎樣進行IO操作呢?六、解決方法:非阻塞式IO為克服阻塞式IO所帶來的問題,現代操作系統開始提供一種新的發起IO請求的方法,這種方法就是異步IO。對應的,阻塞式IO就是同步IO,關於同步和異步詳見上文。異步IO時,假設調用aio_read函數(具體的異步IO API請參考具體的操作系統平台),也就是異步讀取,當我們調用該函數後可以立即返回,並繼續其它事情,雖然此時該文件可能還沒有被讀取,這樣就不會阻塞調用線程了。此外,操作系統還會提供其它方法供調用線程來檢測IO操作是否完成。七、基於事件驅動並行編程的難點雖然有異步IO來解決event loop可能被阻塞的問題,但是基於事件編程依然是困難的。
首先event loop是運行在一個線程中的,顯然一個線程是沒有辦法充分利用多核資源的,有的同學可能會説那就創建多個event loop實例不就可以了,這樣就有多個event loop線程了,但是這樣一來多線程問題又會出現。
其次在於編程方面,異步編程需要結合回調函數(這種編程方式需要把處理邏輯分為兩部分:一部分調用方自己處理,另一部分在回調函數中處理),這一編程方式的改變加重了程序員在理解上的負擔,基於事件編程的項目後期會很難擴展以及維護。八、更好的方法有沒有一種方法既能結合同步IO的簡單理解又不會因同步調用導致線程被阻塞呢?答案是肯定的,這就是用户態線程(user level thread),也就是大名鼎鼎的協程。
雖然基於事件編程有這樣那樣的缺點,但是在當今的高性能高併發服務器上基於事件編程方式依然非常流行,但已經不是純粹的基於單一線程的事件驅動了,而是 event loop + multi thread + user level thread。進程、線程、協程
一、什麼是進程?
1、基本常識
計算機的核心是CPU,它承擔了所有的計算任務;操作系統是計算機的管理者,它負責任務的調度、資源的分配和管理,統領整個計算機硬件;應用程序則是具有某種功能的程序,程序是運行於操作系統之上的。
進程是一個具有一定獨立功能的程序在一個數據集上的一次動態執行的過程,是操作系統進行資源分配和調度的一個獨立單位,是應用程序運行的載體。進程是一種抽象的概念,從來沒有統一的標準定義。進程一般由程序、數據集合和進程控制塊三部分組成:程序用於描述進程要完成的功能,是控制進程執行的指令集;
數據集合是程序在執行時所需要的數據和工作區;
程序控制塊(Program Control Block,簡稱PCB),包含進程的描述信息和控制信息,是進程存在的唯一標誌。
進程的特點:動態性:進程是程序的一次執行過程,是臨時的,有生命期的,是動態產生,動態消亡的;
併發性:任何進程都可以同其他進程一起併發執行;
獨立性:進程是系統進行資源分配和調度的一個獨立單位;
結構性:進程由程序、數據和進程控制塊三部分組成。
**2、為什麼要有多進程?**多進程目的是提高cpu的使用率。假設只有一個進程(先不談多線程),從操作系統的層面看,我們使用打印機的步驟有如下:1)使用CPU執行程序,去硬盤讀取需要打印的文件,然後CPU會長時間的等待,直到硬盤讀寫完成;
2)使用CPU執行程序,讓打印機打印這些內容,然後CPU會長時間的等待,等待打印結束。
在這樣的情況下:其實CPU的使用率其實非常的低。打印一個文件從頭到尾需要的時間可能是1分鐘,而cpu使用的時間總和可能加起來只有幾秒鐘。而後面如果單進程執行遊戲的程序的時候,CPU也同樣會有大量的空閒時間。**使用多進程後:**當CPU在等待硬盤讀寫文件,或者在等待打印機打印的時候,CPU可以去執行遊戲的程序,這樣CPU就能儘可能高的提高使用率。再具體一點説,其實也提高了效率。因為在等待打印機的時候,這時候顯卡也是閒置的,如果用多進程並行的話,遊戲進程完全可以並行使用顯卡,並且與打印機之間也不會互相影響。3、總結進程直觀點説是保存在硬盤上的程序運行以後,會在內存空間裏形成一個獨立的內存體,這個內存體有自己獨立的地址空間,有自己的堆,上級掛靠單位是操作系統。操作系統會進程為單位,分配系統資源(CPU時間片、內存等資源),進程是資源分配的最小單位。二、什麼是線程?****1、基本常識早期操作系統中並沒有線程的概念,進程是能擁有資源和獨立運行的最小單位,也是程序執行的最小單位。任務調度採用的是時間片輪轉的搶佔式調度方式,而進程是任務調度的最小單位,每個進程有各自獨立的一塊內存,使得各個進程之間內存地址相互隔離。後來隨着計算機的發展,對CPU的要求越來越高,進程之間的切換開銷較大,已經無法滿足越來越複雜的程序的要求了。於是就發明了線程。線程是程序執行中一個單一的順序控制流程:1)程序執行流的最小單元
2)處理器調度和分派的基本單位
一個進程可以有一個或多個線程,各個線程之間共享程序的內存空間(也就是所在進程的內存空間)。一個標準的線程由線程ID、當前指令指針(PC)、寄存器和堆棧組成。而進程由內存空間(代碼、數據、進程空間、打開的文件)和一個或多個線程組成。
如上圖所示,在任務管理器的進程一欄裏,有道詞典和有道雲筆記就是進程,而在進程下又有着多個執行不同任務的線程。2、任務調度線程是什麼?要理解這個概念,需要先了解一下操作系統的一些相關概念。大部分操作系統(如Windows、Linux)的任務調度是採用時間片輪轉的搶佔式調度方式。在一個進程中:當一個線程任務執行幾毫秒後,會由操作系統的內核(負責管理各個任務)進行調度,通過硬件的計數器中斷處理器,讓該線程強制暫停並將該線程的寄存器放入內存中,通過查看線程列表決定接下來執行哪一個線程,並從內存中恢復該線程的寄存器,最後恢復該線程的執行,從而去執行下一個任務。上述過程中任務執行的那一小段時間叫做時間片,任務正在執行時的狀態叫運行狀態,被暫停的線程任務狀態叫做就緒狀態,意為等待下一個屬於它的時間片的到來。這種方式保證了每個線程輪流執行,由於CPU的執行效率非常高,時間片非常短,在各個任務之間快速地切換,給人的感覺就是多個任務在“同時進行”,這也就是我們所説的併發(別覺得併發有多高深,它的實現很複雜,但它的概念很簡單,就是一句話:多個任務同時執行)。
3、進程與線程的區別****進程與線程的關係1)線程是程序執行的最小單位,而進程是操作系統分配資源的最小單位;
2)一個進程由一個或多個線程組成,線程是一個進程中代碼的不同執行路線;
3)進程之間相互獨立,但同一進程下的各個線程之間共享程序的內存空間(包括代碼段、數據集、堆等)及一些進程級的資源(如打開文件和信號),某進程內的線程在其它進程不可見;
4)線程上下文切換比進程上下文切換要快得多。
