如何入门掌握Nginx?

1,113 阅读9分钟

前言:由于抱着对Nginx的好奇心,决心把近期的个人学习主题定为了Nginx的入门学习。学习的内容大概就是学习极客时间,陶辉老师出品的《Nginx核心知识100讲》。再配合自己业务场景中的几个实战,得出的些许经验在此分享给大家。在文章中有不少截图是直接截课程中老师的ppt部分进行整理的(侵权即删,我再另作图)。—— 注意,这是教完全没概念的小白如何入门。如果已经了解过的可以直接关闭退出,不要浪费时间

简单入门Nginx

自定义编译构建Nginx

一般来说,如果没有特殊需求。Nginx的构建可以直接依赖yum install nginx或者brew install nginx就安装启动起来,非常舒服。但是当你需要添加一些模块、加一些默认不编译进Nginx的功能、热更新以及等等等等操作时,就会又需要重新学会自定义编译构建。再加上自定义构建其实并没有想象中困难,且易于后续的继续深入学习,所以我的建议是要学会这个简单的自定义构建方式。

获取编译源文件

打开Nginx的官网,直接找到Download标签下下载相应的二进制文件。然后直接下载,或者使用wget 文件路径进行下载。一般稳妥起见都是直接选择Mainline版本即可

开始编译安装

将下载下来的压缩包进行解压tar -xzvf nginx-1.15.12.tar.gz.解压完成后就能看到类似这样的文件目录.

一般来说我们不需要关心这个目录的内容,但是有兴趣的可以稍作了解,进阶的时候会用得到。

nginx-1.15.12/
├── auto 
|   ├── cc 用于编译
|   ├── lib
|   ├── os 操作系统的判断
|   ├── ... 其他内容都是为了支持config文件判定nginx是否支持特性、模块等
├── CHANGES 版本的特性以及bug修复情况重构等的概览
├── conf 示例文件
├── configure 脚本,生成中间文件,执行变编译前的必备操作.可以使用`./configure --help`查看支持参数
├── contrib 提供两个.pl脚本和vim的工具
|   ├── vim 可以执行`cp -r contrib/vim/* ~/.vim/` 来支持vim的nginx语法
├── html 提供两个标准的HTML
|   ├── 50x.html 发生500错误时的展示文件
|   ├── index.html 欢迎界面
├── man linux对于nginx的帮助文件
├── src nginx的源代码
├── objs configure编译时生成的中间文件
|   ├── ngx_modules.c 决定了编译时(make),会被编译进nginx的模块.可以用于查看该nginx共编译了什么模块进去
|   ├── nginx
|         ├── src `C语音`编译时生成的所有文件
|         ├── so 如果使用动态模块的文件

编译的过程,我们一般都是依赖configure文件提供的指令进行,非常简单。执行./configure --help可以查看执行的参数,自定义构建方式。比如,由于权限原因,我没法将nginx安装到系统的默认目录/etc/nginx/...,这时候我需要自定义编译安装的目录可以这样操作

执行会校验模块等的内容,并生成objs编译中间文件,通过里面的ngx_modules.c文件可以快捷地查看到该nginx所支持的功能特性。如果没有更多的报错,就可以执行make以及make install进行安装了。期间如果发生报错,一般都能很简便地从各大搜索引擎中找到相应的解决方案。

编译完成后,我们的操作更多就是在编译安装的那个nginx下了。你也可以看到相应的文件目录结构大抵如此,如果是直接通过yum install的目录结构应该是会有些许不同的。

大部分文件夹我也是没有接触的,这里介绍几个关键的文件:

  • sbin:存放着Nginx的命令行指令。可以将里面的执行文件软链或者直接通过./sbin/nginx + 指令执行
  • logs: 顾名思义存放着日志文件
  • conf: Nginx的配置文件夹,通过里面的配置来告诉Nginx该如何工作(我们的主要工作目录)

一般到这里后执行,./sbin/nginx -s即可启动nginx开始愉快地玩耍了。

Nginx的配置语法

不知道我的理解会不会有所偏差,Nginx的实现其实就是在使用各个模块提供的方法指令来指导Nginx的工作,所以学会它的基本配置语法自然是至关重要的。这里直接贴出语法实例图,大家可以通过实际的语法文件以及每个小箭头的注释来了解一个配置文件大抵由什么部分组成,就不细说了。

1998176F-2824-48C7-8913-F644B924D6DF.png

Nginx的指令

指令部分也是非常简单,一般用于停止、重启等等。

  1. 格式: nginx -s reolad
  2. 帮助: -? 或 -h
  3. 使用指定的配置文件: -c
  4. 指定配置指令: -g 覆写配置文件中的配置项
  5. 指定运行目录: -p 同样是覆写配置文件中的运行目录
  6. 发送信号: -s
    1. 立刻停止服务:stop;
    2. 优雅的停止服务: quit;
    3. 重载配置文件: reload;——修改配置文件中的值后要另其生效(不停止服务)
    4. 重新开始记录日志文件: reopen;
  7. 测试配置文件是否有语法错误: -t 或 -T
  8. 打印nginx的版本信息、编译信息等: -v 或 -V

