后浪笔记一零二四

1. 为什么用Nginx:(Requests/second, RPS)

  1. 高并发,高性能 以RPS为y轴,以并发连接数为x轴, 大部分的web服务器,随着并发数的提升,它的性能会急剧的下降。 而nginx可以同时具备高并发和高性能,随着并发数的提升,它的RPS可以稳定在60%。
  2. 可扩展性好 主要体现在Nginx的模块化设计,模块化设计非常稳定,使得nginx第三方模块生态圈非常丰富,甚至于有openresty这样的第三方插件,在它的基础之上又生成了一套新的生态圈,丰富的生态圈为nginx丰富的功能提供了保证
  3. 高可靠性 就是nginx可以在服务器上不间断地运行数年,而很多的web服务器,往往几周几个月就需要一次重启,对于nginx这样的高并发高性能的web服务器,它往往运行在企业内网的边缘节点上,这个时候企业如果想提供4个9,5个9甚至更高的高可靠性时,对于nginx持续运行能够宕机的时间,一年可能要按秒来计。
  4. 热部署 指可以在不停止服务的情况下升级nginx,这个功能对nginx来说非常重要,因为在nginx上可能跑了数百万的并发连接,如果是普通的服务,我们可能只需要kill进程再重启的方式就可以处理好,但是对于nginx而言,kill掉nginx进程会导致操作系统为所有已经建立连接的客户端发送tcp中的reset异常终止报文,而很多客户端是没有办法很好处理这个报文的,而在大并发场景下,一些偶然事件就会导致必然的恶性结果,所以热部署是非常有必要的。
  5. BSD许可证 nginx是免费的开源的,而且在有定制需求的场景下,去修改nginx的源代码,并将其应用到我们的商业场景下,这是合法的。

2. Nginx的四大主要组成部分:

  1. Nginx二进制可执行文件:由各模块源码编译出的一个文件
  2. access.log访问日志:记录每一条http请求信息
  3. error.log错误日志:定位问题
  4. Nginx.conf配置文件:控制nginx的行为

3. 如何查看Nginx新版本的特性

nginx每发布一个版本会有三个特性: feature(表示新增的功能), bugfix(表示修复的bug), change(表示所做的重构)

版本分为Mainline主干版本,和stable稳定版本。

nginx.org/en/download.html:

                      nginx: download

                     Mainline version: 
CHANGES  nginx-1.15.5  pgp   nginx/Windows-1.15.5 pgp

                     Stable version: 
CHANGES-1.14  nginx-1.14.0  pgp   nginx/Windows-1.14.0 pgp

                     Lagacy versions: 
                   只会列出历史稳定版本
                          ...

CHANGES文件内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Changes with nginx 1.15.4
  *) Feature: now the "ssl_early_data" directive can be used with OpenSSL.
  *) Bugfix: in the ngx_http_uwsgi_module. 
     Thanks to Chris Caputo.
  *) Bugfix: connections with some gRPC backends might not be cached when using the "keepalive" directive.
  *) Bugfix: a socket leak might occur when using the "error_page" directive to redirect early request processing errors, notably errors with code 400.
  *) Bugfix: the "return" directive did not change the response code when returning errors if the request was redirected by the "error_page" directive.
  *) Bugfix: standard error pages and response of the ngx_http_autoindex_module module and the "bgcolor" attribute, and might be displayed incorrectly when using custom color settings in browsers.
  Thanks to Nova DasSarma.
  *) Change: the logging level of the "no suitable key share" and "no suitable signature algorithm" SSL errors has been lowered from "crit" to "info"

4. 编译出适合自己的Nginx

4.1 nginx源码目录介绍:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 下载Nginx
$ wget https://nginx.org/download/nginx-1.26.1.tar.gz

# 查看源码目录
$ tar -xzf nginx-1.26.1.tar.gz
$ cd nginx-1.26.1
$ ls -l
total 848K
drwxr-xr-x 6 502 games 4.0K Jun 15 19:48 auto
-rw-r--r-- 1 502 games 320K May 29 22:30 CHANGES
-rw-r--r-- 1 502 games 490K May 29 22:30 CHANGES.ru
drwxr-xr-x 2 502 games 4.0K Jun 15 19:48 conf
-rwxr-xr-x 1 502 games 2.6K May 28 21:28 configure
drwxr-xr-x 4 502 games 4.0K Jun 15 19:48 contrib
drwxr-xr-x 2 502 games 4.0K Jun 15 19:48 html
-rw-r--r-- 1 502 games 1.4K May 28 21:28 LICENSE
drwxr-xr-x 2 502 games 4.0K Jun 15 19:48 man
-rw-r--r-- 1 502 games   49 May 28 21:28 README
drwxr-xr-x 9 502 games 4.0K May 29 22:30 src

