关于陶辉老师 极客时间里面的Nginx 100讲的汇中理解,梳理自己对Nginx 的理解 以及从中延伸出去的东西然后做一点自己的了解 ,从而深入的理解Nginx 吧。
简单讲下课程的 流程:
- 认识Nginx
- Nginx 的架构基础
- 详解 HTTP 模块
- 反向代理和负载均衡
- Nginx 的系统层性能的优化
- 从源码角度深入使用Nginx 与 OpenResty
Nginx 的一些主要的应用场景: 1. 静态资源服务: 给本地文件系统提供服务 2. 反向代理服务: Nginx 抢到的性能, 缓存 ,负载均衡 3. API 服务 : Nginx 直接进行处理一些简单的 数据库 应用服务 比如OpenResty lua 语言
三个 原因 : 互联网的快速增长,硬件的性能提升, Apache 的低能, 一开始的是 一个连接对应一个进程。
优点 : 高并发高性能, 可扩展性好,(模块化设计) , 高可靠性, 热部署(更换 Nginx sbin 二进制文件。就像 PHP-FPM )
组成部分:
- Nginx sbin 二进制执行文件
- Nginx.conf 配置文件
- access.log 访问日志 记录
- error.log 定位错误。
略。
Nginx plus 收费版 , Tengine 阿里的 版本, 免费的 OpenResty 和收费的OpenResty (lua, 应用Nginx Api 的开发)
# Apt 直接安装 会有很多的 模块没有默认, 所以自己编译 是最好的, 开启模块。
-rw-r--r-- 1 1001 1001 296K Mar 3 23:04 CHANGES # 特性 每个版本的 修改
-rw-r--r-- 1 1001 1001 452K Mar 3 23:04 CHANGES.ru
-rw-r--r-- 1 1001 1001 1.4K Mar 3 23:04 LICENSE # 授权
-rw-r--r-- 1 root root 376 Apr 10 20:09 Makefile
-rw-r--r-- 1 1001 1001 49 Mar 3 23:04 README # readme 文件
drwxr-xr-x 6 1001 1001 4.0K Apr 10 20:09 auto # 系统支持的特性。
drwxr-xr-x 2 1001 1001 4.0K Apr 10 20:09 conf # 默认的 .conf 配置文件
-rwxr-xr-x 1 1001 1001 2.5K Mar 3 23:04 configure # 编译的文件
drwxr-xr-x 4 1001 1001 4.0K Apr 10 20:09 contrib # conf cp -r contrib/vim/* ~/.vim/ 可以vim 配色 , 快捷键什么的。
drwxr-xr-x 2 1001 1001 4.0K Apr 10 20:09 html # 就是默认的启动页面
drwxr-xr-x 2 1001 1001 4.0K Apr 10 20:09 man # Nginx 的帮助目录
drwxr-xr-x 3 root root 4.0K Apr 10 20:12 objs # 执行了编译之后生成的 objs 目录 包含 一些 中间文件 ,比如模块的安装 c文件
drwxr-xr-x 9 1001 1001 4.0K Apr 10 20:09 src # 源码目录
指令以分号结尾 , 指令块用 {} 包含 , include 包含文件(耦合配置文件) , $ 表示变量。 ,location 的正则语法。
一些时间的 单位, 一些内存单位
四个模块 http http 模块, server 域名, upstream 上游服务反向代理, location url 路径
热部署的原理: 自己重新编译了一个sbin 文件之后 替换 以前 的sbin 的二进制 nginx 文件,然后给nginx 进程 发送一个 指令 。新起一个master 进程 然后 新的进程 再启动worker 进程。 然后 老的 Nginx worker 不在监听接口,,新的使用新的配置处理新的请求。 然后可以告诉 进程优雅的退出。 关闭子进程, 这时保留了 新老的master 进程 。新的worker 进程, 如果这是 新的Nginx 如果出了问题, 可以给老的master 进程 发送指令信号, 让老的worker 进程 重新启动 这样 保证回退 ,如果确定没问题之后,就可以发送给老的master 进程 退出。 这样 就是整个部署的流程。 里面涉及到的指令 有这些:
- cp nginx ngixn.old # 备份老的二进制文件
- kill -USR2 pid 告诉 进程 启动用替换后的 二进制文件启动 Nginx master ,(热部署)
- kill -WINCH pid 告诉老的 Nginx 优雅关闭老的worker 进程
- nginx -s reopen 日志切割 ,一般使用 logrotate 或者 自己写脚本( 麻烦
- location 里 alias xxx_static_file_dir/; # 可以用root 或者 alias
- gzip gzip_min_length gzip_type 一些gzip 压缩的 配置指令。
- 显示目录 ftp 服务器 使用 autoindex on 在 location 里 ,这样 就可以 分享静态资源文件目录下的所有了
- set $limit_rate 1k # 对特定资源进行 限速 1k/s
- log_format 的日志 格式配置、
用一台Nginx 将所有的服务器分发请求,这样 达到无感知扩容。
...
upsteam xxx {
server xxx.xxx.xxx.xx:xx;
server xxx.xx.xx.xxx:xx;
}
...
还有 将一些请求头里的东西, 传给上游处理的nginx 服务器。 如果上游服务器 对于很多 不常改版的。可以直接用缓存、 这样就不会给上游的Nginx 服务器下发请求, 直接返回了, 减少压力。 http 模块定义 proxy_cache_path /tmp/nginxcache levels=1:2 kesy_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off; server location 需要缓存的路径 使用 proxy_cache my_cache; proxy_cache_key $host$uri$is_args$args; proxy_cache_vaild 200 304 302 1d;
13 | 用GoAccess实现可视化并实时监控access日志
就是一个工具 可以实时监控log .
goaccess access.log -o report.html --real-time-html --time-format='%H:%M:%S' --date-format='%d/%b/%Y' --log-format=COMBINED
简单介绍些 SSL 协议, 其实就是先用非对称加密, 交换秘钥 然后使用对称加密 进行通信。 首先是 将自己的服务器的公钥 放到 公网上 然后由浏览器认证,其实 https 证书就是这个作用 然后 用户拿到证书(公钥)加密 请求 将(自己的公钥 也一并给,和内容) 和 服务器进行通信 会有 client hello 和 server hello 这几个 前置的操作 就是在协商 能用的对称算法 ,和自己支持的算法,然后我们对称加密使用的算法 确定好之后 ,就切换到 对称加密 进行通信 因为 非对称加密 性能影响 大 , 而对称加密 需要由 非对称加密 交换加密的盐和算法。 这样组合。效果好点,但是这个时候 前期的ssl 握手 也就是非对称加密的过程中会有性能损耗,我们服务器当时 api 项目是用的https 协议 ,当app 打开的时候 会调用 init 和onstart 接口, 一直发现会有1s 多 ,但是 nignx记录的 服务器的消耗 就几百ms 其中就是被握手 有几百 ms 。 有一个解决方案就是下次连接保留上次的握手协议(恢复会话) ,这个是有个配置、
ssl_session_cache :
Nginx配置ssl_session_cache:
有两个标准会话重用机制:session IDs (RFC 5246) 和 session tickets (RFC 5077),使用其中一个技术,一个客户端可以重用之前创建的会话,这个会话是之前和服务器进行握手成功的,这样可以减少一次来回过程。基于SessionID的会话重用适合现代所有浏览器,FireFox和Chrome甚至还支持 session tickets。
目前使用较多的配置是built-in和shared同时使用:
ssl_session_cache builtin:1000 shared:SSL:10m;
但是Nginx官方说只使用shared,性能会更高,配置方法为:
ssl_session_cache shared:SSL:10m;
ssl_session_ticket:
在会话ticket复用中,服务器不用为每个session保存状态,它用一个blob数据保存状态,然后将它发给客户端用来维护后来连接,会话ticket允许服务器将其存储状态委托给客户端,类似HTTP cookie一样。
一个会话ticket是一个加密的数据blob,其中包含需要重用的TLS连接信息,如会话key等,它一般是使用ticket key加密,因为ticket key服务器端也知道,在初始握手中服务器发送一个会话ticket到客户端,存储到客户端本地,当重用会话时,客户端发送会话ticket到服务器,服务器解密然后重用会话。
Session Ticket的安全考虑
会话ticket有潜在的安全问题,一些TLS加密组件如ECDHE-RSA-AES128-SHA256提供一个安全属性成为向前安全forward secrecy,如果黑客获得了服务器的证书私钥,他们也不能获得会话来破解。
使用TLS 会话ticket,偷窃了ticket key1后不会允许黑客来解密先前的会话,这是的ticket key非常有价值,为了保持向前安全forward secrecy, ticket key应该经常轮换。
会话ticket重用在Apache中可以用SSLTicketKeyDefault 配置,在nginx中使用ssl_session_tickets,它们都没有自动轮换ticket key的自动机制,只能通过重启apache nginx来重新加载或创建新的随机key
CA 颁布证书。
OCSP 响应程序 和 CRL 服务器
DV 域名证书(domain validated)
OV 组织证书 (organization validated )
扩展验证证书 (Extend validated) EV 证书
浏览器有根证书 的维护。
各自生成对称 key
不同的算法 加密 对于性能的损耗。
略。 自己就装过了, 什么时候写一篇。
[19 | 基于OpenResty用Lua语言实现简单服务
content_by_lua ' ngx.say(“User-Agent: ), ngx.req.get_headers()[“User-Agent”]) ‘;
添加lua 直接在location 里面写就行。
master-worker 的进程架构。 用非阻塞的时间驱动处理引擎 (传输层状态机, http 状态机, mail 状态机)根据不同的协议不同的状态机 (其实 就是 为了 使用好不同的事件 ,非阻塞的事件处理)
线程池 处理磁盘的阻塞调用(内存不够 会导致 阻塞 ,所以用线程池去 解决阻塞? 有点疑惑、一句带过 ,什么时候查查)
两种进程结构 : 单进程 和多进程的接口 ,一般默认多进程。
多进程 是为了 保证健壮性。 一个进程挂了 不会影响其他的进程, 而线程是同一个进程 会导致所有的都出问题。
- worker进程:处理 请求。
- master进程 管理work 进程 ,配置
- cache 进程 :通信 共享内存。
worker 进程 的 cpu 绑定, 更好的使用cpu 缓存 ,信号量通信。
reload 和 kill -SIGHUP 都是一样 重启 worker 进程
同上
- Master process : CHLD (监控worker) , TERM (stop) ,INT(启动 ), QUIT(quit) HUP(reload) USR1(reopen) (命令行) , USR2 (启动新的master进程,停止老的worker进程 接受请求 )WINCH(优雅的关闭子进程) (这俩只能用kill 处理热部署用到的)
- worker process: 接受同样的型号 CHLD (监控worker) , TERM (stop) ,INT(启动 ), QUIT(quit) HUP(reload) USR1(reopen) (命令行)
reload 会校验语法 ,不一定 要直接 -t
master 监听配置文件是否有新的端口 ,然后用新的配置 启动新的worker 接管所有的端口请求 ,然后向老的worker 子进程 发送QUIT 信号。;老worker 进程关闭监听局部,处理完当前连接后结束
在新的版本中有一个 配置参数 就是超时关闭worker 进程。 worker_shutdown_timeout
说道这儿 想起了 PHP-FPM 平滑重启 kill -USR2 cat /usr/local/php/var/run/php-fpm.pid
这个 就是会重新起所有的worker 进程。
服务器使用这个一直以为会对PHP操作没影响 ,后来发现 每次这样 都会有502 结果发现是 直接就将子进程的请求全部立刻中断了。
发现是 process_control_timeout 这个参数 是默认0 就是 超时时间是0 ,直接立马关闭, 设置成PHP-FPM的超时就好了。
1. 旧的Nginx 文件备份后替换成新编译的。
2. 向master 进程发送USR2 信号
3. master 进程修改pid 文件名, 加后缀 .oldbin
4. master 进程用新的Nginx文件启动新的master 进程
5. 向老的master 进程发送QUIT信号, 关闭老的master 进程 ( 新的master 进程之前的 父进程pid 还是 老的master pid 执行了QUIT 之后 ,新的master 进程的父进程id 就成1 了
6. 回滚: 向老master 进程 发送HUP 向新的master 进程发送QUIT
针对http 请求, (udp ,websocket 不行)
1. 设置定时器 worker_shutdown_timeout
2. 关闭监听句柄
3. 关闭空闲连接
4. 循环等待所有的连接关闭
5. 退出进程。
读事件: Accept 建立连接, Read 读消息。
写事件: Write 写消息
一次请求来到服务器 会产生很多时间 建立tcp 连接, tcp 连接之后的读数据,tcp 关闭的事件, tcp 写事件(就是responses), 磁盘的读写事件(缓存什么的)这些事件就会经过时间收集分发者(producer 生产者)然后有不同的 customer(消费者)去处理。
这样就成为了 事件驱动的 异步框架。(还需要深入了解
wireshark 抓包过程 略。
Nginx 的事件驱动模型,其实也并不是异步的啊,感觉 没有想象中的那种”异步吧“
循环,请求连接事件,如果有,连接之后 就扔给内核(kernel) 内核接着处理, 然后处理内核接收到的各种事件queue 处理的过程中产生新的事件,放置到queue 中, 处理完之后,接着循环处理是否有连接事件。
第三方模块的处理cpu 计算任务的小心。
从kernel 获取事件。 Epoll 和Kqueue 高性能, (select 和poll 是要遍历 队列, 消耗性能)
epoll 主要是 两个数据结构 去维护, 活跃连接 放到 一个link list里, 非活跃的 就是 红黑树 新增删除 修改都是log(n)
单进程单线程 同时处理多连接,用户态连接切换,减少OS进程切换的性能消耗。
陶辉老师针对阻塞非阻塞的看法:
-
操作系统 系统调用,会不会导致进程进入sleep 状态。 - 同步&异步 :写代码的方式。
- accept 调用阻塞 等待ACCEPT 队列
- accept 调用非阻塞 不用等待,直接返回 EAGAIN Error 程序处理。自己代码处理。 异步代码其实就是用函数任务 ,写好后按照指定去执行,然后自己代码 接着写,这样维护和理解上 会看代码不直观。 同步就是 底层接口是非阻塞的,直接将结果按正常的 面向过程的逻辑编写。
模块编译的一些事儿。
ngx_module_t 模块 细分 ngx_core_module_t, ngx_http_module_t , ngx_event_module_t , ngx_mail_conf_ctx_t
高内聚, 不同模块的抽象细分、
- NGX_CORE_MODULE : events ,http, mail ,stream, thread_pool, openssl, errlog, core.
Nginx 的模块目录分类: 在 src 的目录下。
ngx_cycle_t
worker_connection 512 ; // 反向代理消耗两个 指默认的大小 数组 ngx_cycle_t
ngx_connection_s 500 byte 内存大小消耗
连接使用的大致定义。
struct ngx_connection_s {
void *data;
ngx_event_t *read; //读事件
ngx_evnet_t *write;
ngx_socket_t fd;
ngx_recv_pt recv;
ngx_send_pt send; // 抽象解耦os 底层的方法
off_t send ; // bytes_send 变量 就是log 里面的 返回大小。
ngx_log_t *log;
ngx_pool_t *pool; // 出事connection_pool_size 的设置
int type;
struct sockaddr *sockaddr; //网络编程套接字
socklen_t socklen;
...
}
内存池减少内存碎片。
http 请求,大部分 请求量大 ,但是 内存请求小。
从内存池申请小块内存。
connection_pool_size 512; 这个是每次连接的每次预先分配 上下文
request_pool_size 4k ; 这个是每次请求的 url 存储。 所有信息。
两种通信方式: 信号和 共享内存。
锁:(自旋锁) 如果产生竞争,会一直去请求。不会等待。 Nginx 模块需要快速释放, 避免cpu 消耗。 Slab 内存管理器:
rbtree : 共享内存 ,ngx_stream_limit_conn_module, 单链表:
lua (OpenResty) lua_shared_dict me 10m;
lua_shared_dict me 10m;
location /set {
content_by_lua_block{
local me = ngx.shared.me
me:set("lua","hello");
ngx.say("Stored")
}
}
location /get {
content_by_lua_block {
local me = ngx.shared.me
ngx.say(me:get("lua"))
}
}
worker 之间有效的 通信 共享内存
Bestfit: 2 的倍数 消耗、 适合小对象, 避免碎片, 避免重复初始化。
所谓“最佳”是指每次为作业分配内存时,总是把能满足要求、又是最小的空闲分区分配给作业,避免“大材小用”。为了加速寻找,该算法要求将所有的空闲分区按其容量以从小到大的顺序形成一空闲分区链。这样,第一次找到的能满足要求的空闲区,必然是最佳的。孤立地看,最佳适应算法似乎是最佳的,然而在宏观上却不一定。因为每次分配后所切割下来的剩余部分总是最小的,这样,在存储器中会留下许多难以利用的小空闲区。
ngx_slab_stat: 监控Slab 的使用状态。 // Tengine 的模块。
下载 Tengine 的源码 ,将modules 目录里面的 ngx_slab_stat 移到自己 的源码中 去编译。
39 | 哈希表的max_size与bucket_size如何配置
Nginx 的容器:
- 数组
- 链表
- 队列
- 哈希表
- 红黑树
- 基数树 自平衡 ,排序二叉树 ,key 只能是 整型, 用途不多、
Nginx Hash 表 hash 表的 基本上初始化 就不变动了,很少插入删除。 一遍是用于变量
hash 表的 Bucket size 一般 向上对齐 ,CPU cache line 。 使用CPU缓存,所以尽量使用cache line 一次取数据 的 性能优化, 。 Max_size 最大的bucket使用。
红黑树:
是一个二叉查找树 ,自平衡的。 高度差 不会两倍。 增删改查 O(log(n)) 遍历 O(n)
了解这个结构 ,方便在模块中频繁调用一个数据结构的方法时候,性能问题的担忧。更好放心的使用 。
动态模块就是在编译源码的时候指定一些模块是否使用 动态编译,
编译 有两种库 :静态库 将所有的源码编译进了可执行文件里。
动态库 会有一个module shared Object 然后可执行文件 调用这个动态库的。
这样 方便以后编译 不需要全部去编译。需要修改的时候直接重新编译一个 动态库的 文件 直接替换就行了、
load_modules 指令 加载
http 模块的使用
main
http {
upsteam {}
split_clients{}
map{}
geo{}
server{
if(){...}
location {
limit_except{}
}
location {
location{}
}
}
server{}
}
基本的配置模块嵌套
指令的Context
官方文档中
Syntax log_format name [..]
Default: log_format combined "...";
Context http // 这个就是指 你的上下文Context 你能出现的。
指令合并
值指令 : root access_log, gzip
动作指令: rewrite , proxy_pass 不能合并。
值指令 向上覆盖。 子配置 不存在 使用父模块配置。 可以覆盖。
可以去模块源码中去 找到 模块的 合并规则,在ngx_module_t 中 都有定义。
listen unix:/var/run/nginx.sock; // 本机的套接字
listen 127.0.0.1:8000;
分三块
OS kernel | event module | http module
tcp 三次握手之后 --通过负载均衡 选中指定cpu 上的worker 处理, event_module epoll_wait 读取时间之后, accept 调用 非堵塞 ,然后分配连接池(connection_pool_size 512) 然后http 模块就 ngx_http_init_connection 色值超时毁掉 ,然后 epoll_ctl , client_header_timeout :60 s 红黑树中维护 定时器
然后有数据 data Ack event module 就会 epoll_wait 然后http module ngx_http_wait_request_handler 分配内存。 read 读缓冲区, client_header_buffer_size :1k;
接收uri: 分配请求内存池 request_pool_size: 4k; 状态机解析 请求函 \r\n 如果url 过大 ,分配大内存, large_client_header_buffers: 4 8k; // 最多32k
处理完之后 标识uri (变量指针)
接收header 状态机解析header 分配大内存。 移除 定时器 client_header_timeout:60 s 然后就开始接下来的11个阶段的http 请求处理。
这个 可以自己查看。 location 配置用到 (主要
server_name 指令: server_name_in_redirect off // default off on: 会使用主域名 返回location
server_name 正则取变量。
server {
server_name ~^(www\.)?(.+)$;
location / {root /site/$2;}
}
server {
server_name ~^(www\.)?(?<domain>.+)$;
location / { root /sites/$domain;} // 使用正则别名
Server 的匹配顺序 :
- 精准匹配
-
- 在前的泛域名
-
- 在后的泛域名
- 按文件中顺序匹配的表达式域名
- default server 默认域名 listen 指定的default.
http 一共有11 个阶段 :
- POST_READ:realip //
- SERVER_REWRITE: rewrite.
- FIND_CONFIG
- REWRITE
- POST_REWRITE
- PREACCESS : limit_conn, limit_req
- ACCESS: auth_basic,access ,auth_request
- POST_ACCESS
- PRECONTENT: try_files
- CONTENT: index , autoindex, concat
- LOG: access_log
依次处理.
module 有 : realip, limit_req, limit_conn , .,static,…. log ngx_module_name[] 这个会定义这所有的,
char *ngx_module_names[] = {
"ngx_core_module",
"ngx_errlog_module",
"ngx_conf_module",
"ngx_regex_module",
"ngx_events_module",
"ngx_event_core_module",
"ngx_epoll_module",
"ngx_http_module",
"ngx_http_core_module",
"ngx_http_log_module",
"ngx_http_upstream_module",
"ngx_http_static_module",
"ngx_http_autoindex_module",
"ngx_http_index_module",
"ngx_http_mirror_module",
"ngx_http_try_files_module",
"ngx_http_auth_basic_module",
"ngx_http_access_module",
"ngx_http_limit_conn_module",
"ngx_http_limit_req_module",
"ngx_http_geo_module",
"ngx_http_map_module",
"ngx_http_split_clients_module",
"ngx_http_referer_module",
"ngx_http_rewrite_module",
"ngx_http_proxy_module",
"ngx_http_fastcgi_module",
"ngx_http_uwsgi_module",
"ngx_http_scgi_module",
"ngx_http_memcached_module",
"ngx_http_empty_gif_module",
"ngx_http_browser_module",
"ngx_http_upstream_hash_module",
"ngx_http_upstream_ip_hash_module",
"ngx_http_upstream_least_conn_module",
"ngx_http_upstream_random_module",
"ngx_http_upstream_keepalive_module",
"ngx_http_upstream_zone_module",
"ngx_http_write_filter_module",
"ngx_http_header_filter_module",
"ngx_http_chunked_filter_module",
"ngx_http_range_header_filter_module",
"ngx_http_gzip_filter_module",
"ngx_http_postpone_filter_module",
"ngx_http_ssi_filter_module",
"ngx_http_charset_filter_module",
"ngx_http_userid_filter_module",
"ngx_http_headers_filter_module",
"ngx_http_copy_filter_module",
"ngx_http_range_body_filter_module",
"ngx_http_not_modified_filter_module",
NULL
};
顺序相反、
50 | postread阶段:获取真实客户端地址的realip模块
tcp 连接 四元组 :ip
X-Forwarder-For 传递IP // 几层 反向代理的ip list ,
X-Real-IP 用户 ip.
反向代理网络中有很多,
请求头都是很容易修改的。
基于变量使用 ip 本来一开始 的 binary_remote_addr 和 remote_addr 这种变量是直接tcp 连接的ip 经过realip 之后 就会更换。
realip_module 要自己去开启
服务器搞真实ip 获取。
51 | rewrite阶段的rewrite模块:return指令
return 指令 :
return code
return code url
return url;
301 : 永久重定向
302: 临时重定向 禁止缓存。
303: 临时重定向,允许改变方法,禁止被缓存。
307: 临时重定向,不允许改变方法,禁止被缓存
308: 永久重定向,不允许改变方法。
444:自定义的,关闭连接
error_apge
error_page 404 /404.html;
return 405;
location / {return 404 "not found!\n";}"
# 执行顺序?动作指令。
405 -> 404 -> 404(error_page)
52 | rewrite阶段的rewrite模块:重写URL
rewrite 模块: rewrite 指令
rewrite regex replacement [flag]
flag:
--last 用replacement url 和新的location 匹配
--break: break; 停止当前脚本指令执行,等同于独立的break指令
--redirect ;302重定向
--permanet: 301 重定向
rewrite_log 开启 rewrite 重定向的log 记录 在error log 文件中
if 指令:server, location 出现
逻辑判断功能。
54 | find_config阶段:找到处理请求的location指令块
location 指令:
merge_slashes 合并斜杠指令。
location 的匹配规则:
所有的前缀字符串location
匹配上=字符串
匹配^~字符串。
最长正则匹配。
55 | preaccess阶段:对连接做限制的limit_conn模块
并发连接数限制。
limit_conn zone number; // http,server,location
配置
56 | preaccess阶段:对请求做限制的limit_req模块
leaky bucket 算法。 将流量管控。感觉蛮有用
limit_req_zone key zone=name:size rate= rate; rate r/s or r/m;
limit_req zone=name [burst=number][nodelay];
limit_req 在limit_conn 之前 所以会优先限制返回、
控制请求是否可以 ip 限制
allow address;
deny address;
58 | access阶段:对用户名密码做限制的auth_basic模块
401 返回客户端,浏览器提醒输入明码,然后通过http发给服务器
auth_basic string | off;
简单的密码登录
59 | access阶段:使用第三方做权限控制的auth_request模块
统一用户权限验证。略过
、
satisfy 改变执行顺序。
61 | precontent阶段:按序访问资源的try_files模块
try_files: 多个文件 一个一个尝试返回, 以最后一个为结果返回。
就是做异常备份处理吧。
62 | 实时拷贝流量:precontent阶段的mirror模块
mirror copy 请求数据。
流量拷贝
root: 完整映射url 到文件路径中
alias: 只会讲location 后的url映射到文件路径
request_filename , document_root realpath_root
65 | static模块对url不以斜杠结尾却访问目录的做法
略过
index 模块领先 auto_index 模块
ngx_http_concat_module Tengine
一次请求 讲多个小文件 合并到一次http 响应中返回 提高性能。
是在URI后面加上 ?? 然后逗号分隔多个文件 ( 静态资源
concat :on
concat_type:
concat_unique
concat_delimiter
taobao 官网进程这么干 提高HTTP1.0的性能, 一次请求。
ngx_http_log_module
access_log path 可以用变量当path 不打开cache 每几率一条日志就需要打开关闭日志文件
日志缓存。 批量写入磁盘
open_log_file_cache :max =N [inactive=time][min_uses=N][valid=time];
max 缓存内的最大文件句柄数, 超出后用LRU 算法淘汰
inactive : 文件访问完后 这段时间内不关闭 default 10
min_uses: 在inactive 时间内使用次数超过min_uses 才会继续存在内存中 defaul 1
valid : 超出 valid 时间之后, 对缓存的日志检查是否存在 default 60s,
off 关闭
header 过滤模块 : image_filter gzip, 发送HTTP 头
body 过滤模块 image_filter gzip
过滤模块的顺序
"ngx_http_write_filter_module",
"ngx_http_header_filter_module",
"ngx_http_chunked_filter_module",
"ngx_http_range_header_filter_module",
"ngx_http_gzip_filter_module",
"ngx_http_postpone_filter_module",
"ngx_http_ssi_filter_module",
"ngx_http_charset_filter_module",
"ngx_http_userid_filter_module",
"ngx_http_headers_filter_module",
"ngx_http_copy_filter_module",
"ngx_http_range_body_filter_module",
"ngx_http_not_modified_filter_module",
从下往上
ngx_http_sub_filter_module 默认未编译进Nginx
功能 : 对于responses 中指定的字符串 进行替换。
sub_filter string replacement;
sub_filter_last_modified; 请求投中添加 last_modify 的请求头
sub_filter_once 替换一次
sub_filter_types 替换指定的文件类型
几个指令
71 | 用过滤模块在http响应的前后添加内容:addition模块
在响应前 和响应后 添加内容。 未编译进Nginx
add_before_body uri;
add_after_body uri
Nginx 变量 解耦
提供变量模块 使用变量模块
preconfiguration 读配置文件 添加新变量了, 变量名 和变量名的方法 就是 定义的是方法规则, 使用 提供变量的模块 定义的方法。
惰性求值,: 请求 到了 http 处理模块 才会去求值, 前面不会执行。
变量值 是变动的, 在一开始 和到取出来的 那一刻。 是变动的。
variables_hash_bucket_size # 存放变量的哈希表。
variables_hash_max_size
- HTTP 请求相关变量
arg_参数名 url, query_string .args 全部参数 , is_args 有参数,uri ... 很多 http 携带的。
request_body_file request_body request 这是
- TCP 连接变量 connect remote_addr ,remote_port connection_requests 当前连接执行的请求数 对keepalive 有意义。TCP_INFO tcp 内核参数。
- Nginx 处理请求产生的变量。 request_time ,server_name https, request_id ,document_root
同上
referer 就是 上一个url的地址。
refer而 模块 用invalid_referer 变量 referer 头部 是否合法, 用来ban 爬虫。
valid1_refereres :
none, block , server_name .
不同的 处理方式。
76 | 使用变量实现防盗链功能实践:secure_link模块
secure_link
ngx_http_secure_link_module 未编译。
secure_link 值为空 值为1, 值为0.
ngx_http_map_module
这个功能 有意思 :
基于已有的变量 使用 类似switch {case: ... default :... } 的语法创建出新的变量。为其他基于变量值实现的功能模块提供更多的可能性。
map string $variable {...}
map_hash_bucket_size size 32|64|128;
map_hash_max_size size;
map $http_host $name {
hostnames; # hostname 判断的变量
default 0;
~map\.tao\w+\.org.cm 1; # hostname 匹配这个url name 为1
}
78 | 通过变量指定少量用户实现AB测试:split_client模块
就是 nginx 进行 百分比用户 进行 ab 测试。灰度测试。 大部分 应该用服务器进行处理。
geo 根据 ip地址创建变量。
geo $country {
default ZZ;
#include conf/geo.conf;
proxy 116.62.160.193;
127.0.0.0/24 US;
127.0.0.01/32 RU;
}
geoip 获取ip ngx_http_geoip_module 编译进。 https://dev.maxmind.com/geoip/legacy/downloadable/
原理是生成几个变量如geoip_area_code,当一些模块使用到该变量(例如log模块),则调用取变量值的函数(见第72课),在函数中,会调用GeoIP库里的相关函数(例如GeoIP_country_code_by_ipnum),而这些函数的性能还不错,所以对Nginx性能影响不大
(但是 由于 那个网站不在提供 bat 的文件了, 所以 用新 的 ngx_http_geoip2_module)
简单步骤 :
# download libmaxminddb
wget https://github.com/maxmind/libmaxminddb/releases/download/1.4.2/libmaxminddb-1.4.2.tar.gz
tar -zxvf libmaxminddb-1.4.2.tar.gz
#安装了之后
#下载源码之后
git clone https://github.com/leev/ngx_http_geoip2_module.git
# 官网 支持 两种 编译模块的 方式 我选择动态的。
./configure --add-dynamic-module=../ngx_http_geoip2_module
make
make install
# 然后 安装的log 里会发现
#test ! -f '/usr/local/nginx/modules/ngx_http_geoip2_module.so' || mv '/usr/local/nginx/modules/ngx_http_geoip2_module.so' \
# '/usr/local/nginx/modules/ngx_http_geoip2_module.so.old'
#cp objs/ngx_http_geoip2_module.so '/usr/local/nginx/modules/ngx_http_geoip2_module.so'
#make[1]: Leaving directory '/root/nginx/nginx-1.17.9'
#将 ngx_http_geoip2_module.so 的目录地址 用 load_module 模块 加载到Nginx 配置文件中
load_module /usr/local/nginx/modules/ngx_http_geoip2_module.so;
# 然后在http 模块或者 stream 模块中使用这个就行了。
http {
#我 在log 日志文件中尝试了下。
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" $geoip2_data_country_name[$geoip2_data_country_code] - $geoip2_data_city_name';
...
geoip2 /root/nginx/GeoLite2-Country_20200407/GeoLite2-Country.mmdb {
auto_reload 5m;
$geoip2_metadata_country_build metadata build_epoch;
$geoip2_data_country_code default=US source=$remote_addr country iso_code;
$geoip2_data_country_name country names en;
}
geoip2 /root/nginx/GeoLite2-City_20200407/GeoLite2-City.mmdb {
$geoip2_data_city_name default=London city names en;
}
fastcgi_param COUNTRY_CODE $geoip2_data_country_code;
fastcgi_param COUNTRY_NAME $geoip2_data_country_name;
fastcgi_param CITY_NAME $geoip2_data_city_name;
...
}
然后从log 输出中看到了我们想到的数据 ,大功告成
xx.xxx.xxx.xx - - [14/Apr/2020:12:43:01 +0800] “GET /favicon.ico HTTP/1.1” 404 555 “http://xx.xx.xx.xxx:8800/” “Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36” “-” China[CN] - Beijing