【Nginx02】Nginx学习:核心模块Core

2023-08-09 11:12:37 浏览数 (1)

Nginx学习:核心模块Core

对于 Nginx 的学习来说,其实最基础的就是学习配置文件中的各种不同配置项。如果想要深入的理解 Nginx ,那就要去研究它的源码,但是就像之前说过的,C 的水平以及整体的计算机基础知识的水平还不足以支撑我达到可以分析 Nginx 源码的程度。

因此,咱们就是对各种配置的学习。

在 Nginx 中,各种功能其实是以各种模块来提供的,比如最重要的包括核心模块、事件模块、HTTP模块以及Email模块,其中 HTTP 模块又是重中之重。将来如果你学习或者使用到 OpenResty 的话,其实你也会发现,它就是一个 Nginx 的超集,提供了更为丰富的模块功能。

今天,就先来学习 Nginx 核心模块中的部分配置。一些可能比较常见的并且重要的配置,咱们实际动手配一下,看看效果。而另外一些可能不那么常见的,就简单以我的理解来解释下是干啥用的。毕竟整个 Nginx 中,配置项还是不少的,一个一个的调试,一个一个的详细研究,可能真的是要花上不少时间的,咱们的主要目标还是以应用为主嘛。

核心模块是可以在整个 conf 配置文件中全局配置的一些参数,一般来说,可能会放到全局的 nginx.conf 文件最开头的部分。当然,放在其它配置文件中也没啥问题,不过需要注意,不要放到别的子模块中,还是要强调,修改之后一定要 nginx -t 检查。

用户

用户配置实际上就是 Nginx 的工作进程(子进程),是由哪个用户来启动的。这里又是牵涉到 Linux 的用户及权限管理了。通常,主进程可能会使用 root 或者某个指定的用户来启动,而子进程,则会选择大家非常常用的 www 用户。没错,就算是使用 Apache ,大家也喜欢用这个用户名的用户来运行。

同理,我们的应用程序目录尽量也是 www 用户和用户组的。这样,对于系统的安全来说,就有了相当高的保障。

代码语言:javascript复制
user user [group];

可以指定用户,也可以同时指定用户组。我们先来看看当前我的电脑上,是使用哪个用户来运行的。

代码语言:javascript复制
➜  ~ ps -ef | grep nginx
  501 51936     1   0  5:03下午 ??         0:00.00 nginx: master process nginx
  501 51937 51936   0  5:03下午 ??         0:00.00 nginx: worker process

我这里是 Mac 电脑,501 是你默认登录的那个用户,使用 Linux 的话 ps 出来的内容会直接显示用户,不过咱们也可以直接看下 501 是谁。

代码语言:javascript复制
➜  ~ id
uid=501(zhangyue) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),701(com.apple.sharepoint.group.1),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh),400(com.apple.access_remote_ae)

很明显,这个用户就是我登录的这个用户名。好了,接下来我们修改一下,在配置文件中将用户设置为 www ,如果你的系统中没有添加过这个用户,就先添加一下。

代码语言:javascript复制
➜  ~ vim /usr/local/etc/nginx/nginx.conf

user www;
………………

好了,再次启动,这时我们需要使用 root 来启动,如果你还是像原来那样使用 501 这个登录用户启动的话,会报出警告信息。

代码语言:javascript复制
➜  ~ nginx
nginx: [warn] the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /usr/local/etc/nginx/nginx.conf:4

➜  ~ ps -ef | grep nginx
  501 73281     1   0 10:34上午 ??         0:00.00 nginx: master process nginx
  501 73282 73281   0 10:34上午 ??         0:00.00 nginx: worker process
  501 73283 73281   0 10:34上午 ??         0:00.00 nginx: worker process

大概的意思就是用户这个配置启用后,需要让主进程运行在超级用户的权限下,也就是 root 用户。因此,我们需要 sudo 一下再启动 nginx 。

代码语言:javascript复制
➜  ~ sudo nginx
➜  ~ ps -ef | grep nginx
    0 51805     1   0  4:59下午 ??         0:00.00 nginx: master process nginx
   70 51806 51805   0  4:59下午 ??         0:00.00 nginx: worker process

➜  ~ id www
uid=70(_www) gid=70(_www) groups=70(_www),12(everyone),61(localaccounts),701(com.apple.sharepoint.group.1),100(_lpoperator)

