Apache 與 Nginx 效能對比:Web 伺服器最佳化技術

liuqing_hu發表於2018-08-20

譯文首發於 Apache 與 Nginx 效能對比:Web 伺服器最佳化技術,轉載請註明出處。

多年前 Apache 基金會 Web 伺服器 簡稱「Apache」,由於使用者眾多幾乎等同於「Web 伺服器」。httpd(含義是簡單的 http 程式)是它在 Linux 系統上的守護程式 - 同時它被預裝到主流的 Linux 發行版中。

Apache 初版於 1995 年釋出,它在 維基百科 描述如下,「它在全球資訊網(WWW)發展初期發揮了至關重要的作用」。從 W3techs 統計結果來看,它依然是最常用的 Web 伺服器軟體。不過,依據 過去十年的發展趨勢與其它伺服器解決方案比較 的報告的結果來分析,不難發現它的市場份額正在逐年下降。儘管,NetcraftBuiltwith 這兩家提供的報告略有不同,但不得不承認 Apache 市場份額的縮減與 Nginx 伺服器份額在增長這一事實。

Nginx 讀作「engine x」- 由 Igor Sysoev 在 2004 年釋出,最初的願景就是取代 Apache 在 Web 伺服器市場上的領導地位。在 Nginx 的網站上有一篇值得一讀的 文章,對兩款伺服器進行了比較。一開始 Nginx 只是作為 Apache 某些功能的補充,主要提供靜態檔案服務支援。得益於它積極的擴充套件在 Web 伺服器領域相關功能的全方位支援,這使得它能夠穩步增長。

Nginx 通常被用作 反向代理負載均衡HTTP 快取 伺服器。CDN 和 影片提供商使用它來構將效能強勁的內容分發系統(CDN: content delivery system)。

Apache 在其不短的發展歷程中,提供了許多 有用的模組。眾所周知管理 Apache 伺服器對開發者極其友好。動態模組載入 能夠在無需重新編譯主伺服器檔案的基礎上,將模組編譯並新增到 Apache 擴充套件中。通常,這些模組位於 Linux 發行版倉庫中,在使用系統包管理器安裝後,便可以透過諸如 a2enmod 這樣的命令,將其新增到擴充套件中。Nginx 伺服器到目前為止,依然無法靈活的實現動態新增模組的功能。當我們閱讀 如何在 Nginx 伺服器設定 HTTP/2 指南 時,你就會發現模組需要在構建 Nginx 時,透過設定引數選項,才能將其新增進 Nginx 伺服器。

另一個讓 Apache 保持住市場份額的功臣就是 .htaccess 重寫檔案。它就像 Apache 伺服器的萬金油一樣,使其成為共享託管技術的首選方案,因為 .htaccess 重寫支援在目錄級別上控制伺服器配置。在 Apache 伺服器上的每個目錄都能夠配置自己的 .htaccess 檔案。

在這點上 Nginx 不僅沒有相應的解決方案,而且由於重寫效能低、命中率不高而 不被推薦

Server vendors market share 1995–2005. Data by Netcraft

1995–2005 Web 伺服器市場份額。 資料由 Netcraft 提供

LiteSpeed 即 LSWS 是 Web 伺服器市場的另一個競爭者,它兼具 Apache 的靈活性與 Nginx 的效能。支援 Apache 風格的 .htaccessmode_securitymode_rewrite 模組,另外它還支援共享設定。它的設計初衷是替代 Apache 伺服器,並且能夠和 cPanel 和 Plesk 組合使用。從 2015 年開始提供 HTTP/2 支援。

LiteSpeed 有三個版本,OpenLiteSpeed、LSWS 保準版和 LSWS 企業版。標準版和企業版還提供了可選的 快取解決方案,它可以和 Varnish 與 LSCache 一較長短。LSCache 是伺服器內建的快取解決方案,透過 .htaccess 重寫規則配置進行控制。並且,它還提供了內建預防 DDoS 攻擊的解決方案。這個功能同它的事件驅動架構設計一起成為這款伺服器的競爭力保障,不僅能夠滿足以 效能為導向的服務提供商需求,還能兼顧小型伺服器或網站架設市場。

硬體考量(Hardware Considerations)

