Nginx 快速入门
前言
在当今互联网飞速发展的时代,网站性能和稳定性对于用户体验至关重要。无论是个人开发者还是大型企业,都需要一个高效、可靠的 Web 服务器来支撑其在线业务。而 Nginx,作为一个高性能的 HTTP 和反向代理服务器,凭借其卓越的性能、丰富的功能和灵活的配置,已经成为全球最受欢迎的Web服务器之一。
本文将详细讲解如何入门 Nginx,帮助每个后端开发者了解 Nginx 的用途和用法。
简介
Nginx(Engine X) 是由俄罗斯工程师 Igor Sysoev 于 2004 年开发的用于解决 C10k 问题(即在一台服务器上同时处理 10000 个并发连接的挑战)的服务器。自发布以来,Nginx 迅速获得了广泛的应用,尤其是在高并发、大流量的互联网服务中。
选择使用 Nginx 的理由有很多,以下是 Nignx 的一些特点:
- 高并发处理能力:Nginx 采用异步、非阻塞的事件驱动模型,能够高效地处理大量并发连接(单机支持 10w 以上的并发连接);
- 低资源消耗:Nginx 通过事件驱动机制大幅降低了CPU和内存的消耗,即使在高负载下也能保持较低的资源占用(一般情况下,10000个非活跃的 HTTP Keep-Alive 连接在 Nginx 中仅消耗 2.5MB 的内存);
- 灵活的配置和扩展:Nginx 的配置文件简单明了,支持模块化设计,用户可以根据需要加载不同的模块来扩展功能,如负载均衡、缓存、SSL/TLS 等;
- 反向代理和负载均衡:Nginx 不仅可以作为Web服务器,还可以作为反向代理服务器,将请求分发到不同的后端服务器,从而实现负载均衡,提高系统的可靠性和可扩展性;
- 热部署:Nginx 采用了 master 管理进程和 worker 工作进程的架构设计,使得 Nginx 能够实现热部署;
- 广泛的社区支持:Nginx 使用 BSD许可协议开源,拥有庞大的用户和开发者社区,提供了丰富的文档和教程。
快速开始
在 ubuntu 系统上安装 Nginx(本节参考自Nginx官方文档)
依次运行以下三条命令,导入官方的 nginx 签名秘钥:
1 | sudo apt install curl gnupg2 ca-certificates lsb-release ubuntu-keyring |
以上三条命令运行完,在终端上应展示完整的指纹,例如:
1 | pub rsa2048 2011-08-19 [SC] [expires: 2024-06-14] |
接着,为稳定版的 nginx 软件包设置 apt 存储库,并将其设置为首选:
1 | echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \ |
安装 Nginx:
1 | sudo apt update |
启动 Nginx 服务器
安装完 Nginx 后,可以使用以下命令启动 Nginx 服务器:
1 | sudo nginx |
默认情况下,将会加载 /etc/nginx/nginx.conf 作为配置文件,在需要指定配置文件的情况下,可以使用 -c 选项指定配置文件的位置,如:
1 | sudo nginx -c /etc/nginx/nginx.conf.bak |
此时,Nginx 服务器已经运行起来了,可以从浏览器直接访问我们服务器的 ip,看到 nginx 的欢迎页面,如图所示:
使用命令行控制 nginx 行为
可以使用 -s 选项来向 nginx 管理进程发送命令:
1 | sudo nginx -s reload|reopen|stop|quit |
其中:
reload
用于热加载,重新从配置文件中加载配置,不中断服务的情况下进行配置更新;
reopen
用于重新打开日志文件;
stop
用于关闭服务;
quit
用于优雅的关闭服务,服务会拒绝所有新的连接,并在所有旧的连接断开后关闭服务。
使用 -v 选项可以查看 Nginx 版本:
1 | nginx -v |
使用 -t 选项可以测试配置文件是否正确:
1 | nginx -t /etc/nginx/nginx.conf.test |
其他选项和用法,可以使用 -h 参数查看:
1 | nginx -h |
配置文件详解
配置文件的语法规则
1.配置文件由 配置块
以及 配置项
组成
1 | <section> { |
其中:
section
表示的就是配置块,由配置块名和一对大括号组成,大括号中的内容即为配置块的内容,配置块允许嵌套,内层块直接继承外层块,当出现配置冲突时,以内层还是外层为准,取决于解析这个配置块的模块。
directive
和 parameters
共同组成了一个配置项,directive 为配置项名,parameters 为配置项值,两者以空格分隔,配置项的值可以有多个,同样以空格分隔,每个配置项需以分号(;)结尾。如果配置项值包含语法符号(如空格),需要使用单引号或者双引号包住配置项值。下文使用指令来表示配置项。
2.配置文件同样支持注释,使用 #
进行单行注释
1 | #pid run/nginx.pid; |
3.配置文件中可以使用变量,使用 $
引用变量
1 | if ($scheme != "https") { |
这个Nginx配置代码片段用于将所有 HTTP 请求永久重定向到 HTTPS,其中 $scheme
是 http 模块提供的变量,代表请求的协议,$host
表示的是主机名, $1
用于捕获请求的 URI。
4.内嵌其他配置文件,使用 include
1 | include 配置文件名; |
如:
1 | include /etc/nginx/mime.types; |
include 所导入的文件,也需要遵循 Nginx 配置文件的语法,如果路径中出现了通配符,表示可以配置多个文件,如果导入的配置文件与本配置文件存在配置冲突,将以最后一次出现的配置为准。
5.示例
1 | user user; |
全局配置指令
Nginx 中全局配置部分用来指定对整个 Nginx 服务都生效的参数,Nginx 运行必须加载几个核心模块和一个事件类的模块,以下对常用的一些配置进行讲解:
include:导入其他配置文件
1 | include filepath; |
pid:指定 pid 文件的路径
1 | pid filepath; |
该文件用来保存 master 进程的 pid,需要保证 Nginx 有权在对应的路径中创建 pid 文件,否则 Nginx 将启动失败。
user:指定 worker 进程运行的用户及用户组
1 | user username [groupname]; |
该配置指定了 master 进程启动后,fork 出的子进程运行在哪个用户和用户组下。当不指定用户组时,用户组名和用户名一致。
daemon:是否以守护进程的方式启动
1 | daemon on|off; |
master_process:是否以 master/worker 方式工作
1 | master_process on|off; |
nginx 的默认工作方式为使用 master 进程来管理所有的 worker 进程,该配置不建议修改。
error_log:设置错误日志的输出位置和错误级别
1 | error_log filepath level; |
其中,日志级别有以下可选值:
- debug
- info
- notice
- warn
- error
- crit
- alert
- emerg
从上至下,级别逐级递增,设置为一个级别后, 大于等于该级别的日志将会被输出到 filepath 指定的文件中。
env:设置环境变量
1 | env VAR|VAR=VALUE; |
worker_rlimit_nofile:指定 worker 进程最大可以打开的文件句柄个数
1 | worker_rlimit_nofile limit; |
worker_process:指定 worker 进程的个数
1 | worker_processes number; |
worker 进程的数量会直接影响性能。通常来说,这个配置项应该配置为系统内核的个数。每个 worker 进程都是一个单线程的进程,在不出现阻塞式调用的情况下,设置为系统内核个数,可以充分利用系统的内核,同时不会出现频繁的进程间切换。但若是有阻塞式调用的情况,则需要多配置一些 worker 进程,防止影响系统性能。
worker_cpu_affinity:绑定 worker 进程到指定的 CPU 内核
1 | worker_cpu_affinity cpumask[cpumask...]; |
该配置一般会和 worker_process 配置一起出现,将 worker 进程绑定到指定的 CPU 内核上,避免不同进程抢占同一个 CPU,在内核的调度策略上实现了完全的并发。
cpumask 是 cpu 的标识,4核系统中的标识分别为 0001/0010/0100/1000,8核系统则为 00000001/00000010/00000100/00001000/00010000/00100000/01000000/10000000。
注:该配置仅在 Linux 系统中有效,其通过 sched_setaffinity() 系统调用实现这个功能。
accept_mutex:是否打开负载均衡锁,需要在事件模块中使用
1 | accept_mutex on|off; |
accept_mutex 可以让多个 worker 进程轮流、序列化地与新的客户端建立 TCP 连接,当一个 worker 进程建立的连接数达到 worker_connections 配置的最大连接数的 7/8 时,会大大减少该 worker 进程继续建立新 TCP 连接的机会,以此来达到各个 worker 进程的负载均衡。若是关闭该锁,建立 TCP 连接的耗时会减小,但会导致 worker 进程之间的负载会非常不均衡。
worker_connections:指定每个 worker 进程可以同时处理的最大连接数,需要在事件模块中使用
1 | worker_connections number; |
lock_file:指定 lock 文件的路径,需要在事件模块中使用
1 | lock_file filepath; |
该配置仅在打开了 accept 锁且操作系统或其他原因导致 Nginx 不支持原子锁时才会使用到,通过该配置指定的文件来实现文件锁。
accept_mutex_delay:指定 accept 锁获取失败后再次获取的延迟时间,需要在事件模块中使用
1 | accept_mutex_delay Nms; |
使用 accept 锁后,同一时间只有一个 worker 进程可以获取到锁,其他进程获取失败会立即返回。通过该配置可以指定获取失败后间隔多久会再次尝试获取锁。
use:选择事件模型,需要在事件模块中使用
1 | use kqueue|rtsig|epoll|/dev/poll|select|poll|eventport; |
对于Linux操作系统来说,可供选择的事件驱动模型有poll、select、epoll三种。epoll当然是性能最高的一种。
默认情况下,Nginx 会自动选择最合适的事件模型。
HTTP 模块
HTTP 模块用来配置 Nginx 服务器如何处理 HTTP 请求,是 Nginx 使用最多的一个部分。
该模块可以接受非常多的指令,以及多个 server 块和 location 块,以下选择一些常用的指令进行介绍。
keepalive_disable:禁用 keep-alive 请求
keepalive_timeout:设置 keep-alive 连接的持续时间
sendfile:允许 Nginx 直接从磁盘读取文件并发送到网络(on|off)
tcp_nodelay:减少发送小包时的延迟,它会在发送最后一个数据包时立即发送,不等待缓冲区满载(on|off)
tcp_nopush:开启 sendfile 时才会生效,数据包满载时才发送数据(on|off)
变量的定义和使用
在配置文件中支持使用变量,可以使用 set
、map
、geo
等指令来自定义变量,再通过 $
符号来引用变量。同时,系统也预定义了许多变量供我们使用,http 模块常用的预定义有:
- arg_name : 请求中的 name 参数
- args : 所有请求参数
- content_length : 请求头中的 Content-Length
- content_type : 请求头中的 Content-Type
- document_root : 当前请求中指令 root 或 alias 的值
- document_uri : $uri 的别名
- host : 请求头中 Host 的值,若是请求头没有该值,则为匹配该请求的 server_name 的值
- hostname : 运行 Nginx 主机的主机名
- https : 如果是 HTTPS 请求,则值为 on
- is_args : 如果请求有参数,值为
?
, 否则为空字符串 - remote_addr : 客户端的 IP 地址
- remote_port : 客户端的端口
- remote_user : 使用了 HTTP 基本认证时,用于设置用户名
- request : 完整的 HTTP 请求信息
- request_body : HTTP 请求体
- request_method : 请求使用的方法
- request_uri : 请求的完整 URI
- scheme : 请求使用的协议,HTTP/HTTPS
- server_addr : 接收请求的服务器的地址
- server_name : 接收请求的虚拟服务器的 server_name
- server_port : 接收请求的服务器的端口
- server_protocol : 当前请求使用的 HTTP 协议
- status : 响应状态码
server 模块
server 模块用来定义一个虚拟服务器,用来处理 HTTP 请求。一个 http 模块可以有多个 server 模块,每个模块都是一个独立的虚拟服务器。
例如:
1 | http { |
当一个配置文件中包含多个虚拟服务器时,Nginx 将会根据以下的规则来匹配 server 进行请求的处理:
- 匹配 listen 指令指定的 IP 地址和端口号
- 将 HOST 头部作为字符串与 server_name 进行完整匹配
- 将 HOST 头部与 server_name 指定的低级域名通配符进行匹配(*.example.com)
- 将 HOST 头部与 server_name 指定的顶级域名通配符进行匹配(a.example.*)
- 将 HOST 头部与 server_name 指定的通配符域名进行匹配
- 所有匹配都失败,使用 default_server 进行处理
- 未指定 default_server,使用配置文件中的第一个服务器进行处理
server 块常用的指令:
listen:用来监听指定的地址和端口号
1 | listen address[:port]; |
如果省略了地址,将会监听所有地址,如果省略端口,则使用标准端口。没有包含 listen 指令时,监听所有地址的标准端口,标准端口为 80/tcp。
listen 指令还支持以下的一些可选参数(仅列出一部分):
default_server
:指定该虚拟服务器为默认服务器,无其他匹配的服务器处理请求时将使用该服务器处理请求
ssl
:该参数表示该端口仅接受 HTTPS 连接
ipv6only
:该参数设置 IPV6_V6ONLY 参数的值
so_keepalive
:该参数为 TCP 监听套接字配置 keepalive
server_name:用来指定该虚拟服务器的主机头域
1 | server_name a.example.com; |
如果有多个服务器使用相同的地址和端口,则 Nginx 将使用请求的 HOST 头部与 server_name 指定的主机头进行匹配。默认值为 “”,对于没有设置 HOST 字段的请求,将会使用该 server 进行处理。
location 模块
location 模块指定特定的 URI 来处理请求。可以在 http 模块中使用,也可以在 server 块中使用,也支持嵌套使用。
语法:
1 | location [modifier] uri { |
其中,modifier
是修饰符,支持以下几种:
- = : 将 URI 作为字符串进行完整匹配,匹配完成后停止搜索;
- : 留空,表示通常匹配,用以匹配以 URI 开头的请求;
- ~ : 正则匹配,匹配 URI 时区分字母大小写;
- ~* : 正则匹配,匹配 URI 时忽略字母大小写;
- ^~ : 匹配 URI 时只需要前半部分与 URI 参数匹配即可,匹配完成后停止搜索;
- @ : 命名 location,只能在 server 块中定义,进用户 Nginx 内部请求之间的重定向,不处理用户请求。
Nginx 处理请求时的匹配规则如下:
- 匹配到了 ‘=’ 修饰符指定的 location,停止搜索,使用该 location 进行处理;
- 匹配到了 ‘^~’ 修饰符指定的 location,停止所有,使用该 location 进行处理;
- 使用 ‘~’ 或 ‘~*’ 修饰符进行的正则匹配,有多个匹配时使用第一个匹配到的 location 进行处理;
- 以上几个都没有匹配到的话,使用最长通用前缀进行匹配;
- 无匹配的 location,返回 “404 Not Found”
location 块支持的指令:
alias:定义 location 的其他名字,用来替代 location 中匹配的 URI 部分,在文件系统中寻找指定的文件
internal:指定该 location 仅处理内部请求
limit_except:指定 location 可以执行的 HTTP 操作(GET 也包括 HEAD)
HTTP 模块中的文件查找指令
root:设置文档的根目录,URI 会附加在该指令的值后,之后在文件系统中寻找对应的文件。该指令可在 http 块、server 块和 location 块中使用,内层不指定的话会集成外层的,指定了会使用内层的。
示例:
1 | http { |
当 http 请求访问 http://127.0.0.1/hello.html
时,将会在文件系统中找到 /home/user/nginx/l1/html/hello.html
文件并将其返回。
try_files:设置一个参数列表,请求到达时会依次去寻找对应的值,第一个找到的将会被使用。如果没有找到指定的文件或目录,则返回特定的转改吗。可以用在 server 块和 location 块中。
示例:
1 | http { |
当 http 请求访问 http://127.0.0.1/hello.html
是,将会在文件系统中寻找 /home/user/nginx/html/hello.html
文件,若是没找到的话,会使用 @not_found
指定的代理服务器进行处理,若是没有指定 @not_found
location,将会返回 404 状态码。
index:索引指令,可以指定多个文件,Nginx 会依次查找指定的文件并返回第一个找到的文件。
示例:
1 | http { |
当 http 请求访问 http://127.0.0.1/hello.html
是,将会在文件系统中寻找 /images/index.html
并返回,没找到会再去找 /images/index.php
。
客户端交互行为
return:当指定的 URI 需要返回特定的状态码时,可以使用 return
进行返回,可以在 server 和 location 模块中使用 return。
用法:
1 | return status [location/message]; |
default_type:设置响应默认的 MIME 类型,如果文件的 MIME 类型不能被 types
指令指定的类型正确地匹配,就会使用该指令指定的类型
error_page:指定一个用于访问的 URI,在遇到设置的错误代码时会使用该 URI 提供的内容返回
示例:
1 | server { |
可以使用 =
指定返回给客户端的响应码:
1 | server { |
=
也可以不直接指定响应码,而是交给后续的代理服务器返回:
1 | server { |
etag:对于静态资源,禁用自动产生 ETag 响应头(默认值为 on)
types:设置 MIME 类型到文件扩展名的映射。Nginx 在 conf/mime.types 文件中包含了大多数 MIME 类型的映射,大多数情况下导入该文件就足够了
1 | include /etc/nginx/conf/mime.types; |
日志指令
Nginx 支持在各个模块中设置访问日志,也支持为不同级别的日志设置不同格式的日志。
http 模块中的日志指令如下:
access_log:指定在哪里、如何写入访问日志
该指令可以接受多个参数:
第一个参数表示日志文件的存储位置,使用 off 可以禁用访问日志;
第二个参数指定 log_format 指令设置的日志格式,未指定时将使用预定义的 combined 格式;
第三个参数指定写缓存的大小,可以使用 gzip 进行动态压缩;
第四个参数指定缓冲日志 flush 到磁盘前能在内存中停留的最大时间
log_format:指定日志格式
在 log_format 中可以使用换行,增加日志的可读性。
示例:
1 | http { |
上述例子中使用了很多变量,任何变量都可以在 log_format 指令中使用,以下列出一些 log_format 使用的变量及其含义,其中带星号(*)的是只有在 log_format 中能使用的变量,其他变量可以在配置文件中的任何地方使用:
变量名 | 含义 |
---|---|
$body_bytes_sent | 发送到客户端的字节数,不包括响应头 |
$bytes_sent | 发送到客户端的字节数,包括响应头 |
$connection | 指定一个串号,用于标识一个唯一的连接 |
$connection_requests | 通过一个特定连接的请求数 |
$mesc | 以秒为单位的时间,毫秒级别 |
$pipe * | 请求是否是管道 |
$request_length * | 请求的长度 |
$request_time | 请求的处理时间,毫秒级别 |
$time_iso8601 * | 本地时间,ISO8601 格式 |
$time_local * | 本地时间的普通日志格式(%d/%b/%Y:%H:%M:%S %z) |
反向代理
反向代理应该是 Nginx 使用最多的一个功能了,通过正向代理,我们能隐藏自身的 IP 地址,让服务器无法识别出我们本身的地址,而反向代理,则是隐藏了响应请求的服务器的地址,由统一的代理服务器进行处理。我们的请求发送到了代理服务器后,再有代理服务器发送到真正处理请求的服务器,即为反向代理。
使用反向代理常用的指令(一般在 location 块中使用):
1 | location / { |
以上指令中,最重要的就是 proxy_pass
指令了,这是整个反向代理的基础。以下通过几个例子讲解以下 proxy_pass 的使用事项:
示例1:
1 | location /myuri/ { |
在这个例子中,客户端请求 $host/myuri/patha 时,将会由代理服务器上的 http://127.0.0.1:8080/proxy/uri/patha 处理。
示例2:
1 | location /myuri/ { |
在这个例子中,客户端请求 $host/myuri/patha 时,将会由代理服务器上的 http://127.0.0.1:8080/proxy/uri/myuri/patha 处理。
Tips:注意 proxy_pass 结尾加不加斜杠的区别,不加斜杆时会替换 location 的 URI 不分,加了斜杠会保留 location 的 URI 并拼接在 proxy_pass 的结尾。
负载均衡
负载均衡也是 Nginx 常用的一个功能,通过将请求分发到不同的上游服务器上处理,来实现负载平衡。
Nginx 中的负载均衡一般都需要和反向代理共同使用,用法如下:
1 | http { |
upstream
模块支持以下几个指令:
- ip_hash : 启用客户端 IP 哈希,将指定 IP 的请求都发送到同一台上游服务器;
- keepalive : 指定每一个 worker 进程缓存到上游服务器的连接数,使用该指令时,需要将
proxy_http_version
的值设置为 1.1,并且通过proxy_set_header
将Connection
头部设置为 “”; - least_conn : 启用负载均衡算法,将请求发送到活跃连接数最少得服务器;
- server : 上游服务器的地址,该参数有几个可选参数:
- weight 设置该服务器的权重
- max_fails 设置在 fail_timeout 事件之内尝试对该服务器进行连接的最大次数,超过该次数未连接成功,会将该服务器标记为 down
- fail_timeout 标记该服务器的连接超时时间,超过未连接会标记为 down
- down 标记该服务器不再接受任何请求
负载均衡策略
Nginx 原生支持的负载均衡策略有4中,分别是:
- 轮询:请求依次发送到每台上游服务器
- 权重:请求根据权重发送到上游服务器
- IP_HASH:相同的客户端 IP 总是会发送到同一台上游服务器
- 最少连接数:请求会优先发送到活跃连接数最少得上游服务器
以上几种负载均衡策略的配置方式如下:
1 | # 轮询 |
结语
通过这篇文章,我们已经了解了 Nginx 的基本配置和使用方法,从安装、配置文件结构到常见的配置示例,包括反向代理、负载均衡。Nginx 的高性能和灵活性使其成为处理高并发请求和大流量网站的理想选择。无论是用于简单的静态网站托管,还是复杂的反向代理和负载均衡,Nginx 都能胜任。希望这篇文章能帮助你快速上手 Nginx,探索其更多强大功能,优化你的服务器性能和网站体验。继续深入学习和实践,你会发现 Nginx 的更多潜力和应用场景。