nginx系列之一:nginx入門

前言

一、nginx 功能介紹

Nginx因爲它的穩定性、豐富的模塊庫、靈活的配置和低系統資源的消耗而聞名.業界一致認爲它是Apache2.2+mod_proxy_balancer的輕量級代替者,不僅是因爲響應靜態頁面的速度非常快,而且它的模塊數量達到Apache的近2/3。對proxy和rewrite模塊的支持很徹底,還支持mod_fcgi、ssl、vhosts ,適合用來做mongrel clusters的前端HTTP響應。
nginx和Apache一樣使用模塊化設計,nginx模塊包括內置模塊和第三方模塊,其中內置模塊中包含主模塊和事件模塊。

nginx處理請求邏輯圖
在這裏插入圖片描述

二、nginx可以提供的服務

  1. web 服務.
  2. 負載均衡 (反向代理)
  3. web cache(web 緩存)

三、nginx 的優點

  1. 高併發。靜態小文件
  2. 佔用資源少。2萬併發、10個線程,內存消耗幾百M。
  3. 功能種類比較多。web,cache,proxy。每一個功能都不是特別強。
  4. 支持epoll模型,使得nginx可以支持高併發。
  5. nginx 配合動態服務和Apache有區別。(FASTCGI 接口)
  6. 利用nginx可以對IP限速,可以限制連接數。
  7. 配置簡單,更靈活。

四、nginx應用場合

  1. 靜態服務器。(圖片,視頻服務)另一個lighttpd。併發幾萬,html,js,css,flv,jpg,gif等。
  2. 動態服務,nginx——fastcgi 的方式運行PHP,jsp。(PHP併發在500-1500,MySQL 併發在300-1500)。
  3. 反向代理,負載均衡。日pv2000W以下,都可以直接用nginx做代理。
  4. 緩存服務。類似 SQUID,VARNISH。

五、主流web服務產品對比說明

5.1 Apache-特性

  1. 2.2版本本身穩定強大,據官方說:其2.4版本性能更強。
  2. prefork模式取消了進程創建開銷,性能很高。
  3. 處理動態業務數據時,因關聯到後端的引擎和數據庫,瓶頸不在與Apache本身。
  4. 高併發時消耗系統資源相對多一些。
  5. 基於傳統的select模型。
  6. 擴展庫,DSO方法。

5.2 nginx-特性

  1. 基於異步IO模型,(epoll,kqueue),性能強,能夠支持上萬併發。
  2. 對小文件支持很好,性能很高(限靜態小文件1M)。
  3. 代碼優美,擴展庫必須編譯進主程序。
  4. 消耗代碼資源比較低。
  5. lighttpd(百度貼吧,豆瓣)
  6. 基於異步IO模式,性能和nginx相近。
  7. 擴展庫是SO模式,比nginx要靈活。
    8.通過差距(mod_secdownload)可實現文件URL地址加密。

5.3 web服務產品性能對比測試

5.3.1 靜態數據性能對比

  1. 處理靜態文件Apache性能比nginx和lighttpd要差。
  2. nginx在處理小文件優勢明顯。
  3. 處理靜態小文件(小於1M),nginx和lighttpd比Apache更有優勢,lighttpd最強。

5.3.2 動態數據性能對比

  1. 處理動態內容三者相差不大,主要取決於PHP和數據庫的壓力。
  2. 當處理動態數據時,三者差距不大,從測試結果看,Apache更有優勢一點。這是因爲處理動態數據能力取決於PHP和後端數據的提供服務能力。也就是說瓶頸不在web服務器上。
  3. 一般PHP引擎支持的併發參考值300-1000,JAVA引擎併發300-1000,數據庫的併發300-1000.

5.3.3 爲什麼nginx的總體性能比Apache高。

  1. nginx使用最新的epoll和kqueue網絡IO模型,而Apache使用牀頭的select模式。
  2. 目前Linux下能夠承受高併發訪問的squid、Memcached 都採用的是epoll網絡IO模型。

5.3.4 如何選擇WEB服務器:

靜態業務:高併發、採用nginx,lighttpd,根據自己的掌握程度或公司的要求。
動態業務:採用nginx和Apache均可。
既有靜態業務又有動態業務:nginx或Apache,不要多選要單選。
動態業務可以由前端代理(haproxy),根據頁面元素的類型,向後轉發相應的服務器進行處理。
思想:我們工作都不要追求一步到位,滿足需求的前提下,先用,然後逐步完善。
提示:nginx做web(Apache,lighttpd)、反向代理(haproxy,lvs,nat)及緩存服務器(squid)也是不錯的。
最終建議:對外的業務nginx,對內的業務Apache(yum httpd mysql-server php)。

