极客时间专栏——nginx核心100讲
目录:
Nginx适用场景
- 静态资源服务
- 反向代理
- API
Nginx出现历史
略
Nginx优点
- 高并发高性能 高并发只需要每个连接消耗的内存小就可以了,高性能(QPS,每秒处理的请求数)就需要Nginx的架构了
- 可扩展性,插件,更有openresty等
- 高可靠性,不需要经常性重启
- 热更新,kill的时候会发送http的reset包
- BSD许可证
Nginx组成
略
Nginx版本
13年支持websocket,15年支持四层代理
Nginx版本选择
略
Nginx编译
目录主要是contrib可以对nginx配置对应的vim插件,可以cp到系统的$HOME/.vim
configure之后可以在objs/ngx_modules.c内看到加载的模块,被放到一个数组中了
Nginx基本配置
- 指令块,通过{}
- 指令,以;结尾
- include可以加载多个配置
- 注释#
- 变量$
- 时间配置 ms毫秒,s秒,m分,h小时,d天,w周,M月30d,y年365d
- 空间 默认为B,k为KB,m为MB,g为GB
Nginx命令行参数
- -V编译信息
- -g覆盖配置指令
平滑升级nginx版本
kill -USR2
kill -WINCH
日志切割
nginx -s reopen
或者
kill -USR1
Nginx静态资源
- alias
autoindex on
开启目录索引set limit_rate 1k
限制访问速度,参考nginx内置变量- gzip
Nginx缓存
# 配置缓存
proxy_cache_path /tmp/nginxcache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60min use_temp_path=off;
# 配置使用缓存
proxy_cache mycache;
proxy_cache_key $host$uri$is_args$args;
proxy_cache_valib 200 304 302 1d;
goaccess
略
SSL协议
SSL3.0到TLS1.0~1.3
TLS安全密码套件
对称加密和非对称加密
略
SSL证书
nginx可以配置ocsp,ocsp是啥
证书类型,对浏览器来说么有区别
- DV 域名验证
- OV 组织验证
- EV 扩展验证
SSL协议握手
- 对于小文件,握手时主要对QPS的影响指标,椭圆加密算法,RSA算法
- 对于大文件,就是秘钥加密数据的算法了,AES算法
免费获取SSL证书
yum install python2-certbot-nginx
certbot --nginx --nginx-server-root=/etc/nginx/conf/ -d test.whysdomain.com
会选择是否将http协议通过redirect
# 1MB大概4000个session
ssl_session_cache shared:le_nginx_SSL:1m
# 一天
ssl_session_timeout 1440m;
openresty
location /lua {
default_type text/html;
content_by_lua 'ngx.say("User-Agent: ", ngx.req.get_headers()["User-Agent"])'
}
nginx请求流程
epoll通过状态机将请求识别和处理,使用线程池处理磁盘阻塞调用(主要是静态资源和日志相关),然后就是HTTP协议对应处理或者代理到其他的应用层协议
nginx进程结构
如果是单进程,所有线程共享地址空间,如果第三方模块引起地址空间错误的问题可能会导致整体退出
- master进程是做worker进程的管理,master用于监控worker是否需要热加载等
- worker间通信通过共享内存的方式, worker可以进行cpu核数的绑定更好的利用CPU缓存
cache manager
和cache loader
用于反向代理的缓存
Nginx进程结构演示
信号
- TERM,INT立刻退出=stop
- QUIT优雅退出=quit
- HUP重载配置文件=reload
- USR1重新打开日志文件=reopen
- USR2
- WHICH
Nginx的reload流程
- nginx收到HUP信号
- master进程校验nginx配置文件
- master进程打开新的监听端口
- master用新配置启动新的worker进程
- master进程向老的worker子进程发送QUIT信号
- 老的worker进程关闭监听句柄,处理完成后结束进程
master也会启动到定时器,如果worker长时间不结束会强制结束
Nginx热升级
- 新的nginx命令替换旧的
- 向master进程发送USR2信号
- master进程修改pid文件名,改为oldpid
- master进程用新的nginx文件启动新的master进程
- 向老的master进程发送WATCH信号关闭老的worker
回滚的话向老的master发送HUP信号,向新的master发送QUIT信号
Nginx优雅关闭
优雅关闭也只会对HTTP生效,如果代理的TCP和UDP,websocket等,因为没有办法解析websocket的frame帧,TCP等也没法识别报文结束
- 设置
worker_shutdown_timeout
- 关闭监听句柄
- 关闭空闲连接
- 循环等待全部连接关闭
- 进程退出
Nginx网络数据接收
网络事件分为读事件和写事件
读事件
- 请求建立TCP连接(对于Nginx来说是读取TCP报文)
- TCP连接可读(发送一个消息到Nginx也是读事件)
- TCP连接关闭(同理)
- TCP连接可写
- 异步读磁盘成功
写事件(需要写到操作系统中)
- 连接建立时间消费者
- 读事件消费者
- 写事件消费者
- 连接关闭消费者
- 异步读磁盘消费者
- 定时器事件消费者 (是否超时等)
Nginx网络事件实例
TCP握手的时候
- Client端发送SYN包
- Server端返回SYN,ACK包,这时是一个处于半连接状态
- Client返回ACK包,这时操作系统才会通知Nginx通过accept方法建立连接
Nginx事件驱动模型
Nginx在启动的时候监听端口后,处于一个event状态(epoll模式就是epoll wait方法),Nginx也处于sleep状态
当操作系统收到建立连接的时候,处理完握手会通知epoll的阻塞方法并唤醒worker进程,而操作系统会将属于Nginx的事件放到一个队列中,而Nginx就从队列中获取事件并处理
处理事件是一个循环,就是判断队列是否为空,如果不为空就取出并操作,当然这个过程会产生新的事件,例如发现连接被建立就产生定时器定义超时时间或者生产Nginx响应
epoll优劣和原理
对于大并发,活跃的连接是少数的,select和poll会把这些连接放到操作系统扫描活跃连接
epoll维护了一个eventpoll的链表用于存放活跃的连接
Nginx请求切换
对于其他的服务,一个线程处理一个连接,当连接不满足的时候就会切换线程,用其他线程处理其他线程的连接,切换的时间大概是5微秒,但是随着连接增多就是指数型增长,而Nginx一个线程处理多个连接,在线程优先级打的情况下会获取足够的时间片来处理这些请求。
连接不满足是什么情况,一个连接先到的就是HTTP头的部分,对于HEADER有些就可能有对应的处理了,例如转发或者禁止等并且获取是否有Body,第二就是获取Body,第三是返回响应
阻塞或者非堵塞
使用系统调用可能会导致进程进入sleep状态(原因一般是条件不满足)就是进入阻塞状态,非阻塞则是不会因为条件为满足而进入sleep(只要进程还有CPU时间片)
对于Nginx,如果使用accept调用监听套接字设置为阻塞,如果accept队列为空, 就会进入sleep状态,当队列有连接建立的ACK网络分组,就会从队列中将套接字从队列拷贝到进程中,然后处理套接字,这样会造成进程间主动切换
如果使用使用accept调用监听套接字设置为非阻塞,如果accept队列为空,则会返回一个EAGAIN的错误,然后处理EAGAIN
Nginx模块
Nginx的模块在src/*/modules的.c
文件中,需要有个ngx_command_t
用于声明模块的参数,是一个必须的数组结构
Nginx模块分类
略
Nginx连接池
更多的worker的connection数量对会占用更多的内存,一般一个connection约232字节,而且event也约96字节,一个连接就是232+96*2
Nginx内存池对性能的影响
- 连接连接池
connection_pool_size
配置 - 请求连接池
requestion_pool_size
配置
主要是为了减少内存分配,为模块提供内存池,模块就不需要关心这些了
Nginx共享内存
Nginx进程之间通信是信号,而进程间交换数据就是使用共享内存了
共享内存需要使用自旋锁(不释放不会被其他进程获取,其他进程会循环请求,如果有的进程不能快速释放锁,就会造成性能下降)
使用共享内存的模块
- rbtree 用于快速修改数据或者遍历,例如每个server单独的配置项
- 单链表 例如upstream
ngx_http_lua_api
当达到最大的时候会删除最早没有被使用的
Slab
ngx_slab_module
在github并没有,需要下载tengine将其module的拷贝过来,编译的时候使用--add_module
参数编译进openresty等
Nginx容器
- 数组
- 链表
- 队列
- 哈希表
- 红黑树
- 基数树
哈希表需要考虑cpu_cache_line
红黑树
Nginx动态模块
部分模块支持
image_filter resize 15 10;
冲突配置以谁为主
main
http{
server{
location{
location{}
}
}
upstream{}
}
指令有可以存在的一个或多个Context,在多个Context下的有些可以合并,有些不能合并
- 可以合并的是值指令
- 不可以合并的是动作指令
模块的优先级默认是子集中,但是也有不遵循的,可以参考module源码中的merge_conf相关的内容
Listen
可以使用sock,不走本机的网络栈
Nginx处理http请求的流程
建立HTTP连接
- 操作系统内核做三次握手
- 选中cpu上的worker发送
- nginx的事件模块通过epoll_wait获取连接的句柄
- accept分配连接内存池
ngx_http_init_connect 512
设置回调方法epoll_ctl
,并设置超时定时器client_header_timeout
然后处理其他请求去了epoll_wait
收到client端发送的Data,而在TCP层返回一个ACK包ngx_http_wait_request_handler
分配内存读取缓冲区,并从连接内存池分配client_header_buffer_size 1k
开始接收URL
- 接收url需要分配请求内存值
request_pool_size
- 状态机解析请求行,包括方法名,协议,URL等
- 如果内存不够,分配
large_client_header_buffers: 4 8k
(一次分配4次,每次8k) - 状态机解析请求行
- 标识URL(使用指针的方式指向URL)
接收header
- 状态机解析header
- 标识header
- 移除超时定时器
client_header_timeout
- 开始http处理流程
Nginx正则表达式
代码 | 说明 |
---|---|
. |
换行符以外任意字符 |
\w |
字母或数字或下划线或汉子 |
\s |
任意空白符 |
\d |
任意数字 |
\b |
任意开始或结束 |
^ |
字符串开始 |
$ |
字符串结束 |
* |
重复0次或多次 |
+ |
重复1次或多次 |
? |
重复0次或1次 |
{n} |
重复n次 |
{n,} |
重复n次或更多次 |
{n,m} |
重复n次到m次 |
pcretest工具
如何找到请求的server
server_name匹配顺序
- 精准匹配
- *在前的泛解析
- *在后的泛解析
- 按文件顺序匹配正则表达式的域名
- default或者第一个
保证http流程
HTTP请求的11个阶段
除了过滤模块和变量模块,其他模块必须遵循这11个模块
- POST_READ 获取HTTP头部的时候获取一些原始的值
- SERVER_REWRITE
- FIND_CONFIG nginx匹配Location
- REWRITE
- POST_REWRITE
- PREACCESS 在ACCESS之前的工作,检查是否达到最大连接,限速等
- ACCESS 对权限等进行认证
- POST_ACCESS 在ACCESS之后
- PRECONTENT try_file
- CONTENT index
- LOG
阶段请求的模块执行顺序
参考编译后的ngx_module_name
的倒叙
post_read阶段的realip
- X-Forwarded-For用于传递IP
- X-Real-IP用于传递用户IP
经过realip的时候就会覆盖变量binary_remote_addr
,remote_addr
,后续可以根据这个做连接的限制
realip需要单独启用这个模块,会生成realip_remote_addr
和realip_remote_port
rewrite阶段的return
return的状态码
HTTP1.0的标准
- 301 http1.0的永久重定向
- 302 临时重定向不允许被缓存
HTTP1.1的标准
- 303 临时重定向,允许改变方法,不允许被缓存
- 307 临时重定向,不允许改变方法,不允许被缓存
- 308 永久重定向,不允许改变方法
Nginx自定义
- 444 关闭连接
error_page
是在server_rewrite
模块阶段
rewrite阶段的重写url
last rewrite后的结果重新rewrite匹配 break 停止当前脚本指令的执行,停止后续rewrite操作 redirect 返回302重定向 permanent 返回301重定向
rewrite_log on;
可以开启rewrite_log
rewrite阶段进行判断
if指令规则
- 检查变量是否为空或者值为0可直接使用
- 变量字符串匹配可以使用=或!=
- 变量或者正则匹配:大小写敏感使用
~
和!~
大小写不敏感使用~*``!~*
- 检查文件存在-f或!-f
- 检查目录存在-d或!-d
- 检查文件,目录和软连接-e或!-e
- 检查是否为可执行文件-x或!-x
find_config阶段
location指令
- 匹配=
- 匹配^~
- 记住最长匹配的location
- 按照nginx的location出现顺序匹配上的执行
- 都未匹配使用最长匹配的location
preaccess阶段的对连接的限制limit_conn
- 这个参数限制的是所有worker的并发连接,因为基于的是共享内存
- 进入preaccess阶段生效
- 限制的有效性根据key的设计,例如realip模块获取到的真实IP
preaccess阶段的对连接的限制limit_req
- 生效算法为leaky bucket算法
- 这个参数限制的是所有worker的并发连接,因为基于的是共享内存
- 进入preaccess阶段生效
access阶段对ip限制
略
access阶段对用户名和密码做限制的auth_basic
略
access阶段使用第三方模块做用户名和密码限制的auth_request
收到请求后会生成子请求去请求上游服务器,根据返回的状态码来决定是否进行下边的操作
access阶段的satisfy模块
用于是否通过一个access阶段就可以进入postaccess阶段
precontent阶段的try_file模块
略
precontent阶段的mirror
用于实时拷贝数据
content阶段的root和alias
- root将完整的url映射到文件路径
- alias将location匹配后到url映射到文件路径
例如
location /root/ {
root html;
}
会访问html/root下的文件,而如果是alias则会是root
static模块提供的变量
request_filename
待访问文件的完整路径,包括文件名document_root
为URL根据root和alias处理后的路径realpath_root
为将document_root
中的软链等换为真实路径default_type
log_not_found
如果经常有文件找不到的错误可以关闭
static模块对url不以斜杠结尾
发现访问目标会会在结尾加上斜杠并返回301跳转
server_name_in_redirect
可以控制返回的url的是否是host头的内容,返回再Response的Location字段port_in_redirect
同上,是否返回portabsolute_redirect
同上,是否返回host
index和autoindex模块
index先于autoindex
autoindex可以显示目录结构
autoindex_exact_size
是否显示绝对路径autoindex_format
返回格式,默认是html,可以是json,xml,jsonpautoindex_localtime
是否显示本地时间
content阶段的contcat模块
需要去github下,nginx_http_concat
需要js配合
access日志的log模块
可以做cache和gzip?
http阶段过滤模块
- copy_filter复制包体内容到内存
- post_filter处理子请求
- header_filter构造响应头部
- write_filter发送响应
用过滤模块修改响应
http_sub_filter_module
,默认没有被编译进nginx
additon模块
对于某些mime-type进行添加内容
变量
变量hash表大小
variables_hash_bucket_size
variables_hash_max_size
HTTP框架提供的变量
略
防盗链的referer模块
默认是编译到nginx
使用的valid_referer
变量进行匹配,使用invalid_referer
进行判断
生成变量
通过map的方式,根据匹配结果使用对应的值
泛域名前缀优先于后缀
split_client模块
自动进行hash
geo
根据IP进行判断
geoip模块
这种不能在nginx中做啊,多浪费性能
客户端使用keepalive
keepalive是http协议的keepalive,而不是tcp层的keepalive
多个HTTP请求通过复用TCP连接而实现
- 减少握手次数
- 减少并发连接减少服务器资源消耗
- 降低TCP拥塞控制
协议使用了
- Connection头部标识是否支持keepalive
- Keep-Alive头部标识连接保留时间
参数
- keepalive_requests在一个http连接最多执行多少个keepalive请求
- keepalive_timeout是在tcp连接上多久没有新的请求就关闭当前TCP连接,还有二个参数用于返回给Client的keepalive时间
反向代理和负载均衡原理
略
负载均衡算法RR
略
负载均衡算法iphash
略
hash模块
略
负载均衡算法最小连接算法
least_conn配置
可能会有很多后端的连接相同且都为最小,这时候就退化为RR算法
通过共享内存将upstream模块将负载均衡策略数据,运行时每个上游服务的状态数据存放在共享内存
upstream模块的变量
可以获取到cookie,使用upstream_cookie_名称
HTTP反向代理流程
服务器发送请求
- 处理content的proxy_pass指令
- 检查cache是否开启和命中
- 如果未命中根据指令生成发往上游的http头部和包体
- 如果配置
proxy_request_buffer
则读取完整包体(上游可以及时的收取包体)进行发送,如果没有则建立连接实时发送
服务器接收请求
- 接收响应头
- 如果配置
proxy_buffer
则接收完整的包体(内网一般网速很快) - 发送响应头部
- 如果打开cache则缓存包体
- 关闭或复用连接
proxy模块
URL参数规则
- 必须以
http://
或者https://
开头,接下来是域名,IP,Unix socket地址或者upstream名字,前两者可以在域名或者IP后加端口 - 当URL参数中不携带URL,将client请求的url直接发送到上游,当URL参数中携带URL,则对请求的URL中location参数中匹配的一段替换为URL
- URL参数中可以携带变量
- 更复杂的URL替换可以在location中rewrite+break实现
修改发往上游服务器的请求
proxy_method
proxy_http_version
proxy_set_header
proxy_pass_request_headers
是否发送用户的header,默认为onproxy_pass_request_bodys
proxy_set_bodys
接收用户的包体
接收用户的包体是缓存还是转发
proxy_request_buffering
- on的时候适用于client端网速较慢,上游服务器并发处理能力较低,适用于高吞吐量的场景
- off的时候响应及时,降低nginx读磁盘的效率,一旦发送失败
proxy_next_upstream
功能失败
包体的接收
client_body_buffer_size
和client_body_in_single_buffer
默认是关的,需要打开buffer
client_max_body_size
包体最大长度限制,默认为1m
临时文件路径
与上游服务器连接
proxy_connect_timeout
用于三次握手的超时时间,超时为502响应proxy_next_upstream
proxy_socket_keepalive
为TCP层的keepalive,会使用探测包测试应答keepalive
则是HTTP层,复用HTTP连接proxy_bind
用于设置数据包的源地址proxy_ignore_client_abord
是否client断掉连接,是否proxy_send_http
发往上游的请求超时时间
接收上游响应
proxy_buffer_size
从上游返回的header缓存,如果cookie过大可能errorlog有upstream sent too big headerproxy_buffering
是否接收完全部上游返回的body再返回clientproxy_max_temp_file_size
以下三个都是临时文件proxy_temp_file_write_size
proxy_temp_path
proxy_budy_buffer_size
及时转发包体proxy_read_timeout
读取超时时间proxy_limit_rate
从上游获取响应的速度proxy_store_access
对上游返回的文件进行本地存储proxy_store
nginx单独的有一个X-Accel-Buffering头部强制设置proxy_buffering
处理上游的响应头
proxy_ignore_headers
禁用上游响应头部
nginx内部识别的header,基本都是X-Accel开头的,或者是缓存相关的
proxy_hide_header
对于上游的某些请求设置不向client端转发,默认不转发的有
- Date
- Server
- X-Pad
- X-Accel开头
proxy_pass_header
则是相反的
proxy_cookie_domain
和proxy_cookie_path
等这些都应该是业务来做的,用nginx来做就太不好维护了
上游返回失败时的处理方式
前提是没有向client发送字节
使用proxy_next_proxy
对上游使用SSL连接
SSL相关的变量
用好浏览器缓存
- 浏览器发起请求的时候先判断是否有缓存
- 如果有缓存判断是否过期,没有过期直接读取(200 from cache)
- 如果过期判断是否有Etag,如果有会使用If-None-Match:
请求服务器,200则使用新的文件替换缓存,304则使用缓存 - 如果没有Last-Modified,如果有会使用If-Modified-Since请求服务器,200则使用新的文件替换缓存,304则使用缓存
- 如果以上都没有就直接发起请求,进行缓存协商,然后呈现
Nginx决策浏览器时间
略
Nginx缓存
proxy_cache
和proxy_cache_path
进行配置
Nginx缓存流程
proxy_cache_method
对那些方法使用缓存,默认为HEAD和GET
接收上游响应缓存
proxy_cache_valid
对那些状态码进行缓存
防止资源失效导致
合并回源情况
proxy_cache_lock
设置当一个请求发送到上游,而其他请求等待缓存响应proxy_cache_lock_timeout
proxy_cache_lock_age
使用陈旧缓存
proxy_cache_use_stale
配置旧缓存,使用updating
及时清除缓存
ngx_cache_purge
模块
uwsgi,fastcgi和scgi对比
七层反向代理对照
uwsgi | fastcgi | scgi | http代理 | |
---|---|---|---|---|
指定上游 | uwsgi_pass |
fastcgi_pass |
scgi_pass |
proxy_pass |
是否传递请求头部 | uwsgi_pass_request_headers |
fastcgi_pass_request_headers |
scgi_pass_request_headers |
proxy_pass_request_headers |
是否传递请求包体 | uwsgi_pass_request_body |
fastcgi_pass_request_body |
scgi_pass_request_body |
proxy_pass_request_body |
指定请求方法名 | proxy_method |
|||
指定请求协议 | proxy_http_version |
|||
增,改请求头部 | proxy_set_header |
|||
设置请求包体 | proxy_set_body |
|||
是否缓存请求包体 | uwsgi_request_buffering |
fastcgi_request_buffering |
scgi_request_buffering |
proxy_request_buffering |
接收上游请求等等都会对应的模块
memcached反向代理
nginx_http_memcached_module
websocket协议
websocket反向代理
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
协议升级的时候
- 协议为HTTP/1.1
- Connection: keep-alive, Upgrade
- Upgrade: websocket
slice模块
通过分片提升缓存效率
请求通过range的请求头来获取内容
open file cache
对打开文件类都有效,直接使用sendfile,不用用户态打开文件,直接内核态发送到网卡
HTTP/2协议介绍
三个特点
传输数据量减少
- 以二进制传输
- 标头压缩
多路复用
- 消息优先级
服务器消息推送
- 并行推送
核心概念
- connect 一个TCP连接,可以包含多个stream
- stream是一个双向通讯的数据流,包含多条message
- message为http1中的请求或者响应,包含一个或多个frame
- frame数据帧,以二进制格式压缩存在http1中的内容
标头压缩是重复的不传
HTTP2推送消息
ngx_http_v2_module
包含
http2_push_preload
是上游服务通过Link的header指定推送的文件http2_push
是满足条件直接推送
反向代理grpc
ngx_http_grpc_module
模块,依赖ngx_http_v2_module
模块
grpc也有对应的模块
grpc有examples/python可以做测试
stream
做四层代理
stream模块阶段只有7个
stream模块和http同级,也是有server,直接也是listen,但是没有location
proxy_protocol协议
四层增加源地址,目的地址,源端口和目的端口
根据是否有listen proxy_protocol
省略
nginx性能优化
优化分cpu,内存,网络和磁盘
高效使用CPU
- worker进程等于CPU核数
- worker不应在繁忙的时候主动让出CPU
- worker不应调用一些CPU主动让出CPU,主要是openresty的第三方阻塞模块
- 提升优先级
- 在worker节点上部署不进行部署监控之外的程序
配置上可以
worker_processes auto
使用TCP_DEFER_ACCEPT
延迟处理新连接
listen address:port deferred
上下文次数可以通过vmstat的cs或者dstat的csw,或者针对进程使用pidstat -w
设置静态优先级
worker_priority -20
多核之间负载均衡
- 多核之间负载均衡,3.9版本开启reuseport
- 多队列网卡对多核CPU的优化,需要网卡支持RSS特性,使多个cpu都能处理网络的硬中断,RPS就是软中断
- 提升缓存命中率,
worker_cpu_affinity
,三级缓存才是共享的,参考/sys/devices/system/cpu/cpu1/cache/index1/size
- NUMA架构,防止内存总线导致内存请求延迟,可以使用
numactl --hardware
,命中率可以使用numasta
查看命中率
控制TCP三次握手参数
SYN_SENT状态
net.ipv4.tcp_syn_retries
SYN_RCVD状态
net.ipv4.tcp_max_syn_backlog
SYN_RCVD状态连接的个数net.ipv4.tcp_synack_retries
被动建立连接的时候发送SYN/ACK重试的次数
流程是
- SYN分组收到的数据插入到
tcp_max_syn_backlog
的队列并发送回复 - ACK分组收到数据从
tcp_max_syn_backlog
的队列中取出放入ACCEPT队列 - Nginx从ACCEPT队列获取套接字
如果应用程序请求过慢,就会导致accept队列满
net.core.somaxconn
ACCEPT队列
建立TCP连接的优化
应对SYN攻击
net.core.netdev_max_backlog
接收自网卡但是没有被内核协议栈处理的报文队列长度net.ipv4.tcp_max_syn_backlog
SYN_RCVD状态连接的个数net.ipv4.tcp_abort_on_overflow
超出处理能力对新来的SYN包直接返回RST并丢弃连接
当SYN队列满了,新的SYN不进入队列,而是计算出cookie以SYN+ACK回复返回client,正常client发送报文的时候,server根据报文中的cookie恢复连接,但是cookies会占用序列号空间(32位),会使扩充窗口或者时间戳失效
文件句柄上限
操作系统全局
- fs.file-max,可以用
sysctl -a | grep file-max
查看,使用是file-nr
用户级别
- /etc/security/limits.conf的nofile
Nginx进程级别
worker_rlimit_nofile
Nginx连接数
worker_connections
上下游都生效
Server级别控制连接
listen address:port backlog=number
TCP Fast Open
可以减少重复请求的TCP握手
net.ipv4.tcp_fastopen
- 0 关闭
- 1 作为client启用
- 2 作为server启用
- 3 作为client和server都启用
Server级别listen address:port fastopen=number
限制了TFO队列长度
滑动窗口和缓冲区
滑动窗口主要是解决报文的乱序和可靠传输的问题
nginx的limit限速都是通过滑动窗口实现
TCP发送数据的方式
- 使用send方法
- send方法调用tcp_sendmsg将请数据从用户态复制到内核态(tcp发送队列)
- tcp发送队列如果不足就会阻塞等待有空间
- push会使用tcp的慢启动,nagle算法进行发送报文
TCP接收数据的方式
- 网卡接收到有序的报文插入
receive
队列,失序的报文插入out_of_order
队列 - 遍历
out_of_order
队列将失序报文插入到receive
队列 - nginx使用recv接收阻塞socket,调用tcp_recvmsg锁住socket获取最低接收阀值,处理从receive队列中已排序的报文
- 将复制队列中的数据包到用户态
- 检查是否超过最低阀值的TCP消息长度,如果超过则清空这些数据
以上是在nginx正在运行的情况下
丢包重传
- net.ipv4.tcp_retries1=3 达到上限后,更新路由缓存
- net.ipv4.tcp_retries2=15 达到上限后,关闭tcp连接
不过实际会以超时时间限制
优化缓冲区与传输效率
net.ipv4.tcp_rmem
读缓存最小值,默认值和最大值net.ipv4.tcp_wmem
写缓存最小值,默认值和最大值net.ipv4.tcp_mem
系统无内存压力,启动压力模式阀值,最大值,单位为页的数量net.ipv4.tcp_moderate_revbuf
开启自动调整缓存模式
listen也会配置revbuf和sndbuf
调整接收窗口与应用缓存
net.ipv4.tcp_adv_win_scale=1
应用缓存=buffer/(2^tcp_adv_win_scale)
BDP带宽时延积=带宽*时延,可以理解为飞行中的数据包,也就是最大接收窗口的大小
吞吐量=窗口/时延
nagle算法
nagle算法可以把报文进行合并发送,但是会造成时延升高
- 启用nagle算法设置tcp_nodelay为off,吞吐量优先
- 禁用nagle算法设置tcp_nodelay为on,低时延优先
默认就是on,禁用的
nginx单独避免发送小报文使用postpone_output
cork算法
完全禁止小报文,但是只有sendfile开启之后才会生效
- tcp_nopush
慢启动和拥塞窗口
- 拥塞窗口是发送方限制流量
- 通告窗口是接收方限制流量
- 实际流量是以上两者的最小值
慢启动
指数扩展拥塞窗口
- 每收到一个ACK,cwnd=cwnd+1
- 每过一个RTT,cwnd=cwnd*2
拥塞避免
- 当达到阈值threshold每收到一个ACK,cwnd=cwnd+1/cwnd
- 每过一个RTT,窗口加1
拥塞发生
- RTO超过的时候,threshold=cwnd/2,cwnd=1
- fast retransmit,收到三个duplicate ACK,cwnd=cwnd/2,
快速恢复
- fast restransmit出现的时候,cwnd调整为threshold+3*MSS
整体过程
当congestion window<threshold
- 第一次是一个数据包
- 第二次是两个数据包
- 第三次是四个数据包,指数扩展
当达到threshold
- 每次加1
当丢失报文之后
- 如果使用Reno算法,直接threshold=cwnd/2
- 然后再使用cwnd+1扩充
TCP的keepalive
- 维护与client的防火墙的活跃网络包
检测实际断掉的连接
net.ipv4.tcp_keepalive_time
=7200 发送心跳周期net.ipv4.tcp_keepalive_intvl
=75 探测包发送间隔net.ipv4.tcp_keepalive_probes
=9 探测包重试次数
在nginx的配置就是
so_keepalive=30m::10
减少关闭连接的timewait
fin_wait1状态
net.ipv4.tcp_orphan_retries=0
发送FIN报文的重试次数,0相当于8
fin_wait2状态
net.ipv4.tcp_fin_timeout=60
保持在FIN_WAIT_2
的状态时间
time_wait状态
net.ipv4.tcp_tw_reuse=1
开启后client端仍可以使用处于TIME_WAIT
的端口,由于时间戳的存在,操作系统可以设置net.ipv4.tcp_timestamps=1
拒绝迟到的报文
net.ipv4.tcp_max_tw_buckets
TIME_WAIT的连接数量net.ipv4.tcp_tw_recycle=0
开启后client和server都可以使用TIME_WAIT的端口,无法避免报文延迟和重复造成的混乱
lingering延迟关闭TCP连接
当Nginx主动关闭连接的时候,调用close状态,当client仍发送数据到缓冲区,服务仍会给client发送RST包关闭连接,导致了client收到了RST而忽略了http的response
nginx端的配置
lingering_close
off为关闭,on为由nginx判断是否使用,always无条件使用lingering_time 30s
功能启用时,最长的读取用户内容的时长,到达后立刻关闭lingering_timeout 5s
检测client是否仍有请求内容到达,若超时后扔没有数据到达,则立刻关闭连接
读写超时通过RST立刻释放连接,代替四次握手
reset_timedout_connect off
应用层协议优化
TLS和SSL握手优化的
ssl_session_cache off
- off为不使用,nginx告诉client不使用
- none为不使用
- builtin为使用,当一个client两次连接都命中一个worker才生效
- shared:name:size为使用,定义共享内存
Ticket会话验证票证
nginx会将会话的session作为ticket加密发送给client,client下次发起TLS连接的带上ticket,由nginx解密验证恢复会话,但是这样破换了nginx的TSL/SSL的安全机制,有安全风险,需要经常更换
ssl_session_ticket on
ssl_session_ticket_key file
HTTP长连接
可以减少握手次数,减少并发连接消耗服务器资源,降低TCP拥塞控制
keepalive_requests number
gzip
压缩包体
gzip on
gzip_proxied
是否压缩上游请求
磁盘IO优化
介质
机械磁盘适合顺序读写,固态硬盘适合随机读写
减少磁盘IO
- sendfile零拷贝
- ssd磁盘
减少写入
- AIO
- errorlog级别
- 压缩accesslog
线程池
直接IO
对于大文件,不会都放到内存的,所以拷贝到内存是浪费的
- directio size 大于size生效
- directio_alignment 对其方式
异步IO
- aio
- aio_write
异步读线程池
编译要加--with-threads
减少磁盘读写
略
零拷贝和gzip_static模块
- 正常情况下磁盘的文件read到磁盘缓存,然后到应用程序,最后才是socket缓存区
- 零拷贝是直接从磁盘缓存拷贝到socket缓存区
启用aio的时候会关闭sendfile
tcmalloc优化内存分配
- 对于glibc并发能力强
- 减少内存碎片
- 擅长管理小块内存
- 下载编译gpertools生成libtcmalloc.so.4
--with-ld-opt=ltcmalloc --with-google_perftools_module
google pertools分析nginx
gpertools
- 文本展示 pprof --text
- 图形展示 pprof --pdf 需要依赖graphviz和libunwind
文本结果
2 0.9% 51.3% 166 73.5% ngx_epoll_process_event
1 0.4% 96.5% 2 0.9% ngx_open_and_stat_file
每统计周期为10ms
- 当前函数执行总共花费的统计周期
- 当前函数执行时间的百分比
- 当前函数及其之前函数执行时间的百分比
- 当前函数及其所调用函数消耗的统计周期总和
- 当前函数及其所调用函数执行时间总和的百分比
- 函数名
stub_status模块监控
- Active connections 当前client与Nginx间的TCP连接数,等于Reading+Writing+Waiting
- accept 从nginx启动,与client建立的连接数
- handler 从nginx启动,处理过的client连接数,如果没有超出worker_connections,应该是与accept相同
- requests 从nginx启动,处理过的client请求数,由于keepalive,所以会大于handler
- Reading 正在读取HTTP请求头的连接总数,接收完Reading就是Writing状态了
- Writing 正在向Client发送响应的连接总数
- Waiting 当前空闲的keepalive数量
第三方模块源码阅读
提供阅读方法
- config文件
ngx_module_t
模块在启动和关闭的操作ngx_command_t
定义的配置指令- http模块,分析
ngx_http_module_t
在http{}解析前后实现的回调方法 - 根据3和4模块的生效方式
生效方式
- 11个阶段
- 过滤响应
- 负载均衡
- 提供新的变量
Nginx启动流程
- init_module在master进程启动时调用
- init_process在worker进程启动时调用
- exit_process在worker进程退出时调用
- exit_module在master进程退出时调用
进程核心描述ngx_cycle_t
- 根据命令行获取配置文件路径
- 如果处于升级中就监听环境变量传递的监听句柄
- 调用所有核心模块的create_conf方法生成存放配置项的结构体
- 针对所有核心模块解析nginx. conf
- 调用所有核心模块的init_conf方法
- 创建目录,打开文件,初始化共享内存
- 打开nginx模块中配置的监听端口
- 调用所有模块的init_module方法
- 启动master进程
- 启动worker进程
- worker进程调用init_process方法
- 启动cache_manager
- 启动cache_load
- 关闭父进程监听端口
HTTP的第三方模块初始化
略
if指令时邪恶的吗
if会替换配置
解读nginx的核心转储文件
略
通过debug日志定位问题
使用--with-debug编译
在event中添加debug_connection
- 建立连接 例如ssl握手
- 接收Header 请求行处理和请求头部解析
- 11个阶段 寻找处理的location,反向代理构造上游请求,接收客户端请求包体
- 构造响应头部
- 发送响应 过滤模块
openresty概述
第三方模块主要是ngx_http_lua_module
和ngx_stream_lua_module
编写代码
- nginx.conf嵌入lua指令
- lua与nginx交互的SDk
- lua模块
SDK分类
- cosocket通讯 tcp和udp
- 共享内存的字典,shared.DICT
- 定时器
- 基于协程的并发编程
- 获取Client请求和响应
- 修改Client请企和响应
- 发送响应
- 子请求
- 工具类 正则,日志,设置配置,编解码和时间等
使用要点
- 不能破换事件驱动,不使用阻塞nginx进程的方法
- 不破坏nginx低内存的优点,如果大量可能需要考虑状态机分多次处理
- 理解Lua代码如何被嵌入Nginx
- 理解Lua的SDK
- 保持Lua代码高效
如何在Nginx中嵌入lua代码
lua模块类型
*_by_lua
通过引号引入代码*_by_lua_block
通过大括号引入代码*_by_lua_file
通过文件引入代码
lua模块种类
init
在master中进行解析init_worker
在worker启动时调用,实现ngx_module_t
中的init_process方法set
将lua代码加入ngx_http_rewrite_module
模块rewrite
加入rewrite阶段,在set之后access
在access阶段content
在content阶段,以独占方式运行log
在log阶段header_filter
响应头过滤阶段body_filter
包体过滤阶段balance
在反向代理阶段
rewrite和access都在是阶段的最后执行,有两个配置控制
rewrite_by_lua_no_postpone
access_by_lua_no_postpone
默认值都是false
在lua中使用ngx.get_phase可以获取到模块在那个阶段进行影响
Openresty中lua与c代码交互原理
Lua的FFI,提供了一种Lua语言调用C语言函数的功能
获取,修改变量的SDK
- ngx.var.VAR_NAME
客户端提前关闭调用的函数
使用ngx.on_abort(callback)
以下都略