如何提升Web服務端併發效率的異步編程技術

做爲一名web工程師都但願本身作的web應用能被愈來愈多的人使用,若是咱們所作的web應用隨着用戶的增多而宕機了,那麼愈來愈多的人就會變得愈來愈少了,爲了讓咱們的web應用能有更多人使用,咱們就得提高web應用服務端的併發能力。那麼咱們如何作到這點了,根據現有的併發技術咱們會有以下選擇:php

 

第一個作法:爲每一個客戶端發送給服務端的請求都開啓一個線程,等請求處理完畢後該線程就被銷燬掉,這種作法很直觀,可是在現代的web服務器裏這種作法已經不多使用了,緣由是新建一個線程,銷燬一個線程的開銷(開銷是指佔用計算機系統資源例如:cpu、內存等)是很大的,它時常會大於實際處理請求自己的開銷,所以這種方式不能充分利用計算機資源,提高併發的效率是有效的,要是還碰到線程安全的問題,使用到線程的鎖機制,數據同步技術,併發提高就會受到更大的限制;除此以外,來一個請求就開啓一個線程,對線程數量沒有任何控制,這就會很容易致使計算機資源被用盡,對於web服務端的穩定性產生很大的威脅。java

 

第二個作法:鑑於上面的問題,咱們就產生了第二種提升服務端併發量的方法,首先咱們再也不是一個客戶端請求過來就開啓一個新線程,請求處理完畢就銷燬線程,而是使用線程池技術。node

線程池技術就是事先建立一批線程,這批線程被放入到一個池子裏,在沒有請求到達服務端時候,這些線程都是處於待命狀態,當請求到達時候,程序會從線程池裏取出一個線程,這個線程處理到達的請求,請求處理完畢,該線程不會被銷燬,而是被線程池回收,這種方式使用線程咱們下降了隨意建立線程和銷燬線程所致使系統開銷,同時也控制了服務端線程的數量,通常一個線程對應一個請求,也就控制了併發請求的個數,該方案比第一種方案提高了系統的穩定性(控制併發數量,防止併發過多致使服務程序宕機)同時也提高了併發的數量(緣由是減小了建立線程和銷燬線程的開銷,更充分的利用了計算機的系統資源)。nginx

可是作法二也是有很大的問題的,具體以下:web

作法二和作法一相比,作法二要好多了,可是這只是和作法一比,若是按照咱們設計的目標,作法二並不是完美,緣由以下:首先作法二會讓不少技術不紮實人認爲線程池開啓多少線程就決定了系統併發的數量,所以出於讓系統能處理更多請求以及充分利用計算機資源的考慮,有些人會一開始就把線程池裏新建線程的個數設置爲最大,一個web應用的併發量在必定時間裏都是一個曲線形式,峯值在必定時間範圍內都是少數狀況,所以一開始就開啓最大線程數,天然在大多數時間內都是在浪費系統資源,若是這些被浪費被閒置的計算資源能用來處理請求,或許這些請求處理的效率會更高。此外,一個服務器到底預先開啓多少個線程,這個標準很難把控,還有就是無論你用線程池技術仍是新建線程的方式,處理請求的數量和線程數量數量是一一對應的關係,若是有一個時間點過來的請求數量正好超出了線程池裏線程數量,例如就多了一個,那麼這個請求由於找不到對應線程頗有可能會被程序所遺棄掉,其實這多的一個請求並無超出計算機所能承受的負載,而是由於咱們程序設計不合理才被遺棄的,這確定是開發人員所不肯意發生的事情apache

針對這些問題在javaJDK裏提供的線程池作了很好的解決(線程池技術是博大精深的,若是咱們沒有研究透池技術,仍是不要本身去寫個而是用現成的),jdk裏的線程池對線程池大小的設定使用兩個參數,一個是核心線程個數,一個是最大線程個數,核心線程在系統啓動時候就會被建立,若是用戶請求沒有超過核心線程處理能力,那麼線程池不會再建立新線程,若是核心線程個數已經處理不過來了,線程池就會開啓新線程,新線程第一次建立後,使用完畢後也不是當即對其銷燬,也是被會收到線程池裏,當線程池裏的線程總數超過了最大線程個數,線程池將不會再建立新線程,這種作法讓線程數量根據實際請求的狀況進行調整,這樣既達到了充分利用計算機資源的目的,同時也避免了系統資源的浪費,jdk的線程池還有個超時時間,當超出核心線程的線程在必定時間內一直未被使用,那麼這些線程將會被銷燬,資源就會被釋放,這樣就讓線程池的線程的數量老是處在一個合理的範圍裏;若是請求實在太多了,線程池裏的線程暫時處理不過來了,jdk的線程池還提供一個隊列機制,讓這些請求排隊等待,當某個線程處理完畢,該線程又會從這個隊列裏取出一個請求進行處理,這樣就避免請求的丟失,jdk的線程池對隊列的管理有不少策略,有興趣的童鞋能夠問問度娘,這裏我還要說的是jdk線程池的安全策略作的很好,若是隊列的容量超出了計算機的處理能力,隊列會拋棄沒法處理的請求,這個也叫作線程池的拒絕策略。編程

看我這麼詳細的描述作法二,是否是作法二就是一個完美的方案了?答案固然是否認了,作法二並不是最高效的方案,作法二也沒有充分利用好計算機的系統資源。緩存

 

我這裏還有作法三,其具體作法以下:安全

