Nginx - 代理、緩存

Nginx

標籤 : nginx


代理

代理服務可簡單的分爲正向代理和反向代理:

  • 正向代理: 用於代理內部網絡對Internet的連接請求(如VPN/NAT),客戶端指定代理服務器,並將本來要直接發送給目標Web服務器的HTTP請求先發送到代理服務器上, 然後由代理服務器去訪問Web服務器, 並將Web服務器的Response回傳給客戶端:

  • 反向代理: 與正向代理相反,如果局域網向Internet提供資源,並讓Internet上的其他用戶可以訪問局域網內資源, 也可以設置一個代理服務器, 它提供的服務就是反向代理. 反向代理服務器接受來自Internet的連接,然後將請求轉發給內部網絡上的服務器,並將Response回傳給Internet上請求連接的客戶端:

  • 總結來說:

    • 正向代理和客戶端同屬一個陣營,對於目標服務器來說,可將他們看成一個客戶端;
    • 反向代理和目標服務器同屬一個陣營,對於客戶端來說,他們”僞裝”成了一個目標服務器.

正向代理

由於使用Nginx做正向代理服務的相對較少, 因此Nginx提供的代理服務本身也比較簡單, 提供的指令也不多, 直接由ngx_http_core_module模塊支持.


指令

  • resolver
Syntax: resolver address ... [valid=time] [ipv6=on|off];
Default:    —
Context:    http, server, location
- address: DNS服務器IP地址, 如果不指定端口號, 默認使用53; 從1.1.7版本開始, 該指令支持多個IP地址.
- time: 設置數據包在網絡中的有效時間.
  • resolver_timeout
Syntax: resolver_timeout time;
Default:    
resolver_timeout 30s;
Context:    http, server, location
設置DNS服務器域名解析超時時間.
  • proxy_pass
Syntax: proxy_pass URL;
Default: 
Context:    location, if in location, limit_except
設置被代理服務器的協議和地址,在正向代理中,該指令的配置相對固定: proxy_pass http://$http_host$request_uri;

注: proxy_pass不僅用於正向代理,更主要是應用於反向代理服務,後面還有關於它的詳細敘述.