Nginx的文档基本阅读和搜索方法

全文的思路一致贯穿下来的思想就是,前期学习Nginx的过程就是学习其配置。那么第一件事,就是要了解Nginx给我们提供了什么样的功能以及我们应该要怎样合理合法正确地去使用它。

我一般的思路大概是如此,首先,打开你的最心仪的搜索引擎。

image-20190508202107944-7318067.png

一般来说,如果你的需求是比较普遍的,在排名前几的教程文章中你就能搜到你想要的内容。

当然与此同时,我还会根据自己的需求,找到Nginx官方文档中的模块介绍去阅读更加科学的指令指导。每一个模块的介绍中都会包含以下三个方面:

  • Example Configuration 示例
  • Directives 所有指令的相关介绍
  • Embedded Variables 模块提供的变量

其中,我们着重了解一下指令的文档该如何阅读

image-20190508202738735.png

每部分内容都会标明指令名称和支持的参数以及默认值。剩余的内容都是描述这个指令的作用功能。往往当我们怀疑这个指令的使用方式或者功效时,即可在此查阅到完美的解读(毕竟其他教程在经过消化后的知识可能会省略掉很多细节)

ps: 官网有时候还能找到一些比较常见的配置教程(比如websocket如何配置等等,非常值得阅读)

Nginx掌握debug日志调试方法

在我初学Nginx的时候,常常因为苦于没有办法像js那样打断点、加console.log而苦恼不已,直到我发现了开启debug日志的方法,麻麻再也不用担心我不知道自己哪里配置错了。

开启debug的方法其实也很简单,在官网中也能找到相应的示例:A Debugging log

开启的方法要回归到我们上面提到的自定义编译构建,在编译时添加一行参数,开启debug模式

./configure --with-debug

之后同样是执行编译安装后,在相应想要打印错误日志的地方进行相应的配置.

error_log /path/to/log debug

之后重启nginx后便可以生效了。当然官网中其实有提供更加详细的说明,建议阅读相应的文章进一步了解。

需要掌握的重要知识点

掌握了上面的基础知识,基本上就已经具备了配置Nginx的基本能力,基于下面的路径基本能把百分之50%的需求简单解决(我个人的感觉)

但是其实在配置中Nginx还是有不少坑,也或者说有几个特别重要的知识点,如果没有掌握可能会在配置的过程中让你痛不欲生。下面,一个一个知识点过,当然如果直接买课程听陶辉老师讲,那肯定是要深入很多.这里,我只是从自身的角度出发来衡量知识点的重要性。Let’s go!!

指令合并与继承

指令的合并

Nginx的指令可以分为:值指令和动作类指令

  • 值指令:存储配置项的值,可以合并。例如:rootgizp
  • 动作类指令:指定行为,不可以合并。例如:rewriteproxy_pass

指令的继承

  • 子配置不存在时,直接使用父配置块
  • 子配置存在时,直接覆盖父配置块

Server的匹配顺序

当配置多个server配置块且匹配逻辑复杂,极有可能一个请求进来同时命中多个server块时,需要参考一下的优先级顺序来判断进入哪个Server块进行执行。

  1. 精确匹配
  2. *在前的泛域名
  3. *在后的泛域名
  4. 按文件中的顺序匹配正则表达式域名
  5. default server
    1. 第一个
    2. Listen 指定 default

Location指令 [ location [= | ~ | ~* | ^~] uri { … } 或 location @name { ... } ]

Location指令要掌握的内容其实大体和Server一致,也是匹配顺序的问题。不过有一个小细节值得注意的是Location仅匹配URI,忽略参数

image-20190509105518688-7370518.png

邪恶的If指令

当if指令块连续出现时,最后一个为真的if指令块将一直影响后续的处理。通常不使用连续的if,特别是连续为true的情况下会造成误解。

造成错误的原因

  1. if指令在rewrite阶段执行
  2. if {} 块中的配置,会在if条件为真时,替换当前请求的配置。
    1. if {} 同样向上继承父配置
    2. 当rewrite接口顺序执行时,每次if为真都会替换当前请求的配置
  3. if {} 中的配置,会影响rewrite接口之后的阶段执行。
location /only-one-if {
	set $true 1;
	// 不生效
	if ($true) {
	  add_headder X-First 1;
	}
	// 生效的模块
	if ($true) {
		 add_header X-Second 2;
	}
	return 204;
}

所以,if 指令块中要确保可以正确处理请求,不依赖if {}块外的指令。当然还可以使用break阻断后续rewrite阶段的指令执行。