首先我要提出一個問題,併發處理一個任務和單線程的處理一樣一個任務,那種方式的效率更高?也許有不少人會認爲固然是併發處理任務效率更高了,兩我的作一件事情總比一我的要厲害吧,這個問題的答案是要看場景的,在單核時代,單線程處理一個任務的效率每每會比並發方式效率更高。服務器

爲何呢?由於多線程在單核即單個cpu上運算,cpu並非也能夠併發處理的,cpu每次都只能處理一個計算任務,所以併發任務對於cpu而言就有線程的上下文切換操做,而這種線程上下文的開銷是比較大的,所以單核上處理併發請求不必定會比單線程更有效率,可是若是到了多核的計算機,併發任務平均分配給每個cpu,那麼併發處理的效率就會比單線程處理要高不少,由於此時能夠避免線程上下文的切換。

  對於一個網絡請求的處理,是由兩個不一樣類型的操做共同完成,這兩個操做是CPU的計算操做和IO操做,若是咱們以處理效率角度來評判這兩個操做,CPU操做效率是光速的,而IO操做就不盡然了,計算機裏的IO操做就是對存儲數據介質的操做,計算機裏有以下幾個介質能夠存儲數據,它們分別是:CPU的一級緩存、二級緩存、內存、硬盤和網絡,一級緩存存儲和讀取數據的能力接近光速,它比二級緩存快個5倍到6倍,可是不論是一級緩存仍是二級緩存,它們存儲數據量太少了,作不了什麼大事情,下面就是內存了,以一級緩存的效率作參照,一級緩存比內存速度快100多倍,到了硬盤存儲和讀取數據效率就更慢了,一級緩存比硬盤要快1000多萬倍,到了網絡就慢的更不像話了,一級緩存比網絡要快一億多倍,可見一個請求處理的效率瓶頸都是由IO引發的,而CPU雖然處理很快可是CPU對任務的計算都是一個接着一個處理,假如一個請求首先要等待網絡數據的處理在進行CPU運算,那麼必然就拖慢了CPU的處理的總體效率,這一慢就是上億倍了,可是現實中一個網絡請求處理就是由這兩個操做組合而成的。對於IO操做在java裏有兩種方式,一種方式叫作阻塞的IO,一種方式叫作非阻塞的IO,阻塞的IO就是在作IO操做時候,CPU要等待IO操做,這就形成了CPU計算資源的浪費,浪費的程度上文裏已經寫到了,是很可怕的,所以咱們就想當一個請求一個線程作IO操做時候,CPU不用等待它而是接着處理其餘的線程和請求,這種作法效率必然很高,這時候非阻塞IO就登場了,非阻塞IO能夠在線程進行IO操做時候讓CPU去處理別的線程,那麼非阻塞IO怎麼作到這一點的呢?非阻塞IO操做在請求和cpu計算之間添加了一箇中間層,請求先發到這個中間層,中間層獲取了請求後就直接通知請求發送者,請求接收到了,注意這個時候中間層啥都沒幹,只是接收了請求,真正的計算任務還沒開始哦,這個時候中間層若是要CPU處理那麼就讓cpu處理,若是計算過程到了要進行IO操做,中間層就告訴cpu不用等我了,中間層就讓請求作IO操做,CPU這時候能夠處理別的請求,等IO操做作完了,中間層再把任務交給CPU去處理,處理完成後,中間層將處理結果再發送給客戶端,這種方式就能夠充分利用CPU的計算機資源,有了非阻塞IO其實使用單線程也能夠開發多線程任務,甚至這個單線程的處理效率可能比多線程更高,由於它沒有線程建立銷燬的開銷,也沒有線程上下文切換的開銷。其實實現一個非阻塞的請求是個大課題,裏面使用到了不少先進和複雜的技術例如:回調函數和輪詢等,對於非阻塞的開發我目前掌握的還不夠好,等我有天徹底掌握了它我必定會再寫一篇文章,不過這裏要提到的是像javanetty技術,nginxphp的併發處理都用到這種機制的原理,特別是如今很火的nodejs它產生的緣由就是依靠這種非阻塞的技術來編寫更高效的web服務器,能夠說nodejs把這種技術用到了極致,不過這裏要糾正下,非阻塞是針對IO操做的技術,對於nodejsnetty的實現機制有更好的術語描述就是事件驅動(其實就是使用回調函數,觀察者模式實現的)以及異步的IO技術(就是非阻塞的IO技術)。如今咱們回到作法三的描述,作法三的核心思想就是讓每一個線程資源利用率更加有效,作法三是創建在作法二的基礎上,使用事件驅動的開發思想,採用非阻塞的IO編程模式,當客戶端多個請求發到服務端,服務端能夠只用一個線程對這些請求進行處理,利用IO操做的性能瓶頸,充分利用CPU的計算能力,這樣就達到一個線程處理多個請求的效率並不比多線程差,甚至還高,同時單線程處理能力的加強也會致使整個web服務併發性能的提高。你們能夠想一想,按這種方式在一個多核服務器下,假如這個服務器有8個內核,每一個內核開啓一個線程,這8個線程也許就能承載數千併發量,同時也充分利用每一個CPU計算能力,若是咱們開啓線程越多(固然新增的線程數最好是8的倍數,這樣對多核利用率更好)那麼併發的效率也就更高,提高是按幾何倍數進行的,你們想一想nginx,它就採用此模式,因此它剛推出來的時候其併發處理能力是apache服務器的數倍,如今nginx已經和apache同樣普及了,事件驅動的異步機制功不可沒。

相關文章
相關標籤/搜索