六、nginx實戰過程

6.1 安裝依賴包

  • nginx安裝依賴GCC、openssl-devel、pcre-devel和zlib-devel軟件庫。
  • Pcre全稱(Perl Compatible Regular Expressions),中文perl兼容正則表達式,pcre官方站點
yum install  pcre pcre-devel -y 
yum install openssl openssl-devel -y 

6.2 開始編譯

使用./configure --help查看各個模塊的使用情況,使用--without-http_ssi_module的方式關閉不需要的模塊。可以使用--with-http_perl_modules方式安裝需要的模塊。

6.2.1 編譯命令

tar -zxf nginx-1.10.1.tar.gz 
cd nginx-1.10.1/
./configure --prefix=/data/nginx-1.10.1 --user=nginx --group=nginx  --with-http_ssl_module  --with-http_stub_status_module

useradd nginx -M -s /sbin/nologin 
make && make install 
ln -s /data/nginx-1.10.1 /data/nginx

6.2.2 測試nginx配置文件是否正常

/data/nginx/sbin/nginx -t 
nginx: the configuration file /data/nginx-1.10.1/conf/nginx.conf syntax is ok
nginx: configuration file /data/nginx-1.10.1/conf/nginx.conf test is successful

6.2.3 啓動nginx服務器

/data/nginx/sbin/nginx  -t  ##檢查配置文件
/data/nginx/sbin/nginx      ##確定nginx服務
netstat -lntup |grep nginx      ## 檢查進程是否正常
curl http://localhost           ## 確認結果

6.2.4 nginx其他命令

nginx -s signal
signal:
stop — fast shutdown
quit — graceful shutdown
reload — reloading the configuration file
reopen — reopening the log files
用來打開日誌文件,這樣nginx會把新日誌信息寫入這個新的文件中

/data/nginx/sbin/nginx -V 查看已經編譯的參數。

使用kill命令操作nginx。格式:kill -信號 PID

信號名稱

  • TERM,INT 快速關閉
  • QUIT 優雅的關閉,保持吸納有的客戶端連接
  • HUP 重啓應用新的配置文件
  • USR1 重新打開日誌文件
  • USR2 升級程序
  • WINCH 優雅的關閉工作進程

例子

kill -QUIT  `cat /data/nginx/nginx.pid`
kill -HUP `cat /data/nginx/nginx.pid`

七、nginx配置文件

配置基礎配置文件

worker_processes  1;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}
### 測試配置文件是否正常
shell> /data/nginx/sbin/nginx -t 
nginx: the configuration file /data/nginx-1.10.3/conf/nginx.conf syntax is ok
nginx: configuration file /data/nginx-1.10.3/conf/nginx.conf test is successful
shell> curl -I http://localhost
HTTP/1.1 200 OK

八、nginx監控

開啓nginx的監控服務

8.1 開啓狀態頁

#設定查看Nginx狀態的地址   
location /status {  
  stub_status on;   	#表示開啓stubStatus的工作狀態統計功能。
  access_log off;   	#access_log off; 關閉access_log 日誌記錄功能。
  #auth_basic "status";   							#auth_basic 是nginx的一種認證機制。
  #auth_basic_user_file conf/htpasswd;	#用來指定密碼文件的位置。
}

8.2 配置登錄密碼

yum install -y httpd-tools
/usr/local/apache/bin/htpasswd -c /data/nginx/conf/htpasswd biglittleant 
New password:

完成後會在/data/nginx/conf/目錄下生成htpasswd文件。

8.3 訪問URL

curl http://127.0.0.1/status

Active connections:  1
server accepts handled requests
 16 16 18
Reading: 0 Writing: 1 Waiting: 0

#active connections – 活躍的連接數量
#server accepts handled requests — 總共處理了16個連接 , 成功創建16次握手, 總共處理了18個請求
#Reading — 讀取客戶端的連接數: Writing 響應數據到客戶端的數量; Waiting 開啓 keep-alive 的情況下,這個值等於 active – (reading+writing), 意思就是 Nginx 已經處理完正在等候下一次請求指令的駐留連接.

8.3 編寫zabbix監控腳本

