1.进程架构
- master 管理多个woker
- worker 实际处理请求
- cache manager 管理多个work的共享缓存信息
- cache loader 加载到共享缓存里
ps -ef | grep nginx
kill -SIGHUP 9170 # 等价于 nginx -s reload 热更新配置
kill -SIGTERM 6982 # 关闭某个worker
2.信号 管理 父子进程
配置热更新
- 向master进程发送HUP信号 等价于 reload命令
- master进程校验配置语法是否正确
- master进程打开新的监听端口
- master进程用新配置启动新的worker子进程
- master进程向老worker子进程发送QUIT信号
- 老worker进程关闭监听句柄,处理完当前连接后结束进程
热升级nginx
- 将旧Nginx文件换成新Nginx文件 (注意备份)
- 向master进程发送 USR2 信号
- master进程修改pid文件名,加后缀oldbin
- master进程用新Nginx文件启动新master进程
- 向老master进程发送QUIT信号,关闭老master进程
- 回滚:向老master发送HUP,向新master发送QUIT
worker进程:优雅的关闭
- 设置定时器 worker shutdown timeout
- 关闭监听句柄
- 关闭空闲连接
- 在循环中等待全部连接关闭 (当超过指定时间就不在等待)
- 退出进程
3.架构原理
nginx处理流程
由于nginx使用epoll来实现多路复用io,也叫非阻塞事件驱动处理引擎。
所以每次外部流量进入要区分每次请求的数据是具体是给谁操作。需要引入状态机的概念区分模块。
外部流量 > 状态机 > 分发处理的地方
web > http状态机 >
email > email状态机 >
tcp > 传输层状态机 >
状态机再根据配置,转发给不同的处理
- 静态资源
- 反向代理
- api服务(fastCGI memroycahe等)
网络收发与Nginx事件间的对应关系
- 应用层 -> 传输层 -> 网络层 -> 链路层 -> 物理层
- 物理层 使用 以太网 和广域网 传输数据
- 应用层 <- 传输层 <- 网络层 <- 链路层 <- 物理层
TCP/IP协议层级
- 应用层 :只保存基础的数据 data (http,DNS,SSH)
- 传输层TCP : 源端口+目的端口 + data (TCP UDP)
- 网络层IP :源ip+目的ip + 源端口+目的端口 + data (IP,ARP,DHCP)
- 数据链路层 :源mac+目的mac + 源ip+目的ip + 源端口+目的端口 + data (ADSL,wifi)
Reactor事件驱动模型
Nginx的Worker进程首先会调用Nginx的Core核心模块。在Reactor模型中会维护一个运行循环(Run-Loop),主要包括事件收集、事件分发、事件处理,这个工作在Nginx中由Core核心模块负责。
通过统一接收事件,并根据请求的类型转发给不用的消费者处理
- 事件:如:必须是完整的tcp 3次握手,即客户端syn,服务器syn+ack,客户端ack 后才算一个事件。 Nginx对高并发IO的处理使用了Reactor事件驱动模型,实现了多路IO复用
- 事件收集器:负责收集Worker进程的各种I/O请求。
- 事件发送器:负责将I/O事件发送到事件处理器。
- 事件处理器:负责各种事件的响应工作。
事件分类
可以使用 Wireshark 进行抓包
读事件
- Accept建立连接
- Read读消息
写事件
Write写消息,
TCP协议与非阻塞接口
-
阻塞操作:是指在执行操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足。
-
非阻塞操作:在不能进行操作时, 并不挂起,它要么放弃,要么不停地查询,直至可以进行操作为止。
-
等待队列: 阻塞访问最大的好处是当设备不可操作时进程可以挂起进入睡眠,让出CPU。 linux中可以使用等待队列(Wait Queue) 来实现阻塞进程的唤醒。等待队列作为Linux内核中的一个基本单位,以队列为数据结构,与调度机制紧密结合,可以用来同步对系统资源的访问。
4.nginx事件循环流程
- 客户端发起请求
- tcp 3次握手结束后,把消息内容加入等待队列
- 同时通知并唤醒nginx
- nginx的master进程通过Reactor事件系统,去循环读取等待队列
- 这时候把内核态的数据复制到用户态
- 读取一个就转发给work进程处理
- 处理的时候可能产生新的消息内容,直接也插到等待队列
- 等待队列的内容都处理完后,nginx又恢复到 休眠模式
5.epoll
网络IO
网络IO 一般选用 多路复用IO
linux有3种,nginx 选择的epoll
- select
- poll
- epoll
问题
传统的等待队列是数组,连接数有时候可能高达数百万的连接,每次遍历效率很低。
解决方案
nginx 选用epoll的数据结构存储活跃的连接数。因为在高并发连接中,每次处理的活跃连接数量占比很小。
每次新增和删除都只是链表上操作,查询和删除都非常的快。
实现:
红黑树+ 链表
实际结构
eventpoll对象
- lock
- mtx
- wq
- poll_wait
- rdllist epitem对象的集合,红黑树中每个结点都是某于epitem结构中的rdllink成员,
- rbr 对应 单个 epitem对象
- ovflist
- user
epitem对象 红黑树中每个结点都是基于epitem结构中的rbn成员
- rbn
- rdllink
- next
- ffdnwait
- pwqlisted
- fllinktevent
使用
- 创建
- 操作: 添加/修改/删除
- 获取句柄
- 关闭
6.nginx中的请求切换
传统容器处理线程方案
- 每一线程仅处理一连接。
- 当操作系统发现阻塞时,会自动切换到另外一个进程去执行。等其他执行完又再次切回来。
- 依赖操作系统的进程调度实现并发,每次切换成本很高。
nginx容器处理线程方案
- 一条线程同时处理多连接
- 用户态代码完成连接切换,即直接用单个线程通过代码判断,实现处理多个连接的处理。
- 尽量减少操作系统进程切换
7.阻塞&非阻塞 同步&异步之间的区别
- 阻塞调用:如 调用accept建立连接时,会等待队列插入accept信息,等队列有了信息后才继续执行,如果过程中有阻塞,会进入等待,会触发操作系统的进程切换。导致延迟。利用的还是操作系统自动切换。
- 非阻塞:通过代码可以知道当前是否队列有空或不为空,无需等待,直接返回对应的状态码,省去了系统的进程切换。
非阻塞调用下的同步与异步
核心是解决地狱回调问题。
异步代码:下面代码的逻辑被拆分了
rc = ngx_http_read_request_body ( r, ngx_http_upstream_init) ;
if( rc > = NGX_HTTP_SPECIAL_RESPONSE){
return rc ;
}
# 这里定义了 ngx_http_read_request_body 方法
ngx_int_t
ngx_http_read_request_body (ngx_http_request_t * r ,ngx_http_client_body_handler pt post_handler)
# 这是 回调方法
ngx_http_upstream_init(ngx_http_request_t * r){
}
同步代码:符合人的阅读习惯,之上而下
local client = redis:new()
client:set timeout(30000)
local ok,err = client:connect(ip,port)
if not ok then
ngx.say("failed: ",err)
return
end
8.nginx模块原理与使用
流程
- 前提编译进Nginx
- 提供哪些配置项
- 模块何被使用
- 提供哪些变量
# 主模块
ngx_module_t
# 包含子模块
commands :ngx_command_t
ctx: 待每个模块将具体化上下文
# ctx 上下文
ngx_core_module_t # 核心
ngx_http_module_t # http
ngx_event_module_t # event
ngx_mail_conf_ctx_t # mail
# ngx_core_module_t 核心
core
errlog
thread_pool
openssl
events
http
stream
# ngx_http_module_t
upstream模块
响应过滤模块
请求处理模块
ngx_http_core_module模块
# ngx_event_module_t # event
event_core
epoll
源码分析
cd /src/ # 源码位置
core
event
http
mail
misc
os
stream
# http 模块
cd /src/core/http
9.连接池
构成包含:上游服务器的连接和下游的客户端的连接
struct ngx_cycle_t {
# 包含下面打个主要属性
free_connections
connections # 预分配的cannection n个连接
read_events # 预分配的connection n个读事件
write_events # 预分配的connection n个写事件
}
一般长度一样,可以实现下标统一检索数据。
connections指向的连接池中,每个连接所需要的读写事件都以相同的数组序号对应着read_event, write_events读写事件数组,所以相同序号下这三个数组中的元素是配合使用的
核心数据结构
struct ngx_connection_s {
ngx_event_t *read; # 读事件
ngx_event_t *write; # 写事件
ngx_pool_t *pool; # <= 初始 connection pool size配置
}
# 读写事件结构
struct ngx_event_t {
void *data;
}
限制长度
events {
worker_connections 1024;
}
内存池对性能的影响
- 连接内存池
- 请求内存池
ngx_pool_s # 内存池
ngx_pool_data_t # 数据块
ngx_pool_large_s # 大块内存
ngx_pool_cleanup_s # 需要清理的外部资源
# 配置限制参数
request_pool_size size;
connection_pool_size size:
worker共享内存
# 基础同步
1. 信号
2. 共享内存
# 高级通讯
1. 锁
2. Slab内存管理器
官方nginx模块使用了共享内存
- ngx_http_lua_api
http {
lua_shared_dict dogs 10m; # 定义共享变量
server {
location /set {
content_by_lua_block {
local dogs = ngx.shared.dogs
dogs:set("Jim",8)
ngx.say("STORED")
}
}
location /get {
content_by_lua_block {
local dogs = ngx.shared.dogs
ngx.say(dogs:get("Jim"))
}
}
}
}
- rbtree
- ngx_stream_limit_conn_module
- ngx_http_limit_conn_module
- ngx_stream_limit_req_module
- http/cache : ngx_http_file_cache , ngx_http_proxy_module
- ssl :ngx_http_ssl_module
- 单链表
- ngx_http_upstream_zone_module
- ngx_stream_upstream_zone_module
10.Slab内存管理器
- ngx_slab_pool_t* pool 内存池 (包含多个页面)
- ngx_slab_page_t 页面 (包含多个slot块)页面之间通过链表关联
- slot 块
概念
- 将内存按照页进行分配,每页的大小相同, 此处设为page_size。
- 将内存块按照2的整数次幂进行划分, 最小为8bit, 最大为page_size/2。
- 例如,假设每页大小为4Kb, 则将内存分为8, 16, 32, 64, 128, 256, 512, 1024, 2048共9种
- 每种对应一个slot, 此时slots数组的大小n即为9
bestfit 最佳适应算法
选择最优的算法分配slot给页面
- 最多两倍内存消耗
- 适合小对象
- 避免碎片化
- 避免重复初始化
配置 ngx_slab_stat 查看状态
# 安装模块
modules/ngx_slab_stat
# 下载 tengine 版本
https://tengine.taobao.org/download.html
tengine 自带 ngx_slab_stat
# 编译运行
# 在当前的 openretry下 使用 tengine 的库,也可以复制过去
./configure --add-module=../tengine-2.3.3/modules/ngx_slab_stat/
# 配置 查询slab_stat 列表
http {
lua_shared_dict dogs 10m; # 定义共享变量
server {
server {
listen 8080;
server_name localhost;
location = /slab_stat {
slab_stat;
}
location /set {
content_by_lua_block {
local dogs = ngx.shared.dogs
dogs:set("Jim",8)
ngx.say("STORED")
}
}
location /get {
content_by_lua_block {
local dogs = ngx.shared.dogs
ngx.say(dogs:get("Jim"))
}
}
}
}
哈希表的max_size与 bucket_size如何配置
nginx 主要高级数据结构 哈希表 ngx_hash_t
- buckets
- size
- ngx_hash_find()
每个哈希的节点结构 ngx_hash_elt_t
- value
- len
- name
哈希表配置
对齐问题 Bucket size 和 Max size 保存一致长度
红黑树
红黑树 可以解决 节点一遍倒的问题 ,让节点均匀分布
是一种:自平衡二叉查找树
- 高度不会超过2倍log(n)
- 增删改查算法复杂度O(log(n))
- 遍历复杂度O(n)
ngx_rbtree_t 结构体
- root # 根节点
- sentinel # 哨兵节点
- insert
- ngx_rbtree_init()
- ngx_rbtree_insert()
- ngx_rbtree_delete()
红黑树 使用的模块
- ngx_conf_module config_dump_rbtree
- ngx_event_timer_rbtree
- ngx_http_file_cache
- ngx_http_geo_module
- ngx_http_limit_conn_module
- ngx_http_limit_req_module
- ngx_http_lua_shdict:ngx.shared.DICT LRU链表性质
- ngx_resolver_tresolver
- ngx_stream_geo_module
- ngx_stream_limit_conn_module
11.nginx动态模块
有点像window的dll, 编译是指记录链接的指针, 调整新的库,只需要替换库即可,无需重新编译。
创建流程
- Configure加入动态模块
- 编译进binary
- 启动时初始化模块数组
- 读取load module配置
- 打开动态库并加入模块数组
- 基于模块数组开始初始化
压缩图片例子
# 方法1 直接编译
# 编译安装
./configure --with-http_image_filter_module
# conf 配置 设置图片裁剪
location /{
root /html
image_filter resize 100 100; # 设置图片为 100*100
}
# 方法2 编译 生成动态库
./configure --with-http_image_filter_module=dynamic
# 在objs/会生成 ngx_http_image_filter_module.so
cp objs/ngx_http_image_filter_module.so /usr/local/nginx/modules #复制so文件 到 nginx安装目录里
# conf 配置
# 顶部添加
load_module modules/ngx_http_image_filter_module.so;
# 对应server 正常使用 image_filter
location /{
root /html
image_filter resize 100 100; # 设置图片为 100*100
}