Web服務端效能提升方法
導讀 | 隨著網路的不斷發展,日常生活中越來越多的需求透過網路來實現,從衣食住行到金融教育,從口袋到身份,人們無時無刻不依賴網絡,而且越來越多的人透過網路來完成自己的需求。 |
作為直接面對來自客戶請求的Web服務端,無疑要同時承受更多的請求,並為使用者提供更好的體驗。這時候Web端的效能常常會成為業務發展的瓶頸,提升效能刻不容緩。本文作者在開發過程中總結了一些提升Web服務端效能的經驗,與大家分享。
問題分析對於Web服務端效能,首先我們分析一下相關指標。從使用者角度講,使用者呼叫Web服務時,請求返回時間越短,使用者體驗越好。從服務端角度講,同一時間能承載用戶請求量越大,服務端效能越強。綜合兩方面,我們總結性能優化的兩個方向:
1. 增加服務端所能支撐並發請求的最大數量;
2. 提高每個請求處理速度。
明確了最佳化方向,首先介紹一種服務端通常的架構模式,即來自瀏覽器或App的Web一個請求,在服務端經過哪幾層結構被處理並傳回的。
架構模式:IP負載平衡->快取伺服器->反向代理->應用伺服器->資料庫
如圖1所示,為了說明方便,我們來舉個實際的例子: LVS(Keepalived)->Squid->nginx->Go->MySQL
#圖1:服務端架構
我們對請求在每層做分發處理,這樣可以使下一級結構有多個分支同時工作,來提高總體的最大並發數。
結合架構,我們來分析通常有哪些問題在拖了效能的後腿,以及找出對應的解決方法。
正常情況下,IP負載平衡,快取伺服器和nginx代理這幾層主要是叢集穩定性問題。容易出現效能瓶頸的地方往往是應用程式伺服器層和資料庫層,我們下面來列舉幾個例子:
1. 阻塞的影響(1)問題:
大部分Web請求都是阻塞性質的,當一個請求被處理時,程序就會被掛起(佔用CPU)直到請求完成。在大多數情況下,Web請求完成的夠快,所以這個問題並不被關注。然而,對於那些回應時間來完成的請求(像是返回資料量大的請求或外部API),這意味著應用程式被鎖定直到處理結束,這期間,其他的請求不會被處理,很明顯,這些無效的等待時間浪費掉了,並且佔用系統資源,嚴重的影響了我們可以負擔的並發請求的數量。
(2)解決辦法:
Web服務端在等待上一個請求處理的過程中,我們可以讓I/O循環開啟以便處理其他應用請求,直到處理完成時啟動一個請求並給予回饋,而不再是等待請求完成的過程中掛起進程。這樣,我們可以節省一些沒有必要的等待時間,用這些時間去處理更多的請求,這樣我們就可以大大增加請求的吞吐量,也就是在宏觀上提高了我們可處理的並發請求數。
(3)例子
這裡我們用Python的一款Web框架Tornado來具體說明改變阻塞方式提升並發效能。
場景:我們建立一個向遠端(某個十分穩定的網站)發送HTTP請求的簡單Web應用程式。這段期間,網路傳輸穩定,我們不考慮網路來帶的影響。
在這個例子中,我們使用Siege(一款壓力測試軟體)對服務端在10秒內執行大約10個並發請求。
如圖2所示,我們可以很容易看出,這裡的問題是無論每個請求自身返回有多快,伺服器對遠端的存取請求往返都會產生足夠大的滯後,因為進程直到請求完成並且資料被處理前都一直處於強制掛起狀態。當一兩個請求時這還不是一個問題,但達到100個(甚至10個)用戶時,這意味著整體變慢。如圖,不到10秒時間10個相似用戶的平均回應時間達到了1.99秒,共29次。這個例子只展示了非常簡單的邏輯。如果你要加入其他業務邏輯或資料庫的呼叫的話,結果會更糟。當增加更多的使用者請求時,同時可被處理的請求就會成長緩慢,甚至有些請求會發生逾時或失敗。
#圖2:阻塞式回應
下面我們用Tornado執行非阻塞的HTTP請求。
如圖3所示,我們從每秒3.20個事務提升到了12.59,在相同的時間內總共提供了118次請求。這真是一個非常大的改善!正如你所想的,隨著用戶請求增多和測試時間增長時,它將能夠提供更多連接,並且不會遇到上面版本遭受的變慢的問題。從而穩定的提高了可負載的並發請求數。
Web服務端效能提升實務
#圖3:非阻塞式回應
2. 計算效率對回應時間和並發數的影響先來介紹一下基礎知識:一個應用程式是運行在機器上的一個進程;進程是一個運行在自己記憶體位址空間裡的獨立執行體。一個行程由一個或多個作業系統執行緒組成,這些執行緒其實是共享同一個記憶體位址空間的一起工作的執行體。
(1)問題
傳統運算方式單執行緒運行,效率低,運算能力弱。
(2)解決辦法
一種解決辦法就是完全避免使用執行緒。例如,可以使用多個行程將重擔交給作業系統來處理。但是,有個劣勢就是,我們必須處理所有進程間通信,通常這比共享記憶體的並發模型有更多的開銷。
另一種方法是用多線程工作,不過,公認的,使用多線程的應用難以做到準確,同步不同的線程,對資料加鎖,這樣同時就只有一個線程可以變更資料。不過過去的軟體開發經驗告訴我們這會帶來更高的複雜度,更容易讓程式碼出錯以及更低的效能。
其中最主要的問題是記憶體中的資料共享,它們會被多執行緒以無法預知的方式進行操作,導致一些無法重現或隨機的結果(稱作「競態」)。所以這個經典的方法明顯不再適合現代多核心/多處理器程式設計:thread-per-connection 模型不夠有效。在許多比較適合的範式中,有個被稱為Communicating Sequential Processes(順序通訊處理)(CSP, C. Hoare 發明的)還有一個叫做message-passing-model(訊息傳遞)(已經運用在了其他語言中,比如Erlang)。
我們這裡使用辦法是利用並行的架構來處理任務,一個並發程式可以在一個處理器或核心上使用多個執行緒來執行任務,但是只有同一個程式在某個時間點同時運行在多核心或者多處理器上才是真正的並行。
並行是一種透過使用多處理器以提高速度的能力。所以並發程序可以是並行的,也可以不是。
並行模式可以同時使用多執行緒、多核心、多處理器,甚至多計算機,這無疑可以調動更多資源,從而壓縮回應時間,提升運算效率,大大增強了服務端的效能。
(3)例子
這裡用Go語言中的Goroutine來具體說明。
在Go語言中,應用程式並發處理的部分被稱為 goroutines(協程),它可以進行更有效的並發運算。在協程和作業系統執行緒之間並無一對一的關係:協程是根據一個或多個執行緒的可用性,映射(多路復用,執行於)在他們之上的;協程調度器在Go 運行時很好的完成了這個工作。協程是輕量的,比線程更輕。它們痕跡非常不明顯(使用少量的記憶體和資源):使用 4K 的堆疊記憶體就可以在堆中創建它們。因為創建非常廉價,必要的時候可以輕鬆創建並運行大量的協程(在同一個地址空間中 100,000 個連續的協程)。並且它們對堆疊進行了分割,從而動態的增加(或縮減)記憶體的使用;堆疊的管理是自動的,但不是由垃圾回收器管理的,而是在協程退出後自動釋放。協程可以運行在多個作業系統執行緒之間,也可以運行在執行緒之內,讓你可以很小的記憶體佔用就可以處理大量的任務。由於作業系統執行緒上的協程時間片,你可以使用少量的作業系統執行緒就能擁有任意多個提供服務的協程,而且Go 運行時可以聰明地意識到哪些協程被阻塞了,暫時擱置它們並處理其他協程。甚至,程式可以在不同的處理器和電腦上同時執行不同的程式碼段。
我們通常想要將一個長計算過程切分成幾塊,然後讓每個goroutine各自負責一塊工作,這樣對於單一請求的回應時間有成倍的提升。
舉個例子,有一個任務分3個階段,a階段去資料庫a取數據,b階段去資料庫b取數據,c階段合併資料回傳。我們啟動goroutine以後a、b階段可以一起進行,大大縮短了反應時間。
說白了就是部分計算過程由串列轉換為並行,一個任務不需要等待其他無關的任務執行完在執行,實際計算中程式的並行執行會更有用處。
關於這部分佐證的數據就不在這邊過多敘述了,有興趣的同學可以自己看一下這方面的資料。例如Web服務端由Ruby切換為Go效能提升15倍的舊故事(Ruby使用的是綠色線程,也就是只有一個CPU被利用)。雖然這個故事可能有點誇大,但是並行帶來的效能提升是毫無疑問的。 (Ruby切換為Go:http://www.vaikan.com/how-we-went-from-30-servers-to-2-go/)。
3. 磁碟I/O對效能的影響#(1) 問題
磁碟讀取資料靠的是機械運動,每次讀取資料花費的時間可以分為尋道時間、旋轉延遲、傳輸時間三個部分,尋道時間指的是磁臂移動到指定磁軌所需要的時間,主流磁碟一般在5ms以下;旋轉延遲就是我們常聽說的磁碟轉速,例如一個磁碟7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms;傳輸時間指的是從磁碟讀出或將資料寫入磁碟的時間,一般在零點幾毫秒,相對於前兩個時間可以忽略不計。那麼訪問一次磁碟的時間,即一次磁碟I/O的時間約等於9ms(5ms 4.17ms)左右,聽起來還挺不錯的,但要知道一台500 -MIPS的機器每秒可以執行5億條指令,因為指令依靠的是電的性質,換句話說執行一次I/O的時間可以執行40萬條指令,數據庫動輒十萬百萬乃至千萬級數據,每次9毫秒的時間,顯然是個災難。
(2) 解決辦法
磁碟I/O對伺服器效能的影響沒有根本的解決辦法,除非你把磁碟丟掉,換成別的東西。我們能在網路上搜尋各種儲存媒體的反應速度與價格,如果你有錢,你就可以任性的更換儲存媒體。
在不更換儲存媒體的條件下,我們可以減少應用程式對磁碟的存取次數,例如設定緩存,還可以把部分磁碟I/O放到請求週期外,例如用佇列和堆疊來處理資料的I/O等。
4. 最佳化資料庫查詢隨著業務開發模式的變化,敏捷式開發被越來越多的團隊採用,週期越來越短,很多資料庫查詢語句都是按照業務邏輯來寫,時間久了常常就忽略了SQL查詢的格式問題,造成資料庫壓力的增加,使資料庫查詢的回應變慢。這裡簡單介紹MySQL資料庫中,幾個被我們忽略的常見問題與最佳化方式:
最左前綴匹配原則,非常重要的原則,MySQL會一直向右匹配直到遇到範圍查詢(>、 3 and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a ,b,d的順序可以任意調整。
盡量選擇區分度高的欄位作為索引,區分度的公式是count(distinct col)/count(*),表示欄位不重複的比例,比例越大我們掃描的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別欄位可能在大數據面前區分度就是0,那可能有人會問,這個比例有什麼經驗值嗎?使用場景不同,這個值也很難確定,一般需要join的欄位我們都要求是0.1以上,也就是平均1筆掃描10筆記錄。
盡量使用數字型字段,若只含數值資訊的字段盡量不要設計為字元型,這會降低查詢和連接的效能,並會增加儲存開銷。這是因為引擎在處理查詢和連接時會逐個比較字串中每一個字符,而對於數字型而言只需要比較一次就夠了。
索引列不能參與計算,保持列“乾淨”,例如from_unixtime(create_time) = '2014-05-29'就不能使用到索引,原因很簡單,b 樹中存的都是資料表中的欄位值,但進行檢索時,需要把所有元素都套用函數才能比較,顯然成本太大。所以語句應該寫成create_time = unix_timestamp(’2014-05-29’);應盡量避免在 where 子句中對欄位進行 null 值判斷,否則將導致引擎放棄使用。
索引而進行全表掃描,如:
select id from t where num is null
可以在num上設定預設值0,確保表中num列沒有null值,然後這樣查詢:
select id from t where num=0
應盡量避免在 where 子句中使用 or 來連結條件,否則將導致引擎放棄使用索引而進行全表掃描,如:
select id from t where num=10 or num=20
可以這樣查詢:
select id from t where num=10 union all select id from t where num=20
下面的查詢也會導致全表掃描(不能前置百分號):
select id from t where name like ‘�c%’
若要提高效率,可以考慮全文檢索。
in 和 not in 也要慎用,否則會導致全表掃描,如:
select id from t where num in(1,2,3)
對於連續的數值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
以上是Web服務端效能提升方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

Linux適用於服務器、開發環境和嵌入式系統。 1.作為服務器操作系統,Linux穩定高效,常用於部署高並發應用。 2.作為開發環境,Linux提供高效的命令行工具和包管理系統,提升開發效率。 3.在嵌入式系統中,Linux輕量且可定制,適合資源有限的環境。

在Linux上使用Docker可以提高開發和部署效率。 1.安裝Docker:使用腳本在Ubuntu上安裝Docker。 2.驗證安裝:運行sudodockerrunhello-world。 3.基本用法:創建Nginx容器dockerrun--namemy-nginx-p8080:80-dnginx。 4.高級用法:創建自定義鏡像,使用Dockerfile構建並運行。 5.優化與最佳實踐:使用多階段構建和DockerCompose,遵循編寫Dockerfile的最佳實踐。

當 Apache 80 端口被佔用時,解決方法如下:找出佔用該端口的進程並關閉它。檢查防火牆設置以確保 Apache 未被阻止。如果以上方法無效,請重新配置 Apache 使用不同的端口。重啟 Apache 服務。

啟動 Apache 的步驟如下:安裝 Apache(命令:sudo apt-get install apache2 或從官網下載)啟動 Apache(Linux:sudo systemctl start apache2;Windows:右鍵“Apache2.4”服務並選擇“啟動”)檢查是否已啟動(Linux:sudo systemctl status apache2;Windows:查看服務管理器中“Apache2.4”服務的狀態)啟用開機自動啟動(可選,Linux:sudo systemctl

啟動 Oracle 監聽器的步驟如下:檢查監聽器狀態(使用 lsnrctl status 命令)對於 Windows,在 Oracle Services Manager 中啟動 "TNS Listener" 服務對於 Linux 和 Unix,使用 lsnrctl start 命令啟動監聽器運行 lsnrctl status 命令驗證監聽器是否已啟動

本文介紹如何在Debian系統上有效監控Nginx服務器的SSL性能。我們將使用NginxExporter將Nginx狀態數據導出到Prometheus,再通過Grafana進行可視化展示。第一步:配置Nginx首先,我們需要在Nginx配置文件中啟用stub_status模塊來獲取Nginx的狀態信息。在你的Nginx配置文件(通常位於/etc/nginx/nginx.conf或其包含文件中)中添加以下代碼段:location/nginx_status{stub_status

本文介紹兩種在Debian系統中配置回收站的方法:圖形界面和命令行。方法一:使用Nautilus圖形界面打開文件管理器:在桌面或應用程序菜單中找到並啟動Nautilus文件管理器(通常名為“文件”)。找到回收站:在左側導航欄中尋找“回收站”文件夾。如果找不到,請嘗試點擊“其他位置”或“計算機”進行搜索。配置回收站屬性:右鍵點擊“回收站”,選擇“屬性”。在屬性窗口中,您可以調整以下設置:最大大小:限制回收站可用的磁盤空間。保留時間:設置文件在回收站中自動刪除前的保

在Debian系統中,readdir系統調用用於讀取目錄內容。如果其性能表現不佳,可嘗試以下優化策略:精簡目錄文件數量:盡可能將大型目錄拆分成多個小型目錄,降低每次readdir調用處理的項目數量。啟用目錄內容緩存:構建緩存機制,定期或在目錄內容變更時更新緩存,減少對readdir的頻繁調用。內存緩存(如Memcached或Redis)或本地緩存(如文件或數據庫)均可考慮。採用高效數據結構:如果自行實現目錄遍歷,選擇更高效的數據結構(例如哈希表而非線性搜索)存儲和訪問目錄信
