1. 程式人生 > >關於如何提高Web服務端併發效率的非同步程式設計技術

關於如何提高Web服務端併發效率的非同步程式設計技術

  最近我研究技術的一個重點是java的多執行緒開發,在我早期學習java的時候,很多書上把java的多執行緒開發標榜為簡單易用,這個簡單易用是以C語言作為參照的,不過我也沒有使用過C語言開發過多執行緒,我只知道我學習java多執行緒開發是很難的,直到現在寫這篇文章的時候,雖然我對java多執行緒裡的API比以前熟悉更多了,但是如果碰到了生產開發裡如何將多執行緒設計更好,我心裡的底氣還是不足的,哎,缺乏很有意義的實踐,我現在要等待讓我實踐這部分技術的機會了。

  話外話,研究多執行緒是因為我在一本講併發程式設計的書籍裡看到書裡作者把能做好併發程式設計的工程師叫做併發工程師,這和我研究web前端技術時候看到前端工程師的感受類似,因此我想找機會也把自己訓練成為一名併發工程師。

  廢話少說,回到本文的主題,作為一名web工程師都希望自己做的web應用能被越來越多的人使用,如果我們所做的web應用隨著使用者的增多而宕機了,那麼越來越多的人就會變得越來越少了,為了讓我們的web應用能有更多人使用,我們就得提升web應用服務端的併發能力。那麼我們如何做到這點了,根據現有的併發技術我們會有如下選擇:

  第一個做法:為了每個客戶端傳送給服務端的請求都開啟一個執行緒,等請求處理完畢後該執行緒就被銷燬掉,這種做法很直觀,但是在現代的web伺服器裡這種做法已經很少使用了,原因是新建一個執行緒,銷燬一個執行緒的開銷(開銷是指佔用計算機系統資源例如:cpu、記憶體等)是很大的,它時常會大於實際處理請求本身的開銷,因此這種方式不能充分利用計算機資源,提升併發的效率是有效的,要是還碰到執行緒安全的問題,使用到執行緒的鎖機制,資料同步技術,併發提升就會受到更大的限制;除此之外,來一個請求就開啟一個執行緒,對執行緒數量沒有任何控制,這就會很容易導致計算機資源被用盡,對於web服務端的穩定性產生很大的威脅。

  第二個做法:鑑於上面的問題,我們就產生了第二種提高服務端併發量的方法,首先我們不再是一個客戶端請求過來就開啟一個新執行緒,請求處理完畢就銷燬執行緒,而是使用一種池技術即執行緒池技術,執行緒池技術就是事先建立一批執行緒,這批執行緒被放入到一個池子裡,在沒有請求到達服務端時候,這些執行緒都是處於待命狀態,當請求到達時候,程式會從執行緒池裡取出一個執行緒,這個執行緒處理到達的請求,請求處理完畢,該執行緒不會被銷燬,而是被執行緒池回收,這種方式使用執行緒我們降低了隨意建立執行緒和銷燬執行緒所導致系統開銷,同時也控制了服務端執行緒的數量,一般一個執行緒對應一個請求,也就控制了併發請求的個數,該方案比第一種方案提升了系統的穩定性(控制併發數量,防止併發過多導致服務程式宕機)同時也提升了併發的數量(原因是減少了建立執行緒和銷燬執行緒的開銷,更充分的利用了計算機的系統資源)。但是做法二也是有很大的問題的,具體如下:

  做法二和做法一相比,做法二要好多了,但是這只是和做法一比,如果按照我們設計的目標,做法二並非完美,原因如下:首先做法二會讓很多技術不紮實人認為執行緒池開啟多少執行緒就決定了系統併發的數量,因此出於讓系統能處理更多請求以及充分利用計算機資源的考慮,有些人會一開始就把執行緒池裡新建執行緒的個數設定為最大,一個web應用的併發量在一定時間裡都是一個曲線形式,峰值在一定時間範圍內都是少數情況,因此一開始就開啟最大執行緒數,自然在大多數時間內都是在浪費系統資源,如果這些被浪費被閒置的計算資源能用來處理請求,或許這些請求處理的效率會更高。此外,一個伺服器到底預先開啟多少個執行緒,這個標準很難把控,還有就是不管你用執行緒池技術還是新建執行緒的方式,處理請求的數量和執行緒數量數量是一一對應的關係,如果有一個時間點過來的請求數量正好超出了執行緒池裡執行緒數量,例如就多了一個,那麼這個請求因為找不到對應執行緒很有可能會被程式所遺棄掉,其實這多的一個請求並沒有超出計算機所能承受的負載,而是因為我們程式設計不合理才被遺棄的,這肯定是開發人員所不願意發生的事情,針對這些問題在java的JDK裡提供的執行緒池做了很好的解決(執行緒池技術是博大精深的,如果我們沒有研究透池技術,還是不要自己去寫個而是用現成的),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其實使用單執行緒也可以開發多執行緒任務,甚至這個單執行緒的處理效率可能比多執行緒更高,因為它沒有執行緒建立銷燬的開銷,也沒有執行緒上下文切換的開銷。其實實現一個非阻塞的請求是個大課題,裡面使用到了很多先進和複雜的技術例如:回撥函式和輪詢等,對於非阻塞的開發我目前掌握的還不夠好,等我有天完全掌握了它我一定會再寫一篇文章,不過這裡要提到的是像java裡netty技術,nginx,php的併發處理都用到這種機制的原理,特別是現在很火的nodejs它產生的原因就是依靠這種非阻塞的技術來編寫更高效的web伺服器,可以說nodejs把這種技術用到了極致,不過這裡要糾正下,非阻塞是針對IO操作的技術,對於nodejs,netty的實現機制有更好的術語描述就是事件驅動(其實就是使用回撥函式,觀察者模式實現的)以及非同步的IO技術(就是非阻塞的IO技術)。現在我們回到做法三的描述,做法三的核心思想就是讓每個執行緒資源利用率更加有效,做法三是建立在做法二的基礎上,使用事件驅動的開發思想,採用非阻塞的IO程式設計模式,當客戶端多個請求發到服務端,服務端可以只用一個執行緒對這些請求進行處理,利用IO操作的效能瓶頸,充分利用CPU的計算能力,這樣就達到一個執行緒處理多個請求的效率並不比多執行緒差,甚至還高,同時單執行緒處理能力的增強也會導致整個web服務併發效能的提升。大家可以想想,按這種方式在一個多核伺服器下,假如這個伺服器有8個核心,每個核心開啟一個執行緒,這8個執行緒也許就能承載數千併發量,同時也充分利用每個CPU計算能力,如果我們開啟執行緒越多(當然新增的執行緒數最好是8的倍數,這樣對多核利用率更好)那麼併發的效率也就更高,提升是按幾何倍數進行的,大家想想nginx,它就採用此模式,所以它剛推出來的時候其併發處理能力是apache伺服器的數倍,現在nginx已經和apache一樣普及了,事件驅動的非同步機制功不可沒。

  好了,文章寫畢,今天寫這篇文章算是對我最近研究多執行緒的一點總結,也是我最近轉向研究nodejs的開始,nodejs有完美的非同步程式設計模型,但是最近我確一直懷疑它的併發能力,因為我一直沒找到nodejs裡像java裡那麼複雜的非同步程式設計技術,現在我發現,nodejs用了一種更加巧妙的方式解決非同步開發的問題,而且這種方式是高效,就這一點nodejs太有魅力了,所以很值得研究和學習。

