1. 程式人生 > >Nginx 引入執行緒池,提升 9 倍效能

Nginx 引入執行緒池,提升 9 倍效能

介紹

眾所周知,NGINX 採用非同步、事件驅動的方式處理連線。意味著無需對每個請求建立專門的程序或執行緒,它用一個工作程序(worker process)處理多個連線和請求。為了達到這個目的,NGINX採用非阻塞模式的 socket,並利用諸如 epoll 和 kqueue 的高效方法。

全量程序(full-weight process)數很少(通常是一個 CPU 核只有一個)而且恆定、記憶體開銷少、CPU 週期不會浪費在任務切換上。此方法的優勢因為NGINX而廣為人知。它能同時處理成千上萬請求,而且容易擴充套件。

Each process consumes additional memory, and each switch between them consumes CPU cycles and trashes L-caches

每個程序消耗額外的記憶體,程序之間的每次切換都會消耗 CPU 週期和丟棄 CPU 快取

不過非同步、事件驅動方式依然存在一個問題,或者可以說是敵人。其名字就是:阻塞。不幸的是,許多第三方模組採用阻塞方式呼叫,使用者(有時甚至這些模組的開發者)都沒有意識到這個缺陷。阻塞操作會毀掉 NGINX 效能,必須採取一切手段避免這樣的問題。

甚至在當前 NGINX 官方程式碼中,也無法在每個例子中避免阻塞操作,為了解決這個問題,NGINX 1.7.11 版實現了新的執行緒池機制。它是什麼,如何使用?我會在後面說明。我們先看看我們的敵人。

問題

首先,為了更好的理解問題,我們先簡單看看NGINX是如何工作的。

總體來說,NGINX 是一個事件處理器,一個從核心接收所有發生在連線上的事件資訊的控制器,然後給作業系統釋出命令。實際上,NGINX 通過編排作業系統做了全部的辛苦工作,作業系統則做了讀位元組和傳送位元組等日常工作。可見 NGINX 快速及時響應是如此重要。

NGINX-Event-Loop2

工作程序監聽、處理來自核心中的事件。

事件可能是某個超時,或者socket準備讀取或者寫入的通知,或者錯誤發生的通知。NGINX 接收一串事件,接著挨個處理,做一些必要的動作。這些處理都線上程佇列的簡單迴圈中完成。NGINX 從佇列中放出一個事件,接著做出反應,例如寫或者讀一個 socket。在許多案例中,這非常快(也許只需要很少的CPU 週期就可以將資料複製到記憶體中),並且 NGINX  會立即處理佇列中所有的事件。

Events Queue Processing Cycle

所有處理是在一個簡單的迴圈中由某個執行緒完成的

但是如果遇到某些又長又重操作,又會怎樣呢?整個事件處理週期可能會卡在那裡等待此操作結束。

我們說的“阻塞操作”是指會讓處理迴圈明顯停止一段時間的操作。阻塞的原因多種多樣。比如,NGINX忙於漫長的 CPU 密集型處理,或者不得不等待獲取某個資源,比如硬體驅動、某個互斥鎖、庫函式以同步方式呼叫資料庫響應。最關鍵的是處理諸如此類的操作,工作程序就沒有辦法做其他的事情,處理其他的事件,即使系統有更多可用資源可供佇列中某些事件使用。

試想商店裡售貨員,面前排著一個很長的佇列。排第一的顧客需要的貨物在倉庫,不是在店裡,售貨員去倉庫搬運貨物。為此整個佇列需要等待幾個小時,等待的人都會不高興的。你能想象人們的反應麼?佇列中每個人等待時間因為這幾個小時而增加,但他們想買的貨物或許就在商店裡。

Faraway Warehouse

佇列中的每一個人不得不等待第一個人的訂單

幾乎同樣的場景發生在 NGINX 中,需要讀取一個檔案,但它沒有快取在記憶體,不得不從硬碟中讀取。硬碟很慢(特別是旋轉的機械硬碟),然而佇列中其他等待的請求即使無需讀取硬碟,也被迫等待。結果增加了延遲,系統資源沒有被充分利用。