Rewrite模块:Return和Rewrite的区别

return指令

命令Nginx停止处理,直接返回重定向到客户端。return指令简洁有效,相比之下,return的使用优先级高于rewrite。

rewrite指令

rewrite 规则会改变部分或整个用户请求中的 URL,主要有两个用途:

  • 相似于return的指令,通过直接返回http://
  • 控制Nginx的处理流程

在rewrite指令中,有一项falg的标志位,比较重要的有两个:

  • last: 停止处理当前的 ngx_http_rewrite_module 指令集,并开始对匹配更改后的 URI 的新 location 进行搜索(再从 server 走一遍匹配流程)。此时对于当前 serverlocation 上下文,不再处理 ngx_http_rewrite_module 重写模块的指令。

  • break: 停止处理当前的 ngx_http_rewrite_module 指令集

lastbreak的异同:

  • last 重写 url 后,会再从 server 走一遍匹配流程,而 break 终止重写后的匹配
  • break 和 last 都能阻止后面的 rewrite 指令再次执行

变量

Nginx的变量有非常多的用处,但是在使用过程中我们也务必掌握下面两个知识点,非常简单。

  • 变量的惰性求值。相信这个概念大家都不陌生了,也就是变量只有在被使用的那一刻才会执行“解析出变量的方法”。这相当于提高性能的一个方法。
  • 变量值可以为时刻变化,其值为使用那一刻的值——变量的惰性求值而带来的一个作用。也就是变量名在Nginx启动之初即已经定义好,但是假若在能够获取到相应值之前的阶段使用该变量,那么显而易见地只能获取到一个空值。亦或是在后续阶段倘若变量被改变,也不能响应回前面的执行步骤。

有关变量变化需要更深入地去掌握Nginx的运行机制和执行步骤方可更加深入理解。

实践

单域名反向代理支持多套应用服务

需求场景: 只提供单域名,然后需要根据路径前缀反向到具体的应用服务上。

难点:

  • 路由配置反向代理都是很简单的。难点在于应用服务并不知道自己被反向代理了,在获取资源内容或者是发起API请求的时候会用类似这样的URI路径:www.myapp.com/public/xxxxx或者www.myapp.com/api/xxxx/.对于这种通用的请求,可能每套服务上都会被用到,而我们是不便于去找回它的源请求位置的(不希望改动应用层的代码)

实现思路:

  1. 对于刚进入的服务会进行location的匹配。而在相应的location指令块中,我们会设置相应的服务cookie值——标识该请求的目标源应用。location中可以设置重定义cookie的值,如果没有被重定义那么则是采用cookie中的反向代理服务。
  2. 由于单页面应用如果使用history模式,单页面的路径跳转可能改写路径(并不经过nginx)。所以nginx没有时机去重写它的请求URL,在刷新页面时为了方便除了开发人员去查看当前所处系统,需要时刻注意去使用rewrite指令重写URL的内容。

nginx.png

直接看代码吧~

http {
  # 避免超时的相关操作
  fastcgi_connect_timeout 3600;
  fastcgi_send_timeout 3600;
  fastcgi_read_timeout 3600;
  proxy_connect_timeout 3600;
  proxy_send_timeout 3600;
  proxy_read_timeout 3600;
  
  upstream see_test {
     server 127.0.0.1:9501;
  }

  upstream see_poc_fjnx {
     server 127.0.0.1:9502;
  }
  
  server {
    listen 8890 default_server;
    server_name  www.myapp.com;
    
    if ($http_cookie ~* "prefix_url=see_poc_fjnx") {
        set $group see_poc_fjnx;
    }
    if ($http_cookie ~* "prefix_url=see_test") {
        set $group see_test;
    }
    
    location /public {
        proxy_pass http://$group;
        proxy_pass_header Server;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
    }

    location /api {
        proxy_pass http://$group;
        proxy_pass_header Server;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
    }
    

    location /poc/fjnx/ {
        proxy_set_header HOST $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://see_poc_fjnx/;
        proxy_redirect / http://http://www.myapp.com/poc/fjnx/;
        userid on;
        userid_name prefix_url=see_poc_fjnx;
        userid_mark =;
        userid_service 1;
    }
    
    location / {
      if ($group = see_poc_fjnx) {
          rewrite ^/(.+) http://www.myapp.com/poc/fjnx/$1 last;
      }

       proxy_set_header HOST $host;
       proxy_set_header X-Real_IP $remote_addr;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_pass http://see_test/;
       proxy_redirect / http://www.myapp.com/;
       userid on;
       userid_name prefix_url=see_test;
       userid_mark =;
       userid_service 1;
    }
  }
}

后续:其实整篇文章逻辑比较细碎和零散,对于对Nginx已经有一定了解的同学并不适用。同时如果有了一定实践经验,想要更加深入了解Nginx的话的确推荐极客时间的这门课程。


参考文档