當我們最佳化系統時,我們無法忽視硬體配置。無論選擇哪種解決方案,我們都需要擁有足夠的 RAM,這點至關重要。當 Web 伺服器程式或類似 PHP 直譯器程式無可用的 RAM 時,它們就會進行交換(swapping)即需要使用硬碟來補充 RAM 記憶體的不足。這會導致每當訪問這塊記憶體區域時都會帶來訪問延遲。於是便引出了第二個最佳化點 - 硬碟。使用 SSD 固態硬碟來構建網站是提升效能的又一關鍵。此外,我們還應考慮 CPU 可用性和伺服器資料中心同目標使用者的距離。

想要深入研究硬體最佳化方法,可以檢視 Dropbox 的好文

監控(Monitoring)

htop 是一個監控當前伺服器效能及每個程式詳細資訊的實用工具,它能夠在 Linux、Unix 和 macOS 系統上執行,併為我們以不同顏色區分出不同的程式狀態。

htop

其它的監控工具如 New Relic,提供全套的監控解決方案;Netdata 一款開源的監控解決方案,兼具擴充套件性、細粒度指標和可定製的 Web 儀表盤,適用於小型的 VPS 系統和網路伺服器的監控。它可以透過郵件、Slack、pushbullet、Telegram 和 Twilio 等方式給任何應用或系統程式傳送警告訊息。

Netdata dashboard

Monit 是另一款開源的系統監控工具,可以透過配置在重啟程式、重啟系統或任何我們關心事件時給我們的傳送提示資訊。

系統測試(Testing the System)

AB - Apache Benchmark - 是一款有 Apache 基金會提供的簡單的壓測工具,其它壓測工具還有 Siege這篇文章 詳細講解了如何同時安裝這兩款工具,可以閱讀 這篇文章 學習 AB 工具的高階使用技巧,如果需要研究 Siege 可以閱讀 此文

如果你鍾愛 Web 應用,可以使用 Locust 這款基於 Python 的測試工具,一樣可以很方便的對網站進行效能測試。

install locust

在安裝完成 Locust 後,我們需要在專案的根目錄下建立一個 locusfile:

from locust import HttpLocust, TaskSet, task

class UserBehavior(TaskSet):
    @task(1)
    def index(self):
        self.client.get("/")

    @task(2)
    def shop(self):
        self.client.get("/?page_id=5")

    @task(3)
    def page(self):
        self.client.get("/?page_id=2")

class WebsiteUser(HttpLocust):
    task_set = UserBehavior
    min_wait = 300
    max_wait = 3000

然後使用如下命令啟動服務:

locust --host=https://my-website.com

使用壓測工具時需要注意:這些工具可能造成 DDoS 攻擊,所以需要在測試網站時進行限制。

Apache 最佳化技術(Tuning Apache)

Apache 的 mpm 模組

Apache 可以追溯到 1995 年和網際網路的早期階段,當時的伺服器將接收的 HTTP 請求傳入到 TCP 連線上並重新生成一個新程式並響應這個請求。當眾多的請求被接收,也就意味著需要建立處理它們的 worker 程式。由於建立新 worker 程式的系統開銷巨大,所以 Apache 伺服器的技術人員設計了 prefork 模式,並預先生成多個 worker 程式解決重新建立的問題。不過將每個程式嵌入到動態語言的直譯器(如 mod_php)中依然造成大量的資源消耗,這使得 Apache 伺服器經常會出現 伺服器崩潰 的問題。這是因為單個 worker 程式只能同時處理一個連線。

這個模組在 Apache 的 MPM 系統中稱為 mpm_prefork_module。從 Apache 官網可以瞭解到,這個模組僅需極少的 配置 即可完成工作,因為它能夠自動調整,其中最關鍵的是將 MaxRequestWorkers 指令值配置的足夠大,這樣可以處理更多的請求,但是還需要保證有每個 worker 程式有足夠的物理 RAM 可用。

mpm_prefork_module workers

上面的 Locust 壓測顯示 Apache 建立了大量的程式來處理請求。

不得不說,這個模組是 Apache 聲名狼藉的罪魁禍首,它可能導致資源利用率低下的問題。

在 Apache 第二版中引入了兩個新的 MPM 模組,試圖解決 prefork 模式所帶來的問題。即 worker 模組 或曰 mpm_worker_module 以及 event 模組