auto目录下有四个子目录:cc、lib、os、types cc目录是用于编译的,os用于判断操作系统,lib是依赖的库函数 其他所有文件都是为了辅助./configure脚本执行时去判定nginx支持哪些模块、 当前操作系统有什么特性可以供给nginx使用。

conf目录下存放了各种nginx配置的示例文件,为了方便运维,编译时会把示例文件拷贝到安装目录。

contrib目录提供了两个perl脚本,和vim语法提示的插件,cp -r contrib/vim/* ~/.vim/

html目录提供了两个标准的html文件:50x.html和index.html

man目录存放nginx的帮助文件

src目录:nginx的源代码

4.2 执行编译

1
2
# 查看支持哪些参数
./configure --help | less
  1. 确定Nginx执行中它会去找那些目录下的文件
  • –modules-path : 动态模块功能会使用到这个参数
  • –lock-path : 确定nginx.lock文件放在哪里
  • –prefix : 如果没有任何变动的话,只指定prefix就可以了 所有其他的文件会在prefix目录下面,建相应的文件夹
  1. 确定使用哪些模块和不使用哪些模块
  • –with-xxxx : 表示这个模块默认不会被编译进nginx
  • –without-xxxx : 表示这个模块默认会被编译进nginx
  1. 指定nginx编译中需要的一些特殊参数
  • 我用gcc编译的时候需要加一些优化的参数
  • 打印debug级别的日志
  • 需要加一些第三方的模块
1
2
3
4
5
6
7
# OpenSSL 3.3: Support for qlog for tracing QUIC connections has been added
$ wget https://github.com/openssl/openssl/archive/refs/tags/openssl-3.3.1.tar.gz
$ tar -xzf openssl-3.3.1.tar.gz
$ wget https://github.com/PhilipHazel/pcre2/releases/download/pcre2-10.39/pcre2-10.39.tar.gz
$ tar -xzf pcre2-10.39.tar.gz
# 如果开启--with-http_image_filter_module 需要提前安装: yum install gd gd-devel -y
$ ./configure --prefix= --with-http_ssl_module --with-http_v2_module --with-http_v3_module --with-http_realip_module --with-http_addition_module --with-http_dav_module --with-http_stub_status_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_sub_module --with-http_auth_request_module --with-http_random_index_module --with-http_secure_link_module --with-http_slice_module --with-stream --with-stream_ssl_module --with-stream_realip_module --with-pcre=../pcre2-10.39 --with-openssl=../openssl-openssl-3.3.1/

上面命令执行完以后,会生成objs目录,该目录下最重要的一个文件是ngx_modules.c, 它决定了,nginx在编译的时候,会有哪些模块会被编译进nginx。

异常处理:

# 问题:
make[2]: Entering directory `/root/pcre2-10.40'
make[2]: *** No rule to make target `libpcre.la'.  Stop.
make[2]: Leaving directory `/root/pcre2-10.40'
make[1]: *** [/root/pcre2-10.40/.libs/libpcre.a] Error 2

# 解决
$ cp /usr/lib64/libpcre.a  /root/pcre2-10.40/.libs 

解决openssl依赖:

使用--with-http_ssl_module参数的时候,需要提供openssl依赖:
要么:
        yum install -y openssl-devel
要么:
        编译时使用参数: --with-http_ssl_module --with-openssl=/tmp/openssl-1.0.2k
        注意:  /tmp/openssl-1.0.2.k是openssl源码目录

--prefix=./ --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-http_sub_module --with-stream --with-stream_ssl_module --with-stream_realip_module --with-pcre=../pcre2-10.39 --with-openssl=../openssl-OpenSSL_1_0_2k
1
$ make

上面命令会在objs文件中生成大量的中间文件和nginx二进制文件,在执行nginx版本升级的时候, 不能执行make install,而应该把objs目录下的nginx二进制文件拷贝到安装目录中,覆盖旧版本。

如果我们使用到了动态模块,那么动态模块的so文件也会放在objs目录下。

1
$ make install

首次安装时可以使用这个命令。

1
2
3
4
# 进入prefix指定的安装目录
$ cd  /usr/local/nginx
$ ls
conf  html  logs  sbin

sbin: 存放nginx的二进制文件 conf: 存放决定nginx功能的配置文件 logs: 存放access.log和err.log

5. Nginx命令行及演示:重载、热部署、日志切割

5.1 nginx命令行:

  1. 格式: nginx -s reload
  2. 帮助: -? -h
  3. 使用指定的配置文件: -c
  4. 指定配置指定: -g 指定一些配置的指令,这些指令可以使用-g,所谓指令就是nginx.conf文件的指令,但是这些指令,如果我要在命令行中覆盖其中的某条指令的值,这个时候就可以使用-g。
  5. 指定运行目录: -p
  6. 发送信号: -s [stop|quit|reload|reopen] stop: 立刻停止服务 等价于 kill -SIGTERM <进程ID> quit: 优雅的停止服务 等价于 kill -SIGQUIT <进程ID> reload: 重载配置文件 等价于 kill -SIGHUP <进程ID> reopen: 重新开始记录日志文件 kill -SIGUSR1 <进程ID>
  7. 测试配置文件是否有语法错误:-t -T
  8. 打印nginx的版本信息、编译信息等:-v -V

5.2 热部署

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 查询nginx是否在运行
$ ps -ef | grep nginx

# 备份之前的nginx二进制文件
$ cp nginx nginx.old
# 复制编译好的nginx二进制文件,替换掉正在运行的nginx二进制文件
$ cp -r /root/nginx/sbin/nginx . -f

# 给nginx的master进程发送一个信号,告诉它我们开始执行热部署了
$ kill -USR2 13195
# 这个时候,nginx master进程会新启一个master进程
# 老的worker还在运行,新master会启动新的worker,但是老的worker不再监听80和443端口,
# 所以新的请求新的连接,只会进入新的nginx进程中。

# 这个时候,我们可以给老的nginx master进程发送一个信号,告诉它请优雅的关闭你的所有worker进程,但是老的master进程还在。
$ kill -WINCH 13195

# 老的master进程还在,如果这个时候出现了一些问题,就可以给老的master进程发送reload信号,
# 然后把新的master关掉,就可以实现版本的回退。

5.3 切割日志文件

  1. 临时日志切割
1
2
3
4
$ mv access.log bak.log

# reopen相当于执行kill -USR1 <进程ID>
$ ../sbin/nginx -s reopen
  1. 定时执行日志切割 首先,创建一个名为rotate.sh的脚本
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/bin/bash
#Rotate the Nginx logs to prevent a single logfile from consuming too much disk space.
LOGS_PATH=/usr/local/openresty/nginx/logs/history
CUR_LOGS_PATH=/usr/local/openresty/nginx/logs
YESTERDAY=$(date -d "yesterday" +%Y-%m-%d)

mv ${CUR_LOGS_PATH}/taohui_access.log ${LOGS_PATH}/taohui_access_${YESTERDAY}.log
mv ${CUR_LOGS_PATH}/maomaoxiong_access.log ${LOGS_PATH}/maomaoxiong_access_${YESTERDAY}.log
mv ${CUR_LOGS_PATH}/error.log ${LOG_PATH}/error_${YESTERDAY}.log

## 向Nginx主进程发送USR1进程。USR1进程是重新打开日志文件
kill -USR1 $(cat /usr/local/nginx/logs/nginx.pid)

然后创建如下所示的crontab:

1
2
$ crontab -l
0 0 1 * * root /usr/local/nginx/logs/rotate.sh

5.4 为什么kill掉worker进程后,会自动生成一个新的worker进程

如果我向一个worker进程,比如16982发送一个让它退出的信号,那么这个worker进程就会退出, 但是进程在退出时,会自动地向它的父进程9170,也就是master进程,发送一个SIGCHILD,这样一个信号受到以后,master进程就知道它的子进程退出了,它会新起一个worker进程,维持总共两个worker进程这样的进程结构。

5.5 使用信号来管理Nginx的父子进程

Master进程:             Worker进程:              nginx命令行:
>监控worker进程         >接收信号                >reload: HUP
  CHLD                     TERM INT              >reopen: USR1
>管理worker进程            QUIT                  >stop: TERM
>接收信号                  USR1                  >quit: QUIT
  TERM,INT                 WINCH
  QUIT(表示优雅的停                           
  ,可以慢慢的停,但是
  要保证对用户不要发送
  停止连接,像发送TCP的
  reset复位请求这样的报文)
  HUP (表示重载配置文件)
  USR1 (表示重新打开日志文件)
  USR2
  WINCH

5.6 reload重载配置文件的真相

  1. 向master进程发送HUP信号(reload信号)
  2. master进程校验配置语法是否正确
  3. master进程打开新的监听端口(nginx.conf文件新增的监听端口) 为什么在master进程中打开新的监听端口呢? 所有的worker进程是master进程的子进程,子进程会继承父进程 所有已经打开的端口,这是linux操作系统所定义的。
  4. master进程用新配置启动新的worker子进程
  5. master进程向老worker子进程发送QUIT信号
  6. 老worker进程关闭监听句柄,处理完当前连接后结束进程。 异常的情况,如果有些请求出了问题了,那么客户端长时间没有处理, 那么,会导致这个请求长时间的占用在这个worker进程上面,那这个worker 进程就会一直存在,当然因为新的连接已经跑在了新的worker子进程中了, 所以影响并不会很大,唯一会导致这个这个老的worker子进程长时间存在, 但只会影响已存在的连接,不会影响新的连接,这个时候,有没有办法来处理呢? 实际上nginx比较新的版本中,它提供了一个配置项,叫worker_shutdown_timeout

5.7 优雅地关闭worker进程

当nginx代理websocket的时候,在websocket后面进行通讯的frame帧里面,nginx是不解析它的帧的,所以这个时候,它是没有办法。 nginx在做TCP层或者UDP层反向代理的时候,它也没有办法识别一个请求需要经历多少报文才算是结束。 但是对于http请求,nginx可以做到的,所以优雅的关闭我们主要针对的是http请求。

  1. 设置定时器: worker_shutdown_timeout 设置完定时器以后,它会加一个标志位,就是表示我现在进入优雅的关闭这样的一套流程了。
  2. 关闭监听句柄 先关闭监听句柄,保证我的worker进程不会再去处理新的连接了
  3. 关闭空闲连接 接下来,它回去看它的连接池,因为nginx为了保证自己对资源的利用是最大化的,所以它经常 会保存一些空闲的连接,但是没有断开,这时候会首先关闭所有的空闲连接。
  4. 在循环中等待全部连接关闭 这是一个时间可能非常长的一步,因为nginx不是主动地立刻关闭,所以它通过第一步我们加的 一个标志位,在循环中,每当发现一个请求处理完毕,就会把这个请求使用的连接关掉,在循环 中等待全部连接关闭的时间,可能会超过第一步我们说的worker_shutdown_timeout
  5. 退出进程

6. 用GoAccess实现可视化并实时监控access日志

1
2
3
4
5
$ goaccess geek.access.log -o ../html/report.html \
    --real-time-html \
    --time-format='%H:%M:%S' \
    --date-format='%d/%b/%Y' \
    --log-format=COMBINED

添加report.html文件的location:

server {
  listen 8080;
  access_log logs/geek.access.log  main;
  location /report.html {
    alias /usr/local/nginx/html/report.html;
  }
}

7. Nginx的模块究竟是什么?

7.1 使用模块的四个步骤

  1. 保证它被编译到我们的nginx二进制文件中,
  2. 该模块提供了哪些配置项,
  3. 我们要了解这个模块何时被使用 因为有的模块只要你编译进nginx,它默认就会被使用, 而有一些模块,你必须使用相应的配置项并且配置正确时,模块才会被使用。
  4. 这个模块提供哪些变量。

7.2 对于文档不详细的模块,如何确定以上四点呢?

  1. 保证它被编译进nginx二进制文件: 首先我们在开始编译时,我们在它的源代码根目录下会执行./configure,通过–with添加官方模块,通过–add-module添加第三方模块。 我们可以在./configure执行完之后,去{nginx-root-dir}/objs目录下,打开文件ngx_modules.c。 在这个文件中有一个数组,叫做ngx_modules,这个数组包含了所有编译进nginx的模块,这里可以找到对应的模块,比如ngx_http_gzip_filter_module

  2. 该模块提供了哪些配置项(指令) 我们可以在nginx的源代码中有一个叫做{nginx-root-dir}/src/http/modules的目录, 然后我们打开ngx_http_gzip_filter_module.c文件,所有的模块都有对应的这样的一个.c的源文件。 在这个源文件中,我们去搜这样一个结构体,它是唯一的,叫做ngx_command_t。 这是每个模块必须具备的一个结构体。这个结构体是一个数组,数组的每个成员是它支持的指令名,比如gzip的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
static ngx_command_t ngx_http_gzip_filter_commands[] = {
  { ngx_string("gzip"),  // 指令名
    NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                      |NGX_CONF_FLAG,  // 指令的类型
    ngx_conf_set_flag_slot,            // set
    NGX_HTTP_LOC_CONF_OFFSET,          // conf
    offsetof(ngx_http_gzip_conf_t, enable), // offset
    NULL },                                 // post后置处理
  ...
}

指令名的后面会描述,这个指令名的后面可以跟几个参数,参数的类型是怎样的,是空间类的 还是时间类型,可不可以加相应的参数,等等。

ngx_module_t有4个子结构体:ngx_core_command_t,ngx_http_module_t,ngx_event_module_t,ngx_mail_conf_ctx_t

ngx_module_t有个成员index,如果模块间存在冲突,那么先生效的模块会阻碍后生效的模块发挥作用。

7.3 Nginx模块的分类

ngx_module_t有个成员type,它定义了模块的类别。

+-----------------+  +----------------------+   +------------------------+
|{ngx_conf_module}|  |{upstream相关模块}    | +>|{ngx_mail_core_module}  |
| NGX_CONF_MODULE |  |{响应过滤模块}        | | |NGX_MAIL_MODULE         |
+-----------------+  |{请求处理模块}        | | +------------------------+
+-----------------+  |{ngx_http_core_module}| | +------------------------+
|{epoll}          |  | NGX_HTTP_MODULE      | | |{stream模块}            |
|{event_core}     |  |                      | | |{ngx_stream_core_module}|
|NGX_EVENT_MODULE |  |                      | | |NGX_STREAM_MODULE       |
+---------^-------+  +-----------^----------+ | +-----------^------------+
          |                      |            |             |
+---------|----------------------|------------|-------------|------------+
|     {events}                 {http}       {mail}       {stream}        |
|  NGX_CORE_MODULE      {core}        {errlog}    {thread_pool} {openssl}|
+------------------------------------------------------------------------+

分析nginx源码的src目录:

1
2
$ cd src; ls
core dtrace event http mail misc os stream

core目录并不是指NGX_CORE_MODULE,而是指nginx核心框架代码

7.4 使用动态模块来提升运维效率

  1. 动态库和静态库的区别: 静态库会直接把所有的源代码编译进最终的二进制可执行文件中 动态库在nginx二进制可执行文件里,只保留了调用它的位置。

  2. 添加动态库的步骤 (1) Configure加入动态模块 (2) 编译进binary (3) 启动时初始化模块数组 (4) 读取load_module配置 (5) 打开动态库并加入模块数组 (6) 基于模块数组开始初始化

并不是所有的模块都能动态化,只有特定的某些模块才有这个功能。

  1. 使用./configure --help | less来查看支持动态库的模块 例如, --with-http_image_filter_module=dynamic

  2. 使用动态库编译后,会在安装目录下创建一个modules目录, 该目录下会生成对应的so文件。然后在nginx.conf目录的首行添加: load_module modules/xxxx.so,就可以生效了

logs/service/zeus-merge-v2/permission-da-service/migration_upgrade_2.4.1.0_15***8


本文发表于 0001-01-01,最后修改于 0001-01-01。

本站永久域名「 jiavvc.top 」,也可搜索「 后浪笔记一零二四 」找到我。


上一篇 « 下一篇 »

赞赏支持

请我吃鸡腿 =^_^=

i ysf

云闪付

i wechat

微信

推荐阅读

Big Image