nginx 学习4-反向代理/负载均衡2-fastcgi/websocket/stream/http2

409 阅读10分钟

1.uwsgi、fastcgi、scgi、http指令的差异

七层反向代理

CGI

全称通用网关接口 Commmon Gateway Interface。CGI描述了服务器和请求处理程序之间传输数据的一种标准。

是HTTP服务器和动态脚本语言间通信的接口或者工具

fastcgi : php

Fast Common Gateway Interface,早期 CGI的增强版本

差异:

  • CGI 对 Web 服务器的每个请求均建立一个进程进行请求处理
  • FastCGI 服务进程接收 Web 服务器的请求后,由自己的进程自行创建线程完成请求处理。

php的实现fastcgi是 PHP-FPM

wsgi usgi

多用于 python web开发 , django或flask

scgi

Simple Common Gateway Interface 简单通用网关接口 fastCGI类型 但更容易实现,性能比 FastCGI 要弱

2.memcached反向代理的用法

ngx_http_memcached_module 安装

例子

server {
    server_name localhost;
    listen 8319;
    #root html/;
    default_type text/plain;
    location /get { # 通过get转发
        set $memcached_key "$arg_key";
        #memcached_gzip_flag 2;
        memcached_pass localhost:11211; # 代理的memcahed 服务器地址
    }
}

# 写入变量
curl  127.0.0.1:8319/get?key=hello
telnet 127.0.0.1 11211
# set hello 0 0 5
# get hello 
# VALUE hello 0 5
curl  127.0.0.1:8319/get?key=hello -I
#显示不压缩

# 通过加入 gzipkey 告诉浏览器开启gzip压缩
curl  127.0.0.1:8319/get?key=gzipkey -I
#Content-Encoding: gzip

3.websocket反向代理

安装 ngx_http_proxy_module

# 需要升级协议才能使用
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; 
proxy_set_header Connection "upgrade";
  • 数据分片 :有序
  • 不支持多路复用 A Multiplexing Extension for WebSockets
  • 不支持压缩 Compression Extensions for WebSocket

扩展头部

  • Sec-WebSocket-Version: 客户端发送,表示它想使用的WebSocket 协议版本 (13表示RFC 6455)。如果服务器不支持这个版本,必须回应自己支持的版本。
  • Sec-WebSocket-Key: 客户端发送,自动生成的一个键,作为一个对服务器的“挑战”,以验证服务器支持请求的协议版本;
  • Sec-WebSocket-Accept: 服务器响应,包含Sec-WebSocket-Key 的签名值,证明它支持请求的协议版本
  • Sec-WebSocket-Protocol: 用于协商应用子协议: 客户端发送支持的协议列表,服务器必须只回应一个协议名- - Sec-WebSocket-Extensions: 用于协商本次连接要使用的WebSocket扩展: 客户端发送支持的扩展,服务器i同的首部确认自己支持一或多个扩展。

例子

server {
	server_name localhost;
	#root html/;
	default_type text/plain;
	listen 8320;

	location / {
		proxy_http_version 1.1;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection "upgrade";
		proxy_pass ws://121.40.165.18:8800/;
	}
}

## 在线调试
http://www.websocket-test.com/
在网址里输入 本地的的 ws://xx.xx.xx.xx:8320 服务,点击连接 然后发送消息

# 本地监听转发的地址
tcpdump -i eth0 port 8800 and host 121.40.165.18
# 可以监听各种输出

4.用分片提升缓存效率

slice模块

Nginx的slice模块可以将一个请求分解成多个子请求,每个子请求返回响应内容的一个片段,让大文件的缓存更有效率。同时配合http的range 提供客户端下载的速度。

  • 通过range协议将大文件分解为多个小文件,更好的用缓存为客户端的range协议服务模块.
  • http_slice_module,通过--with-http_slice_module启用功能

例子


# 上游服务器
server {
    server_name localhost;
    listen 8012;
    default_type text/plain; 
    root html;
    location /{
            return 200 ;
    } 
}