相關推薦

關於如何提高Web服務併發效率非同步程式設計技術

  最近我研究技術的一個重點是java的多執行緒開發,在我早期學習java的時候,很多書上把java的多執行緒開發標榜為簡單易用,這個簡單易用是以C語言作為參照的,不過我也沒有使用過C語言開發過多執行緒,我只知道我學習java多執行緒開發是很難的,直到現在寫這篇文章的時候,雖然我對java多執行緒裡的AP

搭建一個java web服務

des tin chm rate web項目 initial 安裝目錄 網上 mil   最近也是做了一個簡單的java web 項目,由於以前也是沒接觸過,在這裏記錄下搭建一個web服務端的過程。   一般我們做一個服務端要麽在本地自己的電腦上先安裝環境,一般是windo

WEB服務安全---註入攻擊

serve bst 創建 web .com pfile 足夠 高級技巧 codec 註入攻擊是web領域最為常見的攻擊方式,其本質是把用戶輸入的數據當做代碼執行,主要原因是違背了數據與代碼分離原則,其發生的兩個條件:用戶可以控制數據輸入;代碼拼接了用戶輸入的數據,把數據當做

web服務的架構演變

此文已由作者肖凡授權網易雲社群釋出。 歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。 最近Lofter專案碰到很多效能上的問題,特別是資料庫相關的,每次推送後,告警就會第一時間到來。這些問題隨著產品的不斷擴充套件,我想大家肯定都遇到過。目前我們解決效能問題一般都是兩種方法:一是加快取,減

wsgiref手寫一個web服務

'''通過wsgiref寫一個web服務端先講講wsgiref吧,基於網路通訊其根本就是基於socket,所以wsgiref同樣也是通過對socket進行封裝,避免寫過多的程式碼,將一系列的操作封裝成一個方法(函式),這樣大大減少程式碼量''' from wsgiref.simple_server i

HTTP協議(2)配置Web服務LAMP

在之前的課程中,我們都是通過Appserv或PHPStudy來搭建Web服務環境,在這裡介紹如何通過CentOS7.5來搭建一個真實的LAMP(Linux+Apache+ MySQL +PHP)環境。在部署LAMP時,軟體安裝的一般順序是Linux→Apache→PHP→MySQL。Apache的軟體名和所對

web服務安全---檔案上傳漏洞

1、簡述   檔案上傳漏洞是指使用者上傳了一個可執行的指令碼檔案,並通過此指令碼檔案獲得了執行服務端命令的能力。這種攻擊方式是最直接和有效的,而且網際網路中我們經常會用到檔案上傳功能,它本身是沒有問題的,正常的業務需求,可是檔案上傳後伺服器如果不能安全有效的處理或解釋檔案,往往會造成嚴重的後果。 常見的安