这下看出来效果了吧,子进程的运行用户是 70 ,正是当前系统中的 www 用户。如果你是使用的某些面板或者一键安装工具,那可以直接去看一下,一般它们也都是这样运行的,子进程全部放在 www 用户下去运行。

工作进程

不知道大家有没有了解过,Nginx 是多线程还是多进程的?其实它是多进程的,默认并没有什么多线程。现在,先来看一下怎么配置进程相关的内容。

之前在测试中,我们就已经看到启动起来的 Nginx 有一个主进程,一个子进程。这个子进程就是一个工作进程。而主进程负责管理所有的子进程,就像我们在 Swoole 中学习过的那个进程工作模式。

对于进程数量,建议配置是和 CPU 的核数相当,或者稍微少一点,可以通过下面这个选项来配置。

代码语言:javascript复制
worker_processes number | auto;

可以写具体的数量,也可以选择 auto 。当你的服务器上还有其它的应用时,可以比核数少一两个,也就是让出一两个 CPU 给其它程序使用。

现在咱们把它配置成 2 ,然后再重启一下 Nginx 看看效果。

代码语言:javascript复制
➜  ~ ps -ef | grep nginx
  501 51936     1   0  5:03下午 ??         0:00.01 nginx: master process nginx
  501 52154 51936   0  5:16下午 ??         0:00.00 nginx: worker process
  501 52155 51936   0  5:16下午 ??         0:00.00 nginx: worker process

是不是出现两个工作进程了,这个配置就是这么简单。当然你也可以配置成 auto 试试效果。

worker_cpu_affinity

代码语言:javascript复制
worker_cpu_affinity cpumask ...;
worker_cpu_affinity auto [cpumask];

上面这个配置是用于绑定 CPU 的,是的,你没看错,就是我们指定了几个工作进程,就可以把这些进程绑定到指定的 CPU 上。不过,CPU 的信息需要用二进制来写,比如第一个 CPU 就写 0001 ,第二个 CPU 就写 0010 这样。

代码语言:javascript复制
worker_cpu_affinity 0011 0100;

像这样写,就是绑定到第三和第四个 CPU 上。不过我本机的电脑不支持,需要 FreeBSD 或者原生 Linux 系统,大家可以用 Linux 环境尝试一下。怎么测试效果呢?其实它是为了解决多个进程一直使用一个 CPU 核的问题,也就是均匀分摊 CPU 给不同的工作进程,需要使用压力测试工具进行测试,然后 top 查看 CPU 各个核的负载情况,如果比较平均,就是产生效果了。

这样绑定 CPU 有什么好处呢?一是减少 CPU 切换带来的损耗,二是能够充分利用 CPU 自身的多级缓存,提高缓存命中率。

好吧,还是搭个虚拟机吧,后面的示例我们转到虚拟机上去学习,版本是 1.23.0 。

worker_priority

这个配置用于定义工作进行的优先级,和 Linux 中 nice 的效果是一样的。

代码语言:javascript复制
worker_priority number;

number 参数的取值范围是 -20 到 20 ,越小优先级越高。比如我们设置成 -10 ,然后通过 top 命令查看。

代码语言:javascript复制
[root@localhost ~]# top -u www
………………
PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME  COMMAND
13976 www       10 -10   71692   4304   3104 S   0.0   0.2   0:00.00 nginx

看到 NI 这一列变成 -10 了吧。至于 Linux 中,进程优先级具体的作用,大家可以自行查阅下相关的资料。

进程其它配置

进程相关的配置项中,还有几个配置项,不过平常我们不太会去配置它们,一起了解一下吧。

代码语言:javascript复制
working_directory directory;
worker_rlimit_core size;

首先是如果进程出现问题了,就会记录一个 core 文件,working_directory 用于指定记录日志的文件的存放位置目录,worker_rlimit_core 用于指定记录的文件大小。

代码语言:javascript复制
worker_rlimit_nofile number;

worker_rlimit_nofile 用于指定每个工作进程可以打开的最大文件句柄数,或者就简单的理解为可以建立连接的数量 。与操作系统的 ulimit 设置类似,可以看做是 Nginx 的 ulimit 。

代码语言:javascript复制
worker_shutdown_timeout time;