# 下游服务器
proxy_cache_path /data/nginx/tmpcache3 levels=2:2 keys_zone=three:10m loader_threshold=300 
                     loader_files=200 max_size=200m inactive=1m;

server {
    server_name localhost;
    listen 8321;
    error_log logs/cacherr.log debug;

    location ~ /purge(/.*) {
         proxy_cache_purge three $1$is_args$args$slice_range;
    }

    location /{
        proxy_cache three;
        # 通过制定切割大小实现 缓存优化,slice proxy_cache_key proxy_set_header 需要同时打开
        #slice             1m;
        #proxy_cache_key   $uri$is_args$args$slice_range;
        #proxy_set_header  Range $slice_range;
        proxy_cache_valid 200 206 1m;
        add_header X-Cache-Status $upstream_cache_status;
        proxy_pass http://localhost:8012;
    }
}

# 在没打开 slice ; 时候
# 按指定切片访问nginx 的大文件,虽然只访问了一小部分,但nginx考虑到后面其他链接也会使用,所以会发起完整的请求。
curl -r 5000010-5000019 127.0.0.1:8321/test.mp4 -I 

# nginx会切割成多个 小请求,通过range方式请求上游服务器,通过日志可以看到 
tail -f../logs/access.log
#  日志显示 请求了完整的文件/test.mp4 
#127.0.0.1 23/Dec/2023:14:30:19+08001/video .mp4"HEAD/video.mp4 HTTP/10" 206 curl/7.29.0"

# 当打开了 slice ; 时候
curl -r 5000010-5000019 127.0.0.1:8321/test.mp4 -I 
tail -f../logs/access.log 
# 只请求的切片的大小
#127.0.0.1 23/Dec/2023:14:30:19+08001/video .mp4"HEAD/video.mp4 HTTP/10" 206 57381757 curl/7.29.0"

5.open_file_cache提升系统性能

直接把文件缓存到内存里,检查问价open的开销

open_file_cache_errors on | off;

例子

server {
	listen 8092;
	root html;
	location / {
		open_file_cache max=10 inactive=60s;
		open_file_cache_min_uses 1; 
		open_file_cache_valid 60s; 
		open_file_cache_errors on;
	}
}

# 安装strace strace常用来跟踪进程执行时的系统调用和所接收的信号, 是一个强大的工具
yum install strace -y
# 获取当前nginx的 worker id
ps -ef | grep nginx

strace -p 4669 # 监听指定id
# 可以看到 nginx是否

6. http2协议介绍

HTTP2主要特性

  1. 传输数据量的大幅减少 : 以二进制方式传输,标头压缩
  2. 多路复用及相关功能: 消息优先级
  3. 服务器消息推: 并行推送

HTTP2.0核心概念

  1. 连接Connection:1个TCP连接,包含一个或者多个Stream
  2. 数据流Stream:一个双向通讯数据流,包含多条Message
  3. 消息Message:对应HTTP1中的请求或者响应包含一条或者多条Frame
  4. 数据帧Frame:最小单位,以二进制压缩格式存放HTTP1中的内容

传输中无序,接收时组装

数据流优先级

  • 每个数据流有优先级(1-256)
  • 数据流间可以有依赖关系

标头压缩

Frame格式

TYPE类型:

  • HEADERS:顿仅包含HTTP 标头信息
  • DATA:顿包含消息的所有或部分有效负载
  • PRIORITY:指定分配给流的重要性。
  • RST STREAM: 错误通知:一个推送承诺遭到拒绝。终止流
  • SETTINGS:指定连接配置。
  • PUSH PROMISE: 通知一个将资源推送到客户端的意图
  • PING: 检测信号和往返时间。
  • GOAWAY:停止为当前连接生成流的停止通知
  • WINDOW UPDATE: 用于管理流的流控制。
  • CONTINUATION: 用于延续某个标头碎片序列。

服务器推送PUSH

