歡迎光臨
每天分享高質量文章

程式員修神之路–提高網站的吞吐量

點選上方藍色字型,關註我們

菜菜哥,有個事你還得幫我呀

呦西,YY妹子,最近天這麼熱了,你怎麼還穿這麼多?

苦笑一下…..前幾天寫了幾個介面,領導讓提高一下介面吞吐量

這是你技術提高的大好機會呀

可吞吐量是什麼呀?怎麼提高呢?

來,湊近一點,哥給你解釋一番

吞吐量定義

百科

吞吐量是指對網路、裝置、埠、虛電路或其他設施,單位時間內成功地傳送資料的數量(以位元、位元組、分組等測量)。

 

        以上的定義比較寬泛,定義到網站或者介面的吞吐量是這樣的:吞吐量是指系統在單位時間內處理請求的數量。這裡有一個註意點就是單位時間內,對於網站的吞吐量這個單位時間一般定義為1秒,也就是說網站在一秒之內能處理多少http(https/tcp)請求。與吞吐量對應的衡量網站效能的還有響應時間、併發數、QPS每秒查詢率。

 

響應時間是一個系統最重要的指標之一,它的數值大小直接反應了系統的快慢。響應時間是指執行一個請求從開始到最後收到響應資料所花費的總體時間。

 

併發數是指系統同時能處理的請求數量,這個也是反應了系統的負載能力。

每秒查詢率(QPS)是對一個特定的查詢伺服器在規定時間內所處理流量多少的衡量標準,在因特網上,作為域名系統伺服器的機器的效能經常用每秒查詢率來衡量。對應fetches/sec,即每秒的響應請求數,也即是最大吞吐能力。

        我們以高速收費站為例子也許更直觀一些,吞吐量就是一天之內透過的車輛數,響應時間就是車速,併發數就是高速上同時奔跑的汽車數。由此可見其實以上幾個指標是有內在聯絡的。比如:響應時間縮短,在一定程度上可以提高吞吐量。

其實以上幾個指標主要反映了兩個概念:

1. 系統在單位時間之內能做多少事情

2. 系統做一件事情需要的時間

 

提高吞吐量
以下場景都是在假設程式不發生異常的情況下
伺服器(行程)級別
        伺服器級別增加網站吞吐量也是諸多措施中最容易並且是效果最好的,如果一個網站能透過增加少量的伺服器來提高吞吐量,菜菜覺得是應該優先採用的。畢竟一臺伺服器的費用相比較一個程式員費用來說要低的多。但是有一個前提,就是你的伺服器是系統的瓶頸,網站系統之後的其他系統並非瓶頸。如果你的系統的瓶頸在DB或者其他服務,盲目的增加伺服器並不能解決你的問題。

透過增加伺服器來解決你的網站瓶頸,意味著你的網站需要做負載均衡,如果沒有運維相關人員,你可能還得需要研究負載均衡的方案,比如LVS,Nginx,F5等。我曾經面試過很多入道不久的同學,就提高吞吐量問題,如果沒有回答上用負載均衡方案的基本都pass了,不要說別的,這個方案就是一個基礎,就好比學習一個語言,你連最基本的語法都不會,我憑什麼讓你透過。有噴的同學可以留言哦

 

其實現在很多靜態檔案採用CDN,本質上也可以認為是增加伺服器的策略
 

執行緒級別
        當一個請求到達伺服器並且正確的被伺服器接收之後,最終執行這個請求的載體是一個執行緒。當一個執行緒被cpu載入執行其指令的時候,在同步的狀態下,當前執行緒會阻塞在那裡等待cpu結果,如果cpu執行的是比較慢的IO操作,執行緒會一直被阻塞閑置很長時間,這裡的很長是對比cpu的速度而言,如果你想有一個直觀的速度對比,可以去檢視菜菜以前的文章:

高併發下為什麼更喜歡行程內快取

        當一個新的請求到來的時候,如果沒有新的執行緒去領取這個任務並執行,要麼會發生異常,要麼建立新的執行緒。執行緒是一種很稀缺的資源,不可能無限制的建立。這種情況下我們就要把執行緒這種資源充分利用起來,不要讓執行緒停下來。這也是程式推薦採用非同步的原因,試想,一個執行緒不停的在工作,遇到比較慢的IO不會去等待結果,而是接著處理下一個請求,當IO的結果傳回來得到通知的時候,執行緒再去取IO結果,豈不是能在相同時間內處理更多的請求。

 