最后这个其实从名字就可以看出来了,它是在 shutdown 之后超时的时间。什么意思呢?比如说我们 reload 服务器之后,会产生很多进程 shutdown ,也就是进行优雅退出,如果进程遇到问题一下不退出怎么办?这时就可以为它设置这个超时时间,如果还不退出,就强制关闭。

调试

在 Nginx 的核心模块中,提供了调试相关的配置,仅有三个,其中一个还和进程有关。

代码语言:javascript复制
daemon on | off

第一个是后台运行,默认它就是开启的,我们可以把它设置成 off ,这样如果命令行运行 Nginx 的话,就会一直挂在那里,而且不像 Redis 之类的有输出,所以这个平常我们也是用不到的啦。

代码语言:javascript复制
debug_points abort | stop;

debug_points 是调试检查点的意思,有两个值,当检测到内部错误时,例如重新启动工作进程时套接字泄漏,启用调试点会导致核心文件创建(中止)或进程停止(停止),以便使用系统调试器进行进一步分析。用于 Nginx 内部的几个调试点上,自己写 Nginx 插件时比较有用。

最后一个和进程有关,关闭后它会让 Nginx 程序不 fork 子进程,只使用一个进程运行。

代码语言:javascript复制
master_process on | off;

我们先来看看,当前我们有三个进程,一个主进程和两个工作进程。

代码语言:javascript复制
➜  ~ ps -ef | grep nginx
  501 59597     1   0  9:48上午 ??         0:00.00 nginx: master process nginx
  501 59598 59597   0  9:48上午 ??         0:00.00 nginx: worker process
  501 59599 59597   0  9:48上午 ??         0:00.00 nginx: worker process

然后将 master_process 设置成 off ,接着重启 Nginx ,再看一下。

代码语言:javascript复制
➜  ~ ps -ef | grep nginx
  501 59641     1   0  9:49上午 ??         0:00.00 nginx

现在就只有一个进程在运行了。很明显,线上也是不能这么玩的,这三个配置如果没有特殊需求,还是别踫它们比较好。这个配置不能通过 reload 重载,需要完全关闭再重启。

错误日志

在核心模块的日志中,其实只有一个 error_log 的配置,相信这个配置大家一定不会陌生。而且它是适应很多模块的,我们不仅可以在 core 也就是主配置文件的最外面配置一个全局的错误日志,这个日志主要用于服务实例的一些信息的应用。同时,也可以在 http 的 server 内部配置一个 error_log 去记录指定站点的错误日志,比如 PHP 或请求非 200 的一些错误信息。因此,这个配置大家都不会陌生,咱们就来简单看看吧。

代码语言:javascript复制
error_log file [level];

它的配置非常简单,给个记录文件的路径和文件名,然后就是记录的日志等级。等级包括 debug, info, notice, warn, error, crit, alert, emerg 这些,从低到高记录的信息越来越少,问题也越来越严重,具体就不解释了,相信大家都明白。默认情况下是 error 这个级别,另外,如果使用 debug 的话,需要 Nginx 在编译安装的时候也要加上 --with-debug 参数。

另外在编译时,我们还可以直接指定一个全局的错误日志,这样你就会看到在默认的配置文件中,很多情况下都只有三个注释掉的 error_log 配置。这是因为在编译的时候也可以指定一个默认的错误日志。

代码语言:javascript复制
// nginx -V
--error-log-path=/usr/local/var/log/nginx/error.log

然后我们可以再试试修改它的记录级别,并且新增加一个记录的日志。

代码语言:javascript复制
// nginx.conf
error_log logs/error_debug.log debug;

新增加的这个日志名称为 error_debug.log ,目录就是当前运行程序的目录下面的 logs 目录。这个位置就是我们安装后的 Nginx 的运行目录,如果是 Linux ,大部分会安装到 /usr/local/nginx 下,如果是 Mac 使用 brew 的话,一般会在 /usr/local/Cellar/nginx/版本号/ 这个目录下面。其实它就是 nginx -V 时看到的那个 --prefix=xxx 指向的目录。

代码语言:javascript复制
// /usr/local/Cellar/nginx/1.21.6_1/logs/error_debug.log