web服務安全---文件上傳漏洞

業務 舉例 不可 用戶 改變 一點 web容器 ash bsh 1、簡述   文件上傳漏洞是指用戶上傳了一個可執行的腳本文件,並通過此腳本文件獲得了執行服務端命令的能力。這種攻擊方式是最直接和有效的,而且互聯網中我們經常會用到文件上傳功能,它本身是沒有問題的,正常的業務需求

Dubbo原始碼分析:RPC協議實現-服務併發控制與Semaphore訊號量

概述 Dubbo支援在服務端通過在service或者method,通過executes引數設定每個方法,允許併發呼叫的最大執行緒數,即在任何時刻,只允許executes個執行緒同時呼叫該方法,超過的則拋異常返回,從而對提供者服務進行併發控制,保護資源。 用法 服務級別 限

關於birt報表在web服務啟動的問題

java.lang.NoSuchMethodError: org.eclipse.datatools.connectivity.oda.spec.QuerySpecification.getBaseQuery()Lorg/eclipse/datatools/co

java socket 服務併發處理 與 執行緒池的使用

package yiwangzhibujian.threadserver; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.

Web服務效能提升實踐

隨著網際網路的不斷髮展,日常生活中越來越多的需求通過網路來實現,從衣食住行到金融教育,從口袋到身份,人們無時無刻不依賴著網路,而且越來越多的人通過網路來完成自己的需求。 作為直接面對來自客戶請求的Web服務端,無疑要同時承受更多的請求,併為使用者提供更好的體驗。這個時候Web端的效能常常會成為業務發

python的併發非同步程式設計例項

關於併發、並行、同步阻塞、非同步非阻塞、執行緒、程序、協程等這些概念,單純通過文字恐怕很難有比較深刻的理解,本文就通過程式碼一步步實現這些併發和非同步程式設計,並進行比較。直譯器方面本文選擇python3,畢竟python3才是python的未來,並且pytho

Java 使用科大訊飛MSC SDK,在web服務將文字合成語音,將pcm檔案轉為wav格式返回

本文講述的是使用科大訊飛MSC SDK將語文字合成語音,然後以web介面的形式把合成的音訊資料返回前端。 流程 1、接收介面引數傳入的要合成的資料 2、使用MSC SDK把資料合成*.pcm檔案 3、獲取wav檔案格式頭 4、將格式頭與檔案內容拼接

web服務向客戶傳送提示資訊

加密整理資訊: 之前有一個網站需要向客戶傳送某些資訊。構建了一點思路。 1、可以使用ajax定時請求: 讓瀏覽器隔個幾秒就傳送一次請求,詢問伺服器是否有新資訊。 或者:long poll 其實原理跟 ajax輪詢 差不多,都是採用輪詢的方式,不過採取的是阻塞模型(一直打電話

web服務重定向

網站引導 路徑 創建服務 read http 狀態 login 購物 cat #服務器重定向常見某些網站引導登陸頁面(例如:淘寶點擊購物車會跳轉到登陸頁面)!   服務端的重定向功能主要由響應頭的302 狀態碼來實現   用nodejs,寫的服務端重定向 //1

談談C#多執行緒開發:並行、併發非同步程式設計

閱讀導航 一、使用Task 二、並行程式設計 三、執行緒同步 四、非同步程式設計模型 五、多執行緒資料安全 六、異常處理   概述 現代程式開發過程中不可避免會使用到多執行緒相關的技術,之所以要使用多執行緒,主要原因或目的大致有以下幾個: 1、 業務特性決定程式就是多工的,比如,一邊採集資料、一邊分

寫給產品經理的技術書:客戶服務和交互相關技術

gif manage 輸入法 fad 圖形 三位數 更換 網購 zoom 產品經理有三大領域的技術是需要去攻克的,分別是:客戶端相關技術、服務端相關技術、交互相關技術 一、客戶端相關技術 1.iOS和安卓產品差異 1.1 應用的設備不同: IOS和安卓最大的區別在於本身所應

【Unity】Unity中的非同步程式設計技術詳解

非同步程式設計技術對於很多手遊開發者來說,都是不可避免的話題,因為手遊的遊戲邏輯包含太多需要併發或者希望能夠並行的邏輯。現在的手機硬體發展迅速,多核已成為主導趨勢,對於3A級大作來說,如何充分利用手機多核的效能從而解放主執行緒壓力就顯得尤其重要。本文將由Unit

隱式轉換及併發控制高階程式設計技術實踐-JVM生態程式語言實戰

《JVM生態程式語言實戰》來源於被一位容器雲專家問道如何實現一個執行緒池時,讓我頓感以前研究的Java併發控制相關的理論以及多執行緒併發設計模式忘得九霄雲外,鑑於此,氣憤難平,決定提升程式設計技術。 版權宣告:本套技術專欄是作者(秦凱新)平時工作的總結和昇華,通過從真實商業環境抽取案例進行總結和分享,並給出