示例

    server {
        listen       8001;

        resolver 192.168.111.9 192.168.111.8 192.168.100.8 192.168.100.9;

        location / {
            proxy_pass http://$http_host$request_uri;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

注意:
1. 在配置正向代理的server塊中,不要使用server_name指令,即不能設置虛擬主機名或IP.
2. Nginx正向代理不支持代理HTTPS站點.


反向代理

反向代理是Nginx最常用且最重要的功能之一,由標準HTTP模塊ngx_http_proxy_model支持.同正向代理類似,反向代理一般也單獨配置一個server塊.


指令

  • proxy_pass
Syntax: proxy_pass URL;
Default: 
Context:    location, if in location, limit_except

同正向代理, 該指令用來配置被代理服務器地址,可以是主機名稱/IP地址+端口號等形式:

proxy_pass http://localhost:8000/uri/;
  • upstream
Syntax: upstream name { ... }
Default:    —
Context:    http

如果被代理的是一組服務器的話, 可以使用upstream指令配置一組後端服務器.Defines a group of servers. Servers can listen on different por ts. In addition, servers listening on TCP and UNIX-domain sockets can be mixed.

http {
    ## ...

    upstream proxy_servs {
        server 10.45.156.170:80;
        server 10.45.156.171:80;
        server 10.45.156.172:80;
    }

    server {
        location ~* \.(do|jsp|jspx)?$ {
            proxy_pass http://proxy_servs;
        }

        ## ...
    }
}

注意: 對於proxy_pass/server指令後的URL中是否包含URI, Nginx有不同的處理方式:
1. 如果URL中不包含URI, 則Nginx不會改變原地址的URI;
2. 如果URL中包含了URI, 則Nginx會使用新的URI 代替 原來的URI.

  • 其他反向代理指令
指令 描述
proxy_pass_request_headers on | off; Indicates whether the header fields of the original request are passed to the proxied server.
proxy_pass_request_body on | off; Indicates whether the original request body is passed to the proxied server.
proxy_set_header field value; Allows redefining or appending fields to the request header passed to the proxied server.
proxy_set_body value; Allows redefining the request body passed to the proxied server.
proxy_hide_header field; The proxy_hide_header directive sets additional fields that will not be passed.
proxy_pass_header field; Permits passing “Date”, “Server”, “X-Pad” and “X-Accel-…” header fields from a proxied server to a client.
proxy_bind address [transparent] | off; Makes outgoing connections to a proxied server originate from the specified local IP address.
proxy_connect_timeout time; Defines a timeout for establishing a connection with a proxied server.
proxy_read_timeout time; Defines a timeout for reading a response from the proxied server.
proxy_send_timeout time; Sets a timeout for transmitting a request to the proxied server.
proxy_http_version 1.0 | 1.1; Sets the HTTP protocol version for proxying. By default, version 1.0 is used.
proxy_method method; Specifies the HTTP method to use in requests forwarded to the proxied server instead of the method from the client request.
proxy_ignore_client_abort on | off; Determines whether the connection with a proxied server should be closed when a client closes the connection without waiting for a response.
proxy_ignore_headers field ...; Disables processing of certain response header fields from the proxied server.
proxy_redirect default | off | redirect replacement; Sets the text that should be changed in the “Location” and “Refresh” header fields of a proxied server response.
proxy_intercept_errors on | off; Determines whether proxied responses with codes greater than or equal to 300 should be passed to a client or be redirected to nginx for processing with the error_page directive.
proxy_headers_hash_max_size size; Sets the maximum size of hash tables used by the proxy_hide_header and proxy_set_header directives.
proxy_headers_hash_bucket_size size; Sets the bucket size for hash tables used by the proxy_hide_header and proxy_set_header directives.
proxy_next_upstream [flag]; Specifies in which cases a request should be passed to the next server, detail
proxy_ssl_session_reuse on | off; Determines whether SSL sessions can be reused when working with the proxied server.

Proxy-Buffer

Proxy Buffer啓用後,Nginx會將被代理的服務器的響應數據異步地傳遞給客戶端:

Nginx首先儘可能地從後端服務器那裏接收響應數據放在*Buffer*中,如果在接收過程中發現*Buffer*已經裝滿,Nginx會將部分接收到的數據臨時存放到磁盤的臨時文件中.當一次響應數據被萬千接收或*Buffer*已經裝滿時,Nginx開始向客戶端傳輸數據.此時Nginx處於`BUSY`狀態.

而當Proxy Buffer關閉時, Nginx只要接收到響應數據就會同步地傳遞給客戶端,不會讀取完整響應數據.

指令 描述
proxy_buffering on | off; Enables or disables buffering of responses from the proxied server.
proxy_buffers number size; Sets the number and size of the buffers used for reading a response from the proxied server, for a single connection.
proxy_buffer_size size; Sets the size of the buffer used for reading the first part of the response received from the proxied server.
proxy_busy_buffers_size size; When buffering of responses from the proxied server is enabled, limits the total size of buffers that can be busy sending a response to the client while the response is not yet fully read.
proxy_temp_path path [level1 [level2 [level3]]]; Defines a directory for storing temporary files with data received from proxied servers.
proxy_temp_file_write_size size; Limits the size of data written to a temporary file at a time, when buffering of responses from the proxied server to temporary files is enabled.
proxy_max_temp_file_size size; This directive sets the maximum size of the temporary file.

注意: Proxy Buffer配置是針對每一個請求起作用,而不是全局概念,即每個請求都會按照這些指令來配置各自的Buffer, Nginx不會生成一個公共的Proxy Buffer供代理請求使用.


負載均衡

Nginx反向代理的一個重要用途就是負載均衡:

負載均衡的原理是利用一定的分配策略將網絡負載平衡地分攤到網絡集羣的各個節點, 使得單個重負載任務能夠分擔到多個單元上並行處理,或使得大量的併發訪問數據流量分攤到多個節點上分別處理,從而減少用戶的等待響應時間.

在實際應用中, 負載均衡會根據網絡的不同層次(一般按照ISO/OSI七層參考模型)進行劃分. 現代負載均衡技術主要實現和作用於第四層/第七層,完全獨立於網絡基礎硬件設備; Nginx一般被認爲是第七層負載均衡.
負載均衡算法多種多樣: 靜態負載均衡算法/動態負載均衡算法.靜態負載均衡算法比較簡單,主要有一般輪詢算法/基於比率的加權輪詢算法以及基於優先級的加權輪詢算法等.動態負載均衡算法在較複雜的網絡環境中適應性更強,表現更好,主要有基於任務量的最少連接優先算法/基於性能的最快響應優先算法/預測算法以及動態性能分配算法等; Nginx實現採用基於優先級的加權輪詢算法.


Nginx負載均衡

前在介紹upstream時使用了對所有請求的一般輪詢規則的負載均衡, 下面介紹基於優先級的加權輪詢規則的負載均衡:

http {
    ## ...

    upstream proxy_servs {
        server 10.45.156.170:80 weight=5;
        server 10.45.156.171:80 weight=2;
        server 10.45.156.172:80;    #默認weight=1
    }

    server {
        location ~* \.(do|jsp|jspx)?$ {
            proxy_pass http://proxy_servs;
        }

        ## ...
    }
}

upstream的服務器組中每個server被賦予了不同的優先級,weight就是輪詢策略的”權值”, 其中以10.45.156.170:80優先級最高.


緩存

響應速度是衡量Web應用服務性能優劣的重要指標之一,在動態網站中,除了優化發佈的內容本身之外,另一個重要的方法就是把不需要實時更新動態頁面輸出結果轉化成靜態頁面緩存,進而按照靜態網頁來訪問,提升響應速度.


緩存驅動技術

在Nginx中, 緩存驅動技術有兩種:

404驅動

  • 原理:

    Nginx處理客戶端請求時,一旦發現請求資源不存在,則會產生404錯誤,Nginx通過捕獲該錯誤,進一步轉向後端服務器請求數據,最後將後端服務器響應數據傳回給客戶端,同時在本地進行緩存.

  • 配置:

location / {
    root html;
    error_page 404 =200 @send_to_backend;
}

location @send_to_backend {
    internal;
    proxy_pass http://proxy_servs;

    proxy_set_header Accept-Encoding "";
    proxy_store on;
    proxy_store_access user:rw group:rw all:r;
    proxy_temp_path /var/www/tmp;
}

proxy_store指令是由Nginx-Proxy Store模塊提供的簡單緩存機制,詳見下文介紹.


資源不存在驅動

  • 原理:

    與404驅動大同小異, 該方法時通過location塊中的if條件判斷直接判斷請求資源是否存在, 不存在則直接驅動Nginx與後端服務器通信更新Web緩存.

  • 配置:

location / {
    root html;

    proxy_set_header Accept-Encoding "";
    proxy_store on;
    proxy_store_access user:rw group:rw all:r;
    proxy_temp_path /var/www/tmp;

    if ( !-f $request_filename ){
        proxy_pass http://proxy_servs;
    }
}

!-f判斷請求資源是否存,如不存在就proxy_pass給後端服務器生成數據傳給客戶端,同時Proxy Store緩存.


Nginx緩存

Nginx自身實現了兩種緩存機制, Proxy Cache/Proxy Store:

Proxy Cache

Proxy Cache是Nginx自身實現的一個功能完整,性能不錯的緩存機制.Nginx服務啓動後, 會生成專門的進程對磁盤上的緩存文件進行掃描, 在內存中建立緩存索引, 提高訪問效率, 並且還會生成專門的管理進程對磁盤上的緩存文件進行過期判定/更新等方面的管理. Proxy Cache緩存支持任意連接響應數據的緩存, 不僅限於200狀態的數據.

與前面介紹過的Proxy Buffer不同:Proxy Buffer實現了後端服務器響應數據的異步傳輸, 而Proxy Cahce則實現了Nginx對客戶端數據請求的快速響應. Nginx在接收到後端服務器響應數據後, 一方面通過Proxy Buffer機制將數據傳遞給客戶端, 另一方面根據Proxy Cahce的配置將這些數據緩存到本地, 當客戶端下次訪問相同數據時, Nginx直接從本地檢索數據返回給客戶端, 從而減少與後端服務器的交互時間.

指令 描述
proxy_cache zone | off; Defines a shared memory zone used for caching.
proxy_cache_bypass string ...; Defines conditions under which the response will not be taken from a cache.
proxy_cache_key string; Defines a key for caching.
proxy_cache_lock on | off; When enabled, only one request at a time will be allowed to populate a new cache element identified according to the proxy_cache_key directive by passing a request to a proxied server.
proxy_cache_lock_timeout time; Sets a timeout for proxy_cache_lock.
proxy_cache_min_uses number; Sets the number of requests after which the response will be cached.
proxy_cache_use_stale [stale] Determines in which cases a stale cached response can be used when an error occurs during communication with the proxied server. The directive’s parameters match the parameters of the proxy_next_upstream directive.
proxy_cache_valid [code ...] time; Sets caching time for different response codes.
proxy_cache_path path keys_zone=name:size; Sets the path and other parameters of a cache
proxy_no_cache string ...; Defines conditions under which the response will not be saved to a cache.
http {
    ## ...

    upstream proxy_servs {
        server 10.45.156.170:80;
        server 10.45.156.171:80;
        server 10.45.156.172:80;
    }

    proxy_cache_path /var/www/proxycache levels=1:2 max_size=2m inactive=5m loader_sleep=1m keys_zone=MYPROXYCACHE:10m;
    proxy_temp_path /var/www/tmp;


    server {
        ## ...

        location / {
            proxy_pass http://proxy_servs;
            proxy_cache MYPROXYCACHE;
            proxy_cache_valid 200 302 1h;
            proxy_cache_valid 301 1d;
            proxy_cache_valid any 1m;
        }

        location @send_to_backend {
            proxy_pass http://proxy_servs;
        }
    }
}

注: Proxy Cache依賴於Proxy Buffer.且Proxy Cache沒有實現自動清理磁盤上緩存數據的能力, 因此在長時間使用過程中會對服務器存儲造成一定的壓力.


Proxy Store

Nginx還支持另一種將後端服務器數據緩存到本地的方法Proxy Store, 與Proxy Cache的區別是, 它對來自後端服務器的響應數據, 尤其是靜態數據只進行簡單的緩存, 且只能緩存200狀態碼下的響應數據, 不支持緩存過期更新, 內存索引建立等功能, 但支持設置用戶/用戶組對緩存的訪問權限.

指令 描述
proxy_store on | off | string; Enables saving of files to a disk.
proxy_store_access users:permissions ...; Sets access permissions for newly created files and directories.

Memcached緩存

Memcached是一套高性能的基於分佈式環境的緩存系統,用於動態Web應用可減輕後臺數據服務器的負載, 提高客戶端響應速度.Nginx的標準模塊ngx_http_memcached_module提供了對Memcached的支持.

指令 描述
memcached_pass address; Sets the memcached server address.
memcached_connect_timeout time; Defines a timeout for establishing a connection with a memcached server.
memcached_read_timeout time; Defines a timeout for reading a response from the memcached server.
memcached_send_timeout time; Sets a timeout for transmitting a request to the memcached server.
memcached_buffer_size size; Sets the size of the buffer used for reading the response received from the memcached server.
memcached_next_upstream status ... Specifies in which cases a request should be passed to the next server.

在配置Nginx使用Memcached時,還需要對Nginx配置的全局變量$memcached_key進行設置.


示例

Nginx首先請求Memcached, 如果緩存沒有命中(key爲"$uri?$args"), Nginx則proxy_pass給後端服務器響應該請求, 但此時也需要後端服務器配合, 在將數據響應給客戶端之後, 需要將響應內容手動寫入Memcached, 以供下次直接從Memcached檢索數據.

  • nginx.conf
location / {
    set $memcached_key "$uri?$args";
    memcached_pass 127.0.0.1:11211;
    error_page 404 =200 @send_to_backend;
    index  index.html index.htm;
}

location @send_to_backend {
    proxy_pass http://proxy_servs;
}
  • Java: MemcachedFilter
/**
 * @author jifang.
 * @since 2016/5/21 15:50.
 */
public class MemcachedFilter implements Filter {

    private MemcachedClient memcached;

    private static final int _1MIN = 60;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        try {
            MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses("127.0.0.1:11211"));
            memcached = builder.build();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        // 對PrintWriter包裝
        MemcachedWriter mWriter = new MemcachedWriter(response.getWriter());
        chain.doFilter(req, new MemcachedResponse((HttpServletResponse) response, mWriter));

        HttpServletRequest request = (HttpServletRequest) req;
        String key = request.getRequestURI();

        Enumeration<String> names = request.getParameterNames();
        if (names.hasMoreElements()) {
            String name = names.nextElement();
            StringBuilder sb = new StringBuilder(key)
                    .append("?").append(name).append("=").append(request.getParameter(name));
            while (names.hasMoreElements()) {
                name = names.nextElement();
                sb.append("&").append(name).append("=").append(request.getParameter(name));
            }
            key = sb.toString();
        }

        try {
            String rspContent = mWriter.getRspContent();
            memcached.set(key, _1MIN, rspContent);
        } catch (TimeoutException | InterruptedException | MemcachedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void destroy() {
    }


    private static class MemcachedWriter extends PrintWriter {

        private StringBuilder sb = new StringBuilder();

        private PrintWriter writer;

        public MemcachedWriter(PrintWriter out) {
            super(out);
            this.writer = out;
        }

        @Override
        public void print(String s) {
            sb.append(s);
            this.writer.print(s);
        }

        public String getRspContent() {
            return sb.toString();
        }
    }

    private static class MemcachedResponse extends HttpServletResponseWrapper {

        private PrintWriter writer;

        public MemcachedResponse(HttpServletResponse response, PrintWriter writer) {
            super(response);
            this.writer = writer;
        }

        @Override
        public PrintWriter getWriter() throws IOException {
            return this.writer;
        }
    }
}

分佈式Memcached

爲了充分發揮Memcached分佈式優勢,提升服務器響應速度,我們使用Nginx的一致性Hash模塊, 將request分佈到不同的Memcached Server中, 同時, 對於訪問不命中的情況, 也需要後端服務器的支持, 後端服務器在對客戶端做出響應的同時, 需要將響應數據按照一致性Hash規則, 將響應數據寫入Memcached.

  • 配置Memcached一致性Hash規則

  •     upstream memcached_servs {
            consistent_hash "$uri?$args";
            server 127.0.0.1:11211;
            server 127.0.0.1:11212;
            server 127.0.0.1:11213;
        }
    
        server {
            location / {
                set $memcached_key "$uri?$args";
                memcached_pass memcached_servs;
                error_page 404 =200 @send_to_backend;
                index  index.html index.htm;
            }
        }


    • Java: MemcachedFilter

    注意: Java端的一致性Hash算法的選用需要和Nginx一致, 否則會出現Nginx讀取的Memcached與Java寫入的Memcached不在同一臺的情況.
    public class MemcachedFilter implements Filter {
    
        private MemcachedClient memcached;
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            try {
                MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses("127.0.0.1:11211 127.0.0.1:11212 127.0.0.1:11213"));
                builder.setSessionLocator(new ElectionMemcachedSessionLocator());
                memcached = builder.build();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
        // ...
    }

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