2022/07/13 09:56:41 [notice] 59684#0: signal 23 (SIGIO) received
2022/07/13 09:56:41 [debug] 59888#0: channel: -2
2022/07/13 09:56:41 [debug] 59888#0: timer delta: 107
2022/07/13 09:56:41 [debug] 59888#0: worker cycle
2022/07/13 09:56:41 [debug] 59888#0: kevent timer: -1, changes: 0
2022/07/13 09:56:41 [debug] 59887#0: channel: -2
2022/07/13 09:56:41 [debug] 59684#0: wake up, sigio 0
2022/07/13 09:56:41 [debug] 59887#0: timer delta: 1
2022/07/13 09:56:41 [debug] 59887#0: worker cycle
2022/07/13 09:56:41 [debug] 59887#0: kevent timer: -1, changes: 0
2022/07/13 09:56:41 [debug] 59684#0: sigsuspend
2022/07/13 09:56:41 [notice] 59684#0: signal 23 (SIGIO) received
2022/07/13 09:56:41 [debug] 59684#0: wake up, sigio 0
2022/07/13 09:56:41 [debug] 59684#0: sigsuspend

另外要注意的是,使用 debug 这个日志级别,需要 Nginx 在编译安装时要有 --with-debug 这个参数。

前面说过,这个配置可以全局配置,也可以在 http、server、location 下面单独配置,还可以在后面我们学习的 mail 和 stream 中配置。一般来说,全局会有一个总的错误日志记录文件,同时,大部分情况下也都会在 server 下面根据不同的虚拟站点配置不同的错误日志文件。毕竟所有站点的错误信息都记录在一个文件中还是比较混乱的。至于 location 下面,如果有特殊需求,也可以考虑单独建立一个日志文件。

文件加载

文件加载就是加载外部的配置文件,在默认的配置文件中其实就有很多地方用到了。

代码语言:javascript复制
include file | mask;

在默认的配置文件中,你可能会看到这些。

代码语言:javascript复制
……
include       mime.types;
……
 include        fastcgi_params;