worker 模組不再基於程式模型,而是一種混合了程式-執行緒(process-thread)處理模式。下面引用自 Apache 官網:

單個程式(父程式)負責啟動子程式(worder 程式)。子程式負責建立由 ThreadsPerChild 指令設定的伺服器執行緒,同時還負責監聽接收到的請求,並將請求分發給處理執行緒。

這種模式能提升資源利用率。

在 2.4 版本 Apache 引入了 - event 模組,這個模組基於 worker 模組建立的,並加入了獨立的監聽執行緒來管理 HTTP 請求處理完成後的休眠的 keepalive 連線。它是一種非同步非阻塞模型,記憶體佔用小。可以從 這裡 瞭解這個版本的資訊。

我們在虛擬機器上安裝 WooCommerce 並基於 Apache 2.4 預設的 prefork 和 mod_php 配置傳送 1200 請求進行負載測試。

首先,我們在 https://tools.pingdom.com/ 網站對 libapache2-mod-php7 和 mpm_prefork_module 進行測試:

pingdom testing

然後,我們對 MPM 的 evnet 模組僅需測試。

這需要將 multiverse 加入到 /etc/apt/sources.list

deb http://archive.ubuntu.com/ubuntu xenial main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu xenial-updates main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu xenial-security main restricted universe multiverse
deb http://archive.canonical.com/ubuntu xenial partner

隨後,執行 sudo apt-get update 來安裝 libapache2-mod-fastcgi 和 php-fpm。

sudo apt-get install libapache2-mod-fastcgi php7.0-fpm

由於 php-fpm 獨立於 Apache 伺服器,所以需要重啟服務:

sudo service start php7.0-fpm

然後,關閉 prefork 模組,啟用 event 模式和 proxy_fcgi:

sudo a2dismod php7.0 mpm_prefork
sudo a2enmod mpm_event proxy_fcgi

將下面的程式碼加入到 Apache 虛擬機器:

<filesmatch "\.php$">
    SetHandler "proxy:fcgi://127.0.0.1:9000/"
</filesmatch>

埠號需要與 php-fpm 配置保持一致 /etc/php/7.0/fpm/pool.d/www.conf。可以從 這裡 瞭解 PHP-FPM 配置。

現在,我們調整 mpm_evnet 配置選項 /etc/apache2/mods-available/mpm_event.conf,記住我們的 mini-VPS 資源在測試上受限 - 所以需要減少一些預設值。有關指令的詳細資訊可以檢視 指令文件,關於 event 模組的可以閱讀 這個章節。記住,重啟服務會消耗大量的記憶體資源。MaxRequestWorkers 指令設定最大請求數限制:將 MaxConnectionsPerChild 設定為非零非常重要,它可以防止記憶體洩露。

<ifmodule mpm_event_module>
        StartServers              1
        MinSpareThreads          30
        MaxSpareThreads          75
        ThreadLimit              64
        ThreadsPerChild          30
        MaxRequestWorkers        80
        MaxConnectionsPerChild   80
</ifmodule>

使用 sudo service apache2 restart 重新啟動伺服器,如果我們修改瞭如 ThreadLimit 這類指令,我們還需要顯示的停止和啟動服務 sudo service apache2 stop; sudo service apache2 start

Pingdom 上的測試結果顯示頁面載入時間縮短了一半以上。

Apache 配置其它技巧

禁用 .htaccess.htaccess 允許在無需重啟服務時對根目錄下的每個目錄單獨進行配置。所以,伺服器接收請求後會遍歷所有目錄,查詢 .htaccess 檔案,這會導致效能下降。

以下引用自 Apache 官方文件:

通常,僅當你的主伺服器配置檔案沒有進行相應的訪問控制時才需要使用 .htaccess 檔案。... 一般,需要儘可能避免使用 .htaccess 檔案。當需要使用 .htaccess 檔案時,都可以在主伺服器配置的 directory 配置節點去執行配置

解決方案是到 /etc/apache2/apache2.conf 禁用重寫功能:

AllowOverride None

如果需要在特定目錄啟用重寫功能,可以到虛擬主機配置檔案中指定節點啟用:

AllowOverride All

更多使用技巧:

Apache2buddy tips

Nginx

