主頁 > 軟體設計 > 嘔心瀝血整理,Nginx看這個就夠了

嘔心瀝血整理,Nginx看這個就夠了

2021-09-02 07:52:17 軟體設計

Nginx

  • Nginx官方檔案
  • OpenResty官方檔案
  • 一、Nginx概述
  • 二、Nginx結構
  • 三、Nginx模塊
    • ngx_http_core_module(location、rewrite)
    • ngx_http_access_module
        • 實作基于ip的訪問控制功能
        • 實作基于用戶的訪問控制
    • ngx_http_stub_status_module
    • ngx_http_log_module
    • ngx_http_gzip_module
    • 🌟ngx_http_limit
      • ngx_http_limit_req_module
      • ngx_http_limit_conn_module
      • 實戰
        • 1、限制訪問速率
        • 2、burst快取處理
        • 3、nodelay降低排隊時間
        • 4、自定義回傳值
  • 四、Nginx命令
  • 五、Nginx優化
    • Nginx服務器高性能優化--輕松實作10萬并發訪問量
    • 其他小優化
      • 1.隱藏版本資訊
      • 2.優化nginx行程個數
      • 3.調整nginx單個行程允許的客戶端最大連接數
      • 4.開啟高效檔案傳輸模式
      • 5.Nginx gzip壓縮實作性能優化
      • 6.nginx防爬蟲優化
      • 🌟7.控制nginx并發連接數量
      • 8.nginx錯誤頁面優雅顯示
      • 9.防盜鏈
      • 10.優化nginx連接超時時間
      • 11.限制檔案上傳大小
      • 12、系結Nginx行程到不同的CPU上
  • 六、Nginx演算法
  • Main全域配置常見的配置指令分類
    • 正常運行必備的配置
  • Nginx:后面有無/的區別
    • 除錯和定位問題
  • WEB虛擬主機案例
  • Nginx的生命周期(stage)
  • Nginx FAQ
    • 1、Nginx的優缺點
    • 2、Nginx是如何連接PHP進行頁面決議的
    • 3、Nginx和Tomcat之間的資料傳輸程序(動靜分離)
    • 4、Nginx處理一個請求的程序
  • ??閱讀agentzh-nginx教程
    • set變數
      • 初識變數(用戶自定義變數)
      • 內部跳轉
      • 預定義變數(一)內建變數
      • 預定義變數(二)
      • 預定義變數(三)(取處理程式、map)
      • 預定義變數(四)(主請求、子請求)
      • 預定義變數(五)
      • 預定義變數(六)
      • 預定義變數(七)
    • 執行順序
      • 執行順序(一)開頭
      • 執行順序(二) rewrite—set
      • 執行順序(三) rewrite-access
      • 執行順序(四)access
      • 執行順序(五)content
      • 執行順序(六) ngx_index 模塊, ngx_autoindex 模塊
      • 執行順序(七)nginx_static與配置前綴
      • 執行順序(八)(11個階段)
        • post-read
        • server-rewrite
        • find-config階段
        • 💗rewrite階段
        • preaccess(post-rewrite后,access前)
        • access階段
        • try-files階段
  • 應用場景
    • 一:HTTP服務器
    • 二、靜態服務器

Nginx官方檔案

OpenResty官方檔案

一、Nginx概述

Nginx是免費,開源,高性能的HTTP和反向代理服務器,郵件代理服務器,通用TCP/UDP代理服務器

官方檔案:https://www.nginx.cn/doc/

特性

  • 模塊化設計,較好的擴展性
  • 高可靠性
  • 支持熱部署:不停機更新組態檔,升級版本,更換日志檔案
  • 低記憶體消耗:10000個keep-alive連接模式下的非活動連接,僅需2.5M記憶體
  • event-driven,aio,mmap,sendfile

基本功能

  • 靜態資源的web服務器
  • http協議反向代理服務器
  • pop3/imap4協議反向代理服務器
  • FastCGI(LNMP),uWSGI(python)等協議
  • 模塊化(非DSO),如zip,SSL模塊

web服務相關的功能

  • 虛擬主機(server)
  • 支持keep-alive 和 管道連接
  • 訪問日志(支持基于日志緩沖提高其性能)
  • url rewrrite
  • 路徑別名
  • 基于IP及用戶的訪問控制
  • 支持速率限制及并發數限制
  • 重新配置和在線升級而無須中斷客戶的作業行程
  • Memcached的GET介面

正向代理和反向代理
在這里插入圖片描述

正向代理,代理客戶端,
反向代理,代理服務器,
在這里插入圖片描述

Nginx在做反向代理時,提供性能穩定,并且能夠提供配置靈活的轉發功能,Nginx可以根據不同的正則匹配,采取不同的轉發策略,比如圖片檔案結尾的走檔案服務器,動態頁面走web服務器,只要你正則寫的沒問題,又有相對應的服務器解決方案,你就可以隨心所欲的玩,并且Nginx對回傳結果進行錯誤頁跳轉,例外判斷等,如果被分發的服務器存在例外,他可以將請求重新轉發給另外一臺服務器,然后自動去除例外服務器,

負載均衡

Nginx提供的負載均衡策略有2種:內置策略和擴展策略,內置策略為輪詢,加權輪詢,Ip hash,擴展策略很多,

Ip hash演算法,對客戶端請求的ip進行hash操作,然后根據hash結果將同一個客戶端ip的請求分發給同一臺服務器進行處理,可以解決session不共享的問題,
在這里插入圖片描述

二、Nginx結構

Nginx 的高并發得益于其采用了 epoll 模型,與傳統的服務器程式架構不同,epoll 是Linux 內核 2.6 以后才出現的,Nginx 采用 epoll 模型,異步非阻塞,而 apache 采用的是select 模型

Nginx 默認以 80 埠監聽在服務器上,并且啟動一個 master 行程,同時有 master 行程生成多個作業行程,當瀏覽器發起一個 HTTP 連接請求,每個行程都有可能處理這個連接,

Nginx有兩種作業模式:master-worker模式和單行程模式(方便除錯),在master-worker模式下,有一個master行程和至少一個的worker行程,單行程模式顧名思義只有一個行程,

Nginx的程式架構:master/worker架構

  • 一個master架構:負載加載和分析組態檔、管理worker行程(啟動,殺死,監控,發送訊息/信號等)、平滑升級,不負責業務執行

  • 一個或多個worker行程:處理并回應用戶請求,真正提供服務的是worker行程,通過worker行程來實作重啟服務,平滑升級,更換日志檔案,組態檔生效等功能,

  • 快取相關的行程:

    • cache loader:載入快取物件
    • cache manager:管理快取物件

? ? ? ? ? ? ? 首先每個 worker 行程都是從 Master 行程 fork 出來,在 Master 行程里面,建立好需要listen 的 socket(listenfd)之后,會 fork 出多個 worker 行程,
  所有 worker 行程的 listenfd 會在新連接到來時變得可讀,為保證只有一個行程處理該連接,所有 worker 行程在注冊 listenfd 讀事件前搶 accept_mutex,搶到互斥鎖的那個行程注冊 listenfd 讀事件,在讀事件里呼叫 accept 接受該連接,
  當一個 worker 行程在 accept 這個連接之后,就開始讀取請求、決議請求、處理請求,產生資料后,再回傳給客戶端,最后才斷開連接,這樣形成一個完整的請求流程,如圖

在這里插入圖片描述

master-worker這種模式:

  1. 穩定性高,只要還有worker行程存活,就能夠提供服務,并且一個worker行程掛掉master行程會立即啟動一個新的worker行程,保證worker行程數量不變,降低服務中斷的概率,
  2. 配合linux的cpu親和性配置,可以充分利用多核cpu的優勢,提升性能
  3. 處理信號/配置重新加載/升級時可以做到盡可能少或者不中斷服務

??Nginx組態檔結構
默認的 nginx 組態檔 nginx.conf 內容結構如下:

...              #全域塊

events {         #events塊
   ...
}

http      #http塊
{
    ...   #http全域塊
    server        #server塊
    { 
        ...       #server全域塊
        location [PATTERN]   #location塊
        {
            ...
        }
        location [PATTERN] 
        {
            ...
        }
    }
    server	#每個server用于定義一個虛擬主機
    {
      ...
      server_name 虛擬主機名
      root 主目錄
      alias 路徑別名
      location [PATTERN] 
      {
      	...
      	if CONDITION {
      	
      	}
      }
    }
    ...     #http全域塊
}
mail{
	... #mail協議相關配置段
}
stream{
	... #stream服務器相關配置段
}
  • 1、全域塊:配置影響nginx全域的指令,一般有運行nginx服務器的用戶組,nginx行程pid存放路徑,日志存放路徑,組態檔引入,允許生成worker process數等,
  • 2、events塊:配置影響nginx服務器或與用戶的網路連接,有每個行程的最大連接數,選取哪種事件驅動模型處理連接請求,是否允許同時接受多個網路連接,開啟多個網路連接序列化等,
  • 3、http塊:可以嵌套多個server,配置代理,快取,日志定義等絕大多數功能和第三方模塊的配置,如檔案引入,mime-type定義,日志自定義,是否使用sendfile傳輸檔案,連接超時時間,單連接請求數等,
  • 4、server塊:配置虛擬主機的相關引數,一個http中可以有多個server,
  • 5、location塊:配置請求的路由,以及各種頁面的處理情況,

如下:

########### 每個指令必須有分號結束,#################
#user administrator administrators;  #配置用戶或者組,默認為nobody nobody,
#worker_processes 2;  #允許生成的行程數,默認為1
#pid /nginx/pid/nginx.pid;   #指定nginx行程運行檔案存放地址
error_log log/error.log debug;  #制定日志路徑,級別,這個設定可以放入全域塊,http塊,server塊,級別以此為:debug|info|notice|warn|error|crit|alert|emerg
events {
    accept_mutex on;   #設定網路連接序列化,防止驚群現象發生,默認為on
    multi_accept on;  #設定一個行程是否同時接受多個網路連接,默認為off
    #use epoll;      #事件驅動模型,select|poll|kqueue|epoll|resig|/dev/poll|eventport
    worker_processes  1;		# nginx行程數,一般設定成和CPU個數一樣
    worker_connections  1024;    #最大連接數,默認為512
}
http {
    include       mime.types;   #檔案擴展名與檔案型別映射表
    default_type  application/octet-stream; #默認檔案型別,默認為text/plain
    #access_log off; #取消服務日志 
    
    # 日志格式
    log_format myFormat '$remote_addr$remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for'; #自定義格式
    access_log log/access.log myFormat;  #combined為日志格式的默認值
    
    sendfile on;   #允許sendfile方式傳輸檔案,默認為off,可以在http塊,server塊,location塊,
    sendfile_max_chunk 100k;  #每個行程每次呼叫傳輸數量不能大于設定的值,默認為0,即不設上限,
    keepalive_timeout 65;  #連接超時時間,默認為75s,可以在http,server,location塊,

    upstream mysvr {   
      server 127.0.0.1:7878;
      server 192.168.10.121:3333 backup;  #熱備
    }
    error_page 404 https://www.baidu.com; #錯誤頁
    
    # 服務器配置
    server {
        keepalive_requests 120; #單連接請求上限次數,
        listen       4545;   #監聽埠
        server_name  127.0.0.1;   #監聽地址       
        location  ~*^.+$ {       #請求的url過濾,正則匹配,~為區分大小寫,~*為不區分大小寫,
           #root path;  #根目錄
           #index vv.txt;  #設定默認頁
           proxy_pass  http://mysvr;  #請求轉向mysvr 定義的服務器串列
           deny 127.0.0.1;  #拒絕的ip
           allow 172.18.5.54; #允許的ip           
        } 
    }
}

上面是nginx的基本配置,需要注意的有以下幾點:

1、幾個常見配置項:

  • 1.$remote_addr 與 $http_x_forwarded_for 用以記錄客戶端的ip地址;
  • 2.$remote_user :用來記錄客戶端用戶名稱;
  • 3.$time_local : 用來記錄訪問時間與時區;
  • 4.$request : 用來記錄請求的url與http協議;
  • 5.$status : 用來記錄請求狀態;成功是200;
  • 6.$body_bytes_s ent :記錄發送給客戶端檔案主體內容大小;
  • 7.$http_referer :用來記錄從那個頁面鏈接訪問過來的;
  • 8.$http_user_agent :記錄客戶端瀏覽器的相關資訊;
remote_addr #客戶端 IP 地址
remote_port #客戶端埠
server_addr #服務端 IP 地址
server_port #服務端埠
server_protocol #服務端協議
binary_remote_addr #二進制格式的客戶端 IP 地址
connection #TCP 連接的序號,遞增
connection_request #TCP 連接當前的請求數量
uri #請求的URL,不包含引數
request_uri #請求的URL,包含引數
scheme #協議名, http 或 https
request_method #請求方法
request_length #全部請求的長度,包含請求行、請求頭、請求體
args #全部引數字串
arg_引數名 #獲取特定引數值
is_args #URL 中是否有引數,有的話回傳 ? ,否則回傳空
query_string #與 args 相同
host #請求資訊中的 Host ,如果請求中沒有 Host 行,則在請求頭中找,最后使用 nginx 中設定的 server_name ,
http_user_agent #用戶瀏覽器
http_referer #從哪些鏈接過來的請求
http_via #每經過一層代理服務器,都會添加相應的資訊
http_cookie #獲取用戶 cookie
request_time #處理請求已消耗的時間
https #是否開啟了 https ,是則回傳 on ,否則回傳空
request_filename #磁盤檔案系統待訪問檔案的完整路徑
document_root #由 URI 和 root/alias 規則生成的檔案夾路徑
limit_rate #回傳回應時的速度上限值

2、驚群現象:一個網路連接到來,多個睡眠的行程被同時叫醒,但只有一個行程能獲得鏈接,這樣會影響系統性能,

3、每個指令必須有分號結束,

4、log_format只能出現在http指令塊中,而access_log則可以出現在http和server,location這些指令塊中,

在nginx中存盤值得指令繼承規則是向上覆寫,當子配置存在時,直接覆寫父配置塊, 子配置不存在時,直接使用父配置塊,

三、Nginx模塊

從結構上分為如下三類:
核心模塊: HTTP模塊、EVENT模塊和MAIL模塊
標準模塊:

  • HTTP模塊:ngx_http_* ,HTTP Access模塊、HTTP FastCGI模塊、HTTP Proxy模塊、HTTP Rewrite模塊;
    HTTP Core modules 默認功能
    HTTP Optional modules 需編譯時指定
  • Mail模塊:ngx_mail_*
  • Stream模塊:ngx_stream_*

第三方模塊:HTTP Upstream Request Hash模塊、Notice模塊 和 HTTP Access Key模塊、Limit_req模塊等;

從功能上分為如下三類:

  • Handlers(處理器模塊):此類模塊直接處理請求,并進行輸出內容和修改headers資訊等操作,Handlers處理器模塊一般只能有一個;
  • Filters(過濾器模塊):此類模塊主要對其他處理器模塊輸出的內容進行修改操作,最后由Nginx輸出;
  • Proxies(代理類模塊):此類模塊是Nginx的HTTP Upstream之類的模塊,這些模塊主要與后端一些服務比如FastCGl等進行互動,實作服務代理和負載均衡等功能,(upstream的模塊提供資料轉發功能,upstream使nginx跨越單機的限制,完成網路資料的接收、處理和轉發)

在這里插入圖片描述

  • http_ssl_module:實作服務器加密傳輸的模塊,部署完成后可使用https://協議進行資料傳輸,保證資料傳輸程序的安全,

    worker_processes 1;
    http {
      server {
        listen               443;
        ssl                  on;
        ssl_certificate      /usr/local/nginx/conf/cert.pem;
        ssl_certificate_key  /usr/local/nginx/conf/cert.key;
        keepalive_timeout    70;
      }
    }
    
  • http_image_filter_module:通過該模塊可以實作圖片裁剪,將過大的圖片裁剪為指定大小的圖片,生成縮略圖,保證傳輸速率,該選項默認不開啟,需要人為指定 image_filter resize $h $w;

    編譯安裝引數配置:--with-http_image_filter_module  
    
    location /img/ {
        proxy_pass     http://backend;
        image_filter   resize  150 100;
        error_page     415   = /empty;
    
    }
     
    location = /empty {
        empty_gif;
    }
    
  • http_rewrite_module
    Nginx的地址重寫模塊,功能同Apache的一樣,可以實作通過正則匹配來完成條件判斷,然后進行域名或url的重寫,例如:多域名、http ——》https

    • nginx rewrite 規則中 last、break、redirect、permanent 的含義,
  答:不寫last和break - 那么流程就是依次執行這些rewrite
  1.rewrite break - url重寫后,直接使用當前資源,不再執行location里余下的陳述句,完成本次請求,地址欄url不變
  2.rewrite last - url重寫后,馬上發起一個新的請求,再次進入server塊,重試location匹配,超過10次匹配不到報500錯誤,地址欄url不變
  3.rewrite redirect – 回傳302臨時重定向,地址欄顯示重定向后的url,爬蟲不會更新url(因為是臨時)
  4.rewrite permanent – 回傳301永久重定向, 地址欄顯示重定向后的url,爬蟲更新url
  
  301	永久移動,請求的資源已被永久的移動到新URI,回傳資訊會包括新的URI,瀏覽器會自動定向到新URI,今后任何新的請求都應使用新的URI代替
  302	臨時移動,與301類似,但資源只是臨時被移動,客戶端應繼續使用原有URI
  
  
  break
  作用域: server, location, if
  if ($slow) {
   limit_rate  10k;
   break;
  }
  
  if
  作用域: server, location
  匹配符:=,!=,~*,~,!~,!~*
  使用-f以及!-f檢測一個檔案是否存在
  使用-d以及!-d檢測一個目錄是否存在
  使用-e以及!-e檢測是否存在一個檔案,一個目錄或者一個符號鏈接
  
  return
  作用域: server, location, if
  這個指令根據規則的執行情況,回傳一個狀態值給客戶端,可使用值包括:204,400,402-406,408,410,411,413,416以及500-504,也可以發送非標準的444代碼-未發送任何頭資訊下結束連接,
  
  rewrite
  作用域: server, location, if
  
  
  
  permanent 和 redirect關鍵字的區別
  rewrite … permanent 永久性重定向,請求日志中的狀態碼為301
  rewrite … redirect 臨時重定向,請求日志中的狀態碼為302
  我們常用的80埠轉443,即http轉https的一種配置方案為:
  
  server {
      listen 80;
      server_name demo.com;
      rewrite ^(.*)$ https://${server_name}$1 permanent; 
  }
  server {
      listen       443;
      server_name  demo.com;
      charset utf-8;
      location / {
         alias   /data/web;
         index  index.html index.htm;
      }
}
  會回傳301永久重定向到對應的https
  • nginx組態檔,rewrite和proxy_pass區別 (兩種常見的資源定向)

    • 區別 https://www.jianshu.com/p/10ecc107b5ee https://blog.51cto.com/853056088/2126498

    • rewrite 通過使用flag可以終止定向后進一步的處理

    • 是否影響瀏覽器地址欄,proxy_pass不影響瀏覽器地址欄的url

    • proxy_pass配合upstream實作負載均衡

      • - proxy_set_header Host $host; 作用web服務器上有多個站點時,用該引數header來區分反向代理哪個域名,比如下邊的示例,
        - proxy_set_header X-Forwarded-For $remote_addr; 作用是后端服務器上的程式獲取訪客真實IP,從該header頭獲取,部分程式需要該功能,
  • http_proxy_module
    Nginx反向代理功能,由于Nginx的高并發特性,很多時候我們都選擇使用Nginx為網站的前置服務器,一般會和upstream模塊一起使用,完成壓力分攤作業,

      location / {
      	proxy_pass        http://localhost:8000;
      	proxy_set_header  X-Real-IP  $remote_addr;
      }
    
      如果想將/a的請求,直接轉到80/a路徑下
          location /a/ {
      		proxy_pass http://120.76.x.x:80/;
          }
      http://127.0.0.1:8000/a/  訪問就會跳到80/a
      
      作用域:http,server,location
    

    搭建一臺Nginx方向代理服務器,讓后端服務器還能夠獲取到用戶的IP地址
    1.反向代理NGINX端設定記錄轉發請求的HTTP頭資訊,記錄原有的客戶端IP和原有客戶端請求的服務器地址
    2.后臺服務端利用log_format指令設定日志格式,記錄代理端回傳來的日志資訊

    一、配置反向代理端的nginx服務器
    在server后面增加如下這三個引數用于記錄IP:

      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    

    二、在后端服務器配置如下
    在后端服務器的nginx_http處配置如下:
    配置log_format資訊, 后續的日志后面也需要加上main這個引數

    log_format  main '$remote_addr $remote_user [$time_local] "$request" '                                                                                                                                                           
                      '$status $body_bytes_sent "$http_referer" '                                                                                                                                                                               
                      '$http_user_agent $http_x_forwarded_for $request_time $upstream_response_time $upstream_addr $upstream_status'; 
    
  • http_upstream_module
    Nginx的負載均衡模塊,一般和http_proxy模塊一起使用,用來對后臺服務器的任務調度及分配,分配原則可以通過演算法進行控制,常見模式:Nginx+Apache、Nginx+Tomcat

    upstream backend  {
      server backend1.example.com weight=5;
      server backend2.example.com:8080;
      server unix:/tmp/backend3;
    }
     
    server {
      location / {
        proxy_pass  http://backend;
      }
    }
    
    Ip hash演算法,對客戶端請求的ip進行hash操作,然后根據hash結果將同一個客戶端ip的請求分發給同一臺服務器進行處理,可以解決session不共享的問題,
    
    	upstream group1{
    		server 192.168.0.1:80 weight=1;
    		server 192.168.0.1:81 weight=1;
    	}
        server {
            listen       8000;
            server_name  localhost;
    		default_type text/html
    		
    		location /{
    			echo "hello";
    		}
    
    		location /a/ {
    			proxy_pass http://group1/;
    		}
    	}
    
  • http_gzip_module
    網路傳輸壓縮模塊,這個模塊支持在線實時壓縮輸出資料流

    : gzip             on;   開啟gzip模塊
    : gzip_min_length  1000;
    : gzip_proxied     expired no-cache no-store private auth;
    : gzip_types       text/plain application/xml;
    
    內置變數 $gzip_ratio 可以獲取到gzip的壓縮比率
    
  • cache_purge
    實作快取清除功能