nginx_status_fun(){
    NGINX_PORT=$1
    NGINX_COMMAND=$2
    nginx_active(){
        /usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/status/" 2>/dev/null| grep 'Active' | awk '{print $NF}'
        }
    nginx_reading(){
        /usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/status/" 2>/dev/null| grep 'Reading' | awk '{print $2}'
       }
    nginx_writing(){
        /usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/status/" 2>/dev/null| grep 'Writing' | awk '{print $4}'
       }
    nginx_waiting(){
        /usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/status/" 2>/dev/null| grep 'Waiting' | awk '{print $6}'
       }
    nginx_accepts(){
        /usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/status/" 2>/dev/null| awk NR==3 | awk '{print $1}'
       }
    nginx_handled(){
        /usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/status/" 2>/dev/null| awk NR==3 | awk '{print $2}'
       }
    nginx_requests(){
        /usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/status/" 2>/dev/null| awk NR==3 | awk '{print $3}'
       }
    case $NGINX_COMMAND in
        active)
            nginx_active;
            ;;
        reading)
            nginx_reading;
            ;;
        writing)
            nginx_writing;
            ;;
        waiting)
            nginx_waiting;
            ;;
        accepts)
            nginx_accepts;
            ;;
        handled)
            nginx_handled;
            ;;
        requests)
            nginx_requests;
        esac 
}

九、nginx優化

9.1 nginx內核優化

net.ipv4.tcp_fin_timeout = 2
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_keepalive_time = 600
net.ipv4.ip_local_port_range = 4000    65000
net.ipv4.tcp_max_syn_backlog = 16384
net.ipv4.tcp_max_tw_buckets = 36000
net.ipv4.route.gc_timeout = 100
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_synack_retries = 1
net.core.somaxconn = 16384
net.core.netdev_max_backlog = 16384
net.ipv4.tcp_max_orphans = 16384
#以下參數是對iptables防火牆的優化,防火牆不開會提示,可以忽略不理。
net.ipv4.ip_conntrack_max = 25000000
net.ipv4.netfilter.ip_conntrack_max=25000000
net.ipv4.netfilter.ip_conntrack_tcp_timeout_established=180
net.ipv4.netfilter.ip_conntrack_tcp_timeout_time_wait=120
net.ipv4.netfilter.ip_conntrack_tcp_timeout_close_wait=60
net.ipv4.netfilter.ip_conntrack_tcp_timeout_fin_wait=120