……
include        servers/*;
……

第一个一般都是有的,它是加载一些 MIME 类型信息的,具体的这个 mime.types 文件里面写得是啥,大家自己去看一下就知道了,后面我们也会学习到。第二个一般是在 server 中,也就是服务器相关的配置中,用来指定一些 FastCGI 相关配置的。第三个则是加载一些独立配置文件,也就是我们通常会将虚拟主机分开成不同的配置文件进行保存,这里就是指定加载整个目录中的全部配置文件了。如果你是完全自己重新安装的 Nginx ,在默认的配置文件中没有看到后面两个也没关系,它们确实是经常见到并且非常有用的。另外,通过 nginx -T 也能看到整个 Nginx 加载的所有配置文件信息,include 进来的文件也会展示出来。

接下来我们就简单地试试,还是拿上面的错误日志来测试,先建立一个 err.conf 文件,里面就是 error_log 去记录一个新的日志文件。

代码语言:javascript复制
// vim err.conf
error_log logs/include_log.log info;

接着,在主配置文件中,include 进来。

代码语言:javascript复制
// nginx.conf
include err.conf;

然后 nginx -s reload 配置,接下来,就可以看到 include 进去的配置文件信息也生效了。

代码语言:javascript复制
➜  ~ ll /usr/local/Cellar/nginx/1.21.6_1/logs
total 184
-rw-r--r--  1 zhangyue  admin    33K  7 13 10:24 error_debug.log
-rw-r--r--  1 zhangyue  admin   1.2K  7 13 10:24 include_log.log

除了加载文件之外,还有一个加载外部模块文件的配置项。

代码语言:javascript复制
load_module file;

这个是用于加载外部一些 Nginx 模块的,将来用到的时候再说吧,它和 include 其实也是类似的,但是它加载的不是纯文件的配置信息,而是指定的模块,就像我们为 PHP 安装扩展后也要在 php.ini 中添加 extension=xxx.so 一样。

其它

好了,接下来就是一些其它相关的配置参数了。我们简单的学习一下就好。

lock_file

代码语言:javascript复制
lock_file file;

这个配置主要是用来指定互斥锁文件,如果是在 i386, amd64, sparc64, and ppc64 这些 CPU 架构下使用 gcc, Intel C , 或 SunPro C 编译构建的 Nginx ,会使用原子指令来实现互斥锁,否则的话,就需要配置这个参数,通过一个文件来实现互斥锁的功能。(了解一下就好,至少你得明白互斥锁是干嘛的)

pcre_jit

代码语言:javascript复制
pcre_jit on | off;

这个配置项的名字有点意思啊,它是 正则 JIT ,合起来就是对于正则表达式的即时编译支持,如果开启的话,可以提升正则表达式的处理速度。从 Nginx 1.1.12 之后的版本开始支持,需要在编译安装 Nginx 的时候加上 --with-pcre-jit 参数。

pid

代码语言:javascript复制
pid file;

不多解释了,指定进程通信文件 pid 文件的存放位置。也可以在编译时通过 --pid-path 直接指定,同样是通过 nginx -V 可以查看是不是编译时已经指定了。如果没有指定,默认是放在 logs/nginx.pid 这里。

ssl_engine

用于指定 OpenSSL 使用的引擎。

代码语言:javascript复制
ssl_engine device;

一般也不需要配置,它会找当前操作系统中安装的 OpenSSL 所使用的引擎。

代码语言:javascript复制
[root@localhost etc]# openssl engine -t
(rdrand) Intel RDRAND engine
     [ available ]
(dynamic) Dynamic engine loading support
     [ unavailable ]

thread_pool

指定线程池的名称、数量和最大等待队列数量。用于多线程读取和发送文件,而不阻塞工作进程。

代码语言:javascript复制
thread_pool name threads=number [max_queue=number];

默认的配置是:

代码语言:javascript复制
thread_pool default threads=32 max_queue=65536;

从 Nginx 1.7.11 后开始支持配置的,暂时来看是使用多线程来进行 IO 处理的,具体作用将来如果学习到相关的内容了再深入的学习。

timer_resolution

代码语言:javascript复制
timer_resolution interval;

每次内核事件调用返回时,都会使用 gettimeday() 来更新 Nginx 缓存时钟;timer_resolution 用于定义每隔多久才会由 gettimeday() 更新一次缓存时钟;在 x86-64 系统上,gettimeday() 代价已经很小,可以忽略此配置。

嗯,意思就是平常可以不用配置这个选项。说实话,平常还真没见过。

env

默认情况下,Nginx 会删除从其父进程继承的所有环境变量,但 TZ 变量除外。使用这个配置指令可以允许保留一些继承变量、更改其值或创建新的环境变量。

代码语言:javascript复制
env variable[=value];

它的使用有一些限制,文档中具体的内容也没看太懂,网上找的资料也不多,所以咱们也别瞎配了。它可能不是你想像的那样可以配置通用的全局环境变量的哦。

如果有用过的小伙伴,可以留言或者提供一些学习资料哈!

配置文件单位

在配置文件中,有许多单位信息需要我们提前了解一下。

第一个就是容量单位,容量可以用千字节(k,K)和兆字节(m,M)来描述,比如“8k”,“1m”。 如果没有指定单位,容量以字节为单位。和 Redis 配置文件中的基本一样。

另一个就是时间单位。

单位

说明

ms

毫秒

s

m

分钟

h

小时

d

w

M

月,30天

y

年,365天

具体到配置项上,就比如之后我们要学的 client_body_timeout ,它的默认值就是 60s ,代表 60 秒。另外还有 client_body_buffer_size ,它的默认值就是 8k 或 16k ,代表的就上面千字节。

作用域

在 Nginx 的配置文件中,有很多配置指令是可以配置在多个模块级别中的。比如说最常见的 root 配置,它可以放在 http,server,location 这些模块区域中。和编程语言类似,它是根据模块级别来确定优先级的。比如说我们在 http 上配置了 root ,server 和 location 中没有配置的话,那么就会走全局的这个 http 下面的 root ,而如果同时在 server 中配置了,那么如果访问当前这个 server ,就会走这个 server 模块中配置的那个 root 。

也就是说,根据模块层级,最底层的模块的优先级最高,如果下层没有配置,就会走上层的配置。这个在后面的所有文章中都是通用的,所以在核心模块这里先提一嘴。

总结

今天学习的内容主要是用户权限、进程以及错误日志配置相关的一些核心配置方面的内容。这部分的内容不复杂,但是有一些配置项确实资料很少,或许也只是在一些特殊情况下才会用到,所以大部分人都没怎么接触过。后面的许多配置其实也有类似的情况,因此,咱们的学习也主要就是学习常用的那些配置,而不常见的就尽已所能的根据文档翻译一下。

核心模块其实还有一部分,就是事件模块,这个我们单独再开一篇文章进行学习,下回见。

参考文档:

http://nginx.org/en/docs/ngx_core_module.html

0 人点赞