ngx_http_core_module(location、rewrite)

ngx_http_core_module
與套接字相關的配置:
1、server{...}   在http塊
配置一個虛擬主機
server{
	listen address[:PORT]|PORT;  
	server_name SERVER_NAME;
	root /PATH/TO/DUCUMENT_ROOT;
}

include /etc/nginx/conf.d/vhosts/*.conf;
server{
	listen 8080;   
	//加上default就是默認,不加就按vhosts目錄下的檔案順序找, 
	//listen 80 default_server;  listen IP地址:80 default_server
	server_name www.b.com;
	root /data/siteb;
}


2、listen PORT|address[:port]|unix:/PATH/TO/SOCKET_FILE
listen address[:port][default_server][ssl][http2|spdy][backlog=number][rcvbuf=size][sndbuf=size]
	default_server  設定為默認虛擬主機
	ssl				限制僅能夠通過ssl連接提供服務
	backlog=number  超過并發連接數后,新請求進入后援佇列的長度
	rcvbuf=size	    接識訓沖區大小
	sndbuf=size     發送緩沖區大小
注意:
(1)基于port;
listen PORT;       指令監聽在不同的埠
(2)基于ip的虛擬主機
listen IP:PORT;    IP地址不同
(3)基于hostname
server_name fqdn;  指令指向不同的主機名

3、server_name name...;
虛擬主機的主機名稱后可跟多個由空白字符分隔的字串
支持*通配任意長度的任意字符
  server_name *.abc.com www.abc.*
支持~起始的字符做正則運算式模式匹配,性能原因慎用(要計算匹配,最好精確)
  server_name  ~^www\d+\.abc\.com$
  說明:\d 表示[0-9]
匹配優先級機制從高到低:
(1)首先是字串精確匹配 如:www.abc.com
(2)右側*通配符 如:*.abc.com
(3)右側*通配符 如:www.abc.*
(4)正則運算式 如:~^.*\.abc\.com$
(5)default_server


4.tcp_nodelay on|off;
在keepalived模式下的連接是否啟用TCP_NODELAY選項
當為off時,延遲發送,合并多個請求后再發送
默認On時,不延遲發送
可用于:http,server,location

5、sendfile on|off
是否啟用sendfile功能,在內核中封裝報文直接發送
默認off

6、server_tokens on|off|build|string
是否在相應報文的Server首部顯示nginx版本

匹配,root、location、alias

7、root
設定web資源的路徑映射;用于指明請求的URL所對應的檔案的目錄路徑,可用于http,server,location,if in location
server{
	...
	root /data/www/vhosts;
}
示例:http://www.abc.com/images/logo.jpg
         -->/data/www/vhosts/images/logo.jpg
訪問/images/logo.jpg 相當于訪問 /data/www/vhosts/images/logo.jpg


8、location [=|~|~*|^~]uri{..}
   location @name{..}
放在server塊和location塊
  在一個server中location配置段可存在多個,用于實作從uri到檔案系統的路徑映射;nginx會根據用戶請求的URI來檢查定義的所有location,并找出一個最佳匹配,而后應用其配置
示例:
	server{...
		server_name www.abc.com;
		location /images/{
			root /data/imgs/;
			}
	}
	http://www.abc.com/images/logo.jpg
		--> /data/imgs/images/logo.jpg
訪問images的時候,實際訪問的是/data/imgs下的 images

精確匹配:
=:對URI做精確匹配;
		location = /{
		...
		}
		http://www.abc.com/  匹配
		http://www.abc.com/index.html  不匹配
		
正則匹配
^~:對URI的最左邊部分做匹配檢查,不區分字符大小寫,一般用來匹配目錄
~:對URI做正則運算式模式匹配,區分字符大小寫
~*:對URI做正則運算式模式匹配,不區分字符大小寫

內部location
@

不帶符號:匹配起始于此uri的所有uri
匹配優先級從高到低: =,^~,~/~*,不帶符號(相當于直接一個杠/)
匹配順序:精確匹配、記錄最長前綴匹配、使用第一個正則匹配、使用前面記錄的最長前綴匹配

示例1:匹配http://www.abc.com/
location = / {
	...
}

示例2:匹配http://www.abc.com/index.html
location / {
	...
}

示例3:匹配http://www.abc.com/documents/linux.txt
location /documents/{
	...
}

示例4:匹配http://www.abc.com/documents/logo.jpg
location ~* \.(gif|jpg|jpeg)${
	...
}

示例5:匹配以/images/開頭
location ^~ /images/  

示例6:匹配數字,字母和下劃線
location ~ /\w

nginx中location匹配順序
    首先先檢查使用前綴字符定義的location,選擇最長匹配的項并記錄下來,
    如果找到了精確匹配的location,也就是使用了=修飾符的location,結束查找,使用它的配置,
    然后按順序查找使用正則定義的location,如果匹配則停止查找,使用它定義的配置,
    如果沒有匹配的正則location,則使用前面記錄的最長匹配前綴字符location,

location匹配規則小結:
1 =|^~|~| 普通文本 四個優先級較高的先匹配
(2,4都是匹配開頭,優先級不一樣)
2 同優先級的,匹配程度較高的先匹配
3 匹配程度也一樣,則寫在前面的先匹配

9.alias
	路徑別名,檔案映射的另一種機制;僅能用于location背景關系
示例:
訪問 http://www.abc.com/bbs/index.php
location /bbs/{
	alias /web/forum/;
} --> /web/forum/index.html  實際訪問

location /bbs/{
	root /web/forum/;
} --> /web/forum/bbs/index.html  實際訪問

注意:location中使用root指令和alias指令的意義不同
(a)root,給定的路徑對應于location中的/uri/左側的/
(b)alias,給定的路徑對應location中的/uri/右側的/
10.index file ...;
指定默認網頁檔案,注意:ngx_http_index_module模塊

11、error_page code .. [=[response]] uri;   在server塊
模塊:ngx_http_core_module
定義錯誤頁,以指定的回應狀態碼進行回應
可用位置:http,server,location,if in location
error_page 404 /404.html
error_page 404 = 200 /404.html   訪問其實是404,強行把狀態改為200

示例:
server{
error_page 404 /404.html;
location /404.html{
	root /data/sitea/error/;
	}
}
#mkdir /data/sirea/error/
vim /data/sitea/error/404.html


12、try_files file ... uri;
	try_files file ... = code;
按順序檢查檔案是否存在,回傳第一個找到的檔案或檔案夾(結尾加斜線表示為檔案夾),如果所有的檔案或檔案夾都找不到,會進行一個內部重定向到最后一個引數,只有最后一個引數可以引起一個內部重定向,之前的引數只設定內部URI的指向,最后一個引數是回退URI且必須存在,否則會出現內部500錯誤
location /images/{
	//假設找/images/a.jpg,如果沒有a.jpg就回傳default.gif
	try_files $uri /images/default.gif;
}
location /{
	try_files $uri $uri/index.html $uri.html = 404;
}

定義客戶端請求的相關配置

13、keepalive_timeout timeout [header_timeout];
設定保持連接超時時長,0表示禁止長連接,默認為75s

14、keepalive_requests number;
在一次長連接上鎖允許請求的資源的最大數量,默認為100

15、keepalive_disable none|broswer...
對哪種瀏覽器禁用長連接

16、send_timeout time;
向客戶端發送回應報文的超時時長,此處是指兩次寫操作之間的間隔時長,而非整個回應程序的傳輸時長

17、client_body_buffer_size size;
用于接收每個客戶端請求報文的body部分的緩沖區大小;默認為16k;超出此大小時,其將被暫存到磁盤上的由下面client_body_temp_path指令所定義的位置

18、client_body_temp_path path [level1 [level2 [level3]]];
設定存盤客戶端請求報文的body部分的臨時存盤路徑及子目錄結構和數量
目錄名為16進制的數字;
client_body_temp_path /var/tmp/client_body 1 2 2
1 1級目錄占1位16進制,即2^4=16個目錄 0-f
2 2級目錄占2位16進制,即2^8=256個目錄 00-ff
2 3級目錄占2位16進制,即2^8=256個目錄 00-ff

對客戶端進行限制的相關配置

19、limit_rate rate;
限制回應給客戶端的傳輸速率,單位是bytes/second
默認值0表示無限制

20、limit_except method ... {...},僅用于location
限制客戶端使用除了指定的請求方法之外的其他方法
method:GET,HEAD,POST,PUT,DELETE
MKCOL,COPY,MOVE,OPTIONS,PROPFIND,
PROPPATCH,LOCK,UNLOCK,PATCH
limit_except GET{
	allow 192.168.1.0/24;
	deny all;
}除了GET和HEAD之外其他方法僅允許192.168.1.0/24網段主機使用

檔案操作優化的配置

21、aio on|off|threads|[=pool];
是否啟用aio功能,默認不啟動

22、directio size|off;
當檔案大于等于給定大小時,例如directio 4m,同步(直接)寫磁盤,而非寫快取

23、open_file_cache off;
open_file_cache max=N [inactive=time];
nginx可以快取以下三種資訊:
(1)檔案元資料:檔案的描述符、檔案大小和最近一次的修改時間
(2)打開的目錄結構
(3)沒有找到的或者沒有權限訪問的檔案的相關資訊
max=N:可快取的快取項上限;達到上限后會使用LRU(最近最少)演算法實作管理
inactive=time:快取項的非活動時長,在此處指定的時長內未被命中的活命中的次數少于open_file_cache_min_uses指令所指定的次數的快取項即非活動項,將被洗掉

24、open_file_cache_errors on|off;
是否快取查找時發生錯誤的檔案一類的資訊
默認值為off

25、open_file_cache_min_uses number;
open_file_cache指令的inactive引數指定的時長內,至少被命中此處指定的次數方可被歸類為活動項
默認值為1

26、open_file_cache_valid time;
快取項有效性的檢查頻率
默認值為60s

ngx_http_access_module

實作基于ip的訪問控制功能

1、allow address|CIDR|unix:|all;
2、deny address|CIDR|unix:|all;
http,server,location,limit_except
自上而下檢查,一旦匹配,將生效,條件嚴格的置前
示例:
location /{
	deny 192.168.1.1;
	allow 192.168.1.0/24;
	allow 10.1.1.0/16;
	allow 2001:0db8::/32;
	deny all
}

實作基于用戶的訪問控制

使用basic機制進行用戶認證

1、auth_basic string|off;
2、auth_basic_user_file file;
	location /admin/{
		auth_basic "Admin Area";   # 提示資訊
		auth_basic_user_file /etc/nginx/conf.d/vhosts/nginxuser;   # 其實也可以創建隱藏檔案 .nginxuser
	}
	用戶口令檔案:
	1、明文文本:格式name:password:comment
	2、加密文本:由htpasswd命令實作  (通過httpd-tools, #rpm -ivh httpd-tools)

#htpasswd -cm nginxuser httpuser1  第一次創建要加引數c
#htpasswd -m nginxuser httpuser2

ngx_http_stub_status_module

用于輸出nginx的基本狀態資訊

Active connections:291
server accepts handled requests
	    16630948 16630948 31070465
上面三個數字分別對應accepts,handled,requests三個值
Reading:6 Writing:179 Waiting:106

Activeconnections:當前狀態,活動狀態的連接數
accepts:統計總值,已經接受的客戶端請求的總數
handled:統計總值,已經處理完成的客戶端請求的總數
requests:統計總值,客戶端發來的總的請求數
Reading:當前狀態,正在讀取客戶端請求報文首部的連接的連接數 (請求頭)
Writing:當前狀態,正在向客戶端發關回應報文程序中的連接數 (回應頭)
Waiting:當前狀態,正在等待客戶端發出請求的空閑連接數 (等待的請求數)

1、stub_status;
示例:
location /status{
	stub_status;
	#allow 172.16.0.0/16;
  #deny all;
}
作用域:server,location

ngx_http_log_module

指定日志格式記錄請求,搭配access_log

只能在http的作用域下
#vim /etc/nginx/nginx.conf
1、log_format name string ...;
string可以使用nginx核心模塊及其它模塊內嵌的變數

2、access_log path [format[buffer=size][gzip[=level]][flush=time][if=condition]];
access_log off;
訪問日志檔案路徑,格式及相關的緩沖的配置
buffer=size
flush=time

配合access_log 
access_log可以配置在 http,server,location,if in location,limit_except 這些作用域

日志時間格式:log_format
$time_iso8601
#vim /etc/nginx/nginx.conf 修改[$time_local]為[$time_iso8601]

在這里插入圖片描述
在這里插入圖片描述

日志快取

3、open_log_file_cache max=N[inactiv=time][min_uses=N][valid=time];
open_log_file_cache off;
快取各日志檔案相關的元資料資訊
max:快取的最大檔案描述符數量
min_uses:在inactive指定的時長內訪問大于等于此值方可被當作活動項
inactive:非活動時長
valid:驗證快取中各快取項是否為活動項的時間間隔

ngx_http_gzip_module

用gzip方法壓縮回應資料,節約帶寬

用gzip方法壓縮回應資料,節約帶寬
1、gzip on|off;
啟用或禁用gzip壓縮
2、gzip comp-level level;
壓縮比由低到高 1到9,默認:1
3、gzip_disable regex ...;
匹配到客戶端瀏覽器不執行壓縮
4、gzip_min_length length;
啟用壓縮功能的回應報文大小閾值
5、gzip_http_version 1.0|1.1;
設定啟用壓縮功能時,協議的最小版本, 默認:1.1版本
6、gzip_buffers number size;
支持實作壓縮功能時緩沖區數量及每個快取區的大小, 默認:324k或168k
7、gzip_types mime-type ...;
指明僅對哪些型別的資源執行壓縮操作;即壓縮過濾器
默認包含有text/html,不用顯示指定,否則出錯
8、gzip_vary on|off;
如果啟用壓縮,是否在回應報文首部插入“Vary:Accept-Encoding”
9、gzip_proxied off|expired|no-cache|no-store|private|no_last_modified|no_etag|auth|any ...;
nginx充當代理服務器時,對于后端服務器的回應報文,在何種條件下啟用壓縮功能
off:不啟用壓縮
expired,no-cache,no-store,private:對后端服務器的回應報文首部Cache-Control值任何一個,啟用壓縮功能
示例:
	gzip on;
	gzip_comp_level 6
	gzip_min_length 64;  位元組
	gzip_proxied any;
	gzip_types text/xml text/css application/javascript text/plain;  默認html壓縮
	gzip

🌟ngx_http_limit

  • 連接頻率限制 - limit_conn_module
  • 請求頻率限制 - limit_req_module

ngx_http_limit_req_module

漏桶演算法
在這里插入圖片描述

演算法思想是:

  • 水(請求)從上方倒入水桶,從水桶下方流出(被處理);
  • 來不及流出的水存在水桶中(緩沖),以固定速率流出;
  • 水桶滿后水溢位(丟棄),
  • 這個演算法的核心是:快取請求、勻速處理、多余的請求直接丟棄,
    相比漏桶演算法,令牌桶演算法不同之處在于它不但有一只“桶”,還有個佇列,這個桶是用來存放令牌的,佇列才是用來存放請求的,

從作用上來說,漏桶和令牌桶演算法最明顯的區別就是是否允許突發流量(burst)的處理,漏桶演算法能夠強行限制資料的實時傳輸(處理)速率,對突發流量不欄位外處理;而令牌桶演算法能夠在限制資料的平均傳輸速率的同時允許某種程度的突發傳輸,

Nginx按請求速率限速模塊使用的是漏桶演算法,即能夠強行保證請求的實時處理速度不會超過設定的閾值,

ngx_http_limit_req_module 模塊可以通過定義的 鍵值來限制請求處理的頻率,它可以限制來自單個IP地址的請求處理頻率, 限制的方法是通過一種“漏桶”的方法——固定每秒處理的請求數,推遲過多的請求處理,

示例

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
#設定一塊共享記憶體限制域的引數,它可以用來保存鍵值的狀態, 它特別保存了當前超出請求的數量, 鍵的值就是指定的變數(空值不會被計算),
#狀態被存在名為“one”,最大10M位元組的共享記憶體里面,對于這個限制域來說 平均處理的請求頻率不能超過每秒一次,
#鍵值是客戶端的IP地址, 如果不使用$remote_addr變數,而用$binary_remote_addr變數, 可以將每條狀態記錄的大小減少到64個位元組,這樣1M的記憶體可以保存大約1萬6千個64位元組的記錄,  
    ...
    server {
        ...
        location /search/ {
            limit_req zone=one burst=5;  # 限制平均每秒不超過一個請求,同時允許超過頻率限制的請求數不多于5個,
        }
}

作用域:http,server,location

如果請求的頻率超過了限制域配置的值,請求處理會被延遲,所以 所有的請求都是以定義的頻率被處理的, 超過頻率限制的請求會被延遲,直到被延遲的請求數超過了定義的閾值 這時,這個請求會被終止,并回傳503 (Service Temporarily Unavailable) 錯誤,這個閾值的默認值等于0,

如果不希望超過的請求被延遲,可以用nodelay引數:limit_req zone=one burst=5 nodelay;

limit_req zone 引數配置

Syntax:	limit_req zone=name [burst=number] [nodelay];
Default:	—
Context:	http, server, location
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
作用域:http
  • 第一個引數:$binary_remote_addr 表示通過remote_addr這個標識來做限制,“binary_”的目的是縮寫記憶體占用量,是限制同一客戶端ip地址,
  • 第二個引數:zone=one:10m表示生成一個大小為10M,名字為one的記憶體區域,用來存盤訪問的頻次資訊,
  • 第三個引數:rate=1r/s表示允許相同標識的客戶端的訪問頻次,這里限制的是每秒1次,還可以有比如30r/m的,
limit_req zone=one burst=5 nodelay;
作用域:http、server、location
  • 第一個引數:zone=one 設定使用哪個配置區域來做限制,與上面limit_req_zone 里的name對應,
  • 第二個引數:burst=5,burst爆發的意思,這個配置的意思是設定一個大小為5的緩沖區當有大量請求(爆發)過來時,超過了訪問頻次限制的請求可以先放到這個緩沖區內,
  • 第三個引數:nodelay,如果設定,超過訪問頻次而且緩沖區也滿了的時候就會直接回傳503,如果沒有設定,則所有請求會等待排隊,

日志相關 limit_req_log_level

語法:limit_req_log_level info | notice | warn | error;
默認值:limit_req_log_level error;
背景關系:http, server, location

設定你所希望的日志級別,當服務器因為頻率過高拒絕或者延遲處理請求時可以記下相應級別的日志, 延遲的記錄的日志級別比拒絕的記錄低一個級別;比如, 如果設定“limit_req_log_level notice”, 延遲的日志就是info級別,

Syntax:	limit_req_status code;
Default:	
limit_req_status 503;
Context:	http, server, location

設定拒絕請求的回傳值,值只能設定 400 到 599 之間,

ngx_http_limit_conn_module

這個模塊用來限制單個IP的請求數,并非所有的連接都被計數,只有在服務器處理了請求并且已經讀取了整個請求頭時,連接才被計數,

Syntax:	limit_conn zone number;
Default:	—
Context:	http, server, location

一次只允許每個IP地址一個連接,

limit_conn_zone $binary_remote_addr zone=addr:10m;

server {
    location /download/ {
        limit_conn addr 1;
    }

可以配置多個limit_conn指令,例如,以下配置將限制每個客戶端IP連接到服務器的數量,同時限制連接到虛擬服務器的總數,

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;

server {
    ...
    limit_conn perip 10;
    limit_conn perserver 100;
}

如果區域存盤耗盡,服務器會將錯誤回傳給所有其他請求,

Syntax:	limit_conn_zone key zone=name:size;
Default:	—
Context:	http
limit_conn_zone $binary_remote_addr zone=addr:10m;

當服務器限制連接數時,設定所需的日志記錄級別,

Syntax:	limit_conn_log_level info | notice | warn | error;
Default:	
limit_conn_log_level error;
Context:	http, server, location

設定拒絕請求的回傳值,

Syntax:	limit_conn_status code;
Default:	
limit_conn_status 503;
Context:	http, server, location

實戰

1、限制訪問速率

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit;
    }
}

上述規則限制了每個IP訪問的速度為2r/s,并將該規則作用于根目錄,

2、burst快取處理

Nginx按照毫秒級精度統計,超出限制的請求直接拒絕,這在實際場景中未免過于苛刻,真實網路環境中請求到來不是勻速的,很可能有請求“突發”的情況,也就是“一股子一股子”的,Nginx考慮到了這種情況,可以通過burst關鍵字開啟對突發請求的快取處理,而不是直接拒絕,

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4;
    }
}

加入了burst=4,意思是每個key(此處是每個IP)最多允許4個突發請求的到來,

通過burst引數,我們使得Nginx限流具備了快取處理突發流量的能力,

但是請注意:burst的作用是讓多余的請求可以先放到佇列里,慢慢處理,如果不加nodelay引數,佇列里的請求不會立即處理,而是按照rate設定的速度,以毫秒級精確的速度慢慢處理,

3、nodelay降低排隊時間

通過設定burst引數,我們可以允許Nginx快取處理一定程度的突發,多余的請求可以先放到佇列里,慢慢處理,這起到了平滑流量的作用,但是如果佇列設定的比較大,請求排隊的時間就會比較長,用戶角度看來就是RT變長了,這對用戶很不友好,有什么解決辦法呢?nodelay引數允許請求在排隊的時候就立即被處理,也就是說只要請求能夠進入burst佇列,就會立即被后臺worker處理,請注意,這意味著burst設定了nodelay時,系統瞬間的QPS可能會超過rate設定的閾值,nodelay引數要跟burst一起使用才有作用,

實體二的配置,加入nodelay選項:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4 nodelay;
    }
}

總體耗時變短

但是請注意,雖然設定burst和nodelay能夠降低突發請求的處理時間,但是長期來看并不會提高吞吐量的上限,長期吞吐量的上限是由rate決定的,因為nodelay只能保證burst的請求被立即處理

4、自定義回傳值

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4 nodelay;
        limit_req_status 598;
    }
}

默認情況下 沒有配置 status 回傳值的狀態:404

https://www.cnblogs.com/biglittleant/p/8979915.html

四、Nginx命令

Nginx:默認為啟動nginx

-h 查看幫助選項

-V 查看版本和配置選項

-t 測驗配置nginx.conf檔案是否有語法錯誤

-s reload 重新加載Nginx組態檔,平滑啟動Nginx, 或者kill -HUP /var/run/nginx.pid

-s stop 強制停止Nginx服務

-s quit 優雅地停止Nginx服務(即處理完所有請求后再停止服務)

-c filename.conf 使用另一個組態檔

#查看 nginx 行程
ps -ef|grep nginx
#完美停止 nginx
kill -QUIT `cat /var/run/nginx.pid`
#快速停止 nginx
kill -TERM `cat /var/run/nginx.pid`
或者
kill -INT `cat /var/run/nginx.pid`
#完美停止作業行程(主要用于平滑升級)
kill -WINCH `cat /var/run/nginx.pid`
#強制停止 nginx
pkill -9 nginx
#檢查對 nginx.conf 檔案的修改是否正確
nginx -t -c /etc/nginx/nginx.conf 或者 nginx -t
#停止 nginx 的命令
nginx -s stop 或者 pkill nginx
#查看 nginx 的版本資訊
nginx -v
#查看完整的 nginx 的配置資訊
nginx -V

五、Nginx優化

Nginx服務器高性能優化–輕松實作10萬并發訪問量

如何使Nginx輕松實作10萬并發訪問量,

通常來說,一個正常的 Nginx Linux 服務器可以達到 500,000 – 600,000 次/秒 的請求處理性能,如果Nginx服務器經過優化的話,則可以穩定地達到 904,000 次/秒 的處理性能,大大提高Nginx的并發訪問量,

分析:nginx要成功回應請求,會有如下兩個限制:

1、nginx接受的tcp連接多,能否建立起來?

2、nginx回應程序,要打開許多檔案,能否打開?

所以,只要我們針對上面兩個限制進行優化,就能大幅提升Nginx的效率,

在這里插入圖片描述

Nginx優化配置項:

1)優化 workprocess,cpu

worker_processes 8;      // 根據CPU核數配置
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000  00100000 01000000 10000000;

2)事件處理模型優化
nginx的連接處理機制在于不同的作業系統會采用不同的I/O模型,Linux下,nginx使用epoll的I/O多路復用模型,在freebsd使用kqueue的IO多路復用模型,在solaris使用/dev/pool方式的IO多路復用模型,在windows使用的icop等等,要根據系統型別不同選擇不同的事務處理模型,我們使用的是Centos,因此將nginx的事件處理模型調整為epoll模型,

events {
    worker_connections  10240;    //
    use epoll;
}

說明:在不指定事件處理模型時,nginx默認會自動的選擇最佳的事件處理模型服務,

3)設定work_connections 連接數

 worker_connections  10240;

4)每個行程的最大檔案打開數

worker_rlimit_nofile 65535;  # 一般等于ulimit -n系統值

5)keepalive timeout會話保持時間

keepalive_timeout  60;

6)GZIP壓縮性能優化

gzip on;       #表示開啟壓縮功能
gzip_min_length  1k; #表示允許壓縮的頁面最小位元組數,頁面位元組數從header頭的Content-Length中獲取,默認值是0,表示不管頁面多大都進行壓縮,建議設定成大于1K,如果小于1K可能會越壓越大
gzip_buffers     4 32k; #壓縮快取區大小
gzip_http_version 1.1; #壓縮版本
gzip_comp_level 6; #壓縮比率, 一般選擇4-6,為了性能gzip_types text/css text/xml application/javascript;  #指定壓縮的型別 gzip_vary on; #vary header支持

7)proxy超時設定

proxy_connect_timeout 90;
proxy_send_timeout  90;
proxy_read_timeout  4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k

8)高效傳輸模式

sendfile on; # 開啟高效檔案傳輸模式,
tcp_nopush on; #需要在sendfile開啟模式才有效,防止網路阻塞,積極的減少網路報文段的數量,將回應頭和正文的開始部分一起發送,而不一個接一個的發送,

Linux系統內核層面:(具體還得看檔案)

https://blog.csdn.net/chenlin465373800/article/details/78924780

Nginx要達到最好的性能,出了要優化Nginx服務本身之外,還需要在nginx的服務器上的內核引數,

這些引數追加到/etc/sysctl.conf,然后執行sysctl -p 生效,

1)容納更多等待連接的網路連接數

#表示SYN佇列的長度,默認為1024,加大佇列長度為8192,可以容納更多等待連接的網路連接數,  
net.ipv4.tcp_max_syn_backlog = 4096

2)允許等待中的監聽

net.core.somaxconn = 65535   # 場景:經常處理新請求(request)的高負載的服務

對于一個TCP鏈接,Server與Client需要通過三次握手來建立網路鏈接,當三次握手成功之后,我們就可以看到埠狀態由LISTEN轉為ESTABLISHED,接著這條鏈路上就可以開始傳送資料了

net.core.somaxconn是Linux中的一個內核(kernel)引數,表示socket監聽(listen)的backlog上限,
什么是backlog?backlog就是socket的監聽佇列,當一個請求(request)尚未被處理或者建立時,它就會進入backlog,
而socket server可以一次性處理backlog中的所有請求,處理后的請求不再位于監聽佇列中,
當Server處理請求較慢時,導致監聽佇列被填滿后,新來的請求就會被拒絕, 默認值是128

3) tcp連接重用

net.ipv4.tcp_tw_recycle = 1 
net.ipv4.tcp_tw_reuse = 1   

4)不抵御洪水攻擊

net.ipv4.tcp_syncookies = 0    #當出現SYN等待佇列溢位時,啟用cookies來處理,可防范少量SYN攻擊,默認為0,表示關閉 
net.ipv4.tcp_max_orphans = 262144  #該引數用于設定系統中最多允許存在多少TCP套接字不被關聯到任何一個用戶檔案句柄上,主要目的為防止Ddos攻擊

5)最大檔案打開數

在命令列中輸入如下命令,即可設定Linux最大檔案打開數,

ulimit -n 30000

其他小優化

1.隱藏版本資訊

一般來說,軟體的漏洞都與版本有關,隱藏版本號是為了防止惡意用戶利用軟體漏洞進行攻擊

http {
	...
	server_tokens off; # 隱藏版本號
	server{
	
	}
}
重新加載    nginx -s reload

2.優化nginx行程個數

nginx.conf下 worker_processes 2

Nginx 有 Master 和 worker 兩種行程,Master 行程用于管理 worker 行程,worker 行程用于 Nginx 服務

worker 行程數應該設定為等于 CPU 的核數,高流量并發場合也可以考慮將行程數提高至 CPU 核數 * 2

  1. grep -c processor /proc/cpuinfo # 查看CPU核數
  2. vim nginx.conf # 設定worker行程數
    worker_processes 2;
    user nginx nginx;
    
  3. 檢查語法,并重新加載nginx
    ps -ef | grep nginx | grep -v grep # 驗證是否為設定的行程數

3.調整nginx單個行程允許的客戶端最大連接數

worker_connections 15000;

(1) 控制 Nginx 單個行程允許的最大連接數的引數為 worker_connections ,這個引數要根據服務器性能和記憶體使用量來調整
(2) 行程的最大連接數受 Linux 系統行程的最大打開檔案數限制,只有執行了 “ulimit -HSn 65535” 之后,worker_connections 才能生效
(3) 連接數包括代理服務器的連接、客戶端的連接等,Nginx 總并發連接數 = worker 數量 * worker_connections, 總數保持在3w左右

cat /usr/local/nginx/conf/nginx.conf
worker_processes 2;
worker_cpu_affinity 01 10;
user nginx nginx;
events {
use epoll;
worker_connections 15000;
}

4.開啟高效檔案傳輸模式

(1) sendfile 引數用于開啟檔案的高效傳輸模式,該引數實際上是激活了 sendfile() 功能,sendfile() 是作用于兩個檔案描述符之間的資料拷貝函式,這個拷貝操作是在內核之中的,被稱為 “零拷貝” ,sendfile() 比 read 和 write 函式要高效得多,因為 read 和 write 函式要把資料拷貝到應用層再進行操作
(2) tcp_nopush 引數用于激活 Linux 上的 TCP_CORK socket 選項,此選項僅僅當開啟 sendfile 時才生效,tcp_nopush 引數可以允許把 http response header 和檔案的開始部分放在一個檔案里發布,以減少網路報文段的數量

cat /usr/local/nginx/conf/nginx.conf
......
http {
	include mime.types;
	server_names_hash_bucket_size 512;
	default_type application/octet-stream;
	sendfile on; # 開啟檔案的高效傳輸模式
	tcp_nopush on; # 激活 TCP_CORK socket 選擇
	tcp_nodelay on; # 資料在傳輸的程序中不進快取
	keepalive_timeout 65;
	server_tokens off;
	include vhosts/*.conf;
}

tcp_nodelay這其中采取了Nagle演算法
資料只有在寫快取中累積到一定量之后,才會被發送出去,這樣明顯提高了網路利用率(實際傳輸資料payload與協議頭的比例大大提高),但是這又不可避免地增加了延時;與TCP delayed ack這個特性結合,這個問題會更加顯著,延時基本在40ms左右,當然這個問題只有在連續進行兩次寫操作的時候,才會暴露出來,

連續進行多次對小資料包的寫操作,然后進行讀操作,本身就不是一個好的網路編程模式;在應用層就應該進行優化,
 

5.Nginx gzip壓縮實作性能優化

ngx_http_gzip_module 配置在http標簽端
gzip on 默認是關閉的
https://www.cnblogs.com/kevingrace/p/10018914.html

[root@localhost ~]# vim /usr/local/nginx/conf/nginx.conf

#修改配置為
gzip on;               #開啟gzip壓縮功能
gzip_min_length 1k;    #設定允許壓縮的頁面最小位元組數
gzip_buffers 4 16k;    #設定壓碩訓沖區大小,此處設定為4個16K記憶體作為壓縮結果流快取
gzip_http_version 1.1; #壓縮版本
gzip_comp_level 2;     #設定壓縮比率,最小為1,處理速度快,傳輸速度慢;9為最大壓縮比,處理速度慢,傳輸速度快
gzip types text/css text/xml application/javascript; #制定壓縮的型別
gzip vary on;          #選擇支持vary header;改選項可以讓前端的快取服務器快取經過gzip壓縮的頁面

如下是線上常使用的Gzip壓縮配置

http {
.......
    gzip  on;
    gzip_min_length  1k;
    gzip_buffers     4 16k;
    gzip_http_version 1.1;
    gzip_comp_level 9;
    gzip_types       text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/javascript application/json;
    gzip_disable "MSIE [1-6]\.";
    gzip_vary on;
 
}

6.nginx防爬蟲優化

if ($http_user_agent ~* "qihoobot|Baiduspider|Googlebot|Googlebot-Mobile|Googlebot-Image|Mediapartners-Google|Adsbot-Google|Yahoo! Slurp China|YoudaoBot|Sosospider|Sogou spider|Sogou web spider|MSNBot") {
return 403;
} 禁止百度 谷歌等等來爬取我們的網站

=:對URI做精確匹配
^~:對URI的最左邊部分做匹配檢查,不區分字符大小寫 
~:對URI做正則運算式模式匹配,區分字符大小寫 
~*:對URI做正則運算式模式匹配,不區分字符大小寫 
不帶符號:匹配起始于此uri的所有uri 匹配優先級從高到低: =,^~,~/~*,不帶符號(相當于直接一個杠/) 

🌟7.控制nginx并發連接數量

limit_conn
https://www.cnblogs.com/biglittleant/p/8979915.html
https://www.cnblogs.com/pengyunjing/p/10662612.html

Nginx官方版本限制IP的連接和并發分別有兩個模塊:

  • limit_req_zone 用來限制單位時間內的請求數,即速率限制,采用的漏桶演算法 “leaky bucket”,
  • limit_req_conn 用來限制同一時間連接數,即并發限制,

訪問限制模塊limit_req_zone 和limit_req_conn兩個組件來對客戶端訪問目錄和檔案的訪問頻率和次數進行限制

1. 限制單個 IP 的并發連接數   addr注意
限制連接數:
要限制連接,必須先有一個容器對連接進行計數,在http段加入如下代碼:
"zone=" 給它一個名字,可以隨便叫,這個名字要跟下面的 limit_conn 一致
$binary_remote_addr = 用二進制來儲存客戶端的地址,1m 可以儲存 32000 個并發會話
....
http {
	include mime.types;
	default_type application/octet-stream;
	sendfile on;
	keepalive_timeout 65;
	limit_conn_zone $binary_remote_addr zone=addr:10m; # 用于設定共享記憶體區域,addr 是共享記憶體區域的名稱,10m 表示共享記憶體區域的大小
	
	server {
		listen 80;
		server_name www.abc.com;
		location / {
			root html/www;
			index index.html index.htm;
			limit_conn addr 1; # 限制單個IP的并發連接數為1
			}
	}
}
http {
	limit_conn_zone $binary_remote_addr zone=addr:10m;

接下來需要對server不同的位置(location段)進行限速,比如限制每個IP并發連接數為1,則
	server {
		listen 80;
		server_name 192.168.11.128;
		index index.html index.htm index.php;
		limit_conn addr 1; #是限制每個IP只能發起1個連接 (addr 要跟 limit_conn_zone 的變數對應)
		limit_rate 100k; #限速為 100KB/秒
		root html;

注意事項:
limit_rate 100k; //是對每個連接限速100k,這里是對連接限速,而不是對IP限速!如果一個IP允許兩個并發連接,那么這個IP就是限速limit_rate * 2

8.nginx錯誤頁面優雅顯示

http {
	location / {
		root html/www;
		index index.html index.htm;
		error_page 400 401 402 403 404 405 408 410 412 413 414 415 500 501 502 503 506 = http://www.xxxx.com/error.html;
將這些狀態碼的頁面鏈接到 http://www.xxxx.com/error.html ,也可以單獨指定某個狀態碼的頁面
如 error_page 404 /404.html
	}
}

9.防盜鏈

什么是防盜鏈:簡單地說,就是某些不法網站未經許可,通過在其自身網站程式里非法呼叫其他網站的資源,然后在自己的網站上顯示這些呼叫的資源,使得被盜鏈的那一端消耗帶寬資源 ,

(1) 根據 HTTP referer 實作防盜鏈:referer 是 HTTP的一個首部欄位,用于指明用戶請求的 URL 是從哪個頁面通過鏈接跳轉過來的

(2) 根據 cookie 實作防盜鏈:cookie 是服務器貼在客戶端身上的 “標簽” ,服務器用它來識別客戶端

根據 referer 配置防盜鏈:

#第一種,匹配后綴
location ~ .*.(gif|jpg|jpeg|png|bm|swf|flv|rar|zip|gz|bz2)$ { # 指定需要使用防盜鏈的媒體資源
  access_log off; # 不記錄防盜鏈的日志=
  expires 15d; # 設定快取時間
  valid_referers none blocked *.test.com *.abc.com; # 表示這些地址可以訪問上面的媒體資源
  # 
  if ($invalid_referer) { # 如果地址不如上面指定的地址就回傳403
  return 403
	}
}

#第二種,系結目錄
location /images {
  root /web/www/img;
  vaild_referers nono blocked *.spdir.com *.spdir.top;
  if ($invalid_referer) {
  return 403;
  }
}

valid_referers 指令詳解
該指令后面可以接 none blocked serevr_names string或者是正則運算式

  • 第一個引數none 代表沒有referer

  • 第二個引數blocked 代表有referer但是被防火墻或者是代理給去除了

  • 第三個引數string或者運算式 用來匹配referer
    nginx會通過查看referer欄位和valid_referers后面的referer串列進行匹配,如果匹配到了就invalid_referer欄位值為0 否則設定該值為1

10.優化nginx連接超時時間

  1. 什么是連接超時

當服務器建立的連接沒有接收處理請求時,可以在指定的時間內讓它超時自動退出

  1. 連接超時的作用

(1) 將無用的連接設定為盡快超時,可以保護服務器的系統資源(CPU、記憶體、磁盤)

(2) 當連接很多時,及時斷掉那些建立好的但又長時間不做事的連接,以減少其占用的服務器資源

(3) 如果用戶請求了動態服務,則 Nginx 就會建立連接,請求 FastCGI 服務以及后端 MySQL 服務,設定連接超時,使得在用戶容忍的時間內回傳資料

  1. 連接超時存在的問題

(1) 服務器建立新連接是要消耗資源的,因此,連接超時時間不宜設定得太短,否則會造成并發很大,導致服務器瞬間無法回應用戶的請求

(2) 有些 PHP 站點會希望設定成短連接,因為 PHP 程式建立連接消耗的資源和時間相對要少些

(3) 有些 Java 站點會希望設定成長連接,因為 Java 程式建立連接消耗的資源和時間要多一些,這時由語言的運行機制決定的

  1. 設定連接超時

(1) keepalive_timeout :該引數用于設定客戶端連接保持會話的超時時間,超過這個時間服務器會關閉該連接

(2) client_header_timeout :該引數用于設定讀取客戶端請求頭資料的超時時間,如果超時客戶端還沒有發送完整的 header 資料,服務器將回傳 “Request time out (408)” 錯誤

(3) client_body_timeout :該引數用于設定讀取客戶端請求主體資料的超時時間,如果超時客戶端還沒有發送完整的主體資料,服務器將回傳 “Request time out (408)” 錯誤

(4) send_timeout :用于指定回應客戶端的超時時間,如果超過這個時間,客戶端沒有任何活動,Nginx 將會關閉連接

(5) tcp_nodelay :默認情況下當資料發送時,內核并不會馬上發送,可能會等待更多的位元組組成一個資料包,這樣可以提高 I/O 性能,但是,在每次只發送很少位元組的業務場景中,使用 tcp_nodelay 功能,等待時間反而會比較長

http {
  include mime.types;
  server_names_hash_bucket_size 512;
  default_type application/octet-stream;
  sendfile on;
  keepalive_timeout 65;
  tcp_nodelay on;
  client_header_timeout 15;
  client_body_timeout 15;
  send_timeout 25;
  include vhosts/*.conf;
}

11.限制檔案上傳大小

client_max_body_size

client_max_body_size 用于設定最大的允許客戶端請求主體的大小,在請求首部中有 "Content-Length" ,如果超過了此配置項,客戶端會收到 413 錯誤,即請求的條目過大

http {
	include mime.types;
	server_names_hash_bucket_size 512;
	default_type application/octet-stream;
	sendfile on;
	keepalive_timeout 65;
	server_tokens off;
	client_max_body_size 8m; # 設定客戶端最大的請求主體大小為8M
	include vhosts/*.conf;
}

12、系結Nginx行程到不同的CPU上

默認情況下,Nginx 的多個行程有可能跑在某一個 CPU 或 CPU 的某一核上,導致 Nginx 行程使用硬體的資源不均,因此系結 Nginx 行程到不同的 CPU 上是為了充分利用硬體的多 CPU 多核資源的目的,

  1. 查看cpu個數

grep -c processor /proc/cpuinfo # 查看CPU核數

cpu的個數不同系結親和力方法也不同

worker_processes 2; # 2核CPU的配置
worker_cpu_affinity 01 10;
worker_processes 4; # 4核CPU的配置
worker_cpu_affinity 0001 0010 0100 1000;
worker_processes 8; # 8核CPU的配置
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 1000000;

六、Nginx演算法

常用四種演算法:
1.RR:(默認)輪詢,每個請求按時間順序逐一輪流分配到后端服務器;
2.weight:指定輪詢幾率,weight和訪問比率成正比,根據后端服務器性能分配;
3.ip_hash:每個請求按訪問ip的hash結果分配,這樣每個訪客固定訪問一個后端服務器,可以解決session的問題,
4.fair:(擴展策略),默認不被編譯nginx內核,根據后端服務器回應時間判斷負載情況,選擇最輕的進行處理,

輪詢策略:實作請求的按順序轉發,即從服務srv1–srv2–srv3依次來處理請求

http{
	upstream myapp1 {
		server srv1.example.com;
		server srv2.example.com;
		server srv3.example.com;
	}
	
	server {
		listen 80;
		location / {
			proxy_pass http://myapp1;
		}
	}
}

加權輪詢策略:請求將按照服務器的設定權重來實作請求轉發和處理,如下所示,最終請求處理數將為3:1:1,一般情況下加權的依據是根據服務器配置來決定的,即配置好的機器分配的權重高,

	upstream myapp1 {
      server srv1.example.com weight=3;
      server srv2.example.com;
      server srv3.example.com;
	}

最少連接數策略:請求將轉發到連接數較少的服務器上

	upstream myapp1 {
			least_conn;
      server srv1.example.com;
      server srv2.example.com;
      server srv3.example.com;
	}

Main全域配置常見的配置指令分類

  • 正常運行必備的配置
  • 優化性能相關的配置
  • 用于除錯及定位問題相關的配置
  • 事件驅動相關的配置

幫助檔案:http://nginx.org/en/docs/

正常運行必備的配置

 1、user
 Syntax:  user user [group];
 Default:  user nobody nobody;
 Context:  main
 指定worker行程的運行身份,如組不指定,默認和用戶名同名
 2、pid /PATH/TO/PID_FILE
 指定存盤nginx主行程PID的檔案路徑
 3、include file|mask
 指明包含進來的其他組態檔片斷
 4、load_module file
 模塊加載組態檔:/usr/share/nginx/modules/*.conf
 指明要裝在的動態模塊路徑:/usr/lib64/nginx/modules
 性能優化相關的配置:
 1、worker_processes number | auto
 worker行程的數量;通常應該為當前主機的cpu的物理核心數
 
 2、worker_cpu_affinity cpumask ...
 worker_cpu_affinity auto [cpumask] 提高快取命中率
 CPU MASK:00000001:0號CPU
      00000010:1號CPU
      10000000:8號CPU
  worker_cpu_affinity 0001 0010 0100 1000;
  worker_cpu_affinity 0101 1010;
  
 3、worker_priority number
 指定worker行程的nice值,設定worker行程的優先級:[-20,20]
 
 4、worker_rlimit_nofile number
 worker行程所能夠打開的檔案數量上限,如65535
 事件驅動相關的配置:
 events{
   ...
 }
 1、worker_connections number
 每個worker行程所能夠打開的最大并發連接數數量,如10240
 總最大并發數:worker_processes * worker_connections
 2、use method
 指明并發連接請求的處理方法,默認自動選擇最優方法
   use epoll;
 3、accept_mutex on|off 互斥
 處理新的連接請求的方法;on指由各個worker輪流處理新請求,off指每個新請求的到達都會通知(喚醒)所有的worker行程,但只有一個行程可獲得連接,造成'驚群',影響性能
 
 示例:
 worker_processes 2; 
 worker_rlimit_nofile 65535;
 #pid logs/nginx.pid; 
 events { 
    worker_connections 65535; 
 }
 除錯和定位問題:
 1、daemon on|off
 是否以守護行程方式運行nginx,默認是守護行程方式,  配置放在全域塊,off之后可以到前臺運行
 2、master_process on|off
 是否以master/worker模型運行nginx;默認為on,   off將不啟動worker
 3、error_log filename [level]
 錯誤日志檔案及其級別;出于除錯需要,可設定為debug;但debug僅在編譯時使用了“--with-debug”選項時才有效
 方式:file /path/logfile;
 stderr:發送到標準錯誤
 syslog:server-address[,parameter=values]:發送到syslog memory:size 記憶體
 level:debug|info|notice|warn|error|crit|alter|emerg
 location ^~ /sta/ {  
    alias /usr/local/nginx/html/static/;  
 }
 location ^~ /tea/ {  
    root /usr/local/nginx/html/;  
 }
 server {
     listen      8082;
     server_name localhost;
 
     location ^~ /root/ {
         root    /data/www/root/;
         index   index.html index.htm;
     }
 
     location ^~ /alias/ {
         alias   /data/www/alias/;
         index   index.html index.htm;
     }
 }
 server {
     listen      8082;
     server_name localhost;
 
     location ^~ /alias/ {
         echo "WITH: /";
     }
 
     location ^~ /alias {
         echo "WITHOUT: /";
     }
 }
 server {
     listen        7000;
     server_name   localhost;
     
     location / {
         root  /data/www/proxy_pass;
     }
 }
 # 代理不帶專案名稱,沒有 /
 server {
     listen      7001;
     server_name locahost;
     
     location /proxy/ {
         proxy_pass  http://127.0.0.1:7000;
     }
 }
 
 # 代理不帶專案名稱,但是有 /
 server {
     listen      7002;
     server_name locahost;
     
     location /proxy/ {
         proxy_pass  http://127.0.0.1:7000/;
     }
 }
 
 # 代理帶專案名稱,沒有 /
 server {
     listen      7003;
     server_name locahost;
     
     location /proxy/ {
         proxy_pass  http://127.0.0.1:7000/other;
     }
 }
 
 # 代理帶專案名稱,但是有 /
 server {
     listen      7004;
     server_name locahost;
     
     location /proxy/ {
         proxy_pass  http://127.0.0.1:7000/other/;
     }
 }

在 proxy_pass 中,當我們不是 / 匹配而是帶有自定義專案名匹配的時候:

  1. proxy_pass 后面帶 /,我們的自定義的專案名就不會被視作路徑的一部分去查找后端,

  2. proxy_pass 后面不帶 /,我們自定義的專案名會當成路徑的一部分添加到代理后端的查找中,

當我們在 proxy_pass 代理的導致中還包含專案名稱的時候:

  1. 當后面還跟了專案名,我們自定義的匹配專案名就都不會再作為請求的一部分去查找后端,

  2. 當后面的專案名不帶 / 的時候,除去我們自定義部分,后面的 URI 會直接拼接到我們 proxy_pass 上面,由于他們之間沒有 / 分隔,所以會組成一個新的路徑去查后端,

  3. 當后面的專案帶 / 的時候,則會在拼接的時候相當于多了個 / 的分隔,

https://www.cnblogs.com/Dy1an/p/11254973.html


Nginx:后面有無/的區別

除錯和定位問題

通過 ps -elf | grep nginx 找到 nginx 的worker行程ID 通過 cat /proc/13016/limits 查看,注意其中的Max open files

  • 1.connections不是隨便設定的,而是與兩個指標有重要關聯,一是記憶體,二是作業系統級別的“行程最大可打開檔案數”,
  • 2.記憶體:每個連接數分別對應一個read_event、一個write_event事件,一個連接數大概占用232位元組,2個事件總占用96位元組,那么一個連接總共占用328位元組,通過數學公式可以算出100000個連接數大概會占用 31M = 100000 * 328 / 1024 / 1024,當然這只是nginx啟動時,connections連接數所占用的nginx,
  • 3.行程最大可打開檔案數:行程最大可打開檔案數受限于作業系統,可通過 ulimit -n 命令查詢,以前是1024,現在是65535, nginx提供了worker_rlimit_nofile指令,這是除了ulimit的一種設定可用的描述符的方式, 該指令與使用ulimit對用戶的設定是同樣的效果,此指令的值將覆寫ulimit的值,如:worker_rlimit_nofile xxx; 設定ulimits:ulimit -SHn xxx

WEB虛擬主機案例

vhost匹配

vhost的三種形式:

  • 完整字串(exact)
  • 通配符(*)(asterisk)
  • 正則運算式(regular)

匹配順序:Exact > asterisk(prefix > suffix)>regular

1)在企業生產環境中,通常Nginx不僅僅只發布一套默認網站,會發布多個網站、提高服務器的利用率,避免資源的浪費,
2)將Nginx WEB服務器發布多套網站的方法,稱為虛擬主機(多個網站),Nginx發布虛擬主機的方式主要有以下三種:

  • 基于IP的虛擬主機:同一個8888埠,不同的IP訪問IP地址; servername IP

  • 基于埠的虛擬主機:同一個IP地址,不同的訪問埠; listen xx

  • 基于域名的虛擬主機:同一個8888埠,同一個IP地址,不同的訪問域名, √ servername domain_name

3)基于Nginx WEB服務器,通過同一個8888埠,同一個IP地址,不同的訪問域名,發布兩個網站,兩個網站對應的域名分別是:wp.kolorbgg.top、love.kolorbgg.top,要求用戶通過瀏覽器實作兩個網站頁面的訪問,操作的步驟和方法如下:

#去除Nginx主組態檔中#和空行 三種方式;
awk '!/#/' nginx.conf|awk '!/^$/'   不看井號和空行,加!
sed -e '/#/d' -e '/^$/d' nginx.conf
grep -aivE "#|^$" nginx.conf

如上組態檔過濾、洗掉,nginx.conf主組態檔Server虛擬主機的核心代碼:

	server {
        listen       8888;
        server_name  wp.kolorbgg.top;
        location / {
            root   html/nextcloud;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    server {
        listen       8888;
        server_name  love.kolorbgg.top;
        location / {
            root   html/love;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

創建兩個虛擬主機的發布目錄&默認引導檔案index.html,操作的方法和指令如下:

#創建兩套網站發布目錄;
mkdir -p /usr/local/nginx/html/love/
mkdir -p /usr/local/nginx/html/nextcloud/
#往nextcloud發布目錄中index.html寫入測驗資料;
cat>/usr/local/nginx/html/nextcloud/index.html <<EOF
<h1>nextcloud Test Pages.</h1>
<hr color=red>
EOF
#往love發布目錄中index.html寫入測驗資料;
cat>/usr/local/nginx/html/love/index.html <<EOF
<h1>love Test Pages.</h1>
<hr color=red>
EOF

[root@aliyun conf]# /usr/local/nginx/sbin/nginx -s reload

Nginx的生命周期(stage)

  • ngx_event_accept()
  • ngx_http_init_connection()
  • ngx_http_wait_request_handler()
  • ngx_http_process_request_line()
  • ngx_http_process_request_headers()
  • ngx_http_core_run_phases()
  • ngx_http_finalize_request()
  • ngx_http_finalize_connection()

Nginx FAQ

1、Nginx的優缺點

優點:

  • 輕量級服務,比Apache占用更少的記憶體及資源
  • 并發能力強,nginx處理請求是異步非阻塞的,而apache則是阻塞型的,在高并發下nginx能保持低資源低消耗高性能
  • 處理靜態頁面上表現的更好(簡單、占資源少)
  • 高度模塊化的設計,撰寫模塊相對簡單
  • 社區活躍,各種高性能模塊產出迅速

缺點:

  • 動態處理上需要使用fastcgi連接PHP的FPM服務,相比Apache不占優勢

nginx子行程處理用戶的行程(靜態資源左邊一塊就能完成)
如果nginx接觸的是動態資源請求,就會將動態資源請求交給fastcgi,讓fastcgi去找php服務php-fpm(專門與nginx對接的服務),再去查詢資料庫,
在這里插入圖片描述

Nginx適合做靜態處理,簡單,效率高, Apache適合做動態處理,穩定,功能強
并發較高的情況下優先選擇Nginx,并發要求不高的情況下兩者都可以,規模稍大的可以使用Nginx作為反向代理,然后將動態請求負載均衡到后端Apache上,
在這里插入圖片描述

為什么Nginx的并發能力強,資源消耗低?

答:Nginx以異步非阻塞方式作業

  • 客戶端發送request,服務器分配work行程來處理
  • 能立即處理完的,處理后work行程釋放資源,進行下一個request的處理
  • 不能立即處理完的work行程注冊回傳事件,然后接著去處理其他request
  • 當之前的request結果回傳后,觸發回傳事件,由空閑work行程接著處理通過這種快速處理,快速釋放請求的方式,達到同樣的配置可以處理更大并發量的目的,

同步阻塞型和異步阻塞型
同步:小明收到快遞將送達的短信,在樓下一直等到快遞送達,
異步:小明收到快遞將送達的短信,小明不會下樓去取,而是快遞小哥到樓下后,打電話通知小明,然后小明下樓梯取快遞,

2、Nginx是如何連接PHP進行頁面決議的

Nginx支持PHP
1.Nginx支持fastcgi功能(默認支持)
2.PHP編譯時開啟FPM服務(編譯時指定)
3.在Nginx組態檔中添加匹配規則(匹配后綴是.php)
PHP就是個決議器,被別人呼叫來決議代碼
Nginx沒法直連PHP,因此需要中間連接介面PHP-FPM的9000埠,Nginx通過自身的fastcgi工具連接PHP-FPM,

在這里插入圖片描述

3、Nginx和Tomcat之間的資料傳輸程序(動靜分離)

當用戶請求進來后,由Nginx處理靜態請求,靜態請求處理完直接回傳,
如果是動態請求,則由Nginx反向代理和負載均衡到兩個Tomcat,

在這里插入圖片描述

Nginx動靜分離

動靜分離是在反向代理的基礎上實作的
在這里插入圖片描述

Tomcat處理動態資源,Nginx回應靜態資源

動態資源做負載均衡轉發到tomcat服務器處理,每個資源都需要花1-3ms來獲取,而且回應頭沒有Cache-Control欄位

靜態資源訪問本地磁盤html檔案夾里的靜態資源,靜態資源都是直接從磁盤獲取,回應頭都有Cache-Control欄位,靜態資源的請求時間均為0ms,

4、Nginx處理一個請求的程序

  1. Nginx 使用一個多行程模型來對外提供服務,其中一個 master 行程,
  2. 多個 worker 行程,master 行程負責管理 Nginx 本身和其他 worker 行程,
  3. worker 行程中有一個函式,執行無限回圈,不斷處理收到的來自客戶端的請求,并進行處理,直到整個 Nginx 服務被停止,

??閱讀agentzh-nginx教程

set變數

初識變數(用戶自定義變數)

可以直接把變數嵌入到字串常量中

以構造出新的字串:

set $a hello; 
set $b "$a, $a";

這里我們通過已有的Nginx變數$a的值,來構造變數$b的值,于是這兩條指令順序執行完之后,$a的值是hello,而$b的值則是hello, hello. 這種技術在 Perl 世界里被稱為變數插值(variable interpolation),它讓專門的字串拼接運算子變得不再那么必要,我們在這里也不妨采用此術語,

我們來看一個比較完整的配置示例:

server {
	listen 8080;
	location /test { 
		set $foo hello;
		echo "foo: $foo"; 
	}
}

這個例子省略了 nginx.conf 組態檔中最外圍的 http 配置塊以及 events 配置塊,使用 curl 這個 HTTP 客戶端在命令列上請求這個 /test 介面,
我們可以得到
$ curl 'http://localhost:8080/test' 
foo: hello

在這里插入圖片描述

nginx配置報錯 nginx: [emerg] unknown directive "echo"
實際上,Nginx并沒有內置echo這個指令,所以你貿然使用時,自然會提示說無法識別的指令,它是由agentzh(章亦春)開發的第3方模塊,是功能強大的除錯工具,
https://openresty.org/cn/download.html

安裝后,組態檔默認路徑:

  • /usr/local/etc/openresty/nginx.conf
  • /usr/local/Cellar/openresty/1.19.3.1_1/nginx/logs/error.log
  • cat /usr/local/var/log/nginx/error.log
    在這里插入圖片描述

如果想通過 echo指令直接輸出含有美元符$的字串,有什么辦法?

  • 使用ngx_geo提供的配置指令gemo來為變數 $dollar 賦予字串 “$”,這樣我們在下面需要使用美元符的地方,就直接參考我們的 $dollar 變數就可以了,其實 ngx_geo 模塊最常規的用法是根據客戶端的 IP 地址對指定的 Nginx 變數進行賦值,這里只是借用它以便“無條件地”對我們 的 $dollar 變數賦予“美元符”這個值,

    geo $dollar { 
    	default "$";
    }
    server {
      listen 80;
      location /test {
      echo "This is a dollar sign: $dollar";
    	} 
    }
    
    測驗結果:
    kolor@kolordeMacBook-Pro ~ % curl http://127.0.0.1/test
    This is a dollar sign: $
    

內部跳轉

涉及“內部跳轉”的例子:

server {
  listen 80;
  location /foo { 
  	set $a hello;
  	echo_exec /bar; 
  }
  location /bar {
  	echo "a = [$a]";
  } 
}

這里我們在location /foo中,使用第三方模塊ngx_echo提供的echo_exec配置指令,發起到location /bar的“內部跳轉”,所謂“內部跳轉”,就 是==在處理請求的程序中,于服務器內部,從一個 location 跳轉到另一個 location 的程序==,這不同于利用 HTTP 狀態碼 301 和 302 所進行的“外部跳 轉”,因為后者是由 HTTP 客戶端配合進行跳轉的,而且在客戶端,用戶可以通過瀏覽器地址欄這樣的界面,看到請求的 URL 地址發生了變化,內部跳 轉和Bourne Shell(或Bash)中的exec命令很像,都是“有去無回”,

既然是內部跳轉,當前正在處理的請求就還是原來那個,只是當前的 location 發生了變化,所以還是原來的那一套 Nginx 變數的容器副本,對應到上 例,如果我們請求的是/foo這個介面,那么整個作業流程是這樣的:先在location /foo中通過set指令將 a 變 量 的 值 賦 為 字 符 串 h e l l o , 然 后 通 過 e c h o e x e c 指 令 發 起 內 部 跳 轉 , 又 進 入 到 l o c a t i o n / b a r 中 , 再 輸 出 a變數的值賦為字串hello,然后通 過echo_exec指令發起內部跳轉,又進入到location /bar中,再輸出 ahelloechoe?xeclocation/bara變數的值,因為 a 還 是 原 來 的 a還是原來的 aa,所以我們可以期望得到hello這行輸 出,測驗證實了這一點:

$ curl localhost:80/foo 
a = [hello]

但如果我們從客戶端直接訪問/bar介面,就會得到空的$a變數的值,因為它依賴于location /foo來對$a進行初始化,

從上面這個例子我們看到,一個請求在其處理程序中,即使經歷多個不同的 location 配置塊,它使用的還是同一套 Nginx 變數的副本,這里,我們也 首次涉及到了“內部跳轉”這個概念,

值得一提的是,標準 ngx_rewrite 模塊的 rewrite 配置指令其實也可以發起“內部跳轉”,例如上面那個例子用 rewrite 配置指令可以改寫成下面這樣的形式:

server {
  listen 8080;
  location /foo { 
  	set $a hello;
  	rewrite ^ /bar; 
  }
  location /bar {
  	echo "a = [$a]";
	} 
}

其效果和使用 echo_exec 是完全相同的,后面我們還會專門介紹這個 rewrite 指令的更多用法,比如發起 301 和 302 這樣的“外部跳轉”, 從上面這個例子我們看到,Nginx 變數值容器的生命期是與當前正在處理的請求系結的,而與 location 無關,

結論

  • Nginx 變數的創建和賦值操作發生在全然不同的時間階段,Nginx 變數的創建只能發生在 Nginx 配置加載的時候,或者說 Nginx 啟動的時候; 而賦值操作則只會發生在請求實際處理的時候 這意味著不創建而直接使用變數會導致啟動失敗,同時也意味著我們無法在請求處理時動態地創建新的 Nginx變數,

  • Nginx 變數一旦創建,其變數名的可見范圍就是整個 Nginx 配置,甚至可以跨越不同虛擬主機的 server 配置塊,

    但如果被其中一塊使用,比如某一塊location,那么另一塊location再參考的時候就是空字串

    Nginx 變數名的可見范圍雖然是整個配置,但每個請求都有所有變數的獨立副本,或者說都有各變數用來 存放值的容器的獨立副本,彼此互不干擾

  • 對于 Nginx 新手來說,最常見的錯誤之一,就是將 Nginx 變數理解成某種在請求之間全域共享的東西,或者說“全域變數”,而事實上,Nginx 變數的生命期是不可能跨越請求邊界的,

  • Nginx 變數的另一個常見誤區是認為變數容器的生命期,是與location 配置塊系結的,

    實際上 Nginx 變數值容器的生命期是與當前正在處理的請求系結的,而與 location 無關,

預定義變數(一)內建變數

用戶自定義變數

set
- 執行階段
- 生命周期

map
- 執行階段
- 快取

前面我們接觸到的都是通過 set 指令隱式創建的 Nginx 變數,這些變數我們一般稱為“用戶自定義變數”,或者更簡單一些,“用戶變數”,既然有“用戶自定義變數”,自然也就有由 Nginx 核心和各個 Nginx 模塊提供的“預定義變數”,或者說“內建變數”(builtin variables),

如:
1.uri,url,etc
2.http_xxx,http_response_xxx
3.arg_xxx
4.cookie_xxx

Nginx 內建變數最常見的用途就是獲取關于請求或回應的各種資訊,例如由 ngx_http_core 模塊提供的內建變數 $uri,可以用來獲取當前請求的 URI(經過解碼,并且不含請求引數),而 $request_uri 則用來獲取請求最原始的 URI (未經解碼,并且包含請求引數),請看下面這個例子:

location /test {
  echo "uri = $uri";
  echo "request_uri = $request_uri";
}

另一個特別常用的內建變數其實并不是單獨一個變數,而是有無限多變種的一群變數,即名字以 arg_ 開頭的所有變數,我們估且稱之為 $arg_XXX 變數群, 一個例子是 $arg_name,這個變數的值是當前請求名為 name 的 URI 引數的值,而且還是未解碼的原始形式的值,我們來看一個比較完整的示例:

location /test {
  echo "name: $arg_name"; 
  echo "class: $arg_class";
}

在這里插入圖片描述

其實 $arg_name 不僅可以匹配 name 引數,也可以匹配 NAME 引數,抑或是 Name,等等:

Nginx 會在匹配引數名之前,自動把原始請求中的引數名調整為全部小寫的形式,

如果你想對 URI 引數值中的 %XX 這樣的編碼序列進行解碼,可以使用第三方 ngx_set_misc 模塊提供的 set_unescape_uri 配置指令:

location /test {
  set_unescape_uri $name $arg_name; 
  set_unescape_uri $class $arg_class;
	echo "name: $name";
	echo "class: $class";
}

在這里插入圖片描述

從這個例子我們同時可以看到,這個 set_unescape_uri 指令也像 set 指令那樣,擁有自動創建 Nginx 變數的功能,

像 $arg_XXX 這種型別的變數擁有無窮無盡種可能的名字,所以它們并不對應任何存放值的容器,而且這種變數在 Nginx 核心中是經過特別處理的,第 三方 Nginx 模塊是不能提供這樣充滿魔法的內建變數的,

類似 $arg_XXX 的內建變數還有不少,比如用來取 cookie 值的 $cookie_XXX 變數群,用來取請求頭的 $http_XXX 變數群,以及用來取回應頭的 $sent_http_XXX 變數群,

需要指出的是,許多內建變數都是只讀的,比如我們剛才介紹的 $uri 和 $request_uri. 對只讀變數進行賦值是應當絕對避免的,因為會有意想不到的后果,比如:

? location /bad {
? set $uri /blah;
? echo $uri;          
?}
這個有問題的配置會讓 Nginx 在啟動的時候報錯: 
[emerg] the duplicate "uri" variable in ...

如果你嘗試改寫另外一些只讀的內建變數,比如 $arg_XXX 變數,在某些 Nginx 的版本中甚至可能導致行程崩潰,

預定義變數(二)

也有一些內建變數是支持改寫的,其中一個例子是 $args. 這個變數在讀取時回傳當前請求的 URL 引數串(即請求 URL 中問號后面的部分,如果有的話),而在賦值時可以直接修改引數串,我們來看一個例子:

location /test {
	set $orig_args $args; 
	set $args "a=3&b=4";
	echo "original args: $orig_args";
	echo "args: $args"; 
}

這里我們把原始的 URL 引數串先保存在 $orig_args 變數中,然后通過改寫 $args 變數來修改當前的 URL 引數串,最后我們用 echo 指令分別輸出 $orig_args 和 $args 變數的值,接下來我們這樣來測驗這個 /test 介面:

$ curl 'http://localhost:8080/test' 
original args:
args: a=3&b=4

$ curl 'http://localhost:8080/test?a=0&b=1&c=2' 
original args: a=0&b=1&c=2
args: a=3&b=4

在第一次測驗中,我們沒有設定任何 URL 引數串,所以輸出 $orig_args 變數的值時便得到空,而在第一次和第二次測驗中,無論我們是否提供 URL 引數串,引數串都會在location /test中被強行改寫成a=3&b=4.

需要特別指出的是,這里的 $args 變數和 $arg_XXX 一樣,也不再使用屬于自己的存放值的容器,當我們讀取 $args 時,Nginx 會執行一小段代碼,從 Nginx 核心中 專門存放當前 URL 引數串的位置去讀取資料; 而當我們改寫 $args 時,Nginx 會執行另一小段代碼,對相同位置進行改寫,Nginx 的其他部 分在需要當前 URL 引數串的時候,都會從那個位置去讀資料,所以我們對 $args 的修改會影響到所有部分的功能,我們來看一個例子:

location /test {
  set $orig_a $arg_a;
  set $args "a=5";
  echo "original a: $orig_a"; 
  echo "a: $arg_a";
}

這里我們先把內建變數 $arg_a 的值,即原始請求的 URL 引數 a 的值,保存在用戶變數 $orig_a 中,然后通過對內建變數 $args 進行賦值,把當前請求的引數串改寫為 a=5 ,最后再用 echo 指令分別輸出 $orig_a 和 $arg_a 變數的值,因為對內建變數 $args 的修改會直接導致當前請求的 URL 引數串 發生變化,因此內建變數 $arg_XXX 自然也會隨之變化,測驗的結果證實了這一點:
在這里插入圖片描述

我們看到,因為原始請求的 URL 引數串是 a=3, 所以 $arg_a 最初的值為 3, 但隨后通過改寫 $args 變數,將 URL 引數串又強行修改為 a=5, 所以最終 $arg_a 的值又自動變為了 5.

我們再來看一個通過修改 $args 變數影響標準的 HTTP 代理模塊 ngx_proxy 的例子:

server {
	listen 8080;
	location /test {
    set $args "foo=1&bar=2";
    proxy_pass http://127.0.0.1:8081/args;
	} 
}

server {
  listen 8081;
  location /args {
  	echo "args: $args";
	} 
}

這里我們在 http 配置塊中定義了兩個虛擬主機,第一個虛擬主機監聽 8080 埠,其 /test 介面自己通過改寫 $args 變數,將當前請求的 URL 引數串 無條件地修改為 foo=1&bar=2. 然后 /test 介面再通過 ngx_proxy 模塊的 proxy_pass 指令配置了一個反向代理,指向本機的 8081 埠上的 HTTP 服務 /args, 默認情況下, ngx_proxy 模塊在轉發 HTTP 請求到遠方 HTTP 服務的時候,會自動把當前請求的 URL 引數串也轉發到遠方,

而本機的8081埠上的HTTP服務正是由我們定義的第二個虛擬主機來提供的,我們在第二個虛擬主機的location /args中利用echo指令輸出當 前請求的 URL 引數串,以檢查 /test 介面通過 ngx_proxy 模塊實際轉發過來的 URL 請求引數串,

我們來實際訪問一下第一個虛擬主機的 /test 介面:

$ curl 'http://localhost:8080/test?blah=7'
args: foo=1&bar=2

我們看到,雖然請求自己提供了URL引數串blah=7,但在location /test中,引數串被強行改寫成了foo=1&bar=2.接著經由proxy_pass指令將 我們被改寫掉的引數串轉發給了第二個虛擬主機上配置的 /args 介面,然后再把 /args 介面的 URL 引數串輸出,事實證明,我們對 $args 變數的賦值 操作,也成功影響到了 ngx_proxy 模塊的行為,

在讀取變數時執行的這段特殊代碼,在 Nginx 中被稱為取處理程式(get handler);

而改寫變數時執行的這段特殊代碼,則被稱為 存處理程式(set handler),

不同的 Nginx 模塊一般會為它們的變數準備不同的“存取處理程式”,從而讓這些變數的行為充滿魔法,

結論

  • $arg_XXX變數群

  • Nginx 會在匹配引數名之前,自動把原始請求中的引數名調整為全部小寫的形式,

  • 許多內建變數都是只讀的,避免對內建變數賦值

  • 默認情況下, ngx_proxy 模塊在轉發 HTTP 請求到遠方 HTTP 服務的時候,會自動把當前請求的 URL 引數串也轉發到遠方,

  • $arg_XXX 的實作方式,Nginx 根本不會事先就決議好 URL 參數串,而是在 用戶讀取某個 $arg_XXX 變數時,呼叫其“取處理程式”,即時去掃描 URL 引數串,類似地,內建變數 $cookie_XXX 也是通過它的“取處理程式”,即時去 掃描 Cookie 請求頭中的相關定義的,

預定義變數(三)(取處理程式、map)

在設定了“取處理程式”的情況下,Nginx 變數也可以選擇將其值容器用作快取,這樣在多次讀取變數的時候,就只需要呼叫“取處理程式”計算一次,

下面就來看一個這樣的例子:

map $args $foo { 
	default 0; 
	debug 1;
}
server {
	listen 80;
	location /test {
		set $orig_foo $foo; 
		set $args debug;
    echo "original foo: $orig_foo";
    echo "foo: $foo"; 
	}
}

這里首次用到了標準 ngx_map 模塊的 map 配置指令,這個 map 指令就可以用于定義兩個 Nginx 變數之間的映射關系,或者說是函式關系,回到上面這個例子,我們用map指令定義了用戶變數$foo$args內建變數之間的映射關系,特別地,用數學上的函式記法y = f(x)來說,我們的 $args就是“自變數” x,而 $foo 則是“因變數” y,即 $foo 的值是由 $args 的值來決定的 ,或者按照書寫順序可以說,我們將 $args 變數的值映射到了 $foo 變數上,

再來看 map 指令定義的映射規則:

map $args $foo { 
   default 0; 
   debug 1;
}
  • 花括號中第一行的 default 是一個特殊的匹配條件,即當其他條件都不匹配的時候,這個條件才匹配, 當這個默認條件匹配時,就把“因變數” $foo 映 射到值 0.
  • 而花括號中第二行的意思是說,如果“自變數” $args 精確匹配了 debug 這個字串,則把“因變數” $foo 映射到值 1.
  • 將這兩行合起來,我們就得到如下完整的映射規則:當 $args 的值等于 debug 的時候,$foo 變數的值就是 1,否則 $foo 的值就為 0.

明白了map指令的含義,再來看location /test.在那里,我們先把當前$foo變數的值保存在另一個用戶變數$orig_foo中,然后再強行把$args 的值改寫為 debug,最后我們再用 echo 指令分別輸出 $orig_foo$foo的值,

從邏輯上看,似乎當我們強行改寫 $args 的值為 debug 之后,根據先前的 map 映射規則,$foo 變數此時的值應當自動調整為字串 1, 而不論 $foo原 先的值是怎樣的,然而測驗結果并非如此:

$ curl 'http://localhost:80/test' 

original foo: 0
foo: 0

第一行輸出指示 $orig_foo 的值為 0,這正是我們期望的:上面這個請求并沒有提供 URL 引數串,于是 $args 最初的取值就是空,再根據我們先前定 義的映射規則,$foo 變數在第一次被讀取時的值就應當是 0(即匹配默認的那個 default 條件),

而第二行輸出顯示,在強行改寫 $args 變數的值為字串 debug 之后,$foo 的條件仍然是 0 ,這顯然不符合映射規則,因為當 $args 為 debug 時,$foo 的值應當是 1. 然后結果卻不盡人意?

那就是 $foo 變數在第一次讀取時,根據映射規則計算出的值被快取住了,剛才我們說過,Nginx 模塊可以為其創建的變數選擇使用值容器,作為其“取處理程式”計算結果的快取,顯然, ngx_map 模塊認為變數間的映射計算足夠昂貴,需要自動將因變數的計算結果快取下來,這樣在當前請求的處理程序中如果再次讀取這個因變數,Nginx 就可以直接回傳快取住的結果,而不再呼叫該變數的“取處理程式”再行計算了,

為了進一步驗證這一點,我們不妨在請求中直接指定 URL 引數串為 debug:
在這里插入圖片描述

現在 $orig_foo 的值就成了 1,因為變數 $foo 在第一次被讀取時,自變數 $args 的值就是 debug,于是按照映射規則,“取處理程式”計算 回傳的值便是 1. 而后續再讀取 $foo 的值時,就總是得到被快取住的 1 這個結果,而不論 $args 后來變成什么樣了,

map 指令其實是一個比較特殊的例子,因為它可以為用戶變數注冊“取處理程式”,而且用戶可以自己定義這個“取處理程式”的計算規則,當然,此規則 在這里被限定為與另一個變數的映射關系,同時,也并非所有使用了“取處理程式”的變數都會快取結果,例如我們前面在 (二) 中已經看到 $arg_XXX 并不會使用值容器進行快取,

類似 ngx_map 模塊,標準的 ngx_geo 等模塊也一樣使用了變數值的快取機制,

很多 Nginx 新手都會擔心如此“全域”范圍的 map 設定會讓訪問所有虛擬主機的所有 location 介面的請求都執行一遍變數值的映射計算,然而事實并非 如此,前面我們已經了解到 map 配置指令的作業原理是為用戶變數注冊 “取處理程式”,并且實際的映射計算是在“取處理程式”中完成的,而“取處理程式”只有在該用戶變數被實際讀取時才會執行(當然,因為快取的存在,只在請求生命期中的第一次讀取中才被執行),所以對于那些根本沒有用到相關 變數的請求來說,就根本不會執行任何的無用計算,

在計算領域被稱為“惰性求值”(lazy evaluation), 與之相對的便是“主動求值” (eager evaluation),

  • set $b "$a,$a";
    這里會在執行 set 規定的賦值操作時,“主動”地計算出變數 $b 的值,而不會將該求值計算延緩到變數 $b 實際被讀取的時候,

結論

  • 變數值的快取機制

    Nginx 模塊可以為其創建的變數選擇使用值容器,作為其“取處理程式”計算結果的快取,顯然, ngx_map 模塊認為變數間的映射計算足夠昂貴,需要自動將因變數的計算結果快取下來,這樣在當前請求的處理程序中如果再次讀取這個因變數,Nginx 就可以直接回傳快取住的結果,而不再呼叫該變數的“取處理程式”再行計算了

  • map 指令只能在 http 塊中 使用

  • map 配置指令的作業原理是為用戶變數注冊 “取處理程式”,并且實際的映射計算是在“取處理程式”中完成的,而**“取處理程式”只有在該用戶變數被實際讀取時才會執行**(當然,因為快取的存在,只在請求生命期中的第一次讀取中才被執行),所以對于那些根本沒有用到相關 變數的請求來說,就根本不會執行任何的無用計算,

預定義變數(四)(主請求、子請求)

在 Nginx 世界里有兩種型別的“請求”,一種叫做“主請求”(main request),而另一種則叫做“子請求”(subrequest)

  • 所謂“主請求”,就是由 HTTP 客戶端從 Nginx 外部發起的請求,我們前面見到的所有例子都只涉及到“主請求”,包括之前那兩個使用 echo_exec 和 rewrite 指令發起“內部跳轉”的例子,

  • 而“子請求”則是由 Nginx 正在處理的請求在 Nginx 內部發起的一種級聯請求,“子請求”在外觀上很像 HTTP 請求,但實作上卻和 HTTP 協議乃至網路通信一點兒關系都沒有,它是 Nginx 內部的一種抽象呼叫,目的是為了方便用戶把“主請求”的任務分解為多個較小粒度的“內部請求”,并發或串行地訪問 多個 location 介面,然后由這些 location 介面通力協作,共同完成整個“主請求”,

  • “子請求”的概念是相對的,任何一個“子請求”也可以再發 起更多的“子子請求”,甚至可以玩遞回呼叫(即自己呼叫自己),當一個請求發起一個“子請求”的時候,按照 Nginx 的術語,習慣把前者稱為后者的“父 請求”(parent request),

一個使用子請求的例子:

location /main { 
	echo_location /foo; 
	echo_location /bar;
}
location /foo { 
	echo foo;
}

location /bar { 
	echo bar;
}

這里在location /main中,通過第三方ngx_echo模塊的echo_location指令分別發起到/foo和/bar這兩個介面的GET型別的“子請求”,由 echo_location 發起的“子請求”,其執行是按照配置書寫的順序串行處理的,即只有當 /foo 請求處理完畢之后,才會接著處理 /bar 請求,這兩個“子請求”的輸出會按執行順序拼接起來,作為 /main 介面的最終輸出:
在這里插入圖片描述

我們看到,“子請求”方式的通信是在同一個虛擬主機內部進行的,所以 Nginx 核心在實作“子請求”的時候,就只呼叫了若干個 C 函式,完全不涉及任何網路或者 UNIX 套接字(socket)通信,我們由此可以看出“子請求”的執行效率是極高的,

回到先前對 Nginx 變數值容器的生命期的討論,我們現在依舊可以說,它們的生命期是與當前請求相關聯的,每個請求都有所有變數值容器的獨立副 本,只不過當前請求既可以是“ 主請求”,也可以是“子請求”,即便是父子請求之間,同名變數一般也不會相互干擾,讓我們來通過一個小實驗證明一下 這個說法:

location /main { 
	set $var main;
	echo_location /foo; 
	echo_location /bar;
	echo "main: $var"; 
}
location /foo { 
	set $var foo;
	echo "foo: $var"; 
}
location /bar { 
	set $var bar;
	echo "bar: $var"; 
}

在這個例子中,我們分別在 /main,/foo 和 /bar 這三個 location 配置塊中為同一名字的變數,$var,分別設定了不同的值并予以輸出,特別地, 我們在 /main 介面中,故意在呼叫過 /foo 和 /bar 這兩個“子請求”之后,再輸出它自己的 $var 變數的值,請求 /main 介面的結果是這樣的:
在這里插入圖片描述

顯然,/foo 和 /bar 這兩個“子請求”在處理程序中對變數 $var 各自所做的修改都絲毫沒有影響到“主請求” /main. 于是這成功印證了“主請求”以及各 個“子請求”都擁有不同的變數 $var 的值容器副本,

不幸的是,一些 Nginx 模塊發起的“子請求”卻會自動共享其“父請求”的變數值容器,比如第三方模塊 ngx_auth_request.

下面是一個例子:

location /main { 
	set $var main;
	auth_request /sub;
	echo "main: $var"; 
}
location /sub { 
	set $var sub;
	echo "sub: $var";
}

這里我們在 /main 介面中先為 $var 變數賦初值 main,然后使用 ngx_auth_request 模塊提供的配置指令 auth_request,發起一個到 /sub 介面的“子 請求”,最后利用 echo 指令輸出變數 $var 的值,而我們在 /sub 介面中則故意把 $var 變數的值改寫成 sub. 訪問 /main 介面的結果如下:
在這里插入圖片描述

我們看到,/sub 介面對 $var 變數值的修改影響到了主請求 /main. 所以 ngx_auth_request 模塊發起的“子請求”確實是與其“父請求”共享一套 Nginx 變數 的值容器,

“為什么‘子請求’ /sub 的輸出沒有出現在最終的輸出里呢?”
那就是因為 auth_request 指令會自動忽略“子請求”的回應體,而只檢查“子請求”的回應狀態碼, 當狀態碼是 2XX 的時候,auth_request 指令會忽略“子請求”而讓 Nginx 繼續處理當前的請求,否則它就會立即中斷當前(主)請求的執行,回傳相應的出錯頁, 在我們的例子中,/sub “子請求”只是使用 echo 指令作了一些輸出,所以 隱式地回傳了指示正常的 200 狀態碼,

ngx_auth_request 模塊這樣父子請求共享一套 Nginx 變數的行為,雖然可以讓父子請求之間的資料雙向傳遞變得極為容易,但是對于足夠復雜的配置, 卻也經常導致不少難于除錯的詭異 bug. 因為用戶時常不知道“父請求”的某個 Nginx 變數的值,其實已經在它的某個“子請求”中被意外修改了,諸如此類 的因共享而導致的不好的“副作用”,讓包括 ngx_echo, ngx_lua,以及 ngx_srcache 在內的許多第三方模塊都選擇了禁用父子請求間的變數共享,

預定義變數(五)

Nginx內建變數用在 “子請求” 的背景關系中時,其行為也會變得有些微妙,

通過前面章節中我們已經知道,許多內建變數都不是簡單的“存放值的容器”,它們一般會通過注冊“存取處理程式”來表現得與眾不同,而它們即使有存 放值的容器,也只是用于快取“存取處理程式”的計算結果,

我們之前討論過的 $args 變數正是通過它的“取處理程式”來回傳當前請求的 URL 引數串,因 為當前請求也可以是“子請求”,所以在“子請求”中讀取 $args,其“取處理程式”會很自然地回傳當前“子請求”的引數串,

location /main {
	echo "main args: $args"; 
	echo_location /sub "a=1&b=2";
}
location /sub {
	echo "sub args: $args";
}

這里在 /main 介面中,先用 echo 指令輸出當前請求的 $args 變數的值,接著再用 echo_location 指令發起子請求 /sub. 這里值得注意的是,我們在 echo_location 陳述句中除了通過第一個引數指定“子請求”的 URI 之外,還提供了第二個引數,用以指定該“子請求”的 URL 引數串(即 a=1&b=2),最后 我們定義了 /sub 介面,在里面輸出了一下 $args 的值,請求 /main 介面的結果如下:

image-20201218190412849

顯然,當 $args 用在“主請求” /main 中時,輸出的就是“主請求”的 URL 引數串,c=3;而當用在“子請求” /sub 中時,輸出的則是“子請求”的引數串,a=1&b=2,這種行為正符合我們的直覺

與 $args 類似,內建變數 $uri 用在“子請求”中時,其“取處理程式”也會正確回傳當前“子請求”決議過的 URI:

location /main {
	echo "main uri: $uri"; 
	echo_location /sub;
}
location /sub {
	echo "sub uri: $uri";
}

請求 /main的結果是

image-20201218191154372這種行為依然符合我們的直覺

————

但不幸的是,并非所有的內建變數都作用于當前請求,少數內建變數只作用于“主請求”,比如由標準模塊 ngx_http_core 提供的內建變數 $request_method

變數 $request_method 在讀取時,總是會得到“主請求”的請求方法,比如 GET、POST 之類,我們來測驗一下:

location /main {
   echo "main method: $request_method"; 
   echo_location /sub;   # 這個指令是get請求
}

location /sub {
   echo "sub method: $request_method";
}

在這個例子里,/main 和 /sub 介面都會分別輸出 $request_method 的值,同時,我們在 /main 介面里利用 echo_location 指令發起一個到 /sub 介面的GET “子請求”,我們現在利用 curl 命令列工具來發起一個到 /main 介面的 POST 請求:

curl --data hello 'http://localhost/main'

image-20201218191744278

這里我們利用 curl 程式的 --data 選項,指定 hello 作為我們的請求體資料,同時 --data 選項會自動讓發送的請求使用 POST 請求方法,測驗結果 證明了我們先前的預言, $request_method 變數即使在 GET “子請求” /sub 中使用,得到的值依然是“主請求” /main 的請求方法,POST.

有的讀者可能覺得我們在這里下的結論有些草率,因為上例是先在“主請求”里讀取(并輸出) $request_method 變數,然后才發“子請求”的,所以這些讀 者可能認為這并不能排除 $request_method 在進入子請求之前就已經把第一次讀到的值給快取住,從而影響到后續子請求中的輸出結果,不過,這樣的顧 慮是多余的,因為我們前面在 (五) 中也特別提到過,快取所依賴的變數的值容器,是與當前請求系結的,而由 ngx_echo 模塊發起的“子請求”都禁用了 父子請求之間的變數共享,所以在上例中, $request_method 內建變數即使真的使用了值容器作為快取(事實上它也沒有),它也不可能影響到 /sub 子 請求,

把例子的執行順序修改一下,重新測驗,結果依舊是POST

location /main {
	 echo_location /sub;   # 這個指令是get請求
   echo "main method: $request_method"; 
}

location /sub {
   echo "sub method: $request_method";
}

————

由此可見,我們并不能通過標準的 $request_method 變數取得“子請求”的請求方法,

為了達到我們最初的目的,我們需要求助于第三方模塊 ngx_echo 提供 的內建變數 $echo_request_method:

location /main {
  echo "main method: $echo_request_method"; 
  echo_location /sub;
}
location /sub {
	echo "sub method: $echo_request_method";
}
image-20201218193923732

可以看到,父子請求分別輸出了它們各自不同的請求方法,POST 和 GET.
類似 $request_method,內建變數 $request_uri 一般也回傳的是“主請求”未經決議過的 URL,畢竟“子請求”都是在 Nginx 內部發起的,并不存在所謂的“未決議的”原始形式,

————

內建變數的值快取在共享變數的父子請求之間起了作用,這無疑是災難性的,

在(四)看到ngx_auth_request 模塊發起的“子請求”是與其“父請求”共享一套變數的,下面是一個這樣的可怕例子:

map $uri $tag { 
	default 0; 
	/main 1; 
	/sub 2;
}
server {
	listen 80;
	location /main { 
			auth_request /sub; 
			echo "main tag: $tag";
	}
	location /sub {
			echo "sub tag: $tag";
	} 
}
image-20201218195717467

這里我們使用久違了的 map 指令來把內建變數 $uri 的值映射到用戶變數 $tag 上,當 $uri 的值為 /main 時,則賦予 $tag 值 1,當 $uri 取值 /sub 時, 則賦予 $tag 值 2,其他情況都賦 0. 接著,我們在 /main 介面中先用 ngx_auth_request 模塊的 auth_request 指令發起到 /sub 介面的子請求,然后再 輸出變數 $tag 的值,

其實道理很簡單,因為我們的 $tag 變數在“子請求” /sub 中首先被讀取,于是在那里計算出了值 2(因為 u r i 在 那 里 取 值 / s u b , 而 根 據 m a p 映 射 規 則 , uri 在那里取值 /sub,而根據 map 映射規 則, uri/submaptag 應當取值 2),從此就被 $tag 的值容器給快取住了,而 auth_request 發起的“子請求”又是與“父請求”共享一套變數的,于是當 Nginx 的執 行流回到“父請求”輸出 $tag 變數的值時,Nginx 就直接回傳快取住的結

結論

  • 快取所依賴的變數的值容器,是與當前請求系結的

  • 并不能通過標準的 r e q u e s t m e t h o d 變 量 取 得 “ 子 請 求 ” 的 請 求 方 法 , 可 以 借 助 第 三 方 模 塊 n g x e c h o 提 供 的 內 建 變 量 request_method 變數取得“子請求”的請求方法, 可以借助第三方模塊ngx_echo提供的內建變數 requestm?ethodngxe?choecho_request_method

預定義變數(六)

沒有值的變數也有兩種特殊的值:一種是**“不合法”(invalid),另一種是“沒找到”(not found)**,

舉例說來,當 Nginx 用戶變數 f o o 創 建 了 卻 未 被 賦 值 時 , foo 創建了卻未被賦值時, foofoo 的值便是“不合法”;而如果當前請求的 URL 引數串中并沒有提及 XXX 這個引數,則 $arg_XXX 內建變數的值便是“沒找到”,

無論是“不合法”也好,還是“沒找到”也罷,這兩種 Nginx 變數所擁有的特殊值,和空字串("")這種取值是完全不同的,

由 set 指令創建的變數未初始化就用在“變數插值”中時,效果上等同于空字串,但那是因為 set 指令為它創建的變數自動注冊了一個“取處理程式”,將“不合法”的變數值轉換為空字串,

下面一個例子

location /foo {
	echo "foo = [$foo]";
}

location /bar { 
	set $foo 32;
	echo "foo = [$foo]"; 
}
image-20201218200642914

從輸出上看,未初始化的 $foo 變數確實和空字串的效果等同,

然后查看Nginx錯誤日志error.log,可以看到警告,

[warn] 5765#0: *1 using uninitialized "foo" variable, ...

這一行警告是誰輸出的呢?

答案是set指令為$foo注冊的“取處理程式”,當/foo介面中的echo指令實際執行的時候,它會對它的引數"foo = $foo]" 進行“變數插值”計算,于是,引數串中的 $foo 變數會被讀取,而 Nginx 會首先檢查其值容器里的取值,結果它看到了“不合法”這個特殊值,于是它這才決定繼續呼叫 $foo 變數的“取處理程式”,于是 $foo 變數的“取處理程式”開始運行,它向 Nginx 的錯誤日志列印出上面那條警告訊息,然后回傳一個空字串作為 $foo 的值,并從此快取在 $foo 的值容器中,

細心的讀者會注意到剛剛描述的這個程序其實就是那些支持值快取的內建變數的作業原理只不過 set 指令在這里借用了這套機制來處理未正確初始化 的 Nginx 變數,值得一提的是,只有“不合法”這個特殊值才會觸發 Nginx 呼叫變數的“取處理程式”,而特殊值“沒找到”卻不會,

上面這樣的警告一般會指示出我們的 Nginx 配置中存在變數名拼寫錯誤,抑或是在錯誤的場合使用了尚未初始化的變數,因為值快取的存在,這條警告 在一個請求的生命期中也不會列印多次,當然, ngx_rewrite 模塊專門提供了一條 uninitialized_variable_warn 配置指令可用于禁止這條警告日志,

——

剛才提到,內建變數 $arg_XXX 在請求 URL 引數 XXX 并不存在時會回傳特殊值“找不到”,但遺憾的是在 Nginx 原生配置語言(我們估且這么稱呼它)中 是不能很方便地把它和空字串區分開來的,比如:

location /test {
	echo "name: [$arg_name]";
}

這里我們輸出 $arg_name 變數的值同時故意在請求中不提供 URL 引數 name:
$ curl 'http://localhost:8080/test' 
name: []
我們看到,輸出特殊值“找不到”的效果和空字串是相同的,因為這一回是 Nginx 的“變數插值”引擎自動把“找不到”給忽略了,

那么我們究竟應當如何捕捉到“找不到”這種特殊值的蹤影呢?換句話說,我們應當如何把它和空字串給區分開來呢?

顯然,下面這個請求中,URL 引數 name 是有值的,而且其值應當是空字串:

$ curl 'http://localhost:8080/test?name='
name: []

幸運的是,通過第三方模塊 ngx_lua,我們可以輕松地在 Lua 代碼中做到這一點,請看下面這個例子:

注意:這里代碼格式也很重要!!一開始格式不對得不到結果  
        location /test {
            content_by_lua '
                if ngx.var.arg_name == nil then
                    ngx.say("name: missing")
                else
                    ngx.say("name: [", ngx.var.arg_name, "]")
                end
            ';
        }

在 /test 介面中使用了 ngx_lua 模塊的content_by_lua 配置指令,嵌入了一小段我們自己的 Lua 代碼 來對 Nginx 變數 $arg_name 的特殊值進行判斷,在這個例子中,當 $arg_name 的值為“沒找到”(或者“不合法”)時,/foo 介面會輸出 name: missing 這一行結果:

image-20201218205033582

因為這是我們第一次接觸到 ngx_lua 模塊,所以需要先簡單介紹一下, ngx_lua 模塊將 Lua 語言解釋器(或者 LuaJIT 即時編譯器)嵌入到了 Nginx 核心 中,從而可以讓用戶在 Nginx 核心中直接運行 Lua 語言撰寫的程式,我們可以選擇在 Nginx 不同的請求處理階段插入我們的 Lua 代碼,這些 Lua 代碼既 可以直接行內在 Nginx 組態檔中,也可以單獨放置在外部 .lua 原始碼檔案(或者 Lua 位元組碼檔案)里,然后在 Nginx 組態檔中參考這些檔案的路徑,

回到上面這個例子,我們在 Lua 代碼里參考 Nginx 變數都是通過 ngx.var 這個由 ngx_lua 模塊提供的 Lua 介面,比如參考 Nginx 變數 $VARIABLE 時,就 在 Lua 代碼里寫作 ngx.var.VARIABLE 就可以了,當 Nginx 變數 $arg_name 為特殊值“沒找到”(或者“不合法”)時, ngx.var.arg_name 在 Lua 世界中 的值就是 nil,即 Lua 語言里的“空”(不同于 Lua 空字串),我們在 Lua 里輸出回應體內容的時候,則使用了 ngx.say 這個 Lua 函式,也是 ngx_lua 模 塊提供的,功能上等價于 ngx_echo 模塊的 echo 配置指令,

現在,提供空字串取值的name引數

image-20201218205353072

在這種情況下,Nginx 變數 $arg_name 的取值便是空字串,這既不是“沒找到”,也不是“不合法”,因此在 Lua 里,ngx.var.arg_name 就回傳 Lua空字串(""),和剛才的 Lua nil 值就完全區分開了,

這種區分在有些應用場景下非常重要,比如有的 web service 介面會根據 name 這個 URL 引數是否存在來決定是否按 name 屬性對資料集合進行過濾,而顯然提供空字串作為 name 引數的值,也會導致對資料集中取值為空串的記錄進行篩選操作,

不過,標準的 $arg_XXX 變數還是有一些局限,比如我們用下面這個請求來測驗剛才那個 /test 介面:

image-20201218205732822此時,$arg_name 變數仍然讀出“找不到”這個特殊值,這就明顯有些違反常識,

此外, $arg_XXX 變數在請求 URL 中有多個同名 XXX 引數時,就只會 回傳最先出現的那個 XXX 引數的值,而默默忽略掉其他實體:

image-20201218205927427

要解決這些局限,可以直接在 Lua 代碼中使用 ngx_lua 模塊提供的 ngx.req.get_uri_args 函式,

結論

  • 由 set 指令創建的變數未初始化就用在“變數插值”中時,效果等同于空字串,但那是因為 set 指令為它創建的變數自動注冊了一個“取處理程式”,將“不合法”的變數值轉換為空字串,

  • 通過第三方模塊 ngx_lua 可以解決特殊值“找不到” 和 空字串的區別

預定義變數(七)

與 $arg_XXX 類似,我們在 (一) 中提到過的內建變數 $cookie_XXX 變數也會在名為 XXX 的 cookie 不存在時回傳特殊值“沒找到”:

location /test { 
    content_by_lua '
        if ngx.var.cookie_user == nil then 
            ngx.say("cookie user: missing")
        else
            ngx.say("cookie user: [", ngx.var.cookie_user, "]")
        end 
    ';
}

以 arg_ 開頭的所有變數,我們估且稱之為 $arg_XXX 變數群,

利用curl命令列工具的–cookie name=value選項可以指定name=value為當前請求攜帶的cookie(通過添加相應的Cookie請求頭),下面是若干次測驗結果:

image-20201221094526304

在 Lua 里訪問未創建的 Nginx 用戶變數時,在 Lua 里也會得到 nil 值,而不會像先前的例子那樣直接讓 Nginx 拒絕加載配置:

location /test {
    content_by_lua '
        ngx.say("$blah = ", ngx.var.blah)
    ';
}
image-20201221095024030

這里假設我們并沒有在當前的 nginx.conf 組態檔中創建過用戶變數 $blah,然后我們在 Lua 代碼中通過 ngx.var.blah 直接參考它,上面這個配置可以順利啟動,因為 Nginx 在加載配置時只會編譯 content_by_lua 配置指令指定的 Lua 代碼而不會實際執行它,所以 Nginx 并不知道 Lua 代碼里面參考 了 $blah 這個變數,于是我們在運行時也會得到 nil 值,而 ngx_lua 提供的 ngx.say 函式會自動把 Lua 的 nil 值格式化為字串 “nil” 輸出,

值得注意的地方是,我們在 content_by_lua 配置指令的引數中提及了 b l a h 符 號 , 但 卻 并 沒 有 觸 發 “ 變 量 插 值 ” ( 否 則 N g i n x 會 在 啟 動 時 抱 怨 ? ? blah 符號,但卻并沒有觸發“變數插值”(否則 Nginx 會在 啟動時抱怨 ** blah(Nginx??blah 未創建**),這是因為 content_by_lua 配置指令并不支持引數的“變數插值”功能,我們前面在中提到過,配置指令的引數是否允 許“ 變數插值”,其實取決于該指令的實作模塊,

由 set 指令創建的變數未初始化就用在“變數插值”中時,效果上等同于空字串,但那是因為 set 指令為它創建的變數自動注冊了一個“取處理程式”,將“不合法”的變數值轉換為空字串,

(由 set 指令創建的變數在未初始化時確實是“不合法”,但一旦嘗試 讀取它們時,Nginx 就會自動呼叫其“取處理程式”,而它們的“取處理程式”會自動回傳空字串并將之快取住,于是我們最終得到的是完全合法的空字串,)

location /foo { 
    content_by_lua '
        if ngx.var.foo == nil then 
            ngx.say("$foo is nil")
        else
            ngx.say("$foo = [", ngx.var.foo, "]")
        end 
    ';
}
location /bar { 
    set $foo 32;
    echo "foo = [$foo]"; 
}

測驗結果:可以看到在 Lua 里面讀取未初始化的 Nginx 變數 $foo 時得到的是空字串,

image-20201221100310625

陣列

雖然前面反復指出 Nginx 變數只有字串這一種資料型別,但這并不能阻止像 ngx_array_var 這樣的第三方模塊讓 Nginx 變數也能存 放陣列型別的值,

location /test {
    array_split "," $arg_names to=$array; 
    array_map "[$array_it]" $array; 
    array_join " " $array to=$res;
    echo $res; 
}

這個例子中使用了 ngx_array_var 模塊的 array_split、 array_map 和 array_join 這三條配置指令,其含義很接近 Perl 語言中的內建函式 split、map 和 join(當然,其他腳本語言也有類似的等價物),我們來看看訪問 /test 介面的結果:

image-20201221101026956

我們看到,使用 ngx_array_var 模塊可以很方便地處理這樣具有不定個數的組成元素的輸入資料,例如此例中的 names URL 引數值就是由不定個數的逗 號分隔的名字所組成,不過,這種型別的復雜任務通過 ngx_lua 來做通常會更靈活而且更容易維護,

執行順序

當同一個 location 配置塊使用了多個 Nginx 模塊的配置指令時,這些指令的執行順序很可能會 跟它們的書寫順序大相徑庭,于是許多人選擇了“ 試錯法”,然后他們的組態檔就時常被改得一片狼藉,

rewrite階段指令

set
set_by_lua
rewrite
rewrite_by_lua
more_set_input_headers

(pre)access階段指令

real_ip
allow
deny

content階段

proxy_pass
echo

執行順序(一)開頭

現在就來看這樣一個令人困惑的例子:

location /test { 
set $a 32;
echo $a;
set $a 56; 
echo $a;
}

結果:
56
56

首先需要知道 Nginx 處理每一個用戶請求時,都是按照若干個不同階段(phase)依次處理的,
Nginx 的請求處理階段共有 11 個之多,我們先介紹其中 3 個比較常見的,按照它們執行時的先后順序,依次是 rewrite 階段、access 階段以及content 階段

所有 Nginx 模塊提供的配置指令一般只會注冊并運行在其中的某一個處理階段,比如上例中的 set 指令就是在 rewrite 階段運行的,而 echo 指令就只會 在 content 階段運行,前面我們已經知道,在單個請求的處理程序中,rewrite 階段總是在 content 階段之前執行,因此屬于 rewrite 階段的配置 令也總是會無條件地在 content 階段的配置指令之前執行,

于是在同一個 location 配置塊中, set 指令總是會在 echo 指令之前執行

實際的執行順序應當是

set $a 32; 
set $a 56; 
echo $a; 
echo $a;

即先在 rewrite 階段執行完這里的兩條 set 賦值陳述句,然后再在后面的 content 階段依次執行那兩條 echo 陳述句,分屬兩個不同處理階段的配置指令之 間是不能穿插著運行的,

知道了實際執行順序,沒有機會在第二條set陳述句之前用echo輸出,最后還是輸出56、56,這怎么解決?

通過引入新的用戶變數$saved_a在改寫 $a 之前及時保存了 $a 的初始值,而對于多條 set 指令而言,它們之間的執行順序是由 ngx_rewrite 模塊 來保證與書寫順序相一致的,同理, ngx_echo 模塊自身也會保證它的多條 echo 指令之間的執行順序,

location /test { 
	set $a 32;
	set $saved_a $a;  #就第三個變數, set $b $a;也沒啥問題
	set $a 56;
	
	echo $saved_a;
	echo $a;
}

訪問測驗:
$ curl 'http://127.0.0.1/test'
32
56

執行順序(二) rewrite—set

當 set 指令用在 location 配置塊中時,都是在當前請求的 rewrite 階段運行的,事實上,在此背景關系中, ngx_rewrite 模塊中的幾 乎全部指令,都運行在 rewrite 階段,不過,值得一提的是,當這些指令使用在 server 配置塊 中時,則會運行在一個我們尚未提及的更早的處理階段,server-rewrite 階段,

在 Nginx 變數漫談(六) 中初識了第三方模塊 ngx_lua,它提供的 set_by_lua 配置指令也和 ngx_set_misc 模塊的指令一樣,可以和 ngx_rewrite 模塊的 指令混合使用, set_by_lua 指令支持通過一小段用戶 Lua 代碼來計算出一個結果,然后賦給指定的 Nginx 變數,和 set 指令相似, set_by_lua 指令也有自動創建不存在的 Nginx 變數的功能,

location /test {
        set $a 32;
        set $b 56;
        set_by_lua $c "return ngx.var.a + ngx.var.b";
        set $equation "$a + $b = $c";
        echo $equation;
}
image-20201224165438115

先將 $a 和 $b 變數分別初始化為 32 和 56,然后利用 set_by_lua 指令行內一行我們自己指定的 Lua 代碼,計算出 Nginx 變數 $a 和 $b 的“代數 和”(sum),并賦給變數 $c,接著利用“變數插值”功能,把變數 $a、 $b 和 $c 的值拼接成一個字串形式的等式,賦予變數 $equation,最后再用 echo 指令輸出 $equation 的值,

這個例子值得注意的地方是:首先,我們在 Lua 代碼中是通過 ngx.var.VARIABLE 介面來讀取 Nginx 變數 $VARIABLE 的;其次,因為 Nginx 變數的值只有字串這一種型別,所以在 Lua 代碼里讀取 ngx.var.a 和 ngx.var.b 時得到的其實都是 Lua 字串型別的值 “32” 和 “56”;接著,我們對兩個字串作加法運算會觸發 Lua 對加數進行自動型別轉換(Lua 會把兩個加數先轉換為數值型別再求和);然后,我們在 Lua 代碼中把最終結果通過 return 陳述句回傳給外面的 Nginx 變數 $c;最后, ngx_lua 模塊在給 $c 實際賦值之前,也會把 return 陳述句回傳的數值型別的結果,也就是 Lua 加法計算得出的“和”,自動轉換為字串(這同樣是因為 Nginx 變數的值只能是字串),

執行順序(三) rewrite-access

第三方模塊 ngx_lua 提供的 rewrite_by_lua 配置指令也和 more_set_input_headers 一樣運行在 rewrite 階段的末尾

location /test {
        set $a 1;
        rewrite_by_lua "ngx.var.a = ngx.var.a + 1";
        set $a 56;
        echo $a;
}

$ curl 'http://127.0.0.1/test'
57

既然 more_set_input_headers 和 rewrite_by_lua 指令都運行在 rewrite 階段的末尾,那么它們之間的先后順序又是怎樣的呢?

答案是:不一定,我們應當避免寫出依賴它們二者間順序的配置,

———————— 在 rewrite 階段之后,有一個名叫 access 的請求處理階段

**在 rewrite 階段之后,有一個名叫 access 的請求處理階段,**Nginx 變數漫談(五) 中介紹過的第三方模塊 ngx_auth_request 的指令就運行在 access 階段,在 access 階段運行的配置指令多是執行訪問控制性質的任務,比如檢查用戶的訪問權限,檢查用戶的來源 IP 地址是否合法

例如,標準模塊 ngx_access 提供的 allow 和 deny 配置指令可用于控制哪些 IP 地址可以訪問

location /hello {
	allow 127.0.0.1;  #支持169.200.179.4/24 寫法
	deny all;
	echo "hello world";
}

這個 /hello 介面被配置為只允許從本機(IP 地址為保留的 127.0.0.1)訪問,而從其他 IP 地址訪問都會被拒(回傳 403 錯誤頁), ngx_access 模塊 自己的多條配置指令之間是按順序執行的,直到遇到第一條滿足條件的指令就不再執行后續的 allow 和 deny 指令,如果首先匹配的指令是 allow,則會繼 續執行后續其他模塊的指令或者跳到后續的處理階段;而如果首先滿足的是 deny 則會立即中止當前整個請求的處理,并立即回傳 403 錯誤頁,所以看 上面這個例子,如果是從本地訪問的,則首先匹配allow 127.0.0.1這一條陳述句,于是Nginx就繼續往下執行其他模塊的指令以及后續的處理階段; 而如果是從其他機器訪問,則首先匹配的則是deny all這一條陳述句,即拒絕所有地址,它會導致403錯誤頁立即回傳給客戶端,

值得一提的是, ngx_access 模塊還支持所謂的“CIDR 記法”來表示一個網段,例如 169.200.179.4/24 則表示路由前綴是 169.200.179.0(或者說子 網掩碼是 255.255.255.0)的網段,

執行順序(四)access

ngx_lua 模塊提供了配置指令 access_by_lua,用于在 access 請求處理階段插入用戶 Lua 代碼,這條指令運行于 access 階段的末尾,因此總是在 allow 和 deny 這樣的指令之后運行,雖然它們同屬 access 階段,一般我們通過 access_by_lua 在 ngx_access 這樣的模塊檢查過客戶端 IP 地址之后,再通過 Lua 代碼執行一系列更為復雜的請求驗證操作,比如實時查詢資料庫或者其他后端服務,以驗證當前用戶的身份或權限,

一般我們通過 access_by_lua 在 ngx_access 這樣的模塊檢查過客戶端 IP 地址之后,再通過 Lua 代碼執行一系列更為復雜的請求驗證操作,比如實時查詢資料庫或者其他后端服務,以驗證當前用戶的身份或權限,

利用 access_by_lua 來實作 ngx_access 模塊的 IP 地址過濾功能

1、
location /hello { 
	access_by_lua '
		if ngx.var.remote_addr == "127.0.0.1" then 
			return
		end
		ngx.exit(403) 
	';
	echo "hello world"; 
}


效果等同于之前看的
2、
location /hello { 
	allo 127.0.0.1;
	deny all;
	echo "hello world";
}

在 Lua 代碼中通過參考 Nginx 標準的內建變數 $remote_addr 來獲取字串形式的客戶端 IP 地址,然后用 Lua 的 if 陳述句判斷是否為本機地址,即是 否等于 127.0.0.1. 如果是本機地址,則直接利用 Lua 的 return 陳述句回傳,讓 Nginx 繼續執行后續的請求處理階段(包括 echo 指令所處的 content 階段);而如果不是本機地址,則通過 ngx_lua 模塊提供的 Lua 函式 ngx.exit 中斷當前的整個請求處理流程,直接回傳 403 錯誤頁給客戶端,

兩個例子效果一樣,但性能不同,ngx_access組比access_by_lua組快了大約一個數量級

執行順序(五)content

content階段指令:proxy_pass、echo

content 階段屬于一個比較靠后的處理階段,運行在先前介紹過的 rewrite 和 access 這兩個階段之后,當和 rewrite、access 階段的指令一起使 用時,這個階段的指令總是最后運行,例如:

location /test {
  # rewrite phase
  set $age 1;
  rewrite_by_lua "ngx.var.age = ngx.var.age + 1";
  # access phase
  deny 10.32.168.49;
  access_by_lua "ngx.var.age = ngx.var.age * 3";
  # content phase
	echo "age = $age"; 
}

$ curl 'http://127.0.0.1/test'
age = 6

即使改變它們的書寫順序,也不會影響到執行順序,其中, set 指令來自 ngx_rewrite 模塊,運行于 rewrite 階段;而 rewrite_by_lua 指令來自 ngx_lua 模 塊,運行于 rewrite 階段的末尾;接下來, deny 指令來自 ngx_access 模塊,運行于 access 階段;再下來, access_by_lua 指令同樣來自 ngx_lua 模 塊,運行于 access 階段的末尾;最后,我們的老朋友 echo 指令則來自 ngx_echo 模塊,運行在 content 階段,

進一步地,在 rewrite 和 access 這兩個階段,多個模塊的配置指令可以同時使用,譬如上例中的 set 指令和 rewrite_by_lua 指令同處 rewrite 階段, 而 deny 指令和 access_by_lua 指令則同處 access 階段,但不幸的是,這通常不適用于 content 階段,

絕大多數 Nginx 模塊在向 content 階段注冊配置指令時,本質上是在當前的 location 配置塊中注冊所謂的“內容處理程式”(content handler),每一 個 location 只能有一個“內容處理程式”,因此,當在 location 中同時使用多個模塊的 content 階段指令時,只有其中一個模塊能成功注冊“內容處理程式”,考慮下面這個有問題的例子:

? location /test {
? echo hello;
? content_by_lua 'ngx.say("world")'; 
?}

這里,ngx_echo 模塊的 echo 指令和 ngx_lua 模塊的 content_by_lua 指令同處 content 階段,于是只有其中一個模塊能注冊和運行這個 location 的“內容處理程式”:
$ curl 'http://localhost:8080/test' 
world

應當避免在同一個 location 中使用多個模塊 的 content 階段指令,

content_by_lua 'ngx.say("hello")';

總結

  • 使用多條 echo 指令是沒問題的,因為它們同屬 ngx_echo 模塊,而且 ngx_echo 模塊規定和實作了它們之間的執行順序,值得一提的是,并非所有模 塊的指令都支持在同一個 location 中被使用多次,例如 content_by_lua 就只能使用一次

    錯誤的寫法:
    ? location /test {
    ? content_by_lua 'ngx.say("hello")';
    ? content_by_lua 'ngx.say("world")'; 
    ?}
    
    正確的寫法:
    location /test {
    	content_by_lua 'ngx.say("hello") ngx.say("world")';
    }
    
    即在 content_by_lua 行內的 Lua 代碼中呼叫兩次 ngx.say 函式,而不是在當前 location 中使用兩次 content_by_lua 指令,
    
  • 類似地, ngx_proxy 模塊的 proxy_pass 指令和 echo 指令也不能同時用在一個 location 中,因為它們也同屬 content 階段,不少 Nginx 新手都會犯類似下面這樣的錯誤:

    location /test {
    	echo "before...";
    	proxy_pass http://127.0.0.1:8080/foo; 
    	echo "after...";
    }
    

    要實作這個例子希望達到的效果,需要改用 ngx_echo 模塊提供的 echo_before_body 和 echo_after_body 這兩條配置指令:

    實作這個例子希望達到的效果,需要改用 ngx_echo 模塊提供的 echo_before_body 和 echo_after_body 這兩條配置指令:
    location /test {
      echo_before_body "before..."; 
      proxy_pass http://127.0.0.1:8080/foo; 
      echo_after_body "after...";
    }
    
    
  • 使用多個模塊的 content 階段指令時,只有其中一個模塊能成功注冊“內容處理程式”

執行順序(六) ngx_index 模塊, ngx_autoindex 模塊

在一個 location 中使用 content 階段指令時,通常情況下就是對應的 Nginx 模塊注冊該 location 中的“內容處理程 序”,那么當一個 location 中未使用任何 content 階段的指令,即沒有模塊注冊“內容處理程式”時,content 階段會發生什么事情呢?誰又來擔負起 生成內容和輸出回應的重擔呢?答案就是那些把當前請求的 URI 映射到檔案系統的靜態資源服務模塊,當存在“內容處理程式”時,這些靜態資源服務模 塊并不會起作用;反之,請求的處理權就會自動落到這些模塊上,

Nginx 一般會在 content 階段安排三個這樣的靜態資源服務模塊(除非你的 Nginx 在構造時顯式禁用了這三個模塊中的一個或者多個,又或者啟用了這 種型別的其他模塊),按照它們在 content 階段的運行順序,依次是 **ngx_index 模塊, ngx_autoindex 模塊,以及 ngx_static 模塊,**下面就來逐一介紹一下這三個模塊,

ngx_indexngx_autoindex模塊都只會作用于那些URI以/結尾的請求,例如請求GET /cats/,而對于不以/結尾的請求則會直接忽略,而 ngx_static 模塊則剛好相反,直接忽略那些 URI 以 / 結尾的請求,

ngx_index 模塊主要用于在檔案系統目錄中自動查找指定的首頁檔案,類似 index.html 和 index.htm 這樣的,例如:
location / {
	root /var/www/;
	index index.htm index.html; 
}

如果index檔案不存在,則放棄處理權給 content 階段的下一個模塊,

echo_exec 指令和 rewrite 指令可以發起“內部跳轉”,這種跳轉會自動修改當前請求的 URI,并且重新匹 配與之對應的 location 配置塊,再重新執行 rewrite、access、content 等處理階段,因為是“內部跳轉”,所以有別于 HTTP 協議中定義的基于 302 和 301 回應的“外部跳轉”,最終==用戶的瀏覽器的地址欄也不會發生變化,依然是原來的 URI 位置,==而 ngx_index 模塊一旦找到了 index 指令中列舉的 檔案之后,就會發起這樣的“內部跳轉”,仿佛用戶是直接請求的這個檔案所對應的 URI 一樣,

洗掉index網頁

因為 ngx_index 模塊找不到 index 指令指定的檔案(在這里就是 index.html),接著把處理權轉給 content 階段的后續模塊,而后續的模 塊也都無法處理這個請求,于是 Nginx 只好放棄,輸出了錯誤頁,并且在 Nginx 錯誤日志中留下了類似這一行資訊:

[error] 28789#0: *1 directory index of "/var/www/" is forbidden
所謂 directory index 便是生成“目錄索引”的意思,典型的方式就是生成一個網頁,上面列舉出 /var/www/ 目錄下的所有檔案和子目錄,而運行在

ngx_index 模塊之后的 ngx_autoindex 模塊就可以用于自動生成這樣的“目錄索引”網頁,我們來把上例修改一下:

location / {
	root /var/www/;
	index index.html;
	autoindex on; 
}

當你的檔案系統中存在 /var/www/index.html 時,優先運行的 ngx_index 模塊就會發起“內部跳轉”,根本輪不到 ngx_autoindex 執行,

執行順序(七)nginx_static與配置前綴

location / 中沒有使用運行在 content 階段的模塊指令,于是也就沒有模塊注冊這個 location 的“內容處理程 序”,處理權便自動落到了在 content 階段“墊底”的那 3 個靜態資源服務模塊,

在 content 階段默認“墊底”的最后一個模塊便是極為常用的 ngx_static 模塊,這個模塊主要實作服務靜態檔案的功能,

“配置前綴”是由什么來決定的呢?

默認情況下,就是 Nginx 安裝時的根目錄(或者說 Nginx 構造時傳遞給 ./configure 腳本的 --prefix 選項的 路徑值),如果 Nginx 安裝到了 /usr/local/nginx/ 下,則“配置前綴”便是 /usr/local/nginx/,同時默認的“檔案根目錄”便是 /usr/local/nginx/html/. 不過,我們也可以在啟動 Nginx 的時候,通過 --prefix 命令列選項臨時指定自己的“配置前綴”路徑,假設我們啟動 Nginx 時使用的命令是

nginx -p /home/agentzh/test/
則對于該服務器實體,其“配置前綴”便是 /home/agentzh/test/,而默認的“檔案根目錄”便是 /home/agentzh/test/html/. “配置前綴”不僅會決定默認的“檔案根目錄”,還決定著 Nginx 組態檔中許多相對路徑值如何解釋為絕對路徑,

==獲取當前“ 檔案根目錄”的路徑==有一個非常簡便的方法,那就是請求一個肯定不存在的檔案所對應的資源名,然后看日志,

初學者常犯的一個錯誤是忘記配置 content 階段的模塊指令,而他們自己其實并不期望使用 content 階段預設運行的靜態資源服務,例如:

location /auth {
	access_by_lua '
		-- abc --
	';
}
顯然,這個 /auth 介面只定義了 access 階段的配置指令,即 access_by_lua,并未定義任何 content 階段的配置指令,于是當我們請求 /auth 介面 時,在 access 階段的 Lua 代碼會如期執行,然后 content 階段的那些靜態檔案服務會緊接著自動發生作用,直至 ngx_static 模塊去檔案系統上找 名為 auth 的檔案,而經常地,404 錯誤頁會拋出,除非運氣太好,在對應路徑上確實存在一個叫做 auth 的檔案,所以,一條經驗是,當遇到意外的 404 錯誤并且又不涉及靜態檔案服務時,應當首先檢查是否在對應的 location 配置塊中恰當地配置了 content 階段的模塊指令,例如 content_by_lua、 echo 以及 proxy_pass 之類,

總結:

  • ngx_indexngx_autoindex模塊都只會作用于那些URI以/結尾的請求,例如請求GET /cats/,而對于不以/結尾的請求則會直接忽略,而 ngx_static 模塊則剛好相反,直接忽略那些 URI 以 / 結尾的請求,

  • 很多初學者會想當然地把 404 錯誤理解為某個 location 不存在,其實上面這個例子表明,即使 location 存在并成功匹配,也是可能回傳 404 錯誤 頁的,因為決定著 404 錯誤頁的是抽象的“資源”是否存在,而非某個具體的 location 是否存在,

  • 當遇到意外的 404 錯誤并且又不涉及靜態檔案服務時,應當首先檢查是否在對應的 location 配置塊中恰當地配置了 content 階段的模塊指令,例如 content_by_lua、 echo 以及 proxy_pass 之類

執行順序(八)(11個階段)

Nginx 處理請求的程序一共劃分為 11 個階段,按照執行順序依次是 post-read、server-rewrite、find- config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content 以及 log.

post-read

在這里插入圖片描述

如果在請求上例中的 /test 介面時沒有指定 X-My-IP 請求頭,或者提供的 X-My-IP 請求頭的值不是合法的 IP 地址,那么 Nginx 就不會對來源地址進行改寫

$ curl -H 'X-My-IP: abc' localhost:8080/test 
from: 127.0.0.1

如果從另一臺機器訪問這個 /test 介面,那么即使指定了合法的 X-My-IP 請求頭,也不會觸發 Nginx 對來源地址進行改寫, 這是因為上例已經使用 set_real_ip_from 指令規定了來源地址的改寫操作只對那些來自 127.0.0.1 的請求生效,這種過濾機制可以避免來自其他不受信任的地址的惡意欺騙, 當然,也可以通過 set_real_ip_from 指令指定一個 IP 網段(利用 (三) 中介紹過的“CIDR 記法”),此外,同時配置多個 set_real_ip_from 陳述句也是允許 的,這樣可以指定多個受信任的來源地址或地址段,

下面是一個例子:
set_real_ip_from 127.0.0.0/24;

ngx_realip模塊實際作用是什么?

答案是:當 Nginx 處理的請求經過了某個 HTTP 代理服務器的轉發時,這個模塊就變得特別有用,當原始的用戶請求經過轉發之后,Nginx 接收到的請求的來源地址無一例外地變成了該代理服務器的 IP 地址,于是 Nginx 以及 Nginx 背后的應用就無法知道原始請求的真實來源,所以,一般我們會在 Nginx 之前的代理服務器中把請求的原始來源地址編 碼進某個特殊的 HTTP 請求頭中(例如上例中的 X-My-IP 請求頭),然后再在 Nginx 一側把這個請求頭中編碼的地址恢復出來,這樣 Nginx 中的后續處理階段(包括 Nginx 背后的各種后端應用)就會認為這些請求直接來自那些原始的地址,代理服務器就仿佛不存在一樣,正是因為這個需求,所以 ngx_realip 模塊才需要在第一個處理階段,即 post-read 階段,注冊處理程式,以便盡可能早地改寫請求的來源,

盡量在 server 配置塊中配置 ngx_realip 這樣的模塊,以避免上面介紹的這種棘手的例外情況,

server-rewrite

post-read 階段之后便是 server-rewrite 階段,**當 ngx_rewrite 模塊的配置指令直接書寫在 server 配置塊中時,基本上都是運行在 server-rewrite 階段,**下面就來看這樣的一個例子:

server {
	listen 8080;
	location /test {
		set $b "$a, world"; 
		echo $b;
	}
	set $a hello; 
}

這里,配置陳述句set $a hello直接寫在了server配置塊中,因此它就運行在server-rewrite階段,而server-rewrite階段要早于rewrite 階段運行,因此寫在location配置塊中的陳述句set b " b " b"a, world"便晚于外面的set $a hello陳述句運行,該例的測驗結果證明了這一點:

`$ curl localhost:8080/test`
hello,world

由于 server-rewrite 階段位于 post-read 階段之后,所以 server 配置塊中的 set 指令也就總是運行在 ngx_realip 模塊改寫請求的來源地址之后, 來看下面這個例子:

server {
  listen 8080;
  set $addr $remote_addr;
  set_real_ip_from 127.0.0.1; 
  real_ip_header X-Real-IP;
  location /test {
  	echo "from: $addr";
  } 
}
請求 /test 介面的結果如下:
$ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test
from: 1.2.3.4
在這個例子中,雖然 set 指令寫在了 ngx_realip 的配置指令之前,但仍然晚于 ngx_realip 模塊執行,所以 $addr 變數在 server-rewrite 階段被 set 指令賦值時,從 $remote_addr 變數讀出的來源地址已經是經過改寫過的了,

find-config階段

緊接在 server-rewrite 階段后邊的是 find-config 階段,這個階段并不支持 Nginx 模塊注冊處理程式,而是由 Nginx 核心來完成當前請求與 location 配置塊之間的配對作業,

換句話說,在此階段之前,請求并沒有與任何 location 配置塊相關聯,因此,對于運行在 find-config 階段之 前的 post-read 和 server-rewrite 階段來說,只有 server 配置塊以及更外層作用域中的配置指令才會起作用,這就是為什么只有寫在 server 配置塊中的 ngx_rewrite 模塊的指令才會運行在 server-rewrite 階段,這也是為什么前面所有例子中的 ngx_realip 模塊的指令也都特意寫在了 server 配 置塊中,以確保其注冊在 post-read 階段的處理程式能夠生效,

💗rewrite階段

rewrite階段指令:

1.set

2.set_by_luarewrite_by_lua

3.rewrite

4.rewrite_by_lua

5.more_set_input_headers

1、2 (同理 rewrite和rewrite_by_lua)

set_by_lua 指令支持通過一小段用戶 Lua 代碼來計算出一個結果,然后賦給指定的 Nginx 變數,和 set 指令相似, set_by_lua 指令也有自動創建不存在的 Nginx 變數的功能,

location /test {
        set $a 32;
        set $b 56;
        set_by_lua $c "return ngx.var.a + ngx.var.b";
        set $equation "$a + $b = $c";
        echo $equation;
}

在這里插入圖片描述

location /test {
        set $a 1;
        rewrite_by_lua "ngx.var.a = ngx.var.a + 1";
        set $a 56;
        echo $a;
}

$ curl 'http://127.0.0.1/test'
57

從 rewrite 階段開始,location 配置塊中的指令便可以產生作用,前面已經介紹過,當 ngx_rewrite 模塊的指令用于 location 塊中時,便是運行在 這個 rewrite 階段,另外, ngx_set_misc 模塊的指令也是如此,還有 ngx_lua 模塊的 set_by_lua 指令和 rewrite_by_lua 指令也不例外,

rewrite 階段再往后便是所謂的 post-rewrite 階段,這個階段也像 find-config 階段那樣不接受 Nginx 模塊注冊處理程式,而是由 Nginx 核心完成 rewrite 階段所要求的“內部跳轉”操作(如果 rewrite 階段有此要求的話),

server {
  listen 8080;
  location /foo { 
    set $a hello;
    rewrite ^ /bar; 
  }
  location /bar {
  	echo "a = [$a]";
  } 
}

這里在location /foo中通過rewrite指令把當前請求的URI無條件地改寫為/bar,同時發起一個“內部跳轉”,最終跳進了location /bar中,這 里比較有趣的地方是“內部跳轉”的作業原理,==“內部跳轉”本質上其實就是把當前的請求處理階段強行倒退到 find-config 階段,以便重新進行請求 URI 與 location 配置塊的配對,==比如上例中,運行在 rewrite 階段的 rewrite 指令就讓當前請求的處理階段倒退回了 find-config 階段,由于此時 當前請求的URI已經被rewrite指令修改為了/bar,所以這一次換成了location /bar與當前請求相關聯,然后再接著從rewrite階段往下執行,

不過這里更有趣的地方是,**倒退回 find-config 階段的動作并不是發生在 rewrite 階段,而是發生在后面的 post-rewrite 階段,**上例中的 rewrite 指令只是簡單地指示 Nginx 有必要在 post-rewrite 階段發起“內部跳轉”,這個設計對于 Nginx 初學者來說,或許顯得有些古怪:“為什么不直接在 rewrite 指令執行時立即進行跳轉呢?”答案其實很簡單,那就是為了在最初匹配的 location 塊中支持多次反復地改寫 URI

當然,如果在 server 配置塊中直接使用 rewrite 配置指令對請求 URI 進行改寫,則不會涉及“內部跳轉”,因為此時 URI 改寫發生在 server-rewrite階段,早于執行 location 配對的 find-config 階段,

preaccess(post-rewrite后,access前)

運行在 post-rewrite 階段之后的是所謂的 preaccess 階段,該階段在 access 階段之前執行,故名 preaccess.
標準模塊 ngx_limit_req 和 ngx_limit_zone 就運行在此階段,前者可以控制請求的訪問頻度,而后者可以限制訪問的并發度,

server {
listen 8080;
location /test { 
set_real_ip_from 127.0.0.1; 
real_ip_header X-Real-IP;
set $addr $remote_addr;
echo "from: $addr"; }
}

$ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test
from: 127.0.0.1

這里,我們在 rewrite 階段將 $remote_addr 的值保存到了用戶變數 $addr 中,然后再輸出,因為 rewrite 階段先于 preaccess 階段執行,所以當ngx_realip 模塊尚未在 preaccess 階段改寫來源地址時,最初的來源地址就已經在 rewrite 階段被讀取了,

變數賦值操作 在改寫新地址之前,
在這里插入圖片描述

access階段

pre(access)階段指令:real_ip、allow、deny

access 階段,前面我們已經知道了,標準模塊 ngx_access、第三方模塊 ngx_auth_request 以及第三方模塊 ngx_lua 的 access_by_lua 指令就運行在這個階段,

access 階段之后便是 post-access 階段,從這個階段的名字,我們也能一眼看出它是緊跟在 access 階段后面執行的,這個階段也和 post- rewrite 階段類似,并不支持 Nginx 模塊注冊處理程式,而是由 Nginx 核心自己完成一些處理作業,post-access 階段主要用于配合 access 階段實作標準 ngx_http_core 模塊提供的配置指令 satisfy 的功能,

對于多個 Nginx 模塊注冊在 access 階段的處理程式, satisfy 配置指令可以用于控制它們彼此之間的協作方式,比如模塊 A 和 B 都在 access 階段注冊 了與訪問控制相關的處理程式,那就有兩種協作方式,一是模塊 A 和模塊 B 都得通過驗證才算通過,二是模塊 A 和模塊 B 只要其中任一個通過驗證就 算通過,第一種協作方式稱為 all 方式(或者說“與關系”),第二種方式則被稱為 any 方式(或者說“或關系”),

默認情況下,Nginx 使用的是 all 方 式,下面是一個例子:

location /test { 
	satisfy all;
  deny all;  驗證1
  access_by_lua 'ngx.exit(ngx.OK)';  驗證2
  echo something important; 
}

這里,我們在/test介面中同時配置了ngx_access模塊和ngx_lua模塊,這樣access階段就由這兩個模塊一起來做檢驗作業,其中,陳述句deny all 會讓 ngx_access 模塊的處理程式總是拒絕當前請求,而陳述句 access_by_lua 'ngx.exit(ngx.OK)' 則總是允許訪問,當我們通過 satisfy 指令配置了 all 方式時,就需要 access 階段的所有模塊都通過驗證,但不幸的是,這里 ngx_access 模塊總是會拒絕訪問,所以整個請求就會被拒

然而,如果我們把上例中的satisfy all陳述句更改為satisfy any,即請求反而最終通過了驗證,這是因為在 any 方式下,access 階段只要有一個模塊通過了驗證,就會認為請求整體通過了驗證,而在上例中, ngx_lua 模塊的 access_by_lua 陳述句總是會通過驗證的,

try-files階段

在這里插入圖片描述
緊跟在 post-access 階段之后的是 try-files 階段,這個階段專門用于實作標準配置指令 try_files 的功能,并不支持 Nginx 模塊注冊處理程式,

try_files 指令在許多 FastCGI 應用的配置中都有用到,

try_files 指令接受兩個以上任意數量的引數,每個引數都指定了一個 URI. 這里假設配置了 N 個引數,則 Nginx 會在 try-files 階段,依次把前 N-1 個 引數映射為檔案系統上的物件(檔案或者目錄),然后檢查這些物件是否存在,一旦 Nginx 發現某個檔案系統物件存在,就會在 try-files 階段把當前 請求的 URI 改寫為該物件所對應的引數 URI(但不會包含末尾的斜杠字符,也不會發生 “內部跳轉”),**如果前 N-1 個引數所對應的檔案系統物件都不存在,try-files 階段就會立即發起“內部跳轉”到最后一個引數(即第 N 個引數)所指定的 URI,**當 try_files 指令處理到它的最后一個引數時,總是直接執行“內部跳轉”,而不論其對應的檔案系統物件是否存在,

try_files 指令本質上只是有條件地改寫當前請求的 URI,而這里說的“條件”其實就是**檔案系統上的物件是否存在,**當“條 件”都不滿足時,它就會無條件地發起一個指定的“內部跳轉”,當然,除了無條件地發起“內部跳轉”之外, try_files 指令還支持直接回傳指定狀態碼的 HTTP 錯誤頁,例如:

try_files /foo /bar/ =404;

這行配置是說,當/foo和/bar/引數所對應的檔案系統物件都不存在時,就直接回傳404 Not Found錯誤頁,注意這里它是如何使用等號字符前綴 來標識 HTTP 狀態碼的,

總結:

  • Nginx 處理請求的程序一共劃分為 11 個階段,按照執行順序依次是 post-read、server-rewrite、find- config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content 以及 log.

  • set_real_ip_from的重要性,如果從另一臺機器訪問這個 /test 介面,那么即使指定了合法的 X-My-IP 請求頭,也不會觸發 Nginx 對來源地址進行改寫,

  • ==“內部跳轉”本質上其實就是把當前的請求處理階段強行倒退到 find-config 階段,以便重新進行請求 URI 與 location 配置塊的配對,==倒退回 find-config 階段的動作并不是發生在 rewrite 階段,而是發生在后面的 post-rewrite 階段,

  • 為什么不直接在 rewrite 指令執行時立即進行跳轉呢?”答案其實很簡單,那就是為了在最初匹配的 location 塊中支持多次反復地改寫 URI

  • 如果在 server 配置塊中直接使用 rewrite 配置指令對請求 URI 進行改寫,則不會涉及“內部跳轉”,因為此時 URI 改寫發生在 server-rewrite階段,早于執行 location 配對的 find-config 階段,

  • 盡量在 server 配置塊中配置 ngx_realip 這樣的模塊,這個模塊的注冊處理程式有點復雜,

  • location uri正則運算式

    . :匹配除換行符以外的任意字符
    ? :重復0次或1+ :重復1次或更多次
    * :重復0次或更多次
    \d :匹配數字
    ^ :匹配字串的開始
    $ :匹配字串的結束
    {n} :重復n次
    {n,} :重復n次或更多次
    [c] :匹配單個字符c
    [a-z] :匹配a-z小寫字母的任意一個
    (a|b|c) : 屬線表示匹配任意一種情況,每種情況使用豎線分隔,一般使用小括號括括住,匹配符合a字符 或是b字符 或是c字符的字串
    \ 反斜杠:用于轉義特殊字符
    

應用場景

一:HTTP服務器

Nginx本身也是一個靜態資源的服務器,當只有靜態資源的時候,就可以使用Nginx來做服務器,如果一個網站只是靜態頁面的話,那么就可以通過這種方式來實作部署,

1、首先在檔案根目錄 Docroot(/usr/local/var/www)下創建html目錄, 然后在html中放一個test.html;
即/usr/local/var/www/html/test.html

2、配置nginx.conf中的server

user mengday staff;

http {
    server {
        listen       80;
        server_name  localhost;
        client_max_body_size 1024M;

        # 默認location
        location / {
            root   /usr/local/var/www/html;
            index  index.html index.htm;
        }
    }
}

3、訪問測驗

  • http://localhost/ 指向/usr/local/var/www/index.html, index.html是安裝nginx自帶的html
  • http://localhost/test.html 指向/usr/local/var/www/html/test.html
    注意:如果訪問圖片出現403 Forbidden錯誤,可能是因為nginx.conf 的第一行user配置不對

4、指令簡介

  • server : 用于定義服務,http中可以有多個server塊
  • listen : 指定服務器偵聽請求的IP地址和埠,如果省略地址,服務器將偵聽所有地址,如果省略埠,則使用標準埠
  • server_name : 服務名稱,用于配置域名
  • location : 用于配置映射路徑uri對應的配置,一個server中可以有多個location, location后面跟一個uri,可以是一個正則運算式, / 表示匹配任意路徑, 當客戶端訪問的路徑滿足這個uri時就會執行location塊里面的代碼
  • root : 根路徑,當訪問http://localhost/test.html,“/test.html”會匹配到”/”uri, 找到root為/usr/local/var/www/html,用戶訪問的資源物理地址=root + uri = /usr/local/var/www/html + /test.html=/usr/local/var/www/html/test.html
  • index : 設定首頁,當只訪問server_name時后面不跟任何路徑是不走root直接走index指令的;如果訪問路徑中沒有指定具體的檔案,則回傳index設定的資源,如果訪問http://localhost/html/ 則默認回傳index.html

5、location uri正則運算式

. :匹配除換行符以外的任意字符
? :重復0次或1+ :重復1次或更多次
* :重復0次或更多次
\d :匹配數字
^ :匹配字串的開始
$ :匹配字串的結束
{n} :重復n次
{n,} :重復n次或更多次
[c] :匹配單個字符c
[a-z] :匹配a-z小寫字母的任意一個
(a|b|c) : 屬線表示匹配任意一種情況,每種情況使用豎線分隔,一般使用小括號括括住,匹配符合a字符 或是b字符 或是c字符的字串
\ 反斜杠:用于轉義特殊字符

二、靜態服務器

通常會提供一個上傳的功能,其他應用如果需要靜態資源就從該靜態服務器中獲取,

1、在/usr/local/var/www 下分別創建images和img目錄,分別在每個目錄下放一張test.jpg

http {
    server {
        listen       80;
        server_name  localhost;

        set $doc_root /usr/local/var/www;

        # 默認location
        location / {
            root   /usr/local/var/www/html;
            index  index.html index.htm;
        }

        location ^~ /images/ {
            root $doc_root;
       }

       location ~* \.(gif|jpg|jpeg|png|bmp|ico|swf|css|js)$ {
           root $doc_root/img;
       }
    }
}

自定義變數使用set指令,語法 set 變數名值;參考使用變數名值;參考使用變數名; 這里自定義了doc_root變數,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/296846.html

標籤:其他

上一篇:docker搭建lnmp環境

下一篇:專案實戰2 | 基于Swarm+Prometheus實作雙VIP可監控Web高可用集群

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more