1、nginx原理
1.1 nginx介绍
nginx是C语言编写的开源高性能HTTP和反向代理的Web服务器,具有大并发,扩展性强,热部署等有点。支持SSL、TLS、SNI,可针对静态资源高速高并发访问,可使用反向代理加速,并且可进行数据缓存;具有模块化的架构:过滤器包括 gzip 压缩、ranges 支持、chunked 响应、XSLT、SSI 及图像缩放等功能
1.2 nginx为什么性能这么高
为了榨干多核CPU的价值,Nginx无所不用其极:通过绑定CPU提升二级缓存的命中率,通过静态优先级扩大时间片,通过多种手段均衡Worker进程之间的负载,在独立线程池中隔离阻塞的IO操作,等等。
1、配置worker进程数等于CPU核数,并进行绑核,1个CPU对应1个worker进程,这样就没有多线程的切换开销
2、nginx采用epoll处理模型IO多路复用,避免循环检测网络是否可读的消耗,而且使用的是异步非阻塞模型
3、使用线程池的方式回避磁盘的阻塞读操作,通过一个任务队列以生产者/消费者模型与主线程交换数据
1.3 nginx如何处理一个http请求
首先,nginx在启动时,会解析配置文件,得到需要监听的端口与ip地址,然后在nginx的master进程里面,先初始化好这个监控的socket(创建socket,设置addrreuse等选项,绑定到指定的ip地址端口,再listen)。然后再fork出多个子进程出来,然后子进程会竞争accept新的连接。此时,客户端就可以向nginx发起连接了。当客户端与服务端通过三次握手建立好一个连接后,nginx的某一个子进程会accept成功,得到这个建立好的连接的socket,然后创建nginx对连接的封装,即ngx_connection_t结构体。接着,设置读写事件处理函数并添加读写事件来与客户端进行数据的交换。最后,nginx或客户端来主动关掉连接,到此,一个连接就结束了。
1.4 nginx框架
Nginx 采用的是 master-worker 模型,一个 master 进程管理多个 worker 进程,基本的事件处理都是放在 woker 中。master 负责一些全局初始化,配置文件加载解析,创建子进程,外部信号处理,以及对 worker 的管理;worker负责处理http请求,负责接收和发送数据以及业务逻辑处理。
在OpenResty中,每个 woker 使用一个 LuaVM,当请求被分配到 woker 时,将在这个 LuaVM 里创建一个 coroutine(协程),协程之间数据隔离。
2、nginx配置示例
2.1 全局块
user www www; # 定义Nginx的用户和用户组
worker_processes 4; # worker 数
worker_cpu_affinity 0001 0010 0100 1000; # worker进程绑核
error_log /var/log/error_log info # 定义全局错误日志类型,默认是error
events {
worker_connections 1024; # 最大连接数
}
2.2 http块
http块是全局参数,对整体产生影响,与客户端的配置主要在 http 块中
http {
include mime.types; # 指定包含的其他扩展配置文件
default_type application/octet-stream;
log_format promtail_json '{"@timestamp":"$time_iso8601",'
'"@version":"openresty-1.13.6.2",'
'"server_addr":"$server_addr",'
'"remote_addr":"$remote_addr",'
'"host":"$host",'
'"uri":"$uri",'
'"body_bytes_sent":$body_bytes_sent,'
'"bytes_sent":$body_bytes_sent,'
'"request":"$request",'
'"request_length":$request_length,'
'"request_time":$request_time,'
'"status":"$status",'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent"'
'}';
access_log logs/jsaccess.log promtail_json; # 指定接口访问日志格式
# 性能优化
sendfile on;
tcp_nopush on;
gzip on;
keepalive_timeout 60;
lua_package_path "/home/jiang/resty/bin/nginx/conf/luaSrc/?.lua;/home/jiang/resty/bin/lualib/?.lua";
lua_package_cpath "/home/jiang/resty/bin/lualib/?.so";
lua_code_cache off; # 关闭代码缓存,方便调试,性能测试一定记得关闭 !!!!!!
# proxy_cache_path /data/nginx/cache keys_zone=one:10m;
lua_regex_cache_max_entries 255; # 最大正则缓存条数
location / { # 路由,首页
root html;
index index.html index.htm;
proxy_set_header X-Real-IP $remote_addr # 获取客户端真实ip
proxy_set_header X-Forwarded-For $remote_addr # 防止客户端 IP 伪造
}
}
2.3 server块 & location块
upstream servers { # upstream块,servers代表upstream名字,下面会用到
# max_fails为请求最大失败次数,fail_timeout为请求失败超时时间,weight代表权重
server 192.168.1.11:8081 max_fails=5 fail_timeout=10s weight=3; # 权重越高请求比例越大
server 192.168.1.12:8081 max_fails=5 fail_timeout=10s weight=2;
}
server { # server块是虚拟主机
listen 80; # http监听80端口
server_name test.com baidu.com; # 如果请求中 Host 头和 server_name 匹配则将请求指向对应的server块
location / { # location块在server块下,根据请求 URI 进行配置(URI 是去掉参数后的 URL)
deny 192.168.1.1; # 禁止该ip访问
allow 192.168.1.2; # 允许该ip访问
deny all; # 除了allow的IP以外,其他IP都禁止访问
proxy_pass http://servers; # 通过代理将请求发送给upstream命名的HTTP服务,名字必须和upstream名一致
}
}
3、nginx请求处理阶段
nginx把请求处理划分成了11个阶段
typedef enum {
NGX_HTTP_POST_READ_PHASE = 0, // 接收到完整的HTTP头部后处理的阶段
NGX_HTTP_SERVER_REWRITE_PHASE, // URI与location匹配前,修改URI的阶段,用于重定向
NGX_HTTP_FIND_CONFIG_PHASE, // 根据URI寻找匹配的location块配置项
NGX_HTTP_REWRITE_PHASE, // 上一阶段找到location块后再修改URI
NGX_HTTP_POST_REWRITE_PHASE, // 防止重写URL后导致的死循环
NGX_HTTP_PREACCESS_PHASE, // 下一阶段之前的准备
NGX_HTTP_ACCESS_PHASE, // 让HTTP模块判断是否允许这个请求进入Nginx服务器
NGX_HTTP_POST_ACCESS_PHASE, // 向用户发送拒绝服务的错误码,用来响应上一阶段的拒绝
NGX_HTTP_TRY_FILES_PHASE, // 为访问静态文件资源而设置
NGX_HTTP_CONTENT_PHASE, // 处理HTTP请求内容的阶段,大部分HTTP模块介入这个阶段
NGX_HTTP_LOG_PHASE // 处理完请求后的日志记录阶段
} ngx_http_phases;
4、OpenResty 执行阶段总结
- 注:以下所有指令均在 nginx.conf 文件里编写
阶段 | 概述 |
---|---|
init_by_lua_* | Nginx 加载配置文件时执行 |
init_worker_by_lua_* | 每个 worker 进程被创建时执行 |
set_by_lua_* | 初始化Nginx变量 |
rewrite_by_lua* | 转发、URL重定向、缓存、子请求、控制头等功能 |
access_by_lua* | IP 准入、接口权限等情况集中处理(例如完成简单防火墙) |
content_by_lua* | 内容生成,执行业务逻辑并产生相应 |
header_filter_by_lua* | 响应头部过滤处理(例如添加头部信息) |
body_filter_by_lua* | 响应体过滤处理(例如完成应答内容统一成大写) |
log_by_lua* | 会话完成后本地异步完成日志记录(日志可以记录在本地,还可以同步到其他机器) |
1、init_by_lua_*
执行阶段:loading-config
当 Nginx 的 master 进程加载 Nginx 配置文件会执行指定的代码,每当 Nginx 获得 Hangup 重载信号加载进程时代码都会被重新执行。该阶段一般会初始化lua全局变量、预加载模块、初始化 lua_shared_dict 共享内存数据
可以使用的 lua API 指令:ngx.log、ngx.shared.DICT、print
2、init_worker_by_lua_*
执行阶段:starting-worker
当 master 进程被启动后,每个 worker 进程被创建时都会执行这段 lua 代码,该阶段最常见的功能是执行定时任务,也可以实现后端健康检查功能,用于检测后端HTTP服务器是否正常。
3、set_by_lua_*
执行阶段:rewrite
这里主要用于设置一些nginx变量。rewrite 阶段是 location 请求地址重写阶段,位于location中的 ngx_rewrite 指令就是在这个阶段运行,set_by_lua_* 指令块在 Nginx 中是阻塞操作,不支持非阻塞I/O,尽量在这个阶段执行短少代码。
禁用的 lua API :
输出类型的API函数(如ngx.say和ngx.send_headers)。
控制类型的API函数(如ngx.exit)。
子请求的API函数(如ngx.location.capture和ngx.location.capture_multi)。
Cosocket API函数(如ngx.socket.tcp和ngx.req.socket)。
休眠API函数ngx.sleep。
4、rewrite_by_lua_* 、access_by_lua_* 和 content_by_lua_*
执行阶段分别是:rewrite tail、access tail、content
以上指令分别在 rewrite tail / access tail / content 阶段对每个请求都会执行设置的 lua 代码,这些 lua 代码可以调用所有的 lua API ,并运行在独立的 lua 虚拟机上,以新的协程来执行。
rewrite_by_lua_* :可以实现对请求 URL 进行重定向、读取 MySQL 或 Redis 数据、发送子请求、控制请求头等。
access_by_lua_* :处理一些请求,一般用来进行权限检查和黑白名单配置,完成简单的防火墙。
content_by_lua_* :执行业务逻辑并产生相应,指令不可以和其他内容处理阶段的模块如echo、return、proxy_pass等放在一起。
5、balancer_by_lua_*
执行阶段:content
在upstream的配置中执行并控制后端服务器的地址,它会忽视原upstream中默认的配置。Lua代码的执行环境不支持yield,因此需禁用可能会产生yield的Lua API指令。但可以利用ngx.ctx创建一个拥有上下文的变量,在本阶段前面的某个执行阶段(如rewrite_by_lua*阶段)将数据生成后传入upstream中。
6、header_filter_by_lua_* 和 body_filter_by_lua_*
执行阶段分别是:output-header-filter、output-body-filter
header_filter_by_lua_* :对每个请求执行这段 lua 代码,以此对响应头进行过滤。常用于对响应头进行添加、删除等操作。用于设置输出body_filter_by_lua_* :响应体的过滤器。在此阶段可以修改响应体的内容,如修改字母的大小写、替换字符串等。
禁用的 lua API :
输出类型的API函数(如ngx.say和ngx.send_headers)。
控制类型的API函数(如ngx.exit)。
子请求的API函数(如ngx.location.capture和ngx.location.capture_multi)。
Cosocket API函数(如ngx.socket.tcp和ngx.req.socket)。
7、log_by_lua_*
执行阶段:log
在日志请求处理阶段执行的{ lua-script }代码。它不会替换当前access请求的日志,而会运行在access的前面,适合用来对日志进行定制化处理,且可以实现日志的集群化维护
禁用的 lua API :
输出类型的API函数(如ngx.say和ngx.send_headers)。
控制类型的API函数(如ngx.exit)。
子请求的API函数(如ngx.location.capture和ngx.location.capture_multi)。
Cosocket API函数(如ngx.socket.tcp和ngx.req.socket)。
休眠API函数ngx.sleep。
5、ngx_lua常用库函数
。。。
6、常见问题
5.1 nginx多个worker进程如何监听同一个端口?如何处理客户端连接惊群问题
master进程初始化时,解析配置文件,listen监听对应的端口,然后fork子进程,子进程继承master的fd,保证多个进程同时监听在同一个端口。
惊群:就是一个客户端连接,多个worker进程同时accept抢占处理,但只有一个成功其余都失败,导致其他worker进程做了无效的操作。解决方案:nginx保证同一时刻只有一个进程进入epoll_wait状态(也就是说只有一个进程可以检测到fd可读),当有连接进来时,该进程负责处理请求,下一个进程进入epoll_wait状态。
6.2 3、nginx的模块化体系结构
nginx的内部结构是由核心部分和一系列的功能模块所组成,可以分为以下几种类型:
- event module
搭建了独立于操作系统的事件处理机制的框架,及提供了各具体事件的处理。
handler模块:主要负责处理客户端请求并产生待响应内容,负责客户端的静态页面请求处理并将对应的磁盘文件准备为响应内容输出。 - filter模块
主要是负责对输出的内容进行处理,可以对输出进行修改。例如,可以实现对输出的所有html页面增加预定义的footbar一类的工作,或者对输出的图片的URL进行替换之类的工作。 - upstream模块
实现反向代理的功能,将真正的请求转发到后端服务器上,并从后端服务器上读取响应,发回客户端。upstream模块是一种特殊的handler,只不过响应内容不是真正由自己产生的,而是从后端服务器上读取的。
load-balancer: 负载均衡模块,实现特定的算法,在众多的后端服务器中,选择一个服务器出来作为某个请求的转发服务器。
upstream:转发,浏览器到nginx和nginx到服务器,反向代理就是基于upstream模块
filter:服务器返回和响应是过滤器,服务器返回到nginx和nginx返回到浏览器
handler:浏览器到nginx和nginx直接到浏览器是handler模块