Blocking-Operation

僅僅一個阻塞操作就能長時間地延遲接下來所有的操作

某些作業系統(比如 FreeBSD)提供了一個讀檔案和傳送檔案的非同步介面,NGINX可以呼叫這個介面(見 aio 指令)。不幸的是,Linux 並非都如此。儘管 Linux系統也提供了讀取檔案的非同步介面,但它有兩個重大缺陷。其一是檔案讀取和快取時需要對齊,不過 NGINX 能處理地很好。第二個問題更糟,非同步介面需要在檔案描述符上作 O_DIRECT 標記,這樣任何獲取檔案的操作越過記憶體級的快取,增加了硬碟負載。在很多例子中,這真不是一個好的選擇。

為解決這個問題,NGINX 1.7.11 引入了執行緒池。NGINX Plus 預設狀態下沒有執行緒池,如果你想給 NGINX Plus R6 構建一個執行緒池,請聯絡銷售。

讓我們深入探究什麼是執行緒池、它是如何工作的。

執行緒池

讓我們回到剛才那個可憐的銷售助理,從很遠的倉庫取貨物。但是他變聰明瞭,也或許因為憤怒地顧客鄙視變得聰明瞭?購買了一套配送服務。現在有人想購買遠距離倉庫中的貨物,銷售助理無需前往,只需要將訂單轉給配送服務,後者會處理這個訂單,銷售助理可以繼續為其他顧客服務。由此只有貨物不再商鋪的顧客需要等待貨物提取,其他顧客能夠快速得到服務。

Your Order Next
把訂單轉給配送服務,這樣就不會阻塞隊列了

對 NGINX 而言,執行緒池就是充當配送服務的角色,它由一個任務佇列和一組處理佇列的執行緒組成。一旦工作程序需要處理某個可能的長操作,不用自己操作,將其作為一個任務放出執行緒池的佇列,接著會被某個空閒執行緒提取處理。

Thread Pool

工作程序把阻塞操作轉給執行緒池

像是擁有了一個新的佇列,不過本例中的佇列侷限於某個特定的資源。從硬碟中讀取資料的速度不會超過硬碟生成資料的速度。硬碟沒有延遲處理其他事件,僅僅需要獲取檔案的請求在等待。

硬碟讀取操作通常就是阻塞操作,不過NGINX中的執行緒池可以用來處理任何在主工作週期不適合處理的任務。

此刻分派給執行緒池的任務主要有兩個:許多作業系統上 read() 方法的系統呼叫,以及 Linux 系統的 sendfile()方法。我們會繼續測試(test)和基準測試(benchmark),未來發布的版本或許將其他的操作分派給執行緒池。

基準測試

I到了從理論到實踐的時候了,為了展示利用執行緒池的效果,我們打算設定一個合成基準模擬最糟糕的阻塞或者非阻塞操作。

資料集不能超出記憶體,在一個 48GB 記憶體機器上,生成 256GB 隨機資料,每個檔案大小 4MB ,接著配置 NGINX 1.9.0 為其提供服務。

配置及其簡單:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 worker_processes 16; events { accept_mutex off; } http { include mime.types; default_type application/octet-stream; access_log off; sendfile on; sendfile_max_chunk 512k; server { listen 8000; location / { root /storage; } } }

正如你所看到的,為了獲得更好的效能,我們做了一些調優:關閉了 loggin 和 accept_mutex,同時開啟了 sendfile(),設定 sendfile_max_chunk 大小為512K。最後面的指令可以減少阻塞方法 sendfile() 呼叫的所花費的最大時間,即 NGINX 每次無需傳送整個檔案,只發送 512KB 的塊資料。

計算機含有兩個英特爾至強 E5645 處理器(Intel Xeon E5645),以及 10Gbps 網路介面。硬碟子系統由四個西部資料 WD1003FBYX 硬碟按放在一個 RAID10 陣列中。所有硬體由Ubuntu Server 14.04.1 LTS進行管理。

