nginx 学习4-反向代理/负载均衡1-http模块-浏览器缓存

238 阅读10分钟

1.原理

Nginx 在 AKF 扩展立方体上的应用,分为 X Y Z 轴。

  • X Axis :基于Round-Robin或者least-connected算法分发请求,不用改代码
  • YAxis:基于 URL对功能进行分发, 需要对 Nginx 基于 URL 进行 location 的配置,代码改动大,成本较高。
  • Z Axis:将用户 IP 地址或者其他信息映射到某个特定的服务或者集群。

支持的多个反向代理

  • 上游支持 udp,tcp,http,websocket,scgi,fastcgi,uwcgi,grpc,
  • 下游支持 udp,tcp,http

反向代理缓存

指定上游服务地址的 upstream与 server 指令

功能

指定一组上游服务器地址,其中,地址可以是域名、IP地址或者unix socket地址。 可以在域名或者IP地址后加端口,如果不加端口,那么默认使用80端口。

通用参数

  • backup : 指定当前server为备份服务,仅当非备份server不可用时,请求才会转发到该server
  • down: 标识某台服务已经下线,不在服务

2.Round-Robin算法

加权 Round-Robin 负载均衡算法

功能

在加权轮询的方式访问 server 指指定的上游服务集成在 Nginx的upstream 框架中

指令

  • weight : 服务访问的权重,默认是1
  • max_conns : server的最大并发连接数,仅作用于单worker进程。默认是0,表示没有限制。
  • max_fails: 在fail_timeout时间段内,最大的失败次数。当达到最大失败时,会在fail timeout秒内这台scrvcr不允许再次被选择。
  • fail_timeout:单位为秒,默认值为10秒。具有2个功能:指定一段时间内,最大的失败次数max_fails到达max_fails后,该scrver不能访问的时间。

3.对上游服务设置

对上游服务使用 keepalive 长连接

功能:通过复用连接,降低nginx与上游服务器建立、关闭连接的消耗,提升吞吐量的同时降低时延 默认编译进nginx,通过--without-ngx_http_upstream_keepalive_module 移除

对上游连接的http头部设定

proxy_http_version 1.1; proxy_set_header Connection "";

upstream_keepalive指令

Syntax: keepalive connections;
Default: —
Context: upstream

# 1.15.3 新增
Syntax: keepalive_requests number;
Default: keepalive_requests 100;
Context: upstream
Syntax: keepalive_timeout timeout;
Default: keepalive_timeout 60s;
Context: upstream

指定上游服务域名解析的 resolver 指令

Syntax: resolver address ... [valid=time] [ipv6=on|off];
Default: —
Context: http, server, location
Syntax: resolver_timeout time;
Default: resolver_timeout 30s;
Context: http, server, location

例子

# 上游
 server { 
    listen localhost:8011;
    default_type text/plain;
    limit_rate 1;
    return 200 '8011 server response.\n';
}
server {
    server_name localhost;
    listen 8012;
    default_type text/plain; 
    return 200 '8011 server response.\n';
}

# roundrobin 下游
upstream rrups {
    server localhost:8011 weight=2 max_conns=2 max_fails=2 fail_timeout=5; # weight 权重 
    server localhost:8012;
    keepalive 32;
}