程式非同步化(非阻塞)會明顯提高系統的吞吐量,但是響應時間可能會稍微變大
 

        還有一點,儘量減少執行緒上線文在cpu的切換,因為執行緒上線文切換的成本也是比較大的,在執行緒切換的時候,cpu需要把當前執行緒的背景關係資訊記錄下來用以下次呼叫的時候使用,然後把新執行緒的背景關係資訊載入然後執行。這個過程相對於cpu的執行速度而言,要慢很多。

不要拿Golang反駁以上觀點,golang的協程雖然是使用者級別比執行緒更小的載體,但是最終和Cpu進行互動的還是執行緒。

 

 Cpu級別
        在講cpu級別之前,如果有一定的網路模型的基礎,也許會好一些。這裡大體闡述一下,現代作業系統都採用虛擬定址的方式,它的定址空間(虛擬儲存空間)為4G(2的32次方)。作業系統將虛擬空間分為兩類:核心空間和使用者空間。核心空間獨立於使用者空間,有訪問受保護的記憶體空間、IO裝置的許可權(所有的使用者空間共享)。使用者空間就是我們的應用程式執行的空間,其實使用者空間並沒有操作各種IO裝置的許可權,像我們平時讀取一個檔案,本質上是委託核心空間去執行讀取指令的,核心空間讀取到資料之後再把資料複製到程式執行的空間,最後應用程式再把資料傳回呼叫方。

 

        透過上圖大體可以看出,核心會為每個I/O裝置維護一個buffer(同一個檔案描述符讀和寫的buffer不同),應用程式發出一個IO操作的指令其實透過了核心空間和使用者空間兩個部分,並且發生了資料的複製操作。這個過程其實主要包含兩個步驟:

1. 使用者行程發出操作指令並等待資料

2. 核心把資料傳回給使用者行程(buffer的複製操作)

根據這兩個操作的不同表現,所以IO模型有了同步阻塞,同步非阻塞,非同步阻塞,非同步非阻塞的概念,但是這裡並非此文的重點,所以不在展開詳細介紹。

利用cpu提高系統吞吐量主要標的是提高單位時間內cpu執行的指令數,避免cpu做一些無用功:

cpu負責把buffer的資料copy到應用程式空間,應用程式再把資料傳回給呼叫方,假如這個過程發生的是一次Socket操作,應用程式在得到IO傳回資料之後,還需要網絡卡把資料傳回給client端,這個過程又需要把剛剛得到的buffer資料再次透過核心傳送至網絡卡,透過網路傳送出去。由此可見cpu把buffer資料copy到應用程式空間這個過程完全沒有必要,在核心空間完全可以把buffer資料直接傳輸至網絡卡,這也是零複製技術要解決的問題。具體的零複製技術在這裡不再展開。

 

不要讓任何裝置停下來,不要讓任何裝置做無用功
 

透過增加cpu的個數來增加吞吐量
網路傳輸級別
        至於網路傳輸級別,由於協議大部分是Tcp/ip,所以在協議傳輸方面最佳化的手段比較少,但是應用程式級別協議可以選擇壓縮率更好的,比如採用grpc會比單純的http協議要好很多,http2 要比http 1.1要好很多。另外一方面網絡卡儘量加大傳輸速率,比如千兆網絡卡要比百兆網絡卡速度更快。由於網路傳輸比較偏底層,所以人工幹預的切入點會少很多。

 

最後總結
        大部分程式員都是工作在應用層,針對應用級別程式碼能提高吞吐量的建議:

1
加大應用的行程數,增加併發數,特別在行程數是瓶頸的情況下

2
最佳化執行緒呼叫,儘量池化。

3
應用的程式碼非同步化,特別是非同步非阻塞式程式設計對於提高吞吐量效果特別明顯

4
充分利用多核cpu優勢,實現並行程式設計。

5
減少每個呼叫的響應時間,縮短呼叫鏈。例如透過加索引的方式來減少訪問一次資料庫的時間

 

寫在最後

希望大家有所收穫 –菜菜

恭喜 安淺   QI 兩位喜提上週抽獎Golang書籍。

    贊(0)

    分享創造快樂