7.搭建http2服务并推送资源

  • ngx_http_v2_module,通过--with-http_v2_module编译nginx加入http2协议的支持
  • 功能:对客户端使用http2协议提供基本功能
  • 前提: 开启TLS/SSL协议
  • 使用方法: listen 443 ssl http2;

nginx推送资源

  • http2_push_preload on | off;
  • http2 push uri | off; 如: http2 push/image.png

测试nginx http2协议的客户端工具 github.com/nghttp2/ngh…

centos下使用yum安装:yum install nghttp2

例子

server {
    server_name localhost;
    listen 4430 ssl http2; # managed by Certbot 
    ssl_certificate ./cert/a.crt; 
    ssl_certificate_key ./cert/a.key;  
    root html;
    location / { # 当请求 根目录,则会推送 mirror.txt和video.mp4回去
        http2_push /mirror.txt;
        http2_push /video.mp4;
    }
    location /test {
        add_header Link "</style.css>; as=style; rel=preload";
        http2_push_preload on;
    }
}
nghttp -ns https://localhost:4430/
# 返回  返回html同时,也会推送 mirror.txt和video.mp4回去
nghttp -ns https://localhost:4430/test
# 同时返回 css

## 最大并行推送数
http2_max_concurrent_pushes 10; 

## 超时控制
http2_recv_timeout 30s;
http2_idle_timeout 3m;

## 并发请求控制
http2_max_concurrent_pushes 10; 
http2_max_concurrent_streams 128; 
http2_max_field_size 4k; 

## 连接最大处理请求数
http2_max_requests 1000; 
http2_chunk_size 8k;

## 设置响应包体的分片大小
http2_chunk_size 8k;

## 缓冲区盅椽大小设置
http2_recv_buffer_size 256k; 
http2_max_header_size 16k; 
http2_body_preread_size 64k

8.grpc反向代理

  • gRPC是一种进程间通信技术。在 gRPC 中,客户端可以直接调用不同机器上的服务端的方法,就像调用本地函数一样。
  • RPC是远程过程调用(Remote Procedure Call)的缩写形式
  • grpc协议:grpc.io/
  • ngx_http_grpc_module,通过--without-http_grpc_module禁用依赖ngx_http_v2_module模块

例子

server {
    server_name localhost;
    root html;
    location / {
        grpc_pass localhost:50051; # 50051是本地的python服务
    } 
    listen 4431 http2; 
    ssl_certificate ./cert/a.crt; 
    ssl_certificate_key ./cert/a.key;  
}