server {
    server_name localhost;
    listen 8013;
    error_log myerror.log info;
    location /{
        proxy_pass http://rrups;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

# 每两次 8011 一次8012 循环
curl 127.0.0.1:8013 # 8011
curl 127.0.0.1:8013 # 8011
curl 127.0.0.1:8013 # 8012
curl 127.0.0.1:8013 # 8011
curl 127.0.0.1:8013 # 8011
curl 127.0.0.1:8013 # 8012

# 检查抓包 发现每次都是 8011 确实响应两次
tcpdump -i lo port 8013

4.ip_hash与hash模块

upstream_ip_hash模块

基于客户端IP 地址的 Hash 算法实现负载均

以客户端的IP地址作为hash算法的关键字

默认编译进nginx,ngx_http_upstream_ip_hash_module

  • 对IPV4地址使用前3个字节作为关键字,对IPV6则使用完整地址
  • 可以使用round-robin算法做参数
  • 可以基于realip模块修改用于执行算法的IP地址

指定关键字作为hash key

默认编译进nginx,ngx_http_upstream_ip_hash_module

通过指定关键字作为hash key,基于hash算法映射到特定的上游服务器中。 关键字可以含有变量、字符串

例子

upstream iphashups {
    ip_hash; # 通过请求用户的ip 做规则
    #hash user_$arg_username; # 当使用url上的参数做唯一时,不同参数对应不同服务器
    server 127.0.0.1:8011 weight=2 max_conns=2 max_fails=2 fail_timeout=5;
    server 127.0.0.1:8012 weight=1;
}

server { 
    server_name localhost;
    listen 8311;
    set_real_ip_from  116.62.160.193;
    real_ip_recursive on;
    real_ip_header X-Forwarded-For;
    error_log myerror.log info; 
    location /{
            proxy_pass http://iphashups; 
    }
}

# 测试 一旦ip确定,请求的内容就固定了
curl -H 'X-Forwarded-For: 100.200.20.200' localhost:8311 # 8011
curl -H 'X-Forwarded-For: 100.200.20.200' localhost:8311 # 8011
curl -H 'X-Forwarded-For: 100.200.20.200' localhost:8311 # 8011

curl -H 'X-Forwarded-For: 100.200.20.201' localhost:8311 # 8012
curl -H 'X-Forwarded-For: 100.200.20.201' localhost:8311 # 8012
curl -H 'X-Forwarded-For: 100.200.20.201' localhost:8311 # 8012

# 当使用 hash user_$arg_username; ,屏蔽ip_hash 则以url参数为主
curl -H 'X-Forwarded-For: 100.200.20.200' localhost:8311?a=111 # 8011
curl -H 'X-Forwarded-For: 100.200.20.200' localhost:8311?a=222 # 8012
curl -H 'X-Forwarded-For: 100.200.20.200' localhost:8311?a=111 # 8011
curl -H 'X-Forwarded-For: 100.200.20.200' localhost:8311?a=222 # 8012

5.一致性hash

  • 如果有5台服务器,选中上游server的算法:key%5。
  • 宕机或者扩容时,hash算法引发大量路由变更,可能导致缓存大范围失效。
  • 如:原来5台变4台,选中上游server的算法: key%4

upstream_hash 模块

Syntax: hash key [consistent];
Default: —
Context: upstream

6.最少连接算法以及如何跨 worker进程生效

优先选择连接最少的上游服务器,默认编译upstream_least_conn模块

功能

从所有上游服务器中,找出当前并发连接数最少的一个,将请求转发到它。 如果出现多个最少连接服务器的连接数都是一样的,使用round-robin算法。

Syntax: least_conn;
Default: —
Context: upstream

使用共享内存使负载均衡策略对所有worker进程3发好时间upstream zone模块

默认编译,ngx_http_upstream_ zone _module

分配出共享内存,将其他upstream模块定义的负载均衡策略数据、运行时每个上游服务的状态数据存放在共享内存上以对所有nginx worker进程生效

Syntax: zone name [size];
Default: —
Context: upstream

upstream 模块间的顺序: 功能的正常运行

ngx_module_t *ngx_modules[] = {
...
&ngx_http_upstream_hash_module,
&ngx_http_upstream_ip_hash_module,
&ngx_http_upstream_least_conn_module,
&ngx_http_upstream_random_module,
&ngx_http_upstream_keepalive_module,
&ngx_http_upstream_zone_module,
...
};

6.http upstream模块提供的变量

upstream 模块提供的变量 (不含 cache)

  • upstream_addr 上游服务器的IP地址,格式为可读的字符串,例如127.0.0.1:8012
  • upstream_connect_time 与上游服务建立连接消耗的时间,单位为秒,精确到毫秒
  • upstream_header_time 接收上游服务发回响应中http头部所消耗的时间,单位为秒,精确到毫秒
  • upstream response_time 接收完整的上游服务响应所消耗的时间,单位为秒,精确到毫秒
  • upstream_http_名称 从上游服务返回的响应头部的值
  • upstream_bytes_received 从上游服务接收到的响应长度,单位为字节
  • upstream_response_length 从上游服务返回的响应包体长度,单位为字节
  • upstream_status 上游服务返回的HTTP响应中的状态码。如果未连接上,该变量值为502
  • upstream_cookie_名称 从上游服务发回的响应头Set-Cookie中取出的cookie值
  • upstream_trailer_名称 从上游服务的响应尾部取到的值

例子

# 这里定义打印的变量
log_format  varups  '$upstream_addr $upstream_connect_time $upstream_header_time $upstream_response_time '
                        '$upstream_response_length $upstream_bytes_received '
                        '$upstream_status $upstream_http_server $upstream_cache_status';

upstream iphashups {
    ip_hash; # 通过请求用户的ip 做规则
    server 127.0.0.1:8011 weight=2 max_conns=2 max_fails=2 fail_timeout=5;
    server 127.0.0.1:8012 weight=1;
}

server {
    server_name localhost;
    listen 8311;
    set_real_ip_from  116.62.160.193;
    real_ip_recursive on;
    real_ip_header X-Forwarded-For; 
    error_log myerror.log info;
    access_log logs/upstream_access.log varups; # 指定日志
    location /{
            proxy_pass http://iphashups;  
    }
}

# 测试  
curl -H 'X-Forwarded-For: 100.200.20.200' localhost:8311?username=bbbb # 8011
tail -f 100  logs/upstream_access.log # 查看日志输出

7.http反向代理proxy处理请求的流程

  1. 处理content阶段: proxy_pass指令
  2. 是否命中缓存
  3. 根据指令生成发往上游的http头部及包体(proxy_request_buffering读取完整body)
  4. 根据负载均衡策略选择上游服务器
  5. 根据参数连接上游服务器
  6. 发送请求(边读包体边发)
  7. 上游接收响应头部
  8. 上游处理响应头部(proxy_buffering_on接收完整的包体 )
  9. 上游发送响应头部
  10. 上游发送响应包体(边读包体边发)
  11. 关闭或复用连接, (根据开关判断包体加入缓存)

8.proxy模块中的proxy_pass指令

对上游服务使用http/https协议进行反向代理,默认编译进nginx,通过--without-http_proxy_module禁用

URL参数规则

  1. URL必须以http://或者https://开头,接下来是域名、IP、unix socket地址或者upstream的名字,前两者可以在域名或者IP后加端口。最后是可选的URI。
  2. 当URL参数中携带URI与否,会导致发向上游请求的URL不同:。
  • 不携带URI,则将客户端请求中的URL直接转发给上游location后使用正则表达式、@名字时,应采用这种方式
  • 携带URI,则对用户请求中的URL作如下操作。将location参数中匹配上的一段替换为该URI
  1. 该URL参数中可以携带变量
  2. 更复杂的URL替换,可以在location内的配置添加rewrite break语句

例子

server {
    listen 8012;
    default_type text/plain; 
    root html;
    location / { 
        return 200 '8012 server response.
        uri: $uri
        method: $request_method
        request: $request
        http_name: $http_name
        curtime: $time_local
        \n';
    }
    location /test {
        return 200 '8012 server response.
        uri: $uri
        method: $request_method
        request: $request
        http_name: $http_name
        curtime: $time_local
        \n';
    }
}

upstream iphashups {
    server 127.0.0.1:8012;
}

server { 
    server_name localhost;
    listen 8312;  
    location /{ # 场景1
        proxy_pass http://iphashups;  
    }
    location /aaa{ # 场景2
        proxy_pass http://iphashups;  
    }
    location /bbb{  # 场景3
        proxy_pass http://iphashups/test;  # ip+端口+对应的uri
    }
}

# 场景1 
# location /{
# 	proxy_pass http://iphashups;  
# }
curl 127.0.0.1:8312/test  
# 打印 这里的uri 依赖上游服务器, 匹配/ ,把test直接交给追加给上游
# 8012 server response.
# uri: /test
# method: GET
# request: GET /test HTTP/1.0
# http_name: 
# curtime: 06/Apr/2023:10:44:21 +0800

curl 127.0.0.1:8312/test/111
# 打印  后面的111也一样转发给上游
# 8012 server response.
# uri: /test/111
# method: GET
# request: GET /test/111 HTTP/1.0
# http_name: 
# curtime: 06/Apr/2023:10:48:12 +0800

# 场景2  
# location /aaa{
# 	proxy_pass http://iphashups;  
# }
curl 127.0.0.1:8312/aaa/
# 打印  跟 localtion / 逻辑差不多,直接追加给上游
# 8012 server response.
# uri: /aaa/
# method: GET
# request: GET /aaa/ HTTP/1.0
# http_name: 
# curtime: 06/Apr/2023:13:24:15 +0800

curl 127.0.0.1:8312/aaa/test
# 打印
# 8012 server response.
# uri: /aaa/test
# method: GET
# request: GET /aaa/test HTTP/1.0
# http_name: 
# curtime: 06/Apr/2023:13:25:08 +0800

# 场景3 
# location /bbb{  # 场景3
# 	proxy_pass http://iphashups/test;  
# }
curl 127.0.0.1:8312/bbb/
# 这里 bbb会被替换成最终访问的 /test
# 打印
# 8012 server response.
# uri: /test
# method: GET
# request: GET /test HTTP/1.0
# http_name: 
# curtime: 06/Apr/2023:13:26:30 +0800

curl 127.0.0.1:8312/bbb/111
# bbb/111 替换成 test/111
# 打印 
# 8012 server response.
# uri: /test/111
# method: GET
# request: GET /test/111 HTTP/1.0
# http_name: 
# curtime: 06/Apr/2023:13:28:17 +0800

9.根据指令修改发往上游的请求

server { 
    server_name localhost;
    listen 8313;  
    location /{ # 场景1
        proxy_pass http://iphashups; 
        proxy_method POST;
        proxy_pass_request_headers off;  # 不发送头部
        #proxy_pass_request_body off; # 不发送内容
        proxy_set_body 'hello world!'; # 填充body内容值
        proxy_set_header name ''; 
        proxy_http_version 1.1;
        proxy_set_header Connection "";  
    } 
}

curl -H 'name: myname'  127.0.0.1:8313 
tcpdump -i lo port 8313 -A -s 0 # 通过tcpdump 抓包查看日志

10.接收用户请求包体的方式

proxy_request_buffering

on
#客户端网速较慢
#上游服务并发处理能力低
#适应高吞吐量场景

off
#更及时的响应
#降低nginx读写磁盘的消耗
#一旦开始发送内容,proxynextupstream功能失败

客户端包体的接收

client_body_buffer_size
client_body_in_single_buffer
# 1. 存在包体时,接收包体所分配的内存
# 2. 若接收头部时已经接收完全部包体,则不分配。
# 3. 若剩余待接收包体的长度小于 client_body_buffer_size,则仅分配所需大小
# 4. 分配client body buffer size大小内存接收包体

# 关闭包体缓存时,该内存上内容及时发送给上游
# 打开包体缓存该段大小内存用完时,写入临时文件,释放内存

client_max_body_size_size lm;  #最大包体长度限制  仅对请求头部中含有Content-Length有效超出最大长度后,返回413错误
client_body_temp_path # 临时文件路径格式 
client_body_in_file_only 
client_body_timeout 60s:  #读取包体时的超时  读取包体时超时,则返回408错误

11.与上游服务建立连接

proxy_connect_timeout 60s; # 超时后,会向客户端生成http响应,响应码为502
proxy_next_upstream http_502;
proxy_socket_keepalive off; # 上游连接启用TCP keepalive ,linux系统中tcp的实现原理:定时发送检测包,如果多次没有响应则自动断开tcp
keepalive_connections 100;  #上游连接启用HTTP keepalive   
proxy_bind_address [transparent] | off; # 修改TCP连接中的local address

# 可以使用变量:
proxy_bind $remote_addr: 

# 可以使用不属于所在机器的IP地址
proxy_bind $remote_addr transparent:
proxy_ignore_client_abort  off # 当客户端关闭连接时 
proxy_send_timeout 60s; #向上游发送HTTP请求 
proxy_buffer_size 4k|8k; #接收上游的HTTP响应头部 
proxy_buffers_number 4k|8k;  #接收上游的HTTP包体
proxy_buffering on; #接收上游的HTTP包体 


# 上游包体的持久化 
proxy_store_access users:permissions ...;
proxy_store on;

例子

upstream http_tornado { 
    server 127.0.0.1:8001;
}

server {
    listen 8314
    server_name localhost
    # 省略其他配置
    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|js|html|htm|css)$ {
        root /opt/data/product/blog/cache;
        proxy_store on; # 打开缓存
        proxy_store_access user:rw group:rw all:rw; # 设置权限
        proxy_temp_path /opt/data/product/blog/cache; # 缓存到本地
        if ( !-e $request_filename) {
            proxy_pass  http://http_tornado;
        }
    }
}

curl 127.0.0.1:8314/a.jpg

12.处理上游的响应头部

处理流程

  1. HTTP 过滤模块...
  2. copy_flter: 复制包体内容
  3. HTTP 过滤模块...
  4. postpone_flter: 处理子请求
  5. HTTP过滤模块...
  6. header_filter: 构造响应头部
  7. write_filter: 发送响应

禁用上游响应头部的功能

proxy_ignore_headers field ...;

功能:某些响应头部可以改变nginx的行为,使用proxy_ignore_headers可以禁止它们生效

转发上游的响应: Proxy_hide_header指令

proxy_hide_header: # 对于上游响应中的某些头部,设置不向客户端转发
proxy_hide_header # 默认不转发响应头部:
Date:由ngx_http_header_filter_module # 过滤模块填写,值为nginx发送响应头部时的时间
Server:由ngx_http_header_filter_module # 过滤模块填写,值为nginx版本
X-Pad:通常是Apache # 为避免浏览器BUG生成的头部,默认忽略
X-Accel-: # 用于控制nginx行为的响应,不需要向客户端转发
proxy_pass_header:# 对于已经被proxy hide header的头部,设置向上游转发

例子

# 上游服务器
server {
    listen 8012;
    default_type text/plain; 
    root html;
    location / { 
            add_header aaa 'aaa value'; # 添加参数
            add_header X-Accel-Limit-Rate 10;  # 添加限制
    }
}

# 下游服务器
upstream proxyupstream {
    server 127.0.0.1:8012 weight=1;
}
server {
    listen 8315
    server_name localhost; 
    location / {
        proxy_pass http://proxyupstream;
        #proxy_method POST;
        proxy_hide_header aaa; # 自定义不显示参数
        proxy_pass_header server;
        proxy_ignore_headers X-Accel-Limit-Rate;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
} 

13.上游出现失败时的容错方案

#上游返回失败时的处理小法 
proxy_next_upstream error | timeout # 没有向客户端发送任何内容

# 限制proxy_next upstream的时间与次数  
proxy_next_upstream_timeout 0: 
proxy_next_upstream_tries 0:

proxy_intercept_errors on off; #用error_page拦截上游失败响应,当上游响应的响应码大于等于300时,应将响应返回客户端还是按error page指令处理 

例子

upstream nextups {
    server 127.0.0.1:8013;
    server 127.0.0.1:8011;
}

server {
    server_name localhost;
    listen 8316
    error_log  logs/myerror.log  debug;
    #root html/;
    default_type text/plain;

    error_page 500 /test1.txt;
    location / {
            proxy_pass http://nextups;
    }

    location /test {
    }

    location /error {
            proxy_pass http://nextups;
            proxy_connect_timeout 1s;
            proxy_next_upstream error;
    }
    location /intercept {
            proxy_intercept_errors on; # 拦截nginx 错误码
            proxy_pass http://127.0.0.1:8013;
    }

    location /httperr {
            proxy_next_upstream http_500;
            proxy_pass http://nextups;
    }
}

14.对上游使用 SSL连接

双向认证时的指令示例

下游服务器配置

下游 -> 上游 指定自身使用的证书 (主动访问)

  • proxy_ssl_certificate
  • proxy_ssl_certificate_key

下游 <- 上游 验证服务器证书 (被动访问)

  • proxy_ssl_verify
  • proxy_ssl_trusted_certificate

上游服务器配置

下游 -> 上游(被动访问) 验证客户端证书

  • ssl_verify_client
  • ssl_client_certificate

下游 <- 上游 指定自身使用的证书(主动访问)

  • ssl_certificate
  • ssl_certificate_key

ssl模块提供的变量

安全套件

  • ssl_cipher: 本次通讯选用的安全套件,例如ECDHE-RSA-AES128-GCM-SHA256
  • ssl_ciphers: 客户端支持的所有安全套件
  • ssl_protocol: 本次通讯选用的TLS版本,例如TLSv1.2
  • ssl_curves: 客户端支持的圆曲线,例如secp384rl:secp521rl

证书

  • ssl_client_raw_cert: 原始客户端证书内容
  • ssl_client_cscaped_cert: 返回客户端证书做urlencode编码后的内容
  • ssl_client_cert: 对客户端证书每一行内容前加tab制表符空白,增强可读性
  • ssl_client_fingerprint: 客户端证书的SHA1指纹

创建证书命令

# 创建根证书
## 创建CA私钥
openssl genrsa -out ca.key 2048
## 制作作CA公钥
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt

# 签发证书
## 生成 a 证书 上游服务器
## 创建私钥 a.pem a.key
openssl genrsa -out a.pem 1024
openssl rsa -in a.pem -out a.key
## 生成签发请求  生成 a.csr
openssl req -new -key a.pem -out a.csr
## 使用CA证书进行签发 生成 a.crt
openssl x509 -req -sha256 -in a.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out a.crt
## 验证签发证书是否正确 a是否在ca.crt
openssl verify -CAfile ca.crt a.crt

## 生成 b 证书 下游服务器
## 创建私钥 b.pem b.key
openssl genrsa -out b.pem 1024
openssl rsa -in b.pem -out b.key
## 生成签发请求 生成 b.csr
openssl req -new -key b.pem -out b.csr
## 使用CA证书进行签发 生成 b.crt
openssl x509 -req -sha256 -in b.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out b.crt
## 验证签发证书是否正确 b是否在ca.crt
openssl verify -CAfile ca.crt b.crt

例子

# 上游服务器
server {
    server_name localhost; 
    listen 8443 ssl;  
    root html/; 
    # 设置自己的服务器证书 a 证书
    ssl_certificate ./cert/a.crt; 
    ssl_certificate_key ./cert/a.key;  

    # 设置验证客户端的证书
    ssl_verify_client  optional; # 打开客户端校验
    ssl_verify_depth 2; # 校验的深度
    #ssl_trusted_certificate examples/cert/ca.crt;
    ssl_client_certificate ./cert/ca.crt; # 校验根证书
    #ssl_client_certificate LetsEncryptAuthorityX3.crt;

    location /test {
        default_type text/plain;
        return 200 '
                ssl_client_escaped_cert: $ssl_client_escaped_cert
                ssl_client_cert: $ssl_client_cert
                ssl_client_raw_cert: $ssl_client_raw_cert
                ssl_cipher: $ssl_cipher
                ssl_ciphers: $ssl_ciphers
                ssl_client_fingerprint: $ssl_client_fingerprint
                ssl_client_i_dn: $ssl_client_i_dn
                ssl_client_i_dn_legacy: $ssl_client_i_dn_legacy
                ssl_client_s_dn: $ssl_client_s_dn
                ssl_client_s_dn_legacy: $ssl_client_s_dn_legacy
                ssl_client_serial: $ssl_client_serial
                ssl_client_v_end: $ssl_client_v_end
                ssl_client_v_remain: $ssl_client_v_remain
                ssl_client_v_start: $ssl_client_v_start
                ssl_client_verify: $ssl_client_verify
                ssl_curves: $ssl_curves
                ssl_protocol: $ssl_protocol
                ssl_server_name: $ssl_server_name
                ssl_session_id: $ssl_session_id
                ssl_session_reused: $ssl_session_reused
                ';
    } 
}

# 下游服务器
server {
    server_name localhost; 
    listen 8444 ssl;  
    root html;  
    location /test { 
        proxy_ssl_name a:
        proxy_ssl_verify_depth 4;
        # 指定自身使用的证书 指定b证书 
        proxy_ssl_certificate ./cert/b.crt;
        proxy_ssl_certificate_key ./cert/b.key; # 
        proxy_ssl_server_name on:
        proxy pass https://localhost:8443;
    }
}
# 测试 
curl  localhost:8090/test

15.用好浏览器的缓存

浏览器缓存

优点

  • 使用有效缓存时,没有网络消耗,速度最快.
  • 即使有网络消耗,但对失效缓存使用304响应做到网络流量消耗最小化.

缺点

  • 仅提升一个用户的体验

流程

  1. 判读是否有store缓存,缓存是否过期
  2. store已过期,判断Etag,带上If-None-Match 服务器判断协商缓存200/304
  3. etag已过期,判断Last-modifed,带上if-Modifed-since 服务器判断协商缓存200/304
  4. Last-modifed已过期,服务器返回资源并缓存

etag 文件指纹

  • Syntax: etag on | off;
  • Default: etag on;

nginx缓存

优点

  • 提升所有用户的体验.
  • 相比浏览器缓存,有效降低上游服务的负载0
  • 通过304响应减少nginx与上游服务间的流量消耗

缺点

  • 用户仍然保持网络消耗

16.Nginx决策浏览器过期缓存是否有效

expires指令

expires expires|epoch|max|off;

max: Expires: Thu 31 Dec 2037 23:55:55 GMT
max: Cache-Control:max-age-315360000(10年)
off:不添加或者修改Expires和Cache-Control字段
epoch:Expires: Thu,01 Jan 1970 00:00:01 GMT
time:设定具体时间,可以携带单位

例子

server {
    listen 8317
    server_name localhost; 
    root html; 
    location /a {
        expires 1h; # 1小时内有效  
    }

    location /b { 
        expires -1h;  # 设置已过期 
    }

    location /c { 
        expires @20h30m; # 指定时间内
    }

    location /d { 
        #if_modified_since off;
    } 
    # location /test{
    # 	#expires @20h30m;
    # 	#if_modified_since off;
    # 	proxy_cache two;
    # 	proxy_cache_valid 200 1m;
    # 	add_header X-Cache-Status $upstream_cache_status; 
    # } 
} 

curl 127.0.0.1:8317/a  
# 有效访问内强缓存
# Expires: Sat,22 Dec 2018 11:29:57 GMT
# Cache-Control: max-age=3600

curl 127.0.0.1:8317/b
# 已过期
# Expires: Sat,22 Dec 2018 09:30:44 GMT
# Cache-Control: no-cache

curl 127.0.0.1:8317/c
# 有效访问内 时间按 GMT计算
# Expires: Sat,22 Dec 2018 12:30:00 GMT
# Cache-Control: max-age=7125
curl 127.0.0.1:8317/d

not_modified 过滤模块

功能

  • 客户端拥有缓存,但不确认缓存是否过期,于是在请求中传入If-Modified-Since或者IfNone-Match头部,
  • 该模块通过将其值与响应中的Last-Modified值相比较,决定是通过200返回全部内容,
  • 还是仅返回304 Not Modified头部,表示浏览器仍使用之前的缓存

判断流程

  1. 含有if_unmodified_since > last_modified_time 返回412
  2. if_match == etag 返回 412
  3. 有 if_modified_since > last_modified_time 返回 200
  4. 不包含 if_none_match == etag 返回 200
  5. if_none_match == etag 返回 304

if_modified_since

if_modified_since off |exact |before;    

#off  忽略请求中的if modified since头部
#exact 精确匹配 if modified since头部与last modified的值
#before。若if modificd since大于等于last modified的值,则返回304

If-Match

请求首部ILMatch 的使用表示这是一个条件请求。在请求方法为 GET和EAD 的情况下,服务器仅在请求的资源满足此首部列出的 ETag 之一时才会返回资源。而对于 PUT 或其他非安全方法来说,只有在满足条件的情况下才可以将资源上传。

The comparison with the stored ETag 之间的比较使用的是强比较算法,即只有在每一个比特都相同的情况下,才可以认为两个文件是相同的。在ETag 前面添加 w/前缀表示可以采用相对宽松的算法

以下是两个常见的应用场景:

For GET 和IIEAD 方法,搭配 nge首部使用,可以用来保证新请求的范围与之前请求的范围是对同一份资源的请求。如果 ETag 无法匹配,那么需要返回 416(Range Not Satisfiable,范围请求无法满足)响应。对于其他方法来说,尤其是 PUT,If-Match 首部可以用来避免更新丢失问题。它可以用来检测用户想要上传的不会覆盖获取原始资源之后做出的更新。如果请求的条件不满足,那么需要返回 412(Precondition Failed,先决条件失败)响应。

If-Unmodified-Since

HTTP协议中的If-Ummodified-Since 消息头用于请求之中,使得当前请求成为条式请求: 只有当资源在指定的时间之后没有进行过修改的情况下,服务器才会返回请求的资源,或是接受POST 或其他 non-safe 方法的请求。如果所请求的资源在指定的时间之后发生了修改,那么会返回 412 Precondition Failed)错误常见的应用场景有两种:

与non-salfe 方法如 POST搭配使用,可以用来优化并发控制,例如在某些wiki应用中的做法:假如在原始副本获取之后,服务器上所存储的文档已经被修改,那么对其作出的编辑会被拒绝提交

与含有 If-Range 消息头的范围请求搭配使用,用来确保新的请求片段来自于未经修改的文档.

例子

server {
    listen 8318
    server_name localhost; 
    root html;  
    location /d { 
        if_modified_since on;
    }
} 
 
# 测试 指定 If_Modified_Since   If-None-Match
curl -H 'If_Modified_Since: Sat,10 Nov 2018 00:56:04 GMT' -H'If-None-Match:"5be62ca4-264" 127.0.0.1:8318/d/ -I

# 返回304 或200 

17.缓存的基本用法

nginx缓存:定义存放缓存的载体

proxy_cache_zone  off;
proxy_cache_path path;

proxy_cache path指今

  • path 定义缓存文件存放位置
  • levels 定义缓存路径的目录层级,最多3级,每层目录长度为1或者2字节
  • use_temp_path on使用proxy temppath定义的临时目录 , off直接使用path路径存放临时文件
  • keys_zone_name是共享内存名字,由proxy cache指令使用
  • size 是共享内存大小,1MB大约可以存放8000个key
  • inactive 在inactive时间内没有被访问的缓存,会被淘汰掉默认10分钟
  • max_size 设置最大的缓存文件大小,超出后由cache manager进程按LRU链表淘汰
  • manager_filescache_manager进程在1次淘汰过程中,淘汰的最大文件数.默认100
  • manager_sleep 执行一次淘汰循环后cache manager进程的休眠时间 ,默认200毫秒
  • manager_threshold 执行一次淘汰循环的最大耗时 ,默认50毫秒
  • loader_files_cache_loader 进程载入磁盘中缓存文件至共享内存,每批最多处理的文件数,默认100
  • loader_sleep 执行一次缓存文件至共享内存后,进程休眠的时间。载入默认200毫秒
  • loader_threshold 每次载入缓存文件至共享内存的最大耗时,默认50毫秒
proxy_cache_key string;  ## 缓存的关键字
proxy_cache_valid ##  缓存自定内容
proxy_no_cache  ## 指定不缓存

upstream_cache_status变量

  • MISS:未命中缓存
  • HIT:命中缓存
  • EXPIRED:缓存已经过期
  • STALE:命中了陈旧的缓存
  • UPDATING: 内容陈旧,但正在更新
  • REVALIDATED:Nginx验证了陈日的内容依然有效
  • BYPASS:响应是从原始服务器获得的
proxy_cache_path /data/nginx/tmpcache levels=2:2 keys_zone=two:10m loader_threshold=300 
loader_files=200 max_size=200m inactive=1m;
 
server {
    listen 8012;
    default_type text/plain; 
    root html;
    add header Vary *;  # 这里开关可以查看不同的效果
    add header X-Accel-Expires 3; #添加过期时间 这里开关可以查看不同的效果
    location / { 
        return 200 '8012 server response.  \n';
    }
}

server {
    server_name localhost;
    listen 8318
    root html/; 
    location /{
        #expires @20h30m;
        #if_modified_since off;
        proxy_cache two;
        proxy_cache_valid 200 1m; # 缓存自定内容
        add_header X-Cache-Status $upstream_cache_status; # 打印缓存的情况
        #proxy_cache_use_stale error timeout updating;
        #proxy_cache_key $scheme$uri; # 缓存的关键字
        #proxy_cache_revalidate on;
        #proxy_cache_background_update on;
        #proxy_hide_header      Set-Cookie;
        #proxy_ignore_headers   Set-Cookie;
        #proxy_force_ranges on;
        proxy_pass http://localhost:8012;
    }
}

curl 127.0.0.1:8318/a.txt -I
# X-Cache-status: MISS # 第一次 miss不命中

curl 127.0.0.1:8318/a.txt -I # 第二次命中缓存hit
# X-Cache-Status: HIT

# 当打开 上游服务器 add header Vary *; add header X-Accel-Expires 3; 
curl 127.0.0.1:8318/a.txt -I
# X-Cache-status: MISS # 第一次 miss不命中

curl 127.0.0.1:8318/a.txt -I # 缓存资源过期
# X-Cache-Status: EXPIRED 

18. 对客户端请求的缓存处理流程

  1. 是否开启proxy_cache指令?
  2. 方法是否匹配cache_methods指令?
  3. 根据proxy_cache_convert_head转换GET方法
  4. 根据proxy_key生成关键字并执行md5
  5. 检查cache_pass是否指明不缓存?
  6. 在共享内存中查询缓存是否存在
  7. 更新LRU链表及节点计数
  8. 是否错误类响应且过期?
  9. 文件存在且使用次数超出proxy_cache_min_uses
  10. 共享内存中分配节点
  11. 淘汰已过期的缓存,再次分配
  12. 根据proxy_cache_background_update生成子请求
  13. 向下游发送缓存中的响应
  14. 向上游发送请求

X-Accel-Expires头部

X-Accel-Expires off;

  • 从上游服务定义缓存多长时间
  • 0表示不缓存当前响应
  • @前缀表示缓存到当天的某个时间

Vary头部

Vary 是一个HTTP响应头部信息,它决定了对于未来的一个请求头,应该用一个缓存的回复(response)还是向源服务器请求一个新的回复。它被服务器用来表明在 content neotiation algorithm (内容协商算法)中选择一个资源代表的时候应该使用哪些头部信息 (headers)

在响应状态码为 304 Not Modified 的响应中,也要设置 Vary 首部,而且要与相应的 2000K 响应设置得一模一样

  1. Vary:* 所有的请求都被视为唯一并且非缓存的,使用Cache-Control; private.来实现则更适用,这样用于说明不存储该对象更加清晰。 若没有通过proxy ignore headers设置忽略,则不缓存响应
  2. Vary: ,... 逗号分隔的一系列bttp头部名称,用于确定缓存是否可用

Set-Cookie头部

若Set-Cookie头部没有被proxy_ignore_headers设置忽略,则不对响应进行缓存

缓存流程: 接收上游响应

  1. 是否匹配proxy_no_cache指令?
  2. 方法是否匹配proxy cache valid指令?
  3. 判断响应码是否为200或者206?
  4. 更新cache中的etag和last modified
  5. 处理缓存相关的响应头部,并检查是否有响应头部不使用缓存
  6. 读取、转发上游响应
  7. 将临时文件改名移至缓存目录
  8. 更新共享内存状态

19.如何减轻缓存失效时上游服务的压力

合并回源请求一减轻峰值流量下的压力

  1. proxy_cache_lock on|off: 同一时间,仅第1个请求发向上游,其他请求等待第1个响应返回或者超时后使用缓存响应客户端
  2. proxy_cache_lock_timeout 5s: 等待第1个请求返回响应的最大时间,到达后直接向上游发送请求,但不缓存响应
  3. proxy_cache_lock_age 5s; 上一个请求返回响应的超时时间,到达后再放行一个请求发向上游
## 减少回源请求一使用stale陈旧的缓存
proxy_cache_use_stale error | timeout |  invalid | header | updating | http_500  | http_502 ...;
proxy_cache_background_update on | off;

## 缓存有问题的响应
proxy_cache_background_update on | off;
#当使用proxy_cache use stale允许使用过期响应时,将同步生成一个子请求,通过访问上游服务更新过期的缓存

proxy_cache_revalidate on | off;
#更新缓存时,使用If-Modified-Since和If-None-Match作为请求头部,预期内容未发生变更时通过304来减少传输的内容

20.及时清除缓存

安装

第三方模块ngx_cache_purge : github.com/FRiCKLE/ngx…

proxy_cache_purge on|off|<method> [from all|<ip> [.. <ip>]] 
proxy_cache_purge zone_name key

例子

proxy_cache_path /data/nginx/tmpcache levels=2:2 keys_zone=two:10m loader_threshold=300 
                     loader_files=200 max_size=200m inactive=1m;
server {
    server_name localhost;
    listen 8318
    root html/; 
    location ~ /purge(/.*) {
        proxy_cache_purge two $scheme$1;
    }
    location /{
        proxy_cache two;
        proxy_cache_valid 200 1m;
        add_header X-Cache-Status $upstream_cache_status;
        proxy_cache_key $scheme$uri;
        proxy_pass http://localhost:8012;
    } 
}

curl 127.0.0.1:8318/index.html -I
curl 127.0.0.1:8318/index.html -I
# 这里显示 X-Cache-status: HIT
# 当使用 /purge/index.html访问时候 则会清除缓存
curl 127.0.0.1:8318/purge/index.html -I
# 这里显示 X-Cache-status: MISS # 没有命中