Load Generators
為基準測試,配置 NGINX 和負載生成器

客戶端由兩個配置相同的計算機組成,其中一臺,wrk 通過用 Lua 指令碼建立負載。指令碼通過  200 個並行連線,隨機向伺服器請求檔案。每一個請求可能導致快取失效、產生一個硬碟阻塞讀操作。姑且稱這種負載叫隨機負載。

在第二臺客戶端計算機上,我們執行另一個 wrk 拷貝,用 50 個並行連線多次訪問同一個檔案。因為檔案高頻訪問,它會一直留在記憶體中。通常,NGINX可以非常快地處理這些請求,不過工作程序一旦阻塞被其他請求阻塞,效能就會下滑。姑且稱這種負載為恆定負載。

利用 ifstat 命令獲取第二臺客戶端的 wrk 結果,來監控伺服器吞吐量,並以此測定伺服器效能。

第一次沒有執行緒池參與的執行,並沒有帶給我們什麼驚喜的結果:

1 2 3

相關推薦

Nginx 引入執行提升 9 效能

介紹 眾所周知,NGINX 採用非同步、事件驅動的方式處理連線。意味著無需對每個請求建立專門的程序或執行緒,它用一個工作程序(worker process)處理多個連線和請求。為了達到這個目的,NGINX採用非阻塞模式的 socket,並利用諸如 epoll 和

NGINX引入執行 效能提升9

1. 引言 正如我們所知,NGINX採用了非同步、事件驅動的方法來處理連線。這種處理方式無需(像使用傳統架構的伺服器一樣)為每個請求建立額外的專用程序或者執行緒,而是在一個工作程序中處理多個連線和請求。為此,NGINX工作在非阻塞的socket模式下,並使用了epoll

執行批量執行多個任務(***實用總結***)