with grpc.insecure channel( localhost:50051') as channel:
python greeter client.py # 这里的python 是grpc example提供

9.stream四层反向代理(TCP)

9.1基础

stream模块7个阶段

  • POST_ACCEPT realip
  • PREACCESS limt_conn
  • ACCESS access
  • SSL ssl
  • PREREAD ssl_preread
  • CONTENT return, stream_proxy
  • LOG access_log

传输层相关的变量

  • binary_remote_addr 客户端地址的整型格式,对于IPv4是4字节,对于 IPV6是16字节
  • connection 递增的连接序号
  • remote_addr 客户端地址
  • remote_port 客户端端口
  • proxy_protocol_addr 若使用了proxy_protocol协议则返回协议中的地址,否则返回空
  • proxy_protocol_port 若使用了proxy_protocol协议则返回协议中的端口,否则返回空
  • protocol 传输层协议,值为TCP或者UDP
  • server_addr 服务器端地址
  • server_port 服务器端端口
  • bytes_received 从客户端接收到的字节数
  • bytes_sent 已经发送到客户端的字节数

传输层相关的变量 status

  • 200: session成功结束
  • 400:客户端数据无法解析,例如proxy protocol协议的格式不正确
  • 403:访问权限不足被拒绝,例如access模块限制了客户端IP地址
  • 500:服务器内部代码错误
  • 502:无法找到或者连接上游服务
  • 503:上游服务不可用

Nginx 系统变量

  • time_local 以本地时间标准输出的当前时间,例如14/Nov/2018:15:55:37 +0800
  • time_iso8601 使用ISO 8601标准输出的当前时间,例如2018-11-14T15:55:37+08:00
  • nginx_version Nginx 版本号
  • pid 所属worker 进程的进程id
  • pipe 使用了管道则返回 p,否则返回
  • hostname 所在服务器的主机名,与hostname 命令输出一致
  • msec 1970年1月1日到现在的时间,单位为秒,小数点后精确到毫秒

content阶段: return模块

returm value;

例子

stream {
    log_format basic '$remote_addr [$time_local] '
             '$protocol $status $bytes_sent $bytes_received '
             '$session_time';
    error_log logs/stream_error.log debug;
    access_log logs/stream_access.log basic; 
    server {
        listen 10004;
        set_real_ip_from 127.0.0.1;
        allow 202.112.144.236;
        deny all;
        return '10004 vars:
                bytes_received: $bytes_received
                bytes_sent: $bytes_sent
                proxy_protocol_addr: $proxy_protocol_addr
                proxy_protocol_port: $proxy_protocol_port
                remote_addr: $remote_addr
                remote_port: $remote_port
                realip_remote_addr: $realip_remote_addr
                realip_remote_port: $realip_remote_port
                server_addr: $server_addr
                server_port: $server_port
                session_time: $session_time
                status: $status
                protocol: $protocol
                ';
    }
}
# 测试
telnet localhost 10004

9.2.proxy protocol协议与realip模块

proxy_protocol协议

  • v1协议
  • v2协议

stream处理proxy_protocol流程

  1. 连接建立成功,是否携带listen proxy protocol? 是
  2. 加入读定时器proxy_protocol timeout(默认30秒)
  3. 读取107字节(proxy protocol最大长度)
  4. 判断前12字节是否匹配V2协议的头部? 否
  5. 读取v1协议头部的真实IP地址
  6. 读取v2协议头部的真实IP地址
  7. 进入7个阶段的stream模块处理

读取proxy_protocol协议的超时控制

proxy_protocol_timeout 30s;

post_accept阶段: realip模块

通过proxy_protocol协议取出客户端真实地址,并写入remote_addr及remote_port变量。同时使用realip remote_addr和realip_remote_port保留TCP连接中获得的原始地址。

模块:ngx_stream_realip_module ,通过--with-stream_realip_module启用功能

set_real_ip_from address | CIDR | unix:;

例子

stream {
    log_format basic '$remote_addr [$time_local] '
             '$protocol $status $bytes_sent $bytes_received '
             '$session_time';

    error_log logs/stream_error.log debug;
    access_log logs/stream_access.log basic;
    server {
            listen 10002 proxy_protocol;
            return '10002 server get ip: $remote_addr!\n';
    }

    server {
            listen 10003 proxy_protocol;
            return '10003 server get ip: $remote_addr!\n';
    }

    server { 
            listen 10004 proxy_protocol;
            set_real_ip_from 127.0.0.1;
            allow 202.112.144.236;
            deny all;
            return '10004 vars:
                    bytes_received: $bytes_received
                    bytes_sent: $bytes_sent
                    proxy_protocol_addr: $proxy_protocol_addr
                    proxy_protocol_port: $proxy_protocol_port
                    remote_addr: $remote_addr
                    remote_port: $remote_port
                    realip_remote_addr: $realip_remote_addr
                    realip_remote_port: $realip_remote_port
                    server_addr: $server_addr
                    server_port: $server_port
                    session_time: $session_time
                    status: $status
                    protocol: $protocol
                    ';
    }
}
# 测试
telnet localhost 10004

9.3.限并发连接、限IP、记日志

PREACCESS阶段的limit_conn模块

功能: 限制客户端的并发连接数。使用变量自定义限制依据,基于共享内存所有worker进程同时生效。 模块: ngx_stream_limit_conn_module,通过--without-stream_limit_conn_module禁用模块 limit_conn_zone key zone=name:size;

ACCESS阶段的access模块

功能:根据客户端地址 (realip模块可以修改地址)决定连接的访问权限

模块:ngx_stream_access_module,通过--without-stream_access_module禁用模块

allow address | CIDR | unix: | all deny address | CIDR | unix: | all;

log阶段: stream log模块

access_log path format [buffer=size] [gzip[=level]] [flush=time] [if=condition];
log_format name [escape=default|json|none] string ...;
open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time];
open_log_file_cache off;

