极客时间专栏——nginx核心100讲

时间:Sept. 21, 2019 分类:

目录:

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 managercache loader用于反向代理的缓存

Nginx进程结构演示

信号

  • TERM,INT立刻退出=stop
  • QUIT优雅退出=quit
  • HUP重载配置文件=reload
  • USR1重新打开日志文件=reopen
  • USR2
  • WHICH

Nginx的reload流程

  1. nginx收到HUP信号
  2. master进程校验nginx配置文件
  3. master进程打开新的监听端口
  4. master用新配置启动新的worker进程
  5. master进程向老的worker子进程发送QUIT信号
  6. 老的worker进程关闭监听句柄,处理完成后结束进程

master也会启动到定时器,如果worker长时间不结束会强制结束

Nginx热升级

  1. 新的nginx命令替换旧的
  2. 向master进程发送USR2信号
  3. master进程修改pid文件名,改为oldpid
  4. master进程用新的nginx文件启动新的master进程
  5. 向老的master进程发送WATCH信号关闭老的worker

回滚的话向老的master发送HUP信号,向新的master发送QUIT信号

Nginx优雅关闭

优雅关闭也只会对HTTP生效,如果代理的TCP和UDP,websocket等,因为没有办法解析websocket的frame帧,TCP等也没法识别报文结束

  1. 设置worker_shutdown_timeout
  2. 关闭监听句柄
  3. 关闭空闲连接
  4. 循环等待全部连接关闭
  5. 进程退出

Nginx网络数据接收

网络事件分为读事件和写事件

读事件

  1. 请求建立TCP连接(对于Nginx来说是读取TCP报文)
  2. TCP连接可读(发送一个消息到Nginx也是读事件)
  3. TCP连接关闭(同理)
  4. TCP连接可写
  5. 异步读磁盘成功

写事件(需要写到操作系统中)

  1. 连接建立时间消费者
  2. 读事件消费者
  3. 写事件消费者
  4. 连接关闭消费者
  5. 异步读磁盘消费者
  6. 定时器事件消费者 (是否超时等)

Nginx网络事件实例

TCP握手的时候

  1. Client端发送SYN包
  2. Server端返回SYN,ACK包,这时是一个处于半连接状态
  3. 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连接

  1. 操作系统内核做三次握手
  2. 选中cpu上的worker发送
  3. nginx的事件模块通过epoll_wait获取连接的句柄
  4. accept分配连接内存池
  5. ngx_http_init_connect 512设置回调方法epoll_ctl,并设置超时定时器client_header_timeout然后处理其他请求去了
  6. epoll_wait收到client端发送的Data,而在TCP层返回一个ACK包
  7. ngx_http_wait_request_handler分配内存读取缓冲区,并从连接内存池分配client_header_buffer_size 1k

开始接收URL

  1. 接收url需要分配请求内存值request_pool_size
  2. 状态机解析请求行,包括方法名,协议,URL等
  3. 如果内存不够,分配large_client_header_buffers: 4 8k(一次分配4次,每次8k)
  4. 状态机解析请求行
  5. 标识URL(使用指针的方式指向URL)

接收header

  1. 状态机解析header
  2. 标识header
  3. 移除超时定时器client_header_timeout
  4. 开始http处理流程

Nginx正则表达式

代码 说明
. 换行符以外任意字符
\w 字母或数字或下划线或汉子
\s 任意空白符
\d 任意数字
\b 任意开始或结束
^ 字符串开始
$ 字符串结束
* 重复0次或多次
+ 重复1次或多次
? 重复0次或1次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n次到m次

pcretest工具

如何找到请求的server

server_name匹配顺序

  1. 精准匹配
  2. *在前的泛解析
  3. *在后的泛解析
  4. 按文件顺序匹配正则表达式的域名
  5. 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_addrremote_addr,后续可以根据这个做连接的限制

realip需要单独启用这个模块,会生成realip_remote_addrrealip_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指令

  1. 匹配=
  2. 匹配^~
  3. 记住最长匹配的location
  4. 按照nginx的location出现顺序匹配上的执行
  5. 都未匹配使用最长匹配的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同上,是否返回port
  • absolute_redirect同上,是否返回host

index和autoindex模块

index先于autoindex

autoindex可以显示目录结构

  • autoindex_exact_size是否显示绝对路径
  • autoindex_format返回格式,默认是html,可以是json,xml,jsonp
  • autoindex_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反向代理流程

服务器发送请求

  1. 处理content的proxy_pass指令
  2. 检查cache是否开启和命中
  3. 如果未命中根据指令生成发往上游的http头部和包体
  4. 如果配置proxy_request_buffer则读取完整包体(上游可以及时的收取包体)进行发送,如果没有则建立连接实时发送