1 import java.util.Random;   public class Main {       public static void main(String[] args) {    

Python標準模組--concurrent.futures模組(ThreadPoolExecutor:執行提供非同步呼叫、ProcessPoolExecutor: 程序提供非同步呼叫)

目錄 ProcessPoolExecutor: 程序池 ThreadPoolExecutor:執行緒池  map的用法  回撥函式 https://docs.python.org/dev/library/concurrent.futures.html

【小家java】Java中的執行你真的用對了嗎?(教你用正確的姿勢使用執行

相關閱讀 【小家java】java5新特性(簡述十大新特性) 重要一躍 【小家java】java6新特性(簡述十大新特性) 雞肋升級 【小家java】java7新特性(簡述八大新特性) 不溫不火 【小家java】java8新特性(簡述十大新特性) 飽受讚譽 【小家java】java9

Java中執行你真的會用嗎

轉載自   Java中執行緒池,你真的會用嗎 在《深入原始碼分析Java執行緒池的實現原理》這篇文章中,我們介紹過了Java中執行緒池的常見用法以及基本原理。 在文中有這樣一段描述: 可以通過Executors靜態工廠構建執行緒池,但一般不建議這樣使用。 關於這個

#一篇文章讓你瞭解四種執行學習Java不在困惑

在Java開發中,有時遇到多執行緒的開發時,直接使用Thread操作,對程式的效能和維護上都是一個問題,使用Java提供的執行緒池來操作可以很好的解決問題,於是找了下API看到Java提供四種執行緒池使用,Java通過Executors提供四種執行緒池,分別為: 1、newCachedThrea

Junit單元測試+aop+spring+執行在進行Junit測試時切面中執行內呼叫的方法不執行

一、問題背景: 寫了一個切面,指向某service包下的所有類及方法,當該service包下方法被呼叫時切面執行,切面中用了執行緒池ExecutorService pool = Executors.newFixedThreadPool(5);執行緒池內呼叫了dao層的方法。 二、問題描述:單

socket+執行寫服務端和客戶端進行互動

以下內容轉自: https://www.cnblogs.com/gnoc/p/4866788.html 前言   socket(套接字),Socket和ServerSocket位於java.net包中,持續開啟服務端,接收來自客戶端的資訊,並響應。 最開始,咱們先來兩段最簡單的服

Java中執行你真的瞭解會用嗎

在《 深入原始碼分析Java執行緒池的實現原理 》這篇文章中,我們介紹過了Java中執行緒池的常見用法以及基本原理。 在文中有這樣一段描述: 可以通過Executors靜態工廠構建執行緒池,但一般不建議這樣使用。 關於這個問題,在那篇文章中並沒有深入的展開。作者之所以這

《SpringBoot從入門到放棄》之第(十三)篇——使用@Async非同步呼叫ThreadPoolTaskScheduler執行使用Future以及定義超時

建立 TaskPoolConfig 類,配置執行緒池: package com.test.util; import org.springframework.context.annotation.Bean; import org.springframework.cont

Java中執行你真的會用嗎?

  我騎著小毛驢,喝著大紅牛哇,哩個啷格里格朗,別問我為什麼這木開心,如果活著不是為了浪蕩那將毫無意義      今天來捋一捋我們平日經常用的instanceof和typeof的一些小問題      typeof:      typeof裡面是由一個小坑的  我們今天著重來研

jdk8新特性(Lambda表示式)結合spring 執行一行程式碼實現多執行

1.配置spring 執行緒池 @Configuration @EnableAsync @ConfigurationProperties(prefix="threadpool") public class ExecutePoolConfiguration { @V

JAVA執行--Executors之什麼是執行為什麼使用執行以及執行的使用

1. 為什麼需要執行緒池?      多執行緒技術主要解決處理器單元內多個執行緒執行的問題,它可以顯著減少處理器單元的閒置時間,增加處理器單元的吞吐能力。              假設一個伺服器完成一項任務所需時間為:T1 建立執行緒時間,T2 線上程中執行任務的時間,T

執行這一篇或許就夠了

*本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出 為什麼用執行緒池 建立/銷燬執行緒伴隨著系統開銷,過於頻繁的建立/銷燬執行緒,會很大程度上影響處理效率 例如: 記建立執行緒消耗時間T1,執行任務消耗時間T2,銷燬執行

不推薦使用Executors建立執行推薦通過ThreadPoolExecutor方式建立

執行緒池不允許使用Executors去建立,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確執行緒池的執行規則,規避資源耗盡的風險。 說明:Executors各個方法的弊端: 1)newFixedThreadPool和newSingleThreadExecut

JAVA執行ThreadPoolExecutor實現的四種執行

執行緒池在JAVA中,我們使用執行緒的時候就去建立一個執行緒,這樣實現起來非常簡便。但是就會有一個問題,如果併發的執行緒數量很多,並且每個執行緒都是執行一個時間很短的任務就結束了,這樣頻繁建立執行緒就會大大降低系統的效率,因為頻繁建立執行緒和銷燬執行緒需要時間。那麼有沒有一種

C++11實現的執行可以使用類成員函式來新增執行任務

功能:         1:可以使用類成員函式/全域性函式單獨的建立一個執行緒,可以帶多個引數。        2:可以使用類成員函式/全域性函式單獨的為執行緒池新增一個任務,可以帶多個引數。        3:執行緒池的執行緒數量可手動擴充套件,稍作修改可以修改為自動擴充,

執行處理高併發問題處理大資料量的方法

執行緒池個人認為,執行緒池的作用就是限制系統中執行執行緒的數量,避免伺服器超負荷;減少建立和銷燬執行緒的次數,從而減少了一些開銷。設計一個執行緒池單例,在內部建立指定數目的執行緒,並用一個執行緒空閒隊列表示可分配執行緒。注:還可以使用兩個靜態成員變數的方法限定最大執行緒數量。

守護執行執行執行ThreadLocal

守護執行緒   守護執行緒是一類特殊的執行緒,它和普通執行緒的區別在於它並不是應用程式的核心部分 ,當一個應用程式的所有非守護執行緒終止執行時,即使仍然有守護執行緒在執行,應用程式 也將終止,反之,只要有一個非守護執行緒在執行,應用程式就不會終止。守護執行緒一般被 用於在後