9.4.stream四层反向代理 SSL下游流量

stream模块TLS/SSL应用场景

  1. 客户端 <-TLS/SSL-> nginx <-TLS/SSL-> 上游服务
  2. 客户端 <-TLS/SSL-> nginx <-TCP-> 上游服务
  3. 客户端 <-TCP-> nginx <-TLS/SSL-> 上游服务

stream中的ssl

功能: 使stream反向代理对下游支持TLS/SSL协议 模块: ngx_stream_ssl_module,默认不编译进nginx,通过--with-stream_ssl_module

stream ssl模块提供的变量 (1)

#安全套件
ssl_cipher #本次通讯选用的安全套件,例如ECDHE-RSA-AES128-GCM-SHA256
ssl_ciphers #客户端支持的所有安全套件
ssl_protocol #本次通讯选用的TLS版本,例如TLSv1.2
ssl_curves #客户端支持的圆曲线,例如secp384rl:secp521rl
#证书
ssl_clicnt_raw_cert #原始客户端证书内容
ssl_client_escaped_cert #返回客户端证书做urlencode编码后的内容
ssl_client_cert #对客户端证书每一行内容前加tab制表符空白,增强可读性
ssl_clicnt_fingerprint #客户端证书的SHA1指纹

STREAM SSL模块实战

客户端 <-HTTPS-> nginx <-HTTP-> 上游服务

注意:nginx使用stream四层反向代理的stream ssl module模块解析TLS协议

例子

server {
    listen 4433;
    resolver 114.114.114.114;
    resolver timeout 60s;
    proxy_pass $ssl_preread_server_name:443;
    ssl_preread on;
}

server {
    server_name localhost; 
    listen 4434  ssl;   
    # 设置自己的服务器证书 a 证书
    ssl_certificate ./cert/a.crt; 
    ssl_certificate_key ./cert/a.key;  
    proxy_pass localhost:80;
}

server {
    server_name localhost;
    #root html/;
    default_type text/plain;
    listen 8320;
    location / {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass ws://121.40.165.18:8800/;
    }
}
# 通过浏览器访问 http://xx.xx.xx.xx:8320:
# 测试
tcpdump -i lo port 8320 -A -s 0 # 通过tcpdump 抓包查看日志

9.5.stream_preread 模块取出SSL关键信息

SSL PREREAD模块

模块 : stream_ssl_preread_module,使用--with-stream_ssl_preread_module启用模块 功能 :解析下游TLS证书中信息,以变量方式赋能其他模块

提供变量

  • $ssl_preread_protocol :客户端支持的TLS版本中最高的版本,例如TLSv1.3
  • $ssl_preread_server name : 从SNI中获取到的服务器域名
  • $ssl_preread_alpn_protocols : 通过ALPN中获取到的客户端建议使用的协议,例如h2,http/1.1

流程

  • POST_ACCEPT realip
  • PREACCESS limt_conn
  • ACCESS access
  • SSL ssl
  • PREREAD ssl_preread
  • CONTENT return, stream_proxy
  • LOG access_log

preread阶段: ssl preread模块

preread_buffer_size 16k; preread_timeout 30s; ssl_preread off;

STREAM SSL PREREAD模块实战

STREAM SSL模块实战

客户端 <-HTTPS-> nginx <-HTTPS-> 上游服务

基于stream_ssl_preread_module模块中取得的证书中域名,选择上游的服务

9.6.stream proxy四层反向代理的用法

反向代理stream_proxy模块

模块 ngx_stream_proxy_module,默认在Nginx中