Nginx 是一款 事件驅動(event-driven) 非阻塞模式的 Web 伺服器。下面摘自 Hacker News

與事件迴圈相比 fork 子程式消耗更多系統資源。基於事件的 HTTP 伺服器完勝。

這個言論引發了對 Hacker News 的吐槽,從我的經驗來看,從 Apache 的 mpm_prefork 切換到 Nginx 可以保證網站不當機。簡單的將 Web 伺服器切換到 Nginx 就可做到這點。

nginx-arch

可以從 這裡 獲取 Nginx 架構的全面分析。

配置 Nginx

Nginx 推薦將 worker 程式數量設定為 PC 的 核心數(類似 Apache 的 mpm_event 配置),將 /etc/nginx/nginx.conf 配置檔案中 worker_processes 指令設定為 auto (預設為 1)。

worker_connections 設定單個 worker 程式能夠處理的連線數。預設為 512,不過通常可以增加處理連線數量。

keepalive 連線數 一樣會影響伺服器效能,在基準測試中一般看不到這個 請求頭

keepalive connection in nginx

從 Nginx 網站了解到

HTTP keepalive 連線數是能夠有效減少延遲提升 web 頁面載入速度的最佳化效能手段。

建立新的 TCP 連線會 消耗資源 - 尤其是啟用安全的 HTTPS 加密協議。HTTP/2 協議透過 複用特性 可以減少資源消耗。複用已經建立好的連線能夠降低請求時間。

Apache 的 mpm_prefork 和 mpm_worker 對比 keepalive 事件迴圈在併發處理能力上存在不足。所以在 Apache 2.4 中引入 mpm_event 模組對此進行了修復,然而對於 Nginx 事件驅動是唯一預設處理模式。Nginx 的 worker 程式可以同時處理數千個連線,如果使用它作為反向代理或負載均衡器的話,Nginx 還可以使用本地 keepalive 連線池,而無需使用 TCP 連線所帶來的開銷。

keepalive_requests 指令用於設定單個客戶端能夠在一個 keepalive 連線上處理的請求數量。

keepalive_timeout 設定空閒 keepalive 連線保持開啟的時間。

keepalive 是關於 upstream(上游) 伺服器和 Nginx 連線有關的配置 - 當 Nginx 充當代理或負載均衡伺服器角色時。表示在空閒狀態 upstream 伺服器在單個 worker 程式中支援的 keepalive 連線數。

當使用 upstream keepalive 連線處理請求時,需要將如下指令新增到 nginx 主配置檔案中:

proxy_http_version 1.1;
proxy_set_header Connection "";

nginx upstream 連線由 ngx_http_upstream_module 模組管理。

如果我們的客戶端應用需要不斷輪詢服務端應用進行資料更新,可以透過 keepalive_requestskeepalive_timeout 增加連線數。同時 keepalive 指令值不應太大,這樣就能夠保證其他的 upstream 伺服器也能夠處理其它請求。

這些配置需要基於不同的應用的測試結果來進行單獨配置。這或許就是 keepalive 沒有預設值的原因。

使用 UNIX 套接字

預設情況下,nginx 使用單獨的 PHP 程式將 HTTP 請求轉發到 PHP 檔案。這種場景就是代理(類似 Apache 需要設定 php7.0-fpm)。

我們所用的 Nginx 虛擬主機配置如下:

location ~ \.php$ {
    fastcgi_param REQUEST_METHOD $request_method;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass 127.0.0.1:9000;
}

由於 FastCGI 與 HTTP 是不同的協議,前兩行配置是將一些引數和請求頭轉發到 php-fpm 程式管理器,最後一行設定了請求的代理方式 - 透過本地網路套接字完成。

這對於多伺服器它很實用,因為 nginx 可以對遠端伺服器進行代理轉發。

但是,如果我們將網站託管在一臺伺服器上時,我們就應該使用 UNIX 套接字來監聽 php 程式:

fastcgi_pass unix:/var/run/php7.0-fpm.sock;

UNIX 套接字相比 TCP 連線有更好的 效能,從安全形度來講這個設定也是更優的選擇。你可以從 Rackspace 站點的 這篇文章 掌握更多配置細節。

這個技巧同樣適用於 Apache 伺服器。可以到 這裡 進行學習。