服务器接收请求

  1. 接收响应头
  2. 如果配置proxy_buffer则接收完整的包体(内网一般网速很快)
  3. 发送响应头部
  4. 如果打开cache则缓存包体
  5. 关闭或复用连接

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,默认为on
  • proxy_pass_request_bodys
  • proxy_set_bodys

接收用户的包体

接收用户的包体是缓存还是转发

proxy_request_buffering

  • on的时候适用于client端网速较慢,上游服务器并发处理能力较低,适用于高吞吐量的场景
  • off的时候响应及时,降低nginx读磁盘的效率,一旦发送失败proxy_next_upstream功能失败

包体的接收

client_body_buffer_sizeclient_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 header
  • proxy_buffering是否接收完全部上游返回的body再返回client
  • proxy_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_domainproxy_cookie_path等这些都应该是业务来做的,用nginx来做就太不好维护了

上游返回失败时的处理方式

前提是没有向client发送字节

使用proxy_next_proxy

对上游使用SSL连接

SSL相关的变量

用好浏览器缓存

  1. 浏览器发起请求的时候先判断是否有缓存
  2. 如果有缓存判断是否过期,没有过期直接读取(200 from cache)
  3. 如果过期判断是否有Etag,如果有会使用If-None-Match: 请求服务器,200则使用新的文件替换缓存,304则使用缓存
  4. 如果没有Last-Modified,如果有会使用If-Modified-Since请求服务器,200则使用新的文件替换缓存,304则使用缓存
  5. 如果以上都没有就直接发起请求,进行缓存协商,然后呈现

Nginx决策浏览器时间

Nginx缓存

proxy_cacheproxy_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重试的次数

流程是

  1. SYN分组收到的数据插入到tcp_max_syn_backlog的队列并发送回复
  2. ACK分组收到数据从tcp_max_syn_backlog的队列中取出放入ACCEPT队列
  3. Nginx从ACCEPT队列获取套接字

如果应用程序请求过慢,就会导致accept队列满

  • net.core.somaxconnACCEPT队列

建立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发送数据的方式

  1. 使用send方法
  2. send方法调用tcp_sendmsg将请数据从用户态复制到内核态(tcp发送队列)
  3. tcp发送队列如果不足就会阻塞等待有空间
  4. push会使用tcp的慢启动,nagle算法进行发送报文

TCP接收数据的方式

  1. 网卡接收到有序的报文插入receive队列,失序的报文插入out_of_order队列
  2. 遍历out_of_order队列将失序报文插入到receive队列
  3. nginx使用recv接收阻塞socket,调用tcp_recvmsg锁住socket获取最低接收阀值,处理从receive队列中已排序的报文
  4. 将复制队列中的数据包到用户态
  5. 检查是否超过最低阀值的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并发能力强
  • 减少内存碎片
  • 擅长管理小块内存
  1. 下载编译gpertools生成libtcmalloc.so.4
  2. --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数量

第三方模块源码阅读

提供阅读方法

  1. config文件
  2. ngx_module_t模块在启动和关闭的操作
  3. ngx_command_t定义的配置指令
  4. http模块,分析ngx_http_module_t在http{}解析前后实现的回调方法
  5. 根据3和4模块的生效方式

生效方式

  1. 11个阶段
  2. 过滤响应
  3. 负载均衡
  4. 提供新的变量

Nginx启动流程

  • init_module在master进程启动时调用
  • init_process在worker进程启动时调用
  • exit_process在worker进程退出时调用
  • exit_module在master进程退出时调用

进程核心描述ngx_cycle_t

  1. 根据命令行获取配置文件路径
  2. 如果处于升级中就监听环境变量传递的监听句柄
  3. 调用所有核心模块的create_conf方法生成存放配置项的结构体
  4. 针对所有核心模块解析nginx. conf
  5. 调用所有核心模块的init_conf方法
  6. 创建目录,打开文件,初始化共享内存
  7. 打开nginx模块中配置的监听端口
  8. 调用所有模块的init_module方法
  9. 启动master进程
  10. 启动worker进程
  11. worker进程调用init_process方法
  12. 启动cache_manager
  13. 启动cache_load
  14. 关闭父进程监听端口

HTTP的第三方模块初始化

if指令时邪恶的吗

if会替换配置

解读nginx的核心转储文件

通过debug日志定位问题

使用--with-debug编译

在event中添加debug_connection

  • 建立连接 例如ssl握手
  • 接收Header 请求行处理和请求头部解析
  • 11个阶段 寻找处理的location,反向代理构造上游请求,接收客户端请求包体
  • 构造响应头部
  • 发送响应 过滤模块

openresty概述

第三方模块主要是ngx_http_lua_modulengx_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)

以下都略