Apache 與 Nginx 性能對比:Web 服務器優化技術

譯文首發於 Apache 與 Nginx 性能對比:Web 服務器優化技術,轉載請註明出處。

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

Apache 第一版於 1995 年發佈,它在 維基百科 描述以下,「它在萬維網(WWW)發展初期發揮了相當重要的做用」。從 W3techs 統計結果來看,它依然是最經常使用的 Web 服務器軟件。不過,依據 過去十年的發展趨勢與其它服務器解決方案比較 的報告的結果來分析,不難發現它的市場份額正在逐年降低。儘管,NetcraftBuiltwith 這兩家提供的報告略有不一樣,但不得不認可 Apache 市場份額的縮減與 Nginx 服務器份額在增加這一事實。css

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

Nginx 一般被用做 反向代理負載均衡HTTP 緩存 服務器。CDN 和 視頻提供商使用它來構將性能強勁的內容分發系統(CDN: content delivery system)。python

Apache 在其不短的發展歷程中,提供了許多 有用的模塊。衆所周知管理 Apache 服務器對開發者極其友好。動態模塊加載 可以在無需從新編譯主服務器文件的基礎上,將模塊編譯並添加到 Apache 擴展中。一般,這些模塊位於 Linux 發行版倉庫中,在使用系統包管理器安裝後,即可以經過諸如 a2enmod 這樣的命令,將其添加到擴展中。Nginx 服務器到目前爲止,依然沒法靈活的實現動態添加模塊的功能。當咱們閱讀 如何在 Nginx 服務器設置 HTTP/2 指南 時,你就會發現模塊須要在構建 Nginx 時,經過設置參數選項,才能將其添加進 Nginx 服務器。linux

另外一個讓 Apache 保持住市場份額的功臣就是 .htaccess 重寫文件。它就像 Apache 服務器的萬金油同樣,使其成爲共享託管技術的首選方案,由於 .htaccess 重寫支持在目錄級別上控制服務器配置。在 Apache 服務器上的每一個目錄都可以配置本身的 .htaccess 文件。nginx

在這點上 Nginx 不只沒有相應的解決方案,並且因爲重寫性能低、命中率不高而 不被推薦git

Server vendors market share 1995–2005. Data by Netcraft

1995–2005 Web 服務器市場份額。 數據由 Netcraft 提供github

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

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

硬件考量(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

更多使用技巧:

  • 使用 mod_expire 控制瀏覽器緩存 - 經過設值 expires 響應頭。
  • 關閉 HostNameLookups 功能 - HostNameLookups 自 Apache 1.3 器默認關閉 off,因爲它會致使性能降低,因此直接關閉就好。
  • Apache2buddy 是一個簡單的腳本,咱們能夠運行並得到調整系統的提示:curl -sL https://raw.githubusercontent... | perl

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 指令對於緩存的配置細節能夠閱讀 這裏

proxy_cache_* 用於靜態資源緩存,不過一般咱們但願可以緩存動態內容 - 如 CMS 或其餘應用。此時,咱們可使用 fastcgi_cache_* 指令來代替 proxy_cache_*

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

相關文章
相關標籤/搜索