gzip_static:在 web 伺服器優對靜態檔案進行壓縮處理是公認的行之有效的技術。這表示我們對大檔案做出讓步,會對哪些超過指定大小的檔案進行壓縮處理,因為這些檔案在請求時消耗更多的資源。Nginx 提供一個 gzip_static 指令,允許我們使用伺服器的 gzip 壓縮工具對檔案進行壓縮 - 壓縮後的副檔名為 .gz 而非不同檔案:

location /assets {
    gzip_static on;
}

這樣 Nginx 伺服器會長時間 style.css 壓縮成 style.css.gz 檔案(此時我們需要自己處理解壓)。

透過這種方式,在 CPU 週期內無需在每個請求時動態的對檔案進行壓縮處理。

啟用 Nginx 伺服器快取

如果不涉及講解如何進行快取配置,那麼對 Nginx 講解就是不是完整的。由於 Nginx 快取非常高效,以至於諸多系統管理員認為使用單獨的 HTTP 快取 都是多餘的(如 Varnish)。Nginx 快取配置也十分簡單。

proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g
  inactive=60m;

這些指令配置在 server 塊級指令中。proxy_cache_path 引數可以是任何快取儲存的路徑。 levels 設定 Nginx 可以快取什麼層級目錄。出於效能考量,兩層目錄通常就可以了。因為目錄遞迴處理非常消耗資源。keys_zone 引數用於識別共享記憶體的快取鍵名,10m 表示該鍵名能夠使用的記憶體大小(10 MB 通常就夠了;這不是實際快取內容的空間大小)。可選的 max_size 指令設定快取的內容上限 - 這裡是 10GB。如果未設定該值,則會佔用所有可用的儲存空間。inactive 指令設定資料未被命中時可被快取的有效期。

設定完成後,將快取鍵名新增到 serverlocation 指令塊就好了:

proxy_cache my_cache;

Nginx 容錯層能夠通知源伺服器或 upstream 伺服器在伺服器出錯或關閉時從快取中獲取命中的資料:

proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;

有關 serverlocation 指令對於快取的配置細節可以閱讀 這裡

proxycache 用於靜態資源快取,不過通常我們希望能夠快取動態內容 - 如 CMS 或其他應用。此時,我們可以使用 fastcgicache\ 指令來代替 proxycache*

 fastcgi_cache_path /var/run/nginx-cache levels=1:2 keys_zone=my_cache:10m inactive=60m;
 fastcgi_cache_key "$scheme$request_method$host$request_uri";
 fastcgi_cache_use_stale error timeout invalid_header http_500;
 fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
 add_header NGINX_FASTCGI_CACHE $upstream_cache_status;

上面的最後一行會設定響應頭,來告知我們內容是否從快取中獲取。

然後,在我們的 serverlocation 塊中,我們可以為快取設定一些無需快取的場景 - 例如,當請求 URL 中存在查詢字串時:

if ($query_string != "") {
    set $skip_cache 1;
}

另外,在 server 指令下的 \.php 塊指令裡,我們會新增如下內容:

    try_files $uri =404;
    include fastcgi_params;

    fastcgi_read_timeout 360s;
    fastcgi_buffer_size 128k;
    fastcgi_buffers 4 256k;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

    fastcgi_pass unix:/run/php/php7.0-fpm.sock;

    fastcgi_index index.php;
    fastcgi_cache_bypass $skip_cache;
    fastcgi_no_cache $skip_cache;
    fastcgi_cache my_cache;
    fastcgi_cache_valid  60m;

以上,fastcgi_cache*fastcgi_no_cache 就配置完可快取和不可快取的所有規則。

你可以從 Nginx 官網 文件 中獲取這些指令的指引。

要了解更多資訊,Nginx 提供了相關主題的 會議,還有好多免費的 電子書

總結

我們試圖介紹一些有助於我們改進 Web 伺服器效能的技術,以及這些技術背後的理論。但是這個主題才涉及皮毛:我們還沒有涵蓋 Apache 和 Nginx 或多伺服器有關如何設定反向代理的講解。使用這兩種伺服器實現最佳方式是依據測試和分析特定的案例來進行選擇。這是一個永無止境的話題。

Apache vs Nginx Performance: Optimization Techniques

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章