Nginx负载均衡和反向代理
负载均衡
负载均衡:Nginx 的负载均衡是保障服务可用性的重要手段。当服务异常或者处于扩容中无法提供服务时,可以让请求绕过这些节点。
Nginx 在 AKF 扩展立方体上的应用
- X:基于 Round-Robin 或者
least-connected
算法分发请求 - Y:基于 URL 对功能进行分发
- X:将用户 IP 地址或者其他信息映射到某个特定的服务或者集群
支持多种协议的反向代理
- 四层代理:依靠 IP地址实现,通过
stream
模块支持,将下游发过来的 TCP、UDP 请求转发出去 - 七层代理:依靠业务信息,例如 Header、Method、URI 之类的,可以将 HTTP 请求作为
memcached
、scgi
等协议的请求转发到上游
反向代理与缓存
缓存可以分为两类:
- 时间缓存:将上游应用服务器的响应缓存在 Nginx 服务器的磁盘上,当下次同样的请求到达 Nginx 时,则直接返回磁盘上的内容。
- 空间缓存:当 Nginx 访问上游服务器时,可以预取一些内容缓存到 Nginx 磁盘
指定上游服务地址的 upstream 与 server 指令
Nginx 中将负责与上游服务交互的模块统称为 upstream
模块,包括 stream
、http_upstream
。并且提供 rb
负载均衡算法。
https://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream
1 | # name 会交由后面的反向代理使用 |
功能:指定一组上游服务器地址,其中,地址可以是域名、IP 地址或者 unix socket
地址。可以在域名或者 IP 地址后加端口,如果不加端口,那么默认使用 80 端口。
通用参数:
backup
:指定当前server
为备份服务,仅当非备份server
不可用时,请求才会转发到该server
down
:标识某台服务已经下线,不在服务(主要方便管理和维护)
1 | upstream backend { |
加权 Round-Robin 负载均衡算法
功能:在加权轮询的方式询问 server
指令指定的上游服务。默认集成在 Nginx 的 upstream
框架中。
指令:
weight
:服务访问的权重,默认是 1max_conns
:server
的最大并发连接数,仅作用于单worker
进程。默认是 0,表示没有限制max_fails
:在fail_timeout
时间段内,最大的失败次数。当达到最大失败时,会在fail_timeout
秒内这台server
不允许再次被选择fail_timeout
:单位为秒,默认 10 秒,具有两个功能:- 指定一段时间内,也是
fail_timeout
,最大的失败次数max_fails
- 到达
max_fails
后,该server
不能访问的时间
- 指定一段时间内,也是
对上游服务使用 keepalive 长连接
功能:通过复用连接,降低 Nginx 与上游服务器建立、关闭连接的消耗,提升吞吐量的同时,降低时延(上游服务器数量有限,效果更好)
对上游连接的 http
头部增加设定:
1 | proxy_http_version 1.1; # http 1.0 协议不支持,为了防止用户发来的是 1.0 版本协议,设置覆盖 1.1 |
1 | # 配置 Nginx 到上游配置的服务器,每个工作进程最多保持多少个空闲的连接用于 keepalive 请求 |
指定上游服务域名解析
resolver
指令,解析上游服务域名,以及域名解析超时时间
1 | Syntax: resolver address ... [valid=time] [ipv4=on|off] [ipv6=on|off] [status_zone=zone]; |
基于客户端 IP 地址的 Hash 算法实现负载均衡
https://nginx.org/en/docs/http/ngx_http_upstream_module.html#ip_hash
ip_hash
功能:以客户端的 IP 地址(remote-addr
)作为 hash 算法的关键字,映射到特定的上游服务器中。
- 对 IPv4 地址使用前 3 个字节作为关键字,对 IPv6 则使用完整地址(16个字节)
- 可以使用
round-robin
算法的参数 - 可以基于
realip
模块修改用于执行算法的 IP 地址
1 | Syntax: ip_hash; |
https://nginx.org/en/docs/http/ngx_http_upstream_module.html#hash
hash
功能:通过指定关键字作为 hash key
,基于 hash
算法映射到特定的上游服务器中。
- 关键字可以含有变量、字符串
- 可以使用
round-robin
算法的参数
1 | Syntax: hash key [consistent]; |
1 | upstream backend { |
hash 算法的问题
当上游服务器异常、扩容、缩容时,hash 算法会引发大量路由变更,可能导致缓存大范围失效。
缩容后:
一致性 hash 算法
在一个环上获取放置节点,当扩容或者缩容,可以将部分流量迁移到其他流量,而不是所有流量。
扩容后:
1 | Syntax: hash key [consistent]; |
使用 consistent
开启一致性哈希算法。
优先选择连接最少的上游服务器
https://nginx.org/en/docs/http/ngx_http_upstream_module.html#least_conn
从所有上游服务器中,找出当前并发连接数最少的一个,将请求转发到它。
- 如果出现多个最少连接服务器的连接数都是一样的,使用
round-robin
算法。
1 | Syntax: least_conn; |
使用共享内存使负载均衡策略对所有 worker 进程生效
https://nginx.org/en/docs/http/ngx_http_upstream_module.html#zone
分配出共享内存,将其他 upstream
模块定义的负载均衡策略数据、运行时每个上游服务的状态(连接数、权重、失败次数)存放在共享内存上,以对所有 nginx worker
进程生效。
1 | Syntax: zone name [size]; |
upstream 模块间的顺序
保障功能的正常运行
1 | ngx_module_t *ngx_modules[] = { |
upstream 模块提供的变量(不含 cache)
upstream_addr
:上游服务器的 IP 地址,格式为可读的字符串,例如192.168.1.1:80
upstream_connect_time
:与上游服务建立连接消耗的时间,单位为 秒,精确到毫秒upstream_header_time
:接收上游服务发回响应中http
头部所消耗的时间(响应先返回header
),单位为秒,精确到毫秒upstream_response_time
:接收完整的上游服务响应所消耗的时间,单位为秒,精确到毫秒upstream_http_名称
:从上游服务返回的响应头部的值upstream_bytes_received
:从上游服务接收到的响应长度,单位为字节upstream_response_length
:从上游服务返回的响应包体长度,单位为字节upstream_status
:上游服务返回的 HTTP 响应中的状态码。如果未连接上,该变量值为 502upstream_cookie_名称
:从上游服务发回的响应头 Set-Cookie 中取出的cookie
值upstream_trailer_名称
:从上游服务的响应尾部取到的值
反向代理
流程
- 当缓存未命中,与上游服务器发送请求时,是先生成请求
header
和body
,甚至会读取请求完整的body
(是否读取请求完整的body
依据proxy_request_buffering
参数,默认打开,也就是缓存到磁盘上),然后根据负载均衡策略连接上游服务器(避免上游服务器并发不强时产生影响) - 上游服务发回响应是先发送
header
,Nginx 先处理响应头部,再决定如何处理body
。如果proxy_buffering on
(默认),则接收完整的响应包体之后,再发送响应包体。
Proxy 模块
对上游服务使用 http/https
协议进行反响代理。默认编译进 Nginx
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
1 | Syntax: proxy_pass URL; |
URL 参数规则
- URL 必须以
http://
或者https://
开头,接下来是域名、IP
、unix socket
地址或者upstream
的名字(代表一个集群),前两者可以在域名或者 IP 后加端口。最后时可选的 URI - 当 URL 参数中携带 URL 与否,会导致发向上游请求的 URL 不同:
- 不携带 URL,则将客户端请求中的 URL 直接转发给上游
location
后使用正则表达式、@名字时,应采用这种方式
- 携带 URL,则对用户请求中的 URL 做如下操作:
- 将
location
参数中匹配上的一段替换为该 URI
- 将
- 不携带 URL,则将客户端请求中的 URL 直接转发给上游
- 该 URI 参数中可以携带变量
- 更复杂的 URL 替换,可以在
location
内的配置添加rewrite break
语句
1 | # 例如下面,在端口后面有内容,就是携带 URL,此时请求 /xxx/abc,则会请求到上游的 /uri/abc |
根据指令修改发往上游的请求
生成请求行
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_method
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_http_version
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass_request_headers
1 | # 设置 method |
生成包体
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass_request_body
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_body
1 | # 是否将用户请求中的 body 传递给上游 |
Nginx 接收请求包体的方式
收完再转发还是边收边转发。
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_request_buffering
1 | Syntax: proxy_request_buffering on | off; |
启用或禁用客户端请求主体的缓冲。
当启用缓冲时,整个请求主体从客户端读取后再发送给代理服务器。
当禁用缓冲时,请求主体在接收到后立即发送给上游服务器。在这种情况下,如果 nginx 已经开始发送请求主体,则无法将请求传递给下一个服务器。当使用 HTTP/1.1 分块传输编码来发送原始请求主体时,除非为代理启用了 HTTP/1.1,否则不管指令值如何都会对请求主体进行缓冲。
on
:接收完再转发,将body
缓存在磁盘中- 客户端网速慢
- 上游服务并发处理能力低
- 适应高吞吐量场景(打开之后,非常依赖 Nginx 的高吞吐能力)
off
:边接收边转发- 更及时的响应
- 降低 Nginx 读写磁盘的消耗
- 一旦开始发送内容,
proxy_next_upstream
功能失败
客户端包体的接收
https://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size
1 | Syntax: client_body_buffer_size size; |
客户端发过来的请求中存在包体时,接收包体所分配的内存
- 若接收头部时已经接收完全部包体,则部分配(接收
header
时,可能也接收到了一点包体) - 若剩余待接收包体的长度小于
client_body_buffer_size
,则仅分配所需大小 - 分配
client_body_buffer_size
大小内存接收包体- 关闭包体缓存时,该内存上内容及时发送给上游
- 打开包体缓存
- 该段大小内存用完时,写入临时文件,释放内存
最大包体长度限制
1 | Syntax: client_max_body_size size; |
仅对请求头部中含有 Content-Length
有效超出最大长度后,返回 413 错误。
临时文件路径格式
1 | # 设置在哪个目录下放置 body,Nginx 启动后,默认会创建这个目录 |
读取包体时的超时
1 | Syntax: client_body_timeout time; |
读取包体时超时,则返回 408 错误。
向上游服务建立连接
https://nginx.org/en/docs/stream/ngx_stream_proxy_module.html#proxy_connect_timeout
1 | Syntax: proxy_connect_timeout time; |
超时后,会向客户端生成 http
响应,响应码为 502(如果路由失效,不会等待 60s)
1 | Syntax: proxy_next_upstream on | off; |
当无法与代理服务器建立连接时,确定客户端连接是否将传递到下一个服务器。
上游连接启用 TCP keepalive
1 | Syntax: proxy_socket_keepalive on | off; |
TCP keepalive
是用于 TCP 连接,当没有数据传输时,会通过定时发送探测包,维护 TCP 连接。(由操作系统实现)
上游连接启用 HTTP keepalive
https://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive
1 | Syntax: keepalive connections; |
使用了 HTTP 的 keepalive
,大多数时候可以覆盖 TCP 的 keepalive
。
修改 TCP 连接中的 local address
https://nginx.org/en/docs/stream/ngx_stream_proxy_module.html#proxy_bind
1 | Syntax: proxy_bind address [transparent] | off; |
- 可以使用变量:
proxy_bind $remote_addr;
- 可以使用不属于所在机器的 IP 地址:(需要有 root 权限)
proxy_bind $remote_addr transparent;
使用场景:
- 如果到达上游服务器有多个路由,
proxy_bind
可以指定 IP,而不使用系统路由 - 透传 IP 地址
proxy_bind
其实是在修改 IP 报文中的 Source IP Address
当客户端关闭连接时
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ignore_client_abort
1 | Syntax: proxy_ignore_client_abort on | off; |
当下游客户端出现失败,关闭客户端到 nginx
的连接,proxy
是否要忽略这个报错,是否一并关闭。
向上游发送 HTTP 请求
1 | Syntax: proxy_send_timeout time; |
Nginx 从接收到客户端请求,到生成请求,将请求发送给上游的超时时间。
接收上游的响应
接收上游的 HTTP 响应头部
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffer_size
1 | Syntax: proxy_buffer_size size; |
这个值也限制了 header
的大小,如果 header
中携带 cookie
,超过了 proxy_buffer_size
,那么 Nginx 无法正常处理这个请求。
出现这个问题时,Nginx 日志中会出现:error.log: upstream sent too big header
接收上游的 HTTP 包体
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffers
1 | Syntax: proxy_buffers number size; |
当指定大小的内存能存放上游的包体,则不会向磁盘中写入。
1 | Syntax: proxy_buffering on | off; |
启用或禁用来自代理服务器的响应缓冲。
- 当启用缓冲时,
nginx
尽快从代理服务器接收响应,并将其保存到由proxy_buffer_size
和proxy_buffers
指令设置的缓冲区中。如果整个响应无法完全放入内存,则部分内容可以保存到磁盘上的临时文件中。写入临时文件受proxy_max_temp_file_size
和proxy_temp_file_write_size
指令控制。 - 当禁用缓冲时,响应会同步传递给客户端,即在接收到后立即传递。Nginx 不会尝试从代理服务器读取整个响应。Nginx 一次从服务器接收数据的最大大小由
proxy_buffer_size
指令设置。也可以通过在X-Accel-Buffering
响应头字段中传递yes
或no
来启用或禁用缓冲功能。此功能可通过使用proxy_ignore_headers
指令进行禁用。
1 | Syntax: proxy_max_temp_file_size size; |
及时转发包体
1 | Syntax: proxy_busy_buffers_size size; |
虽然缓存响应,但是也希望更及时向客户端发送响应。此时收到 8k|16k
(与机器相关)时,则转发给客户端。
接收上游时网络速度相关指令
1 | # 两次读取上游响应操作之间的超时时间 |
上游包体的持久化
1 | # 将转发的临时文件做持久化处理 |
启用将文件保存到磁盘。on
参数会将文件保存在与指令别名或根路径对应的路径中。off
参数会禁用文件保存功能。此外,可以使用带有变量的字符串来明确设置文件名:
处理上游的响应头部
加工响应内容
当接收到请求,必须经过 HTTP 过滤模块处理,来自上游模块的响应同理。
禁用上游响应头部的功能
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ignore_headers
1 | Syntax: proxy_ignore_headers field ...; |
- 功能:某些响应头部可以改变 Nginx 的行为,使用
proxy_ignore_headers
可以禁止它们生效 - 可以禁用功能的头部: “X-Accel-Redirect”, “X-Accel-Expires”, “X-Accel-Limit-Rate” (1.1.6), “X-Accel-Buffering” (1.1.6), “X-Accel-Charset” (1.1.6), “Expires”, “Cache-Control”, “Set-Cookie” (0.8.44), and “Vary” (1.7.7).
转发上游的响应
1 | Syntax: proxy_hide_header field; |
功能:对上游响应中的某些头部,设置不向客户端转发
默认不转发的响应头部:“Date”, “Server”, “X-Pad”, and “X-Accel-…”
1 | yntax: proxy_pass_header field; |
功能:对于已经被 proxy_hide_header
的头部,设置向客户端转发
修改返回的 Set-Cookie 头部
1 | # 修改 cookie 中的域名 |
返回修改的 Location 头部
1 | Syntax: proxy_redirect default; |
替换上游返回的头部中的 Location
上游出现失败时的容错方案
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream
当第一台上游服务器返回错误给 Nginx 时,Nginx 的一些容错方案
1 | Syntax: proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | http_429 | non_idempotent | off ...; |
前提:没有向客户端发送任何内容,如果向客户端发送字节了,代表这个上游已经生效,此时无法选择新的上游服务
配置:(当上游返回以下情况时)
error
:例如一些网络错误timeout
:请求超时invalid_header
:上游返回的header
不合法http_
:具体的响应码non_idempotent
:通常,使用非幂等方法(POST
、LOCK
、PATCH
)的请求不会被传递到下一个服务器,如果已经向上游服务器发送了请求(1.9.13);显式启用此选项允许重试这些请求;off
:关闭将请求传递给下一个上游服务器
1 | # 限制请求可以传递到下一个服务器的时间。值为0会关闭此限制。 |
用 error_page 拦截上游失败响应
当上游响应的响应码大于等于 300
时,应将响应返回客户端还是按 error_page
指令处理
1 | Syntax: proxy_intercept_errors on | off; |
对上游服务使用 SSL
双向认证时的指令实例
对下游使用证书
https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate
1 | Syntax: ssl_certificate file; |
验证下游证书
1 | Syntax: ssl_verify_client on | off | optional | optional_no_ca; |
对上游使用证书
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ssl_certificate
1 | Syntax: proxy_ssl_certificate file; |
验证上游使用证书
1 | Syntax: proxy_ssl_trusted_certificate file; |
ssl 模块提供的变量
创建证书的示例
浏览器缓存与 Nginx 缓存
互联网中,使用缓存可以大大提升访问效率。
- 浏览器缓存
- 优点
- 使用有效缓存时,没有网络消耗,速度最快
- 即使由网络消耗,但对失效缓存使用 304 响应做到网络流量消耗最小化
- 缺点
- 仅提升一个用户的体验(也就是用浏览器这一个用户的体验)
- 优点
- Nginx 缓存
- 优点
- 提升所有用户的体验(作为入口,可以提升所有访问这个入口的用户的体验)
- 相比浏览器缓存,有效降低上游服务的负载
- 通过 304 响应减少 Nginx 与上游服务间的流量消耗
- 缺点
- 用户仍然保持网络消耗
- 优点
- 同时使用浏览器缓存与 Nginx 缓存
浏览器缓存
Etag 头部
https://nginx.org/en/docs/http/ngx_http_core_module.html#etag
1 | Syntax: etag on | off; |
启用或禁用静态资源的“ETag”响应头字段的自动生成。(会使用时间的16进制加上返回的字节数)
生成规则
1 | ngx_sprintf(etag->value.data, "\"%xT-%xO\"", r->headers_out.last_modified_time,r->headers_out.content_length_n) |
If-None-Match
If-Modified-Since 头部
not_modified 过滤模块
功能:用户端拥有缓存,但不确认缓存是否过期,于是在请求中传入 If-Modified-Since
或者 If-None-Match
头部,该模块通过将其值与响应中的 Last-Modified
值相比较,决定是通过 200 返回全部内容,还是仅返回 304 Not Modified 头部,表示浏览器仍然使用之前的缓存。
使用前提:原返回响应码为 200
https://nginx.org/en/docs/http/ngx_http_headers_module.html#expires
1 | Syntax: expires [modified] time; |
https://nginx.org/en/docs/http/ngx_http_core_module.html#if_modified_since
1 | Syntax: if_modified_since off | exact | before; |
off
:忽略请求中的if_modified_since
头部exact
:精确匹配if_modified_since
头部与last_modified
的值before
:若if_modified_since
大于等于last_modified
的值,则返回 304
If-Match
If-Unmodified-Since
Nginx 缓存
定义存放缓存的载体
配置上游服务器响应的缓存
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache
1 | # 定义共享内存 |
例如
1 | proxy_cache_path /data/nginx/cache keys_zone=cache_zone:10m; |
缓存的关键字
1 | Syntax: proxy_cache_key string; |
缓存什么样的响应
1 | Syntax: proxy_cache_valid [code ...] time; |
- 对不同的响应码缓存不等的时长
- 例如:
code 404 5m
- 例如:
- 只标识时间
- 进对以下缓存码缓存
- 200
- 301
- 203
- 进对以下缓存码缓存
- 通过响应头部控制缓存时长
X-Accel-Expires
,单位 秒- 为 0 时标识禁止 Nginx 缓存内容
- 通过 @ 设置缓存到一天中的某一时刻
- 响应头若含有 Set-Cookie 则不缓存
- 响应头含有
Vary: *
则不缓存
哪些内容不使用缓存
参数为真,响应不存入缓存(例如有些数据存放在 cookie 中)
1 | Syntax: proxy_no_cache string ...; |
参数为真,不使用缓存内容(即使响应存入缓存,但是不使用)
1 | Syntax: proxy_cache_bypass string ...; |
变更 HEAD 方法
可以将 HEAD 方法变更为 GET 方法
1 | Syntax: proxy_cache_convert_head on | off; |
启用或禁用将 HEAD
方法转换为 GET
以进行缓存。当禁用转换时,缓存键应配置为包括 $request_method
。
upstream_cache_status 变量
标识这个缓存的状态
对客户端请求的缓存处理流程
proxy_cache_methods
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_methods
1 | Syntax: proxy_cache_methods GET | HEAD | POST ...; |
接收上游响应的缓存处理流程
X-Accel-Expires 头部
从上游服务定义缓存多长时间:
0
表示不缓存当前响应@
前缀标识缓存到当前的某个时间
Vary 头部
Set-Cookie 头部
若 Set-Cookie
头部没有被 proxy_ignore_headers
设置忽略,则不对响应进行缓存。
减轻缓存失效时上游服务的压力
当缓存大量失效或者重启服务,会导致缓存穿透
第一种方式:合并回源请求(也叫单飞、请求收束)
1 | Syntax: proxy_cache_lock on | off; |
同一时间,仅第一个请求发向上游,其他请求等待第一个响应返回或者超时后,使用缓存响应客户端
1 | Syntax: proxy_cache_lock_timeout time; |
等嗲第一个请求返回响应的最大时间,到达后直接向上游发送请求,但不缓存响应
1 | Syntax: proxy_cache_lock_age time; |
上一个请求返回响应的超时时间,到达后再放行一个请求发向上游
第二种方式:使用 stale 陈旧的缓存(也称作降级)
1 | # 配置为 updating 表示使用旧缓存 |
proxy_cache_use_stale
指令:定义陈旧缓存的用法
缓存有问题的响应
1 | Syntax: proxy_cache_background_update on | off; |
当使用 proxy_cache_use_stale
允许使用过期响应时,将同步生成一个子请求,通过访问上游服务更新过期的缓存
1 | Syntax: proxy_cache_revalidate on | off; |
更新缓存时,使用 If-Modified-Since
和 If-None-Match
作为请求头部,预期内容未发生变更时,通过 304 来减少传输内容。
这个过程,跟浏览器和 Nginx 交互的流程一样
及时清除缓存
原生缓存会通过定时器决定缓存是否失效,第三方模块提供缓存立即失效的方法
模块:
- 第三方模块:https://github.com/FRiCKLE/ngx_cache_purge
功能:
- 接收到指定 HTTP 请求后立刻清除缓存
1 | syntax: proxy_cache_purge on|off|<method> [from all|<ip> [.. <ip>]] |
其他协议的反向代理
七层反向代理对照:构造请求内容
建立连接并发送请求
接收上游响应
转发响应
SSL
缓存类指令
独有配置
memcached 反向代理
应用级的反向代理
https://nginx.org/en/docs/http/ngx_http_memcached_module.html
功能:
- 将 HTTP 请求转换为
memcached
协议中的get
请求,转发请求至上游 memcached 服务 get
命令:get <key>*\r\n
(协议简单,只能转成get
请求)- 控制命令:
<command name> <key> <flags> <exptime> <bytes> [noreply]\r\n
- 通过设置
memcached_key
变量构造key
键
模块默认编译进 Nginx
指令:
websocket 反向代理
https://nginx.org/en/docs/http/websocket.html
让服务器主动向浏览器推送请求
例如:
1 | location /chat/ { |
协议升级
websocket
协议帧
websocket
协议和扩展
用分片提升缓存效率
当上游服务器响应的内容很大,可以通过分片提升缓存效率
https://nginx.org/en/docs/http/ngx_http_slice_module.html
1 | Syntax: slice size; |
功能:通过 range
协议将大文件分解为多个小文件,更好的用缓存为客户端的 range
协议服务
默认没有编译到 Nginx
运行流程:
将文件以分块的形式构建多个请求,将多个请求的分片缓存下来。
1 | location / { |
通过 open file cache 提升性能
https://nginx.org/en/docs/http/ngx_http_core_module.html#open_file_cache
1 | Syntax: open_file_cache off; |
缓存哪些元信息
缓存文件句柄,下次使用时,可以不再重新打开文件。
1 | Syntax: open_file_cache_errors on | off; |
HTTP2 协议
主要特性
- 传输数据量的大幅减少
- 以二进制方式传输
- 标头压缩(主要针对
cookie
)
- 多路复用及相关功能
- 消息优先级
- 服务器消息推送
- 并行推送
核心概念
- 连接 Connection:1个 TCP 连接,包含一个或者多个 Stream
- 数据流 Stream:一个双向通讯数据流,包含多条 Message
- 消息 Message:对应 HTTP1 中的请求或者响应,包含一条或者多条 Frame
- 数据帧 Frame:最小单位,以二进制压缩格式存放 HTTP1 中的内容
协议分层
多路复用
传输中无序,接收时组装
数据流优先级
- 每个数据流有优先级( 1-256)
- 数据流间可以有依赖关系
标头压缩
Frame 格式
服务器推送 PUSH
http2
https://nginx.org/en/docs/http/ngx_http_v2_module.html
默认没有编译进 Nginx
功能:对客户端使用 http2 协议提供基本可能
前提:开启 TLS/SSL 协议
使用方法:listen 443 ssl http2;
Nginx 推送资源
1 | # 例如 如果上游头部添加 Link: style.css,则 Nginx 会将 style.css 文件推送给客户端 |
测试 Nginx http2 协议的客户端工具:https://github.com/nghttp2/nghttp2/releases
并发请求控制
1 | # 最大并行推送数 |
超时控制
1 | Syntax: http2_recv_timeout time; |
连接最大处理请求数
1 | Syntax: http2_max_requests number; |
设置响应包体的分片大小
1 | Syntax: http2_chunk_size size; |
缓冲区大小设置
1 | Syntax: http2_recv_buffer_size size; |
grpc 反向代理
协议:https://grpc.io/
模块:https://nginx.org/en/docs/http/ngx_http_grpc_module.html
默认编译进 Nginx 中,依赖 ngx_http_v2_module
模块
指令对照表
对照 SSL 部分
stream 模块
四层网络功能,相比七层网络而言,四层简单很多。
与七层一样,四层也支持代理,四层网络代理是代理 TCP/UDP 请求。
stream 模块处理请求的 7 个阶段
模块:https://nginx.org/en/docs/stream/ngx_stream_core_module.html
1 | Syntax: stream { ... } |
可以看到,四层反向代理和七层反向代理在配置上的区别是:
四层反向代理的配置在 main
下的 stream
配置块中;
七层反向代理的配置在 main
下的 http
配置块中;
传输层相关的变量
Nginx 系统变量
content 阶段
return
模块
1 | Syntax: return value; |
proxy_protocol 协议
网络中流量经过网关和防火墙可能会丢失真实的客户端地址,此时需要有额外协议携带真实地址。
v1 协议中,填写的是:源IP 目标IP 源端口 目标端口
读取 proxy_protocol
协议的超时控制
1 | Syntax: proxy_protocol_timeout timeout; |
stream 处理 proxy_protocol 流程
首先查看 Nginx 配置中是否携带 listen proxy_protocol
post_accept 阶段
realip 模块
https://nginx.org/en/docs/stream/ngx_stream_realip_module.html
功能:通过 proxy_protocol
协议获取出客户端真实地址,并写入 remote_addr
及 remote_port
变量。同时使用 realip_remote_addr
和 realip_remote_port
保留 TCP 连接中获得的原始地址
默认没有编译到 Nginx 中
1 | Syntax: set_real_ip_from address | CIDR | unix:; |
设置可行地址,从可行地址中读取对端地址。
preaccess 阶段的 limit_conn 模块
https://nginx.org/en/docs/stream/ngx_stream_limit_conn_module.html
功能:限制客户端的并发连接数,使用变量自定义限制依据,基于共享内存所有 worker
进程同时生效。
默认编译进 Nginx
1 | Syntax: limit_conn_zone key zone=name:size; |
access 阶段的 access 模块
https://nginx.org/en/docs/stream/ngx_stream_access_module.html
根据客户端地址(realip
模块可以修改地址)决定连接的访问权限。
默认编译进 Nginx 模块
1 | Syntax: allow address | CIDR | unix: | all; |
log 阶段的 stream_log 模块
https://nginx.org/en/docs/stream/ngx_stream_log_module.html
1 | Syntax: access_log path format [buffer=size] [gzip[=level]] [flush=time] [if=condition]; |
stream 模块 TLS/SSL 应用场景
绿色代表网络安全的连接。
Nginx 可以单独为客户端和上游服务提供 TLS/SSL 协议,也可以将与客户端之间 TLS/SSL 协议连接转换为到上游的裸 TCP 连接。同样的,也可以将与客户端的 TCP 协议转换为到上游服务的 TLS/SSL 协议。
stream 中的 ssl
https://nginx.org/en/docs/stream/ngx_stream_ssl_module.html
使 stream
反向代理对下游支持 TLS/SSL 协议。
默认不编译进 Nginx
指令对比 HTTP 模块
steam
中的 SSL 与 HTTP 模块中基本一致。
配置基本参数
提升性能
验证客户端证书
ssl 模块提供的变量
可以基于 stream 四层反向代理的 stream_ssl_module
模块解析 TLS 协议
ssl_preread 模块
https://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html
解析下游 TLS 证书中信息,以变量方式赋能其他模块。
提供变量:
preread 阶段:ssl_preread 模块
https://nginx.org/en/docs/stream/ngx_stream_core_module.html#preread_buffer_size
1 | Syntax: preread_buffer_size size; |
通过 ssl_preread
模块,可以获取到下游服务请求的域名,通过域名可以对应代理到不同的上游服务。
反向代理 stream_proxy 模块
https://nginx.org/en/docs/stream/ngx_stream_proxy_module.html
功能:
- 提供 TCP/UDP 协议的反向代理
- 支持与上游的连接使用 TLS/SSL 协议
- 支持与上游的连接使用
proxy protocol
协议
proxy 模块对上下游的限速指令
限制读取上游服务数据的速度
https://nginx.org/en/docs/stream/ngx_stream_proxy_module.html#proxy_download_rate
1 | Syntax: proxy_download_rate rate; |
限制读取客户端数据的速度
1 | Syntax: proxy_upload_rate rate; |
stream 反向代理指令
stream ssl 指令与 http proxy 模块对照
UDP 反向代理
在实时视频会议、音频会议的场景中,使用 UDP 的效果更好。
理论
https://nginx.org/en/docs/stream/ngx_stream_proxy_module.html#proxy_requests
1 | Syntax: proxy_requests number; |
- 指定一次会话
session
中最多从客户端接受到多少报文就结束session
- 仅会话结束才会记录
access
日志 - 同一个会话中,Nginx 使用同一端口连接上游服务(也就是上图中的 端口C)
- 设置为 0 表示不限制,每次请求都会记录
access
地址
- 仅会话结束才会记录
1 | Syntax: proxy_responses number; |
- 指定对应一个请求报文,上游应返回多少个响应报文(常见的是 1,也就是一个请求一个响应,但是很多时候也不是)
- 与
proxy_timeout
结合使用,控制上游服务是否不可用
- 与
透传 IP 地址
请求通过网络设备后,很多时候可能会丢失原 IP 地址,解决方案有以下几种:
proxy_protocol
协议:数据载荷前端带有源IP地址和端口- 修改 IP 报文:IP 报文头里面有原始 IP 地址
- 步骤:
- 修改 IP 报文中的源地址
- 修改路由规则
- 方案:
- IP 地址透传,经由 Nginx 转发上游返回的报文给客户端(TCP/UDP)
- DSR:上游直接发送报文给客户端(仅 UDP)
- 步骤:
修改源 IP 和路由
操作:
DSR 方案
方案1:修改 TCP 中的源 IP 和源端口,修改路由,返回经由 Nginx 出口
方案二:修改 TCP 中的源 IP 和源端口,在上游服务进程中,修改源地址为网关,可去掉路由。