十、擴展一:nginx全局變量

  • $args:這個變量等於請求行中的參數,同$query_string。
  • $is_args: 如果已經設置$args,則該變量的值爲"?",否則爲""。
  • $content_length: 請求頭中的Content-length字段。
  • $content_type: 請求頭中的Content-Type字段。
  • $document_uri: 與$uri相同。
  • $document_root: 當前請求在root指令中指定的值。
  • $host: 請求主機頭字段,否則爲服務器名稱。
  • $http_user_agent: 客戶端agent信息。
  • $http_cookie: 客戶端cookie信息。
  • $limit_rate: 這個變量可以限制連接速率。
  • $request_method: 客戶端請求的動作,通常爲GET或POST。
  • $remote_addr: 客戶端的IP地址。
  • $remote_port: 客戶端的端口。
  • $remote_user: 已經經過Auth Basic Module驗證的用戶名。
  • $request_body_file`: 客戶端請求主體的臨時文件名。
  • $request_uri: 請求的URI,帶參數
  • $request_filename: 當前請求的文件路徑,由root或alias指令與URI請求生成。
  • $scheme: 所用的協議,比如http或者是https,比如rewrite ^(.+)$ $scheme://example.com$1 redirect;
  • $server_protocol: 請求使用的協議,通常是HTTP/1.0或HTTP/1.1。
  • $server_addr: 服務器地址,在完成一次系統調用後可以確定這個值。
  • $server_name: 服務器名稱。
  • $server_port: 請求到達服務器的端口號。
  • $request_uri: 包含請求參數的原始URI,不包含主機名,如:/foo/bar.php?arg=baz,它無法修改。
  • $uri: 不帶請求參數的當前URI,$uri不包含主機名,如/foo/bar.html可能和最初的值有不同,比如經過重定向之類的。它可以通過內部重定向,或者使用index指令進行修改。不包括協議和主機名,例如/foo/bar.html。

例子:

訪問鏈接是:http://localhost:88/test1/test.php 
網站路徑是:/var/www/html

$host:localhost
$server_port:88
$request_uri:http://localhost:88/test1/test.php
$document_uri:/test1/test.php
$document_root:/var/www/html
$request_filename:/var/www/html/test1/test.php

nginx plus – ngx_http_status_module

商業版的 nginx plus 通過他的 ngx_http_status_module 提供了比 nginx 更多的監控指標,可以參看 http://demo.nginx.com/status.html

nginx access log 分析

nginx 的 access log 中可以記錄很多有價值的信息,通過分析 access log,可以收集到很多指標。
python 編寫的 linux 工具 ngxtop 就實現了對 access log 的分析功能。

NDK – ngx_devel_kit

NDK 是一個拓展nginx服務器核心功能的模塊,第三方模塊開發可以基於它來快速實現。NDK提供函數和宏處理一些基本任務,減輕第三方模塊開發的代碼量。

nginx lua – lua-nginx-module

nginx的lua模塊,通過這個模塊,可以對nginx做定製開發

十、擴展二:web服務器事件處理模型

select

select最早於1983年出現在4.2BSD中,它通過一個select()系統調用來監視多個文件描述符的數組,當select()返回後,該數組中就緒的文件描述符便會被內核修改標誌位,使得進程可以獲得這些文件描述符從而進行後續的讀寫操作。
select目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優點,事實上從現在看來,這也是它所剩不多的優點之一。
select的一個缺點在於單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上一般爲1024,不過可以通過修改宏定義甚至重新編譯內核的方式提升這一限制。
另外,select()所維護的存儲大量文件描述符的數據結構,隨着文件描述符數量的增大,其複製的開銷也線性增長。同時,由於網絡響應時間的延遲使得大量TCP連接處於非活躍狀態,但調用select()會對所有socket進行一次線性掃描,所以這也浪費了一定的開銷。

poll

poll在1986年誕生於System V Release 3,它和select在本質上沒有多大差別,但是poll沒有最大文件描述符數量的限制。
poll和select同樣存在一個缺點就是,包含大量文件描述符的數組被整體複製於用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨着文件描述符數量的增加而線性增大。
另外,select()和poll()將就緒的文件描述符告訴進程後,如果進程沒有對其進行IO操作,那麼下次調用select()和poll()的時候將再次報告這些文件描述符,所以它們一般不會丟失就緒的消息,這種方式稱爲水平觸發(Level Triggered)。

epoll

直到Linux2.6纔出現了由內核直接支持的實現方法,那就是epoll,它幾乎具備了之前所說的一切優點,被公認爲Linux2.6下性能最好的多路I/O就緒通知方法。
epoll可以同時支持水平觸發和邊緣觸發(Edge Triggered,只告訴進程哪些文件描述符剛剛變爲就緒狀態,它只說一遍,如果我們沒有采取行動,那麼它將不會再次告知,這種方式稱爲邊緣觸發),理論上邊緣觸發的性能要更高一些,但是代碼實現相當複雜。
epoll同樣只告知那些就緒的文件描述符,而且當我們調用epoll_wait()獲得就緒文件描述符時,返回的不是實際的描述符,而是一個代表就緒描述符數量的值,你只需要去epoll指定的一個數組中依次取得相應數量的文件描述符即可,這裏也使用了內存映射(mmap)技術,這樣便徹底省掉了這些文件描述符在系統調用時複製的開銷。
另一個本質的改進在於epoll採用基於事件的就緒通知方式。在select/poll中,進程只有在調用一定的方法後,內核纔對所有監視的文件描述符進行掃描,而epoll事先通過epoll_ctl()來註冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會採用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便得到通知。

nginx -s reload 過程

nginx主進程讀取配置文件,如果發現配置文件變更,會創建一個新的主進程,然後同時舊的進程,及舊的子進程關閉,舊進程會拒絕新的連接,服務到自己的連接結束,然後關閉。

Apache select模型和 nginx epoll 模型對比講解

Nginx的高併發得益於其採用了epoll模型,與傳統的服務器程序架構不同,epoll是linux內核2.6以後纔出現的。下面通過比較Apache和Nginx工作原理來比較。

傳統Apache都是多進程或者多線程來工作,假設是多進程工作(prefork),apache會先生成幾個進程,類似進程池的工作原理,只不過這裏的進程池會隨着請求數目的增加而增加。對於每一個連接,apache都是在一個進程內處理完畢。具體是 recv(),以及根據 URI 去進行磁盤I/O來尋找文件,還有 send()都是阻塞的。其實說白了都是 apche 對於套接字的I/O,讀或者寫,但是讀或者寫都是阻塞的,阻塞意味着進程就得掛起進入sleep狀態,那麼一旦連接數很多,Apache必然要生成更多的進程來響應請求,一旦進程多了,CPU對於進程的切換就頻繁了,很耗資源和時間,所以就導致apache性能下降了,說白了就是處理不過來這麼多進程了。其實仔細想想,如果對於進程每個請求都沒有阻塞,那麼效率肯定會提高很多。

Nginx採用epoll模型,異步非阻塞。對於Nginx來說,把一個完整的連接請求處理都劃分成了事件,一個一個的事件。比如accept(), recv(),磁盤I/O,send()等,每部分都有相應的模塊去處理,一個完整的請求可能是由幾百個模塊去處理。真正核心的就是事件收集和分發模塊,這就是管理所有模塊的核心。只有核心模塊的調度才能讓對應的模塊佔用CPU資源,從而處理請求。拿一個HTTP請求來說,首先在事件收集分發模塊註冊感興趣的監聽事件,註冊好之後不阻塞直接返回,接下來就不需要再管了,等待有連接來了內核會通知你(epoll的輪詢會告訴進程),cpu就可以處理其他事情去了。一旦有請求來,那麼對整個請求分配相應的上下文(其實已經預先分配好),這時候再註冊新的感興趣的事件(read函數),同樣客戶端數據來了內核會自動通知進程可以去讀數據了,讀了數據之後就是解析,解析完後去磁盤找資源(I/O),一旦I/O完成會通知進程,進程開始給客戶端發回數據send(),這時候也不是阻塞的,調用後就等內核發回通知發送的結果就行。整個下來把一個請求分成了很多個階段,每個階段都到很多模塊去註冊,然後處理,都是異步非阻塞。異步這裏指的就是做一個事情,不需要等返回結果,做好了會自動通知你。

select/epoll的特點

select的特點:select 選擇句柄的時候,是遍歷所有句柄,也就是說句柄有事件響應時,select需要遍歷所有句柄才能獲取到哪些句柄有事件通知,因此效率是非常低。但是如果連接很少的情況下, select和epoll的LT觸發模式相比, 性能上差別不大。
這裏要多說一句,select支持的句柄數是有限制的, 同時只支持1024個,這個是句柄集合限制的,如果超過這個限制,很可能導致溢出,而且非常不容易發現問題, 當然可以通過修改linux的socket內核調整這個參數。
epoll的特點:epoll對於句柄事件的選擇不是遍歷的,是事件響應的,就是句柄上事件來就馬上選擇出來,不需要遍歷整個句柄鏈表,因此效率非常高,內核將句柄用紅黑樹保存的。
對於epoll而言還有ET和LT的區別,LT表示水平觸發,ET表示邊緣觸發,兩者在性能以及代碼實現上差別也是非常大的。

不管是Nginx還是Squid這種反向代理,其網絡模式都是事件驅動。事件驅動其實是很老的技術,早期的select、poll都是如此。後來基於內核通知的更高級事件機制出現,如libevent裏的epoll,使事件驅動性能得以提高。事件驅動的本質還是IO事件,應用程序在多個IO句柄間快速切換,實現所謂的異步IO。事件驅動服務器,最適合做的就是這種IO密集型工作,如反向代理,它在客戶端與WEB服務器之間起一個數據中轉作用,純粹是IO操作,自身並不涉及到複雜計算。反向代理用事件驅動來做,顯然更好,一個工作進程就可以run了,沒有進程、線程管理的開銷,CPU、內存消耗都小。

所以Nginx、Squid都是這樣做的。當然,Nginx也可以是多進程 + 事件驅動的模式,幾個進程跑libevent,不需要Apache那樣動輒數百的進程數。Nginx處理靜態文件效果也很好,那是因爲靜態文件本身也是磁盤IO操作,處理過程一樣。至於說多少萬的併發連接,這個毫無意義。隨手寫個網絡程序都能處理幾萬的併發,但如果大部分客戶端阻塞在那裏,就沒什麼價值。

再看看Apache或者Resin這類應用服務器,之所以稱他們爲應用服務器,是因爲他們真的要跑具體的業務應用,如科學計算、圖形圖像、數據庫讀寫等。它們很可能是CPU密集型的服務,事件驅動並不合適。例如一個計算耗時2秒,那麼這2秒就是完全阻塞的,什麼event都沒用。想想MySQL如果改成事件驅動會怎麼樣,一個大型的join或sort就會阻塞住所有客戶端。這個時候多進程或線程就體現出優勢,每個進程各幹各的事,互不阻塞和干擾。當然,現代CPU越來越快,單個計算阻塞的時間可能很小,但只要有阻塞,事件編程就毫無優勢。所以進程、線程這類技術,並不會消失,而是與事件機制相輔相成,長期存在。

總言之,事件驅動適合於IO密集型服務,多進程或線程適合於CPU密集型服務,它們各有各的優勢,並不存在誰取代誰的傾向。

相關參考

nginx-日誌高級技巧](https://segmentfault.com/a/1190000000703319)
nginx-官方文檔
nginx優化
查看網站排名
nginx-status-demo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章