功能

  • 提供TCP/UDP协议的反向代理
  • 支持与上游的连接使用TLS/SSL协议
  • 支持与上游的连接使用proxy protocol协议

proxy模块对上下游的限速指令

proxy_download_rate 0;  # 限制读取上游服务数据的速度( 客户端 -> nginx )
proxy_upload_rate 0; #    限制读取客户端数据的速度  ( nginx <- 上游 )

stream proxy模块实战

客户端 <-HTTP (proxy protocol)-> nginx <-HTTP (proxy protocol)-> nginx上游服务

客户端 <-HTTP-> nginx(加入proxy_protocol_on;配置) <-HTTP proxy protocol(各种slb)-> nginx上游服务

注意:nginx上游服务 使用 http模块listen_proxy_protocol

例子

stream {
    server {
        listen 9001 proxy_protocal;
        location / {
            return 200 '
            proxy_protocol_addr: $proxy_protocol_addr
            proxy_protocol_port: $proxy_protocol_port
            '; 
        } 
    }
}
http{
    server {
        listen 4435;
        proxy_pass localhost:9001;
        proxy_protocol on;
    }
}
# 测试
telnet localhost:4435
curl localhost:4435
tcpdump -i lo port 9001 -A -s 0 # 通过tcpdump 抓包查看日志

10.UDP反向代理

UDP反向代理的理论依据

src 源, dst 目标

  • 客户端 发送端口A
  • nginx 监听端口B 发送端口C
  • 上游服务 监听端口D

所有端口都是session

指定一次会话session中最多从客户端接收到多少报文就结束session。 (1.15.7非稳定版本)

  • 仅会话结束时才会记录access日志.
  • 同一个会话中,nginx使用同一端口连接上游服务
  • 设置为0表示不限制,每次请求都会记录access日志

proxy_requests number;

指定对应一个请求报文,上游应返回多少个响应报文.与proxy timeout结合使用,控制上游服务是否不可用

proxy_responses number;

例子

stream {
    server {
         listen 4436 udp;
         proxy_pass localhost:9999;
         proxy_requests 1; # 1-3
         proxy_reponses 2;
         proxy_timeout 2s; 
         access_log logs/udp_access.log udplog;
    }
}
# 测试
python client.py 4436
# 查看日志
tail -f udp_access.log
tcpdump -i lo port 9001 -A -s 0 # 通过tcpdump 抓包查看日志

11.透传IP地址的3个方案

透传IP地址

proxy_protocol协议

修改IP报文:步骤:1.修改IP报文中的源地址 2.修改路由规则

方案

  • IP地址透传:经由nginx转发上游返回的报文给客户端(TCP/UDP)
  • DSR: 上游直接发送报文给客户端 (仅UDP)
  • 客户端: IP地址是A
  • nginx :IP地址是B
  • 上游: IP地址是C

IP地址透传

proxy_bind $remote_addr transparent;
调节上游服务所在主机上的网关为nginx所在主机:
# route del default gw 10.0.2.2 (原网关IP)
# route add default gw 172.16.0.1 (Nginx所在主机的IP)
调节nginx所在主机上的路由规则,使它把接收自上游的、目标I是客户端IP的报文转发nginx
# ip rule add fwmark 1 lookup 100
# ip route add local 0.0.0.0/0 dev lo table 100
# iptables -t mangle -A PREROUTING -p tcp -s 172.16.0.0/28 --sport 80 -i MARK-set-xmark 0x1/0xffffffff

DSR方案

DSR上游服务直接向客户端回包

proxy_responses 0 # # 简化版ip透传,由上游服务直接将报文发送给客户端
proxy_bind $remote_addr:$remote_port transparent; #若上游服务在内网无公网ip,则可由nginx所在主机转发。 在上游服务所在主机上添加路由
route add default gw nginx-ip-address #允许操作系统转发ip报文
sysctl -w net.ipy4.ip_forward=10 # 转发时修改源地址为nginx所在主机的地址

问题

nginx检测不到上游服务是否回包,负载均衡策略受限

参考

time.geekbang.org/course/intr…