1、Nginx简介

1.1 Apache服务器软件

介绍Nginx之前,有必要先了解一下Apache服务软件,即Apache Http Server(后面说到的Apache服务器软件就是Apache Http Server),因为Nginx的出现就是为了替代Apache的。以下是百度百科的相关词条:

Apache(音译为阿帕奇)是世界使用排名第一的Web服务器软件。它可以运行在几乎所有广泛使用的计算机平台上,由于其跨平台和安全性被广泛使用,是最流行的Web服务器端软件之一。它快速、可靠并且可通过简单的API扩充,将Perl/Python解释器编译到服务器中。

上面词条中有一个初学者容易遗漏的信息:平时常说的Apache服务器实际是说的是Apache服务器软件,是安装在服务器上对外提供web服务器功能的,而并不是什么物理服务器……Apache服务器软件具有以下特性:

  • 支持最新的HTTP/1.1通信协议;
  • 拥有简单而强有力的基于文件的配置过程;
  • 支持通用网关接口
  • 支持基于IP和基于域名的虚拟主机;
  • 支持多种方式的HTTP认证;
  • 集成Perl处理模块;
  • 集成代理服务器模块;
  • 支持实时监视服务器状态和定制服务器日志;
  • 支持服务器端包含指令(SSI);
  • 支持安全Socket层(SSL);
  • 提供用户会话过程的跟踪;
  • 支持FastCG;
  • 通过第三方模块可以支持JavaServlets。

也就是说你买了一批服务器,你想让这批服务器成为web端服务器对外提供服务,必须要安装Apache这类的基础软件,就像运行Java程序至少要有JRE一样。由于Apache服务器是免费开源的,且已经被使用了很多年,目前Apahce服务器是最流行的Web服务器端软件,市场占用率保持第一,很多公司比如百度、苹果、新浪和Tumber等都使用Apache服务端软件。除了Apache Http Server,还有其他的服务器软件,比如Nginx、lighttpd等,本文主要介绍的是Nginx。

1.2 Nginx简介

1.2.1 Nginx产生背景

1.1中提到了Apache Http Server发展时期很长,而且毫无争议是市场占用规模最大的服务器,且有着开源、跨平台、文档丰富等优点,但是Apache服务器却有着致命的缺点:Apache Http Server对高并发支持不够好,是重量级的服务器软件。在Apache服务器上运行数以万计的并发访问会导致服务器消耗大量内存,且操作系统对其进行进程或线程间的切换也消耗了大量的 CPU 资源,导致 HTTP 请求的平均响应速度降低,这些都决定了在互联网业务中Apache Http Server并不是个最优的选择,在这个背景下,轻量级高并发服务器软件Nginx就应运而生了。

1.2.2 Nginx是什么

Nginx是一款轻量级的Web服务器、反向代理服务器,由于它的内存占用少,启动极快,高并发能力强,在互联网项目中广泛应用。Nginx是俄罗斯的工程师 Igor Sysoev在为 Rambler Media 工作期间,使用 C 语言开发的一款基于事件驱动架构(后面的文章再介绍)的服务器软件。国内使用Nginx服务器软件的公司有百度、京东、新浪、网易、阿里、腾讯等,几乎所有的互联网公司都会用到。
如何查看服务器使用的Apache还是Nginx呢?可以通http请求里的头域信息里的Server字段去查看,如下图:
Nginx入门教程 - 图1

Nginx入门教程 - 图2

1.2.3 Nginx在系统架构中的位置

Nginx在我们的系统架构中处于一个什么位置呢?一句话:最靠近客户端的地方,由Nginx集群将客户请求转发给不同应用服务器(比如tomcat、Jetty),如下图:
Docker结构图(端口).png
上面图中,用户客户端请求先经过LVS负载均衡的集群,负载均衡后打到Nginx集群,再由Nginx集群将请求发给具体的业务系统集群中的某个节点,请求进入业务系统一般先要过业务系统的网关,再由网关将请求根据url转发给业务系统中具体的微服务。
这里有个问题:既然Nginx提供了负载均衡的功能,为什么在Nginx之前还要加LVS?回答这个问题前,需要先搞清两个概念:同步转发和异步转发。

  • 同步转发:同步转发是转发服务器收到请求后,立即重定向(redirect)到一个后端服务器,由客户端和后端服务器直接建立连接。只有请求流量经过转发服务器网络,返回流量由后端服务器网络返回;
  • 异步转发:异步转发是转发服务器收到请求后,在保持与客户端连接的同时,发起一个相同内容的新请求到一个后端服务器,等后端服务器返回结果后,再由转发服务器将结果返回给客户端。请求流量和返回流量都经过转发服务器网络。

LVS就是同步转发机制,Nginx是异步转发机制。LVS和Nginx搭配使用是相辅相成的关系,如果只用LVS,一旦后端服务器有问题,这次请求就失败了,而使用Nginx可以将请求再转发给其他后端服务器,直到请求成功或者试到最后一台后端服务器为止;如果只用Nginx,请求流量和响应流量都经过Nginx集群,一旦后端的服务器规模庞大时,Nginx的网络带宽就成了一个巨大的瓶颈,而前面加上LVS可以确保达到Nginx的流量是已经分流后的流量,避免了经过Nginx流量过大的问题。

2、Nginx相关的三个重要概念

使用Nginx可以实现反向代理、负载均衡和动静分离,第2节先介绍这三个基本概念,再在后面的实战中介绍如何使用Nginx实现反向代理、负载均衡和动静分离。

2.1 正向代理和反向代理

2.1.1 正向代理

因为某些原因我们无法直接访问目标服务器,比如公司内网直接访问公网一般是不通的,出于安全或者机密考虑,公司一般会在内网和外网间架设防火墙,我们可以在浏览器配置公司的代理服务器来访问公网,这个代理服务器实现的就是正向代理。
正向代理隐藏了真实的客户端,客户端的请求会被转发到代理服务器,由代理服务器将请求转发给目标服务器上,比如科学上网、使用VPN上网都是正向代理,我们平时说的代理一般指的是正向代理。正向代理代理的是客户端。正向代理示意图如下:
Nginx入门教程 - 图4

2.1.2 反向代理

反向代理隐藏的是真实的服务端,比如访问www.baidu.com,我们并不是直接访问百度的后端服务器集群,通过www.baidu.com访问到的是代理服务器,再由代理服务器将请求转发给后端服务器集群中的某一台机器。反向代理代理的是服务端。反向代理示意图如下:
Nginx入门教程 - 图5

2.2 Nginx的负载均衡

负载均衡分为硬件负载均衡和软件负载均衡,Nginx实现的负载均衡属于软件层面的负载均衡。Nginx中支持通过配置文件进行多种负载均衡算法的选择配置,默认是采用轮询的方式。

2.2.1 轮询(round-robin)

轮询为负载均衡中较为基础也较为简单的算法,它不需要配置额外参数,也是Nginx中默认的负载均衡算法。假设配置文件中共有多台服务器,该算法遍历服务器节点列表,每轮按照节点顺序依次选择一台服务器处理请求,当所有节点均被调用过一次后,该算法将从第一个节点开始进行新一轮的遍历。
由于该算法中每个请求按时间顺序依次分配到不同的服务器处理,因此适用于服务器性能较相近的集群情况,每个服务器承载相同的负载。但对于服务器性能不同的集群而言,该算法容易引发资源分配不合理等问题,因此有了加权轮询的算法。
相关upstream配置:

  1. upstream xxx_server_name{
  2. server 192.168.10.1:80;
  3. server 192.168.10.2:80;
  4. server 192.168.10.3:80;
  5. }

2.2.2 加权轮询

为了避免普通轮询带来的弊端,加权轮询应运而生。在加权轮询中,每个服务器对应一个权重 weight,一般情况下,weight 的值越大意味着该服务器的性能越好,可以承载更多的请求。该算法中,客户端的请求按权值比例分配,weight和访问概率成正比,当一个请求到达时,优先为其分配权值最大的服务器(但不是百分百概率分配到权重最大的服务器,只是说权重较大的服务器的概率较高)。加权轮询可以应用于服务器性能高低不一的集群中,使资源分配更加合理化。
相关upstream配置:

  1. upstream xxx_server_name{
  2. server 192.168.10.1:80 weight=1;
  3. server 192.168.10.2:80 weight=2;
  4. server 192.168.10.3:80 weight=3;
  5. }

2.2.3 IP哈希(IP Hash)

ip_hash 依据发出请求的客户端 IP 的 hash 值来分配服务器,该算法可以保证同 IP 发出的请求映射到同一服务器,或者具有相同 hash 值的不同 IP 发出的请求映射到同一服务器。该算法在一定程度上解决了集群部署环境下 Session 不共享的问题。 “Session 不共享问题是说,假设用户已经登录过,此时发出的请求被分配到了 A 服务器,但 A 服务器突然宕机,用户的请求则会被转发到 B 服务器。但由于 Session 不共享,B 服务器无法直接读取用户的登录信息来继续执行其他操作。 实际应用中,我们也可以利用 ip_hash实现其他一些功能。

  • 灰度发布:这里的灰度发布是指上了某服务的新版本,出于降低新版本带来的风险的考虑,想将一部分请求打到老版本的服务上,剩下的打到新版本的服务上。利用ip_hash可以将特定的一系列ip的请求打到特定的服务器上的特性,将那些无关紧要或者内部用户的请求打到新版本服务上,VIP客户的请求打到老版本服务上。
  • 文件分段上传:文件分段上传是指当上传的文件很大,比如1G等,只能将该文件拆分成若干小段上传,上传后再重新合并成完整的原文件。各大云厂商的Iaas云服务里的对象存储服务都是实现了对象多段上传的。利用 ip_hash 进行文件的分片上传,它可以保证同客户端发出的文件切片最终转发到同一服务器上,利于其接收所有切片后进行合并操作。

相关upstream配置:

  1. upstream xxx_server_name{
  2. ip_hash;
  3. server 192.168.10.1:80;
  4. server 192.168.10.2:80;
  5. server 192.168.10.3:80;
  6. }

2.2.4 url hash(第三方)

url_hash 是根据请求的 URL 的 hash 值来分配服务器。该算法的特点是,相同 URL 的请求会分配给固定的服务器,当存在缓存的时候效率一般较高(下次相同url请求来的时候直接走缓存返回而不走后端数据库或者其他微服务)。然而 Nginx 默认不支持这种负载均衡算法,需要依赖第三方库ngx_http_upstream_hash_module
相关upstream配置:

  1. upstream xxx_server_name{
  2. server 192.168.10.1:80;
  3. server 192.168.10.2:80;
  4. server 192.168.10.3:80;
  5. hash $request_uri;
  6. hash_method crc32;
  7. }

2.2.5 fail(第三方)

fail是比 weight、ip_hash更加智能的负载均衡算法,fair算法可以根据页面大小和加载时间长短智能地进行负载均衡,也就是根据后端服务器的响应时间来分配请求,响应时间短的优先分配。Nginx本身不支持fair,如果需要这种调度算法,则必须安装upstream_fair模块。
相关upstream配置:

  1. upstream xxx_server_name{
  2. server 192.168.10.1:80;
  3. server 192.168.10.2:80;
  4. server 192.168.10.3:80;
  5. fail;
  6. }

2.2.6 最少连接数(least_conn)

假设有多台服务器,当有新的请求出现时,遍历服务器节点列表并选取其中连接数最小的一台服务器来响应当前请求,连接数可以理解为当前处理的请求数。最少连接数适用于客户端与后端服务器需要保持长连接的业务。
相关upstream配置:

  1. upstream backend {
  2. least_conn;
  3. server 192.168.10.1:80;
  4. server 192.168.10.2:80;
  5. server 192.168.10.3:80;
  6. }

2.3 动静分离

首先介绍一下web中静态资源和动态资源的概念。

  • 静态资源:即前端的固定页面,包含了HTML、CSS、JS和图片的资源,不需要查数据库也不需要后台服务处理返回,直接能够显示的页面。具体的形式为:客户端发送请求到web服务器,web服务器仅是从内存中获取响应的文件返回给客户端,客户端解析并渲染显示出来。
  • 动态资源:请求需要后台程序处理(比如从数据库中读取数据),由后台程序将结果返回给web服务器,web服务器再返回给客户端解析并渲染显示出来。

再介绍一下是什么是动静分离?为什么要动静分离?
动静分离就是将网站的动态资源和静态资源分离开,将静态资源做缓存处理,这样如果请求的是静态资源则直接从缓存中获取,如果请求的是动态资源再从后台服务(比如查询数据库)获取,提高了获取资源的响应速度。

最后介绍一下Nginx中动静分离的大致思路。
为了将静态资源和动态资源分离开,将静态资源部署在Nginx上,动态资源还是在后端服务器上。如果是静态资源的请求,则直接从Nginx配置的静态资源目录下获取资源;如果是动态资源的请求,则利用Nginx的反向代理,将请求转发到后台服务器中获取动态资源,从而实现动静分离。

需要注意的是,实际项目中,静态资源一般也不直接部署在Nginx服务器上,而是通过CDN服务获取静态资源。

3、Docker安装Nginx

Docker安装Nginx,根据nginx.conf文件是进入容器内修改还是做本地的卷挂载,分为两种:

  • nginx.conf文件没有做卷挂载,适合改动少,简单使用的情况;
  • nginx.conf文件用docker宿主机上nginx.conf文件做卷挂载映射,之后就可以在主机对应的目录下修改nginx.conf文件,适合频繁修改,复杂使用的情况。

说白了,你要是不嫌每次进入容器都要输入命令,可以使用第一种;你要是嫌麻烦就像在本地该了,就用第二种。

3.1 nginx.conf不做本地卷挂载的安装

(1)下载nginx镜像

  1. docker pull nginx

(2)创建nginx容器

  1. docker run -it -d --name nginx01 -p 8080:80 nginx

将本地端口8080与nginx容器80端口映射。
(3)nginx.conf文件位置

  1. cd /etc/nginx/

可以看到相关的nginx的配置文件都在/etc/nginx目录下,如图:
image.png
这个时候直接使用vim和vi命令是打不开nginx.conf文件的,需要安装一个vim:

  1. apt-get update
  2. apt-get install vim

速度很慢…然后就可以使用vim打开nginx.conf文件了。
(4)默认首页html文件位置

  1. cd /usr/share/nginx

可以看到nginx默认页面就在/usr/share/nginx目录下,如图:
image.png
(5)nginx日志文件

  1. cd /var/log/nginx

可以看到nginx的接入日志:access.log和错误日志error.log都在/var/log/nginx这个目录下,如图:
image.png
(6)验证nginx是否安装成功
在阿里云ECS上放通80端口,方法见:https://www.yuque.com/docs/share/9214ef22-4760-405c-a7bc-cc84ea3ea872?
nginx成功后,后台curl命令ping 8080端口,如图:
image.png
外网访问8080端口,如图:
image.png

3.2 nginx.conf做本地卷挂载的安装

(1)下载指定版本的nginx镜像,因为nginx.conf文件要与nginx镜像的版本一致。
从nginx官网预先下载好stable的1.18.0版本,如图:
image.png
将压缩包解压,找到nginx.conf文件,如图:
image.png
下载nginx 1.18.0版本的镜像:

  1. docker pull nginx:1.18.0

(2)建立本地映射目录
在宿主机的/etc目录下建立nginx目录,在下面建立conf目录,将刚才本地解压缩的nginx.conf文件放到conf目录下,并加上-x权限和转换成unix格式:

  1. chmod +x nginx.conf
  2. dos2unix nginx.conf

(3)修改nginx.conf文件
主要修改的是location的配置,如下:

  1. location / {
  2. root /usr/share/nginx/html;
  3. index index.html;
  4. }

(4)自定义nginx默认页面index.html
在(2)步骤中/etc/nginx/html目录下新建index.html:

  1. touch index.html

目录如下图所示:
image.png
将index.html的内容写成如下形式:

  1. <html>
  2. <body>
  3. <h1>hello my nginx</h1>
  4. </body>
  5. </html>

html文件里写中文容易出现乱码,这里就让页面简单显示一句英文:hello my nginx。
(5)启动容器并做卷挂载

  1. docker run -d --name nginx01 -p 8080:80 -v /etc/nginx/conf/nginx.conf:/etc/nginx/nginx.conf -v /etc/nginx/html:/usr/share/nginx/html nginx:1.18.0

(6)验证修改的配置是否成功
本地发curl命令:
image.png
公网访问nginx容器:
image.png
这里可能会出现访问nginx时页面返403的问题,这里要排查两点:

  • 映射文件及目录的权限是否够,建议改成+x;
  • index.html文件是否在nginx容器下的映射目录:/usr/share/nginx/html里面。

    4、Nginx配置文件

    Nginx配置文件为nginx.conf文件,我们通过Nginx实现反向代理、负载均衡和动静分离也是通过修改nginx.conf实现的。Nginx配置文件主要分为三个部分:全局块events块http块,其中http块又可以细分为http全局块server块,Nginx配置文件的整体结构如下图:
    Nginx入门教程 - 图16
    以一份具体的nginx.conf配置文件为例,说明上图中的每一部分对应的具体的配置,如下图:
    Nginx入门教程 - 图17
    具体每个配置代表的含义见下面(参考链接:Nginx配置文件详解): ```bash
    Nginx配置文件nginx.conf中文详解

定义Nginx运行的用户和用户组

user www www;

nginx进程数,建议设置为等于CPU总核心数。

worker_processes 8;

全局错误日志定义类型,[ debug | info | notice | warn | error | crit ]

error_log /usr/local/nginx/logs/error.log info;

进程pid文件

pid /usr/local/nginx/logs/nginx.pid;

指定进程可以打开的最大描述符:数目

工作模式与连接数上限

这个指令是指当一个nginx进程打开的最多文件描述符数目,理论值应该是最多打开文件数(ulimit -n)与nginx进程数相除,但是nginx分配请求并不是那么均匀,所以最好与ulimit -n 的值保持一致。

现在在linux 2.6内核下开启文件打开数为65535,worker_rlimit_nofile就相应应该填写65535。

这是因为nginx调度时分配请求到进程并不是那么的均衡,所以假如填写10240,总并发量达到3-4万时就有进程可能超过10240了,这时会返回502错误。

worker_rlimit_nofile 65535;

events {

  1. #参考事件模型,use [ kqueue | rtsig | epoll | /dev/poll | select | poll ]; epoll模型
  2. #是Linux 2.6以上版本内核中的高性能网络I/O模型,linux建议epoll,如果跑在FreeBSD上面,就用kqueue模型。
  3. #补充说明:
  4. #与apache相类,nginx针对不同的操作系统,有不同的事件模型
  5. #A)标准事件模型
  6. #Select、poll属于标准事件模型,如果当前系统不存在更有效的方法,nginx会选择select或poll
  7. #B)高效事件模型
  8. #Kqueue:使用于FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0 和 MacOS X.使用双处理器的MacOS X系统使用kqueue可能会造成内核崩溃。
  9. #Epoll:使用于Linux内核2.6版本及以后的系统。
  10. #/dev/poll:使用于Solaris 7 11/99+,HP/UX 11.22+ (eventport),IRIX 6.5.15+ 和 Tru64 UNIX 5.1A+。
  11. #Eventport:使用于Solaris 10。 为了防止出现内核崩溃的问题, 有必要安装安全补丁。
  12. use epoll;
  13. #单个进程最大连接数(最大连接数=连接数*进程数)
  14. #根据硬件调整,和前面工作进程配合起来用,尽量大,但是别把cpu跑到100%就行。每个进程允许的最多连接数,理论上每台nginx服务器的最大连接数为。
  15. worker_connections 65535;
  16. #keepalive超时时间。
  17. keepalive_timeout 60;
  18. #客户端请求头部的缓冲区大小。这个可以根据你的系统分页大小来设置,一般一个请求头的大小不会超过1k,不过由于一般系统分页都要大于1k,所以这里设置为分页大小。
  19. #分页大小可以用命令getconf PAGESIZE 取得。
  20. #[root@web001 ~]# getconf PAGESIZE
  21. #4096
  22. #但也有client_header_buffer_size超过4k的情况,但是client_header_buffer_size该值必须设置为“系统分页大小”的整倍数。
  23. client_header_buffer_size 4k;
  24. #这个将为打开文件指定缓存,默认是没有启用的,max指定缓存数量,建议和打开文件数一致,inactive是指经过多长时间文件没被请求后删除缓存。
  25. open_file_cache max=65535 inactive=60s;
  26. #这个是指多长时间检查一次缓存的有效信息。
  27. #语法:open_file_cache_valid time 默认值:open_file_cache_valid 60 使用字段:http, server, location 这个指令指定了何时需要检查open_file_cache中缓存项目的有效信息.
  28. open_file_cache_valid 80s;
  29. #open_file_cache指令中的inactive参数时间内文件的最少使用次数,如果超过这个数字,文件描述符一直是在缓存中打开的,如上例,如果有一个文件在inactive时间内一次没被使用,它将被移除。
  30. #语法:open_file_cache_min_uses number 默认值:open_file_cache_min_uses 1 使用字段:http, server, location 这个指令指定了在open_file_cache指令无效的参数中一定的时间范围内可以使用的最小文件数,如果使用更大的值,文件描述符在cache中总是打开状态.
  31. open_file_cache_min_uses 1;
  32. #语法:open_file_cache_errors on | off 默认值:open_file_cache_errors off 使用字段:http, server, location 这个指令指定是否在搜索一个文件是记录cache错误.
  33. open_file_cache_errors on;

}

设定http服务器,利用它的反向代理功能提供负载均衡支持

http {

  1. #文件扩展名与文件类型映射表
  2. include mime.types;
  3. #默认文件类型
  4. default_type application/octet-stream;
  5. #默认编码
  6. #charset utf-8;
  7. #服务器名字的hash表大小
  8. #保存服务器名字的hash表是由指令server_names_hash_max_size 和server_names_hash_bucket_size所控制的。参数hash bucket size总是等于hash表的大小,并且是一路处理器缓存大小的倍数。在减少了在内存中的存取次数后,使在处理器中加速查找hash表键值成为可能。如果hash bucket size等于一路处理器缓存的大小,那么在查找键的时候,最坏的情况下在内存中查找的次数为2。第一次是确定存储单元的地址,第二次是在存储单元中查找键 值。因此,如果Nginx给出需要增大hash max size 或 hash bucket size的提示,那么首要的是增大前一个参数的大小.
  9. server_names_hash_bucket_size 128;
  10. #客户端请求头部的缓冲区大小。这个可以根据你的系统分页大小来设置,一般一个请求的头部大小不会超过1k,不过由于一般系统分页都要大于1k,所以这里设置为分页大小。分页大小可以用命令getconf PAGESIZE取得。
  11. client_header_buffer_size 32k;
  12. #客户请求头缓冲大小。nginx默认会用client_header_buffer_size这个buffer来读取header值,如果header过大,它会使用large_client_header_buffers来读取。
  13. large_client_header_buffers 4 64k;
  14. #设定通过nginx上传文件的大小
  15. client_max_body_size 8m;
  16. #开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成off。
  17. #sendfile指令指定 nginx 是否调用sendfile 函数(zero copy 方式)来输出文件,对于普通应用,必须设为on。如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络IO处理速度,降低系统uptime。
  18. sendfile on;
  19. #开启目录列表访问,合适下载服务器,默认关闭。
  20. autoindex on;
  21. #此选项允许或禁止使用socke的TCP_CORK的选项,此选项仅在使用sendfile的时候使用
  22. tcp_nopush on;
  23. tcp_nodelay on;
  24. #长连接超时时间,单位是秒
  25. keepalive_timeout 120;
  26. #FastCGI相关参数是为了改善网站的性能:减少资源占用,提高访问速度。下面参数看字面意思都能理解。
  27. fastcgi_connect_timeout 300;
  28. fastcgi_send_timeout 300;
  29. fastcgi_read_timeout 300;
  30. fastcgi_buffer_size 64k;
  31. fastcgi_buffers 4 64k;
  32. fastcgi_busy_buffers_size 128k;
  33. fastcgi_temp_file_write_size 128k;
  34. #gzip模块设置
  35. gzip on; #开启gzip压缩输出
  36. gzip_min_length 1k; #最小压缩文件大小
  37. gzip_buffers 4 16k; #压缩缓冲区
  38. gzip_http_version 1.0; #压缩版本(默认1.1,前端如果是squid2.5请使用1.0)
  39. gzip_comp_level 2; #压缩等级
  40. gzip_types text/plain application/x-javascript text/css application/xml; #压缩类型,默认就已经包含textml,所以下面就不用再写了,写上去也不会有问题,但是会有一个warn。
  41. gzip_vary on;
  42. #开启限制IP连接数的时候需要使用
  43. #limit_zone crawler $binary_remote_addr 10m;
  44. #负载均衡配置
  45. upstream piao.jd.com {
  46. #upstream的负载均衡,weight是权重,可以根据机器配置定义权重。weigth参数表示权值,权值越高被分配到的几率越大。
  47. server 192.168.80.121:80 weight=3;
  48. server 192.168.80.122:80 weight=2;
  49. server 192.168.80.123:80 weight=3;
  50. #nginx的upstream目前支持4种方式的分配
  51. #1、轮询(默认)
  52. #每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
  53. #2、weight
  54. #指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
  55. #例如:
  56. #upstream bakend {
  57. # server 192.168.0.14 weight=10;
  58. # server 192.168.0.15 weight=10;
  59. #}
  60. #2、ip_hash
  61. #每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
  62. #例如:
  63. #upstream bakend {
  64. # ip_hash;
  65. # server 192.168.0.14:88;
  66. # server 192.168.0.15:80;
  67. #}
  68. #3、fair(第三方)
  69. #按后端服务器的响应时间来分配请求,响应时间短的优先分配。
  70. #upstream backend {
  71. # server server1;
  72. # server server2;
  73. # fair;
  74. #}
  75. #4、url_hash(第三方)
  76. #按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。
  77. #例:在upstream中加入hash语句,server语句中不能写入weight等其他的参数,hash_method是使用的hash算法
  78. #upstream backend {
  79. # server squid1:3128;
  80. # server squid2:3128;
  81. # hash $request_uri;
  82. # hash_method crc32;
  83. #}
  84. #tips:
  85. #upstream bakend{#定义负载均衡设备的Ip及设备状态}{
  86. # ip_hash;
  87. # server 127.0.0.1:9090 down;
  88. # server 127.0.0.1:8080 weight=2;
  89. # server 127.0.0.1:6060;
  90. # server 127.0.0.1:7070 backup;
  91. #}
  92. #在需要使用负载均衡的server中增加 proxy_pass http://bakend/;
  93. #每个设备的状态设置为:
  94. #1.down表示单前的server暂时不参与负载
  95. #2.weight为weight越大,负载的权重就越大。
  96. #3.max_fails:允许请求失败的次数默认为1.当超过最大次数时,返回proxy_next_upstream模块定义的错误
  97. #4.fail_timeout:max_fails次失败后,暂停的时间。
  98. #5.backup: 其它所有的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻。
  99. #nginx支持同时设置多组的负载均衡,用来给不用的server来使用。
  100. #client_body_in_file_only设置为On 可以讲client post过来的数据记录到文件中用来做debug
  101. #client_body_temp_path设置记录文件的目录 可以设置最多3层目录
  102. #location对URL进行匹配.可以进行重定向或者进行新的代理 负载均衡
  103. }
  104. #虚拟主机的配置
  105. server
  106. {
  107. #监听端口
  108. listen 80;
  109. #域名可以有多个,用空格隔开
  110. server_name www.jd.com jd.com;
  111. index index.html index.htm index.php;
  112. root /data/www/jd;
  113. #对******进行负载均衡
  114. location ~ .*.(php|php5)?$
  115. {
  116. fastcgi_pass 127.0.0.1:9000;
  117. fastcgi_index index.php;
  118. include fastcgi.conf;
  119. }
  120. #图片缓存时间设置
  121. location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$
  122. {
  123. expires 10d;
  124. }
  125. #JS和CSS缓存时间设置
  126. location ~ .*.(js|css)?$
  127. {
  128. expires 1h;
  129. }
  130. #日志格式设定
  131. #$remote_addr与$http_x_forwarded_for用以记录客户端的ip地址;
  132. #$remote_user:用来记录客户端用户名称;
  133. #$time_local: 用来记录访问时间与时区;
  134. #$request: 用来记录请求的url与http协议;
  135. #$status: 用来记录请求状态;成功是200,
  136. #$body_bytes_sent :记录发送给客户端文件主体内容大小;
  137. #$http_referer:用来记录从那个页面链接访问过来的;
  138. #$http_user_agent:记录客户浏览器的相关信息;
  139. #通常web服务器放在反向代理的后面,这样就不能获取到客户的IP地址了,通过$remote_add拿到的IP地址是反向代理服务器的iP地址。反向代理服务器在转发请求的http头信息中,可以增加x_forwarded_for信息,用以记录原有客户端的IP地址和原来客户端的请求的服务器地址。
  140. log_format access '$remote_addr - $remote_user [$time_local] "$request" '
  141. '$status $body_bytes_sent "$http_referer" '
  142. '"$http_user_agent" $http_x_forwarded_for';
  143. #定义本虚拟主机的访问日志
  144. access_log /usr/local/nginx/logs/host.access.log main;
  145. access_log /usr/local/nginx/logs/host.access.404.log log404;
  146. #对 "/" 启用反向代理
  147. location / {
  148. proxy_pass http://127.0.0.1:88;
  149. proxy_redirect off;
  150. proxy_set_header X-Real-IP $remote_addr;
  151. #后端的Web服务器可以通过X-Forwarded-For获取用户真实IP
  152. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  153. #以下是一些反向代理的配置,可选。
  154. proxy_set_header Host $host;
  155. #允许客户端请求的最大单文件字节数
  156. client_max_body_size 10m;
  157. #缓冲区代理缓冲用户端请求的最大字节数,
  158. #如果把它设置为比较大的数值,例如256k,那么,无论使用firefox还是IE浏览器,来提交任意小于256k的图片,都很正常。如果注释该指令,使用默认的client_body_buffer_size设置,也就是操作系统页面大小的两倍,8k或者16k,问题就出现了。
  159. #无论使用firefox4.0还是IE8.0,提交一个比较大,200k左右的图片,都返回500 Internal Server Error错误
  160. client_body_buffer_size 128k;
  161. #表示使nginx阻止HTTP应答代码为400或者更高的应答。
  162. proxy_intercept_errors on;
  163. #后端服务器连接的超时时间_发起握手等候响应超时时间
  164. #nginx跟后端服务器连接超时时间(代理连接超时)
  165. proxy_connect_timeout 90;
  166. #后端服务器数据回传时间(代理发送超时)
  167. #后端服务器数据回传时间_就是在规定时间之内后端服务器必须传完所有的数据
  168. proxy_send_timeout 90;
  169. #连接成功后,后端服务器响应时间(代理接收超时)
  170. #连接成功后_等候后端服务器响应时间_其实已经进入后端的排队之中等候处理(也可以说是后端服务器处理请求的时间)
  171. proxy_read_timeout 90;
  172. #设置代理服务器(nginx)保存用户头信息的缓冲区大小
  173. #设置从被代理服务器读取的第一部分应答的缓冲区大小,通常情况下这部分应答中包含一个小的应答头,默认情况下这个值的大小为指令proxy_buffers中指定的一个缓冲区的大小,不过可以将其设置为更小
  174. proxy_buffer_size 4k;
  175. #proxy_buffers缓冲区,网页平均在32k以下的设置
  176. #设置用于读取应答(来自被代理服务器)的缓冲区数目和大小,默认情况也为分页大小,根据操作系统的不同可能是4k或者8k
  177. proxy_buffers 4 32k;
  178. #高负荷下缓冲大小(proxy_buffers*2)
  179. proxy_busy_buffers_size 64k;
  180. #设置在写入proxy_temp_path时数据的大小,预防一个工作进程在传递文件时阻塞太长
  181. #设定缓存文件夹大小,大于这个值,将从upstream服务器传
  182. proxy_temp_file_write_size 64k;
  183. }
  184. #设定查看Nginx状态的地址
  185. location /NginxStatus {
  186. stub_status on;
  187. access_log on;
  188. auth_basic "NginxStatus";
  189. auth_basic_user_file confpasswd;
  190. #htpasswd文件的内容可以用apache提供的htpasswd工具来产生。
  191. }
  192. #本地动静分离反向代理配置
  193. #所有jsp的页面均交由tomcat或resin处理
  194. location ~ .(jsp|jspx|do)?$ {
  195. proxy_set_header Host $host;
  196. proxy_set_header X-Real-IP $remote_addr;
  197. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  198. proxy_pass http://127.0.0.1:8080;
  199. }
  200. #所有静态文件由nginx直接读取不经过tomcat或resin
  201. location ~ .*.(htm|html|gif|jpg|jpeg|png|bmp|swf|ioc|rar|zip|txt|flv|mid|doc|ppt|
  202. pdf|xls|mp3|wma)$
  203. {
  204. expires 15d;
  205. }
  206. location ~ .*.(js|css)?$
  207. {
  208. expires 1h;
  209. }
  210. }

}

Nginx配置文件nginx.conf中文详解
  1. <a name="6x7gN"></a>
  2. ## 4.1 全局块
  3. 全局块里主要会设置一些影响Nginx服务器整体运行的的配置指令,通常包括以下几个部分:
  4. - 配置运行Nginx服务器用户(组);
  5. - worker process数;
  6. - Nginx进程pid存放路径;
  7. - 错误日志的存放路径;
  8. - 配置文件的引入。
  9. 这里主要说明一下worker process数和Nginx进程pid存放路径两个配置:
  10. - **worker process数:**Nginx的工作进程数,整数,Nginx服务器实现并发处理服务的关键,worker process值越大,Nginx服务器处理并发请求的能力越强,但是会受到硬件和软件等制约,在nginx.conf中的形式为:

worker_process 3;

  1. - **Nginx进程pid存放路径:**Nginx进程是作为系统的守护进程运行,需要在某个文件中保存当前运行程序的主进程号,该文件可以通过这个配置自定义,在nginx.conf中的形式为:

pid logs/nginx.pid;

  1. <a name="VGbvL"></a>
  2. ## 4.2 events块
  3. events块的配置主要影响Nginx服务器与用户的网络连接,主要包括以下几个部分:
  4. - 设置网络连接的序列化;
  5. - 是否允许同时接受多个网络连接;
  6. - 事件驱动模型的选择;
  7. - 最大连接数的配置。
  8. 这里主要介绍一下最大连接数这个配置:
  9. - **最大连接数:**最大连接数表示每一个worker_process可以同时开启的最大连接数,该值默认为512,值越大Nginx服务器处理并发请求的能力越强,在nginx.conf中的形式为:

worker_connections 1024;

  1. <a name="9LB37"></a>
  2. ## 4.3 http块
  3. http块是Nginx服务器配置中最频繁的部分,尤其是http块中的server块,代理、缓存和日志定义等绝大多数功能和第三方模块的配置都在这里。一个http块由一个http全局块和多个server块组成,这两部分的配置如下:<br />**http全局块:**
  4. - 定义MIME-TYPE;
  5. - 自定义服务日志;
  6. - 允许sendfile方式传输文件;
  7. - 连接超时时间;
  8. - 単连接请求数上限。
  9. **server块:**
  10. - 配置网络监听;
  11. - 基于名称的虚拟主机配置;
  12. - 基于IP的虚拟主机配置。
  13. 这里介绍一下http快中的単连接请求数上限:
  14. - 単连接请求数上限:该配置用户限制用户通过某一个连接向Nginx服务器发送请求的次数,在nginx.conf中的形式为:

keepalive_requests 3;

  1. <a name="13BvY"></a>
  2. ## 4.4 nginx配置文件的嵌套
  3. 如果nginx.conf文件本身不复杂,可以将全局块、event块、http全局块和server块四个部分共同写在一个配置文件,即nginx.conf中;如果nginx配置比较复杂,且强调server块里的规则(比如反向代理的转发规则)要与全局设置区分开,则需要使用到conf.d和default.conf,方法如下:
  4. 1. 在http全局块中添加nginx额外的配置引用,如下:

http { …

  1. #gzip on;
  2. include /etc/nginx/conf.d/*.conf;

}

  1. 其中`include /etc/nginx/conf.d/*.conf;`意思是将conf.d目录下文件名以.conf结尾的所有配置文件都加载进来。
  2. 2. conf.d目录下添加default.conf配置文件,如下:

server { listen 80; server_name localhost;

  1. #charset koi8-r;
  2. #access_log /var/log/nginx/host.access.log main;
  3. location / {
  4. root /usr/share/nginx/html;
  5. index index.html index.htm;
  6. }
  7. #代理配置
  8. location /business {
  9. proxy_pass http://business.app.com;
  10. }
  11. #代理配置
  12. location /user {
  13. proxy_pass http://user.app.com;
  14. }

}

  1. conf.d目录下的default.conf中配置具体的转发规则,将不同类型的配置写在不同的配置文件中,可能更清晰不容易误改吧。且nginx容器里默认都有这个conf.d目录和default.conf文件,但nginx.conf默认并没有引用conf.d目录下的配置文件。
  2. <a name="8maHC"></a>
  3. # 5、Nginx实现反向代理
  4. 其实通过Nginx实现反向代理、负载均衡和动静分离,重点就是在修改nginx的配置文件nginx.conf,通过配置正确的配置文件来实现上面三个场景。另外Nginx是支持配置文件的热加载的,所谓热加载即修改了nginx.conf配置文件后,无需重启nginx容器,仅需一条命令就可以将修改后的配置作用在正在运行的nginx的容器,该命令是:`nginx -s reload`
  5. <a name="vKLBl"></a>
  6. ## 5.1 反向代理相关配置文件
  7. 反向代理的配置主要集中在http块里的server块里的location块,location代码块的作用就是匹配对应的用户请求url,并将其映射到服务器的指定目录。详细配置如下:

location / { proxy_pass http://127.0.0.1:88; proxy_redirect off; proxy_set_header X-Real-IP $remote_addr;

  1. #后端的Web服务器可以通过X-Forwarded-For获取用户真实IP
  2. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  3. #以下是一些反向代理的配置,可选。
  4. proxy_set_header Host $host;
  5. #允许客户端请求的最大单文件字节数
  6. client_max_body_size 10m;
  7. #缓冲区代理缓冲用户端请求的最大字节数,
  8. #如果把它设置为比较大的数值,例如256k,那么,无论使用firefox还是IE浏览器,来提交任意小于256k的图片,都很正常。如果注释该指令,使用默认的client_body_buffer_size设置,也就是操作系统页面大小的两倍,8k或者16k,问题就出现了。
  9. #无论使用firefox4.0还是IE8.0,提交一个比较大,200k左右的图片,都返回500 Internal Server Error错误
  10. client_body_buffer_size 128k;
  11. #表示使nginx阻止HTTP应答代码为400或者更高的应答。
  12. proxy_intercept_errors on;
  13. #后端服务器连接的超时时间_发起握手等候响应超时时间
  14. #nginx跟后端服务器连接超时时间(代理连接超时)
  15. proxy_connect_timeout 90;
  16. #后端服务器数据回传时间(代理发送超时)
  17. #后端服务器数据回传时间_就是在规定时间之内后端服务器必须传完所有的数据
  18. proxy_send_timeout 90;
  19. #连接成功后,后端服务器响应时间(代理接收超时)
  20. #连接成功后_等候后端服务器响应时间_其实已经进入后端的排队之中等候处理(也可以说是后端服务器处理请求的时间)
  21. proxy_read_timeout 90;
  22. #设置代理服务器(nginx)保存用户头信息的缓冲区大小
  23. #设置从被代理服务器读取的第一部分应答的缓冲区大小,通常情况下这部分应答中包含一个小的应答头,默认情况下这个值的大小为指令proxy_buffers中指定的一个缓冲区的大小,不过可以将其设置为更小
  24. proxy_buffer_size 4k;
  25. #proxy_buffers缓冲区,网页平均在32k以下的设置
  26. #设置用于读取应答(来自被代理服务器)的缓冲区数目和大小,默认情况也为分页大小,根据操作系统的不同可能是4k或者8k
  27. proxy_buffers 4 32k;
  28. #高负荷下缓冲大小(proxy_buffers*2)
  29. proxy_busy_buffers_size 64k;
  30. #设置在写入proxy_temp_path时数据的大小,预防一个工作进程在传递文件时阻塞太长
  31. #设定缓存文件夹大小,大于这个值,将从upstream服务器传
  32. proxy_temp_file_write_size 64k;
  33. }
  1. 下面重点介绍以下几项配置。
  2. <a name="5ay6q"></a>
  3. ### 5.1.1 location 路径匹配
  4. nginx通过server块中location配置用以匹配不同url访问,location的语法规则如下:

[]里的内容不是必填

location [=|~|~*|^~] /uri/ {…}

  1. 注意:一个server块里可以配置多个转发规则(即多个location块)。
  2. location配置方式主要包括**精准匹配**、**普通匹配**和**正则匹配**三种,如下:
  3. | **location匹配分类** | **具体模式** | **含义** |
  4. | :---: | :---: | :---: |
  5. | 精准匹配 | location = /uri | = 表示精确匹配,只有完全匹配上才能生效 |
  6. | 普通匹配 | location ^~ /uri | 以/uri开头对URL路径进行前缀匹配,并且在正则匹配之前 |
  7. | | location /uri | 以/uri开头对URL路径进行前缀匹配,但是在正则匹配之后 |
  8. | 正则匹配 | location ~ pattern | 开头表示区分大小写的正则匹配 |
  9. | | location ~* pattern | 开头表示不区分大小写的正则匹配 |
  10. | | location / | 通用匹配,任何未匹配到location的请求都会匹配到,相当于switch中的default |
  11. 匹配优先级如下(未验证,根据博客里的规则整理):
  12. 1. 首先进行精准匹配,如果匹配成功则进行转发,否则进行第2步;
  13. 1. 进行^~普通匹配,将请求uri和所有的^~类型的普通匹配规则进行匹配,如果多个规则均命中,则选择规则最长的进行匹配并转发;否则进行第3步;
  14. 1. 进行正则匹配,正则匹配的顺序按location的编写顺序依次匹配,一旦匹配成功,则停止匹配并进行转发;否则进行第四步;
  15. 1. 进行非^~普通匹配,匹配成功则准发;否则报404 NOT FOUND
  16. 匹配顺序的流程图如下:<br />![yuque_diagram.jpg](https://cdn.nlark.com/yuque/0/2021/jpeg/1536187/1614761003425-b6a8ca58-4123-4ba7-a9ab-5f95722b3da1.jpeg#align=left&display=inline&height=1279&margin=%5Bobject%20Object%5D&name=yuque_diagram.jpg&originHeight=2554&originWidth=731&size=112366&status=done&style=none&width=366)
  17. <a name="geOiV"></a>
  18. ### 5.1.2 proxy_pass
  19. nginx.conf中有2个部分都有proxy_pass指令,分别是`ngx_stream_proxy_module`模块的`proxy_pass`指令和`ngx_http_proxy_module`模块的`proxy_pass`指令。
  20. - `ngx_stream_proxy_module`模块的`proxy_pass`指令:在server块的server全局块中使用, 只需要提供域名或ip地址和端口,不需要提供协议(比如http/https)。可以理解为端口转发,可以是tcp端口,也可以是udp端口,举例:

server { listen 127.0.0.1:12345; proxy_pass 127.0.0.1:8080; }

server { listen 12345; proxy_connect_timeout 1s; proxy_timeout 1m; proxy_pass example.com:12345; }

server { listen 53 udp; proxy_responses 1; proxy_timeout 20s; proxy_pass dns.example.com:53; }

server { listen [::1]:12345; proxy_pass unix:/tmp/stream.socket; }

  1. - `ngx_http_proxy_module`模块的`proxy_pass`指令:在sever块的location块中使用,除了需要提供域名或ip地址和端口外,还需要提供协议,如"http""https",还有一个可选的uri可以配置,举例:

server { listen 80; server_name www.test.com;

  1. # 正常代理,不修改后端url的
  2. location /some/path/ {
  3. proxy_pass http://127.0.0.1;
  4. }
  5. # 修改后端url地址的代理(本例后端地址中,最后带了一个斜线)
  6. location /testb {
  7. proxy_pass http://www.other.com:8801/;
  8. }
  9. # 使用 if in location
  10. location /google {
  11. if ( $geoip_country_code ~ (RU|CN) ) {
  12. proxy_pass http://www.google.hk;
  13. }
  14. }
  15. location /yongfu/ {
  16. # 没有匹配 limit_except 的,代理到 unix:/tmp/backend.socket:/uri/
  17. proxy_pass http://unix:/tmp/backend.socket:/uri/;;
  18. # 匹配到请求方法为: PUT or DELETE, 代理到9080
  19. limit_except PUT DELETE {
  20. proxy_pass http://127.0.0.1:9080;
  21. }
  22. }

}

  1. server块中的location块中的`proxy_pass`指令是与Nginx反向代理相关的,当请求url匹配到Nginxlocation的表达式时,会将请求转发到location块中的`proxy_pass`指令配置的ip/域名和端口上。
  2. **proxy_pass后面的url'/'和不加'/'的区别:**<br />**如果proxy_pass对应的url的末尾配置了'/',则转发到的url里不加location里的url路径;否则要加。**<br />举例:下面表格中的location配置的nginx都是用相同的url:[http://192.168.1.1/proxy/test.html](http://192.168.1.1/proxy/test.html) 进行访问的。
  3. | **location配置** | **转发到的URL** |
  4. | :---: | :---: |
  5. | location /proxy/ {<br />proxy_pass [http://127.0.0.1/;](http://127.0.0.1/;)<br />} | [http://127.0.0.1/test.html](http://127.0.0.1/test.html) |
  6. | location /proxy/ {<br />proxy_pass [http://127.0.0.1;](http://127.0.0.1;)<br />} | [http://127.0.0.1/proxy/test.html](http://127.0.0.1/proxy/test.html) |
  7. | location /proxy/ {<br />proxy_pass [http://127.0.0.1/aaa/](http://127.0.0.1/aaa/);<br />} | [http://127.0.0.1/aaa/test.html](http://127.0.0.1/aaa/test.html) |
  8. | location /proxy/ {<br />proxy_pass [http://127.0.0.1/aaa](http://127.0.0.1/aaa);<br />} | [http://127.0.0.1/aaatest.html](http://127.0.0.1/aaatest.html)(不确定是不是这个) |
  9. <a name="XTF8h"></a>
  10. ### 5.1.3 proxy_set_header
  11. proxy_set_header用来设定被代理服务器接收到的header信息。<br />语法:`proxy_set_header field value;`<br />`field `:为要更改的项目,也可以理解为变量的名字,比如host;<br />`value `:为变量的值。<br />如果不设置proxy_set_header,则默认host的值为proxy_pass后面跟的那个域名或者IP(一般写IP)。
  12. proxy_set_header常用在一个需求场景:获取请求客户端的真实ip地址,进一步统计客户端的访问次数等,如果部设置proxy_set_header,则header信息中并不会透传远程真实客户端的ip地址。<br />为了获取请求客户端的真实ip地址,在location块中proxy_set_header要添加如下配置:

proxy_set_header host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

  1. Java代码中,通过类似`request.getHeader("X-Forwarded-For")`来获取客户端的真实ip
  2. <a name="K4HZk"></a>
  3. ## 5.2 反向代理实战
  4. <a name="xR6xb"></a>
  5. ### 5.2.1 需求澄清
  6. 应用场景:
  7. > 云计算里有个很重要的概念:region,即某一个物理区域内所有机房的机器组成一个region集群,一般会将发往某个region的请求的url路径里写入region名。比如华北片区的所有机器组成cn-north-1region,华南片区的所有机器组成cn-south-1region,这样做的目的是为了使请求客户端与服务端的物理距离更近,比如请求客户端是在华北区域,那客户端访问cn-north-1region里的服务器肯定比访问cn-south-1region里的服务器,请求响应时间,速度等都高效。
  8. 假设有2region,每个region上都有一套完整的系统,每个完整的系统由两个微服务组件组成,每个微服务做实验时可以用一个tomcat容器代替。需要实现的是:发给不同region不同服务的请求统一先发往nginx,再由nginx根据url路径匹配,将请求转发到正确的tomcat容器上,效果如下图所示:<br />![nginx反向代理.png](https://cdn.nlark.com/yuque/0/2021/png/1536187/1614685051756-522c90c9-4236-46d3-abb7-78f28eadd807.png#align=left&display=inline&height=1088&margin=%5Bobject%20Object%5D&name=nginx%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86.png&originHeight=1088&originWidth=1658&size=128916&status=done&style=none&width=1658)
  9. <a name="C0cg2"></a>
  10. ### 5.2.2 配置过程
  11. <a name="6WyFd"></a>
  12. #### 5.2.2.1 配置4个tomcat容器
  13. 1)新建4tomcat容器,用来代替2region下的4个微服务<br />以cn-north-1下的服务A为例说明。<br />下载tomcat镜像:
  14. ```bash
  15. docker pull tomcat

(2)新建tomcat
在本地/etc/tomcat/tomcat01目录下新建一个hello.html,输入以下代码:

  1. <h1>hello cn-north-1 serviceA</h1>

作为访问每个tomcat容器是的html页面,输入不同的字符串加以区分。
注意hello.html修改一下权限:chmod +x hello.html
(3)启动tomcat容器:

  1. # 指定cn-north-1下的服务A对应的tomcat容器通过本地的8081端口访问,后面的tomcat容器端口号递增
  2. # 由于容器内没有vi命令,打不开文件,这里直接将html页面做卷挂载
  3. docker run -d --name tomcat01 -p 8081:8080 -v /etc/tomcat/tomcat01/hello.html:/usr/local/tomcat/webapps/hello.html tomcat

(4)放通8081端口

  1. 本地通过iptables放通内网8081端口;
  2. 阿里云ECS服务器新建安全组规则放通外网8081端口。

具体放通端口操作上面介绍了。
(5)tomcat容器相关操作
tomcat容器的webapps目录在:/usr/local/tomcat/webapps
将/usr/local/tomcat/webapps.dist目录下的内容全部复制到/usr/local/tomcat/webapps下,否则报404:

  1. cp -r /usr/local/tomcat/webapps.dist/* /usr/local/tomcat/webapps

再在/usr/local/tomcat/webapp目录下新建一个hello目录,将通过卷挂载映射上来的hello.html文件拷贝一份到hello目录。
(6)验证tomcat容器是否配置成功
浏览器访问url:http://你的服务器ip地址:8081/hello/hello.html,如下:
image.png
剩下的三个tomcat容器按照上面的方法配置,注意改一些对应的参数。
最终应该有4个tomcat容器和1个nginx容器正常运行,如图:
image.png

5.2.2.2 配置nginx.conf实现反向代理

修改后的nginx.conf部分如下:

  1. server {
  2. listen 80;
  3. server_name localhost;
  4. location /cn-north-1/serviceA/ {
  5. proxy_pass http://47.108.179.215:8081/;
  6. }
  7. location /cn-north-1/serviceB/ {
  8. proxy_pass http://47.108.179.215:8082/;
  9. }
  10. location /cn-south-1/serviceA/ {
  11. proxy_pass http://47.108.179.215:8083/;
  12. }
  13. location /cn-south-1/serviceB/ {
  14. proxy_pass http://47.108.179.215:8084/;
  15. }
  16. }

注意:

  • listen的值对应nginx容器的端口,而不是宿主机对应的端口,因为我创建nginx容器时的端口映射为-p 8080:80,即宿主机的8080端口与nginx容器的80端口做映射,因此listen的值是nginx容器的80端口;
  • 注意proxy_pass的值结尾到底要不要加’/‘,加上’/‘转发后的url没有proxy_pass的路径,否则会有;
  • 注意location的匹配规则,视频里是~ /url正则匹配,我这个例子里不需要,仅需/url也支持url路径前缀匹配;
  • 实战中我遇到了个bug,即使创建nginx时做了宿主机nginx.conf和nginx容器里nginx.conf的映射,但实际过程中对宿主机的nginx.conf做了修改后并没有同步到容器里的nginx.conf,而容器里由于没有vim/vi,apt upgrade需要花些时间,排查初期一直没有检查容器中的nginx.conf,这就导致了宿主机里的nginx.conf做了正确的修改但实际nginx容器并没有做匹配转发,一直以为是匹配规则或者端口没有找对,耽误了不少时间……

测试结果:
image.pngimage.png

image.pngimage.png
从结果看,通过统一访问一个端口8080(即映射到nginx容器的端口80),nginx可以通过匹配规则和url路径将请求转发给指定的后端服务器的服务(端口),实现了基于一个端口的不同region服务的访问。

6、 Nginx实现负载均衡

6.1 负载均衡相关配置

在http全局块里配置upstream配置,即负载均衡配置,如下:

  1. upstream 负载均衡名称 {
  2. [在这里指定负载均衡算法,可选(比如轮询算法就不需要声明),比如ip_hash];
  3. server ip:host [weight=value,可选(比如权重轮询需要在这里声明权重值,权重值一般为1-100之间的整数)];
  4. server ip:host;
  5. server ip:host;
  6. }

此外,还可以在upstream块中定义负载均衡设备ip及设备状态,如下:

  1. upstream demo {
  2. ip_hash;
  3. server 127.0.0.1:9090 down;
  4. server 127.0.0.1:8080 weight=2;
  5. server 127.0.0.1:6060 max_fails=2 fail_timeout=5s;
  6. server 127.0.0.1:7070 backup;
  7. }

对上面例子中参数进行说明:

  • down:表示当前的server暂时不参与负载;
  • weight:权重,weight值越大表示请求转发给该负载的概率越大;
  • max_fails:允许请求失败的次数,默认为1,当超过最大次数时,返回proxy_next_upstream模块定义的错误;
  • fail_timeout:max_fails次失败后,暂停的时间,要加单位s;
  • backup:其它所有的非backup机器down或者忙的时候,将请求转发给backup机器,所以这台机器压力会最轻。

除了在http全局块中进行upstream的配置外,还需要在server块中对proxy_pass进行配置,如下:

  1. server {
  2. listen nginx容器监听的端口;
  3. servre_name 域名或者ip名;
  4. location 匹配规则 {
  5. proxy_pass http://http全局块中upstream的负载均衡名称;
  6. }
  7. }

说明:

  • proxy_pass中,http后面一定要指定你采用的是哪个负载均衡upstream块,根据具体业务需求决定要不要以’/‘结尾。

    6.2 负载均衡实战

    6.2.1 需求澄清

    模拟的真实业务场景是:将一个请求转发给多台后端服务器,且后端服务器性能间有差异,需要采用加权轮询的负载均衡策略。实验中后端服务器用tomcat代替,设置5.2节中的4个tomcat容器,tomcat01-tomcat04的权重依次为:50, 10, 10, 50。

    6.2.2 配置过程

    nginx.conf中http块里的关键配置如下:

    1. http {
    2. upstream mylvs {
    3. server 47.108.179.215:8081 weight=50;
    4. server 47.108.179.215:8082 weight=10;
    5. server 47.108.179.215:8083 weight=10;
    6. server 47.108.179.215:8084 weight=50;
    7. }
    8. server {
    9. listen 80;
    10. server_name localhost;
    11. location / {
    12. proxy_pass http://mylvs;
    13. }
    14. }
    15. }

    测试:
    image.png
    不断刷新网页,结果是显示4种不同的字符串(tomcat配置见5.2小节),且四种字符串出现的概率应该符合:5:1:1:5。

    7、 Nginx实现动静分离

    7.1 动静分离相关配置

    动静分离相关的配置跟反向代理中的有很多是一样的,一般静态资源和动态资源分开配置负载均衡服务器及配置,在http块中,具体如下:

    1. http {
    2. # 静态资源配置
    3. upstream stasticlvs {
    4. server 127.0.0.1:81;
    5. server 127.0.0.1:82;
    6. }
    7. server {
    8. listen 80;
    9. server_name stastic_host;
    10. location .*\.(js|css|ico|png|jpg|eot|svg|ttf|woff) {
    11. root 静态资源目录;
    12. index 静态资源名;
    13. }
    14. }
    15. # 动态资源配置
    16. upstream dynamiclvs {
    17. server 127.0.0.1:83;
    18. server 127.0.0.1:84;
    19. }
    20. server {
    21. listen 80;
    22. server_name dynamic_host;
    23. location / {
    24. proxy_pass http://127.0.0.1;
    25. }
    26. }
    27. }

    7.2 动静分离实战

    7.2.1 需求澄清

    通过nginx实现对以下动静资源的访问:
    静态资源:

  • hello.html

  • image.jpeg

动态资源:

  • tomcat01代替后端服务

    7.2.2 配置过程

    动静分离相关配置如下,截取的http块中跟动静分离有关的:

    1. http {
    2. server {
    3. listen 80;
    4. server_name 47.108.179.215;
    5. # 静态资源
    6. location /www/ {
    7. root /data/;
    8. index hello.html;
    9. }
    10. location /image/ {
    11. root /data/;
    12. # 这个配置是显示文件目录的
    13. autoindex on;
    14. }
    15. # 动态配置
    16. location / {
    17. # 访问tomcat01的静态html页面代替访问后端动态资源
    18. proxy_pass http://47.108.179.215:8081/hello/hello.html;
    19. }
    20. }
    21. }

    注意:

  • 这里好像跟反向代理的规则有些许不一样,如果location匹配到了url,访问资源的目录,是root的值拼接location中的目录,且index的值是文件名。比如hello.html文件是在目录:/data/www/下,这个目录就是root中的/data/和location中的/www/拼接而成,index的值hello.html是文件名。

在nginx容器的以下目录放置静态资源html页面和图片:
hello.html
image.png
10.jpg
image.png

验证:

  • 访问静态html页面:

image.png

  • 访问图片:

image.png

  • 访问动态资源(tomcat01的静态html页面代替)

image.png

8、Nginx高可用集群模式

再介绍Nginx高可用之前,先介绍一下实现高可用的软件:keepalived,我们需要借助keepalived实现Nginx的高可用。

8.1 高可用keepalived

8.1.1 keepalived简介

Keepalived软件起初是专为LVS负载均衡软件设计的,用来管理并监控LVS集群系统中各个服务节点的状态,后来又加入了可以实现高可用的VRRP功能。因此,Keepalived除了能够管理LVS软件外,还可以作为其他服务(例如:Nginx、Haproxy、MySQL等)的高可用解决方案软件。
Keepalived软件主要是通过VRRP协议实现高可用功能的。VRRP是Virtual Router RedundancyProtocol(虚拟路由器冗余协议)的缩写,VRRP出现的目的就是为了解决静态路由单点故障问题的,它能够保证当个别节点宕机时,整个网络可以不间断地运行。
keepalived是如何基于VRRP协议实现高可用的呢?

  • 首先集群通过VRRP协议的竞选机制确定节点的主备身份,工作时,主节点优先获取所有的资源,备用节点处于等待状态;
  • 在keepalived服务正常工作时,主Master节点会不断地向备节点通过多播的方式发送心跳消息,通知备节点自己还活着;
  • 当主Master节点发生故障时就无法发送心跳消息,备节点也就无法继续检测到来自主Master节点的心跳了,此时备节点会调用自身的接管程序,接管主Master节点的资源及服务(切换时间最快小于1s,但切换的1s的时间内服务不可用?);
  • 当主Master节点恢复时,备节点又会释放掉自己接管的主节点的资源及服务,恢复到备用身份。

    8.1.2 keepalived.conf

    一个完整的keepalived配置文件可以包含三个部分:全局定义块VRRP实例块(包含健康检查脚本配置)以及虚拟服务器定义块。其中全局定义块和虚拟服务器块是必须的,VRRP实例块是可选的,具体细节如下:

    1. # 全局定义模块
    2. ! Configuration File for keepalived
    3. global_defs {
    4. notification_email {
    5. acassen@firewall.loc
    6. failover@firewall.loc
    7. # 邮件报警,可以不设置,后期nagios统一监控
    8. sysadmin@firewall.loc
    9. }
    10. notification_email_from Alexandre.Cassen@firewall.loc
    11. smtp_server 192.168.200.1
    12. smtp_connect_timeout
    13. # router_id为负载均衡标识,在局域网内应该是唯一的。
    14. router_id LVS_DEVEL
    15. vrrp_skip_check_adv_addr
    16. vrrp_strict
    17. vrrp_garp_interval
    18. vrrp_gna_interval
    19. }
    20. # 健康检查脚本配置
    21. vrrp_script chk_nginx {
    22. # 心跳检测脚本路径
    23. script "/etc/keepalived/check_nginx.sh"
    24. # 脚本执行间隔,每2s检测一次
    25. interval 2
    26. # 脚本结果导致的优先级变更,检测失败(脚本返回非0)则优先级 -20
    27. weight -20
    28. # 检测连续2次失败才算确定是真失败。会用weight减少优先级(1-255之间)
    29. fall 3
    30. # 检测1次成功就算成功。但不修改优先级
    31. rise 2
    32. }
    33. # VRRP实例定义块
    34. vrrp_instance VI_1 {
    35. # 表示状态是MASTER主机还是备用机BACKUP
    36. state MASTER
    37. # 该实例绑定的网卡名称
    38. interface ens33
    39. # 保证主备节点的这个配置项的值一致即可
    40. virtual_router_id 51
    41. # 权重,master权重一般高于backup,如果有多个,那就是选举,谁的权重高,谁就当选
    42. priority 100
    43. # 主备之间同步检查时间间隔,单位秒
    44. advert_int 2
    45. # 认证权限密码,防止非法节点进入
    46. authentication {
    47. auth_type PASS
    48. auth_pass 1111
    49. }
    50. # 虚拟出来的ip,可以有多个(vip)
    51. virtual_ipaddress {
    52. 192.168.1.108
    53. }
    54. }
    55. #虚拟服务器定义块
    56. #虚拟IP,来源与上面的虚拟IP地址,后面加空格加端口号
    57. virtual_server 192.168.200.100 443 {
    58. # 健康检查间隔,单位为秒
    59. delay_loop 6
    60. # 负载均衡调度算法,一般用wrr、rr、wlc
    61. lb_algo rr
    62. # 负载均衡转发规则,一般包括DR,NAT,TUN 3种
    63. lb_kind NAT
    64. # 会话保持时间,会话保持,就是把用户请求转发给同一个服务器,不然刚在1上提交完帐号密码,就跳转到另一台服务器2上了。
    65. persistence_timeout 50
    66. # 转发协议,有TCP和UDP两种,一般用TCP
    67. protocol TCP
    68. # 真实服务器,包括IP和端口号
    69. real_server {
    70. # 权重,数值越大,权重越高
    71. weight 1
    72. # 连接超时时间
    73. connect_timeout 3
    74. # 重连次数
    75. nb_get_retry 3
    76. delay_before_retry
    77. }
    78. }

    上面繁杂的配置项中,有以下几个是我们这次试验要修改适配的:

  • 全局块:

    • router_id LVS_DEVEL:同一个keepalived集群中的节点上该配置相同,且在局域网应是全局唯一的;
  • VRRP实例块:
    • state MASTER:状态只有MASTER和BACKUP两种,表明当前keepalived节点是主节点还是备节点;
    • interface ens33:绑定的网卡名称;
    • virtual_router_id 51:负载均衡标识,局域网中相同virtual_router_id的keepalived节点组成了一套高可用集群,virtual_router_id在局域网内应该是唯一的;
    • priority 100:优先级,Master的priority必须必Backup的priority高;
    • virtual_ipaddress:keepalived集群对外暴露的虚Ip。

      8.2 Nginx高可用试验

      8.2.1 实现效果

      Nginx高可用示意图.png
      如上图所示,一台节点上同时部署keepalived和Nginx,由这样的三台节点组成一个高可用集群,三台keepalived节点向客户端暴露一个共同的虚Ip;每一台Nginx都反向代理到三台Tomcat节点,Tomcat集群模拟后端服务集群。要达到的效果是:

      8.2.2 准备工作

      (1)拉取centos镜像
      1. docker pull centos:7
      (2)创建centos7容器
      1. docker run -it -d --name centos1 centos:7
      (3)进入容器centos1,并安装常用工具 ```bash docker exec -it centos1 bash

yum update yum install -y vim yum install -y wget yum install -y gcc-c++ yum install -y pcre pcre-devel yum install -y zlib zlib-devel yum install -y openssl openssl—devel yum install -y popt-devel yum install -y initscripts yum install -y net-tools

  1. **(4)将容器centos1打包成镜像,以后可以使用该镜像生成比较完善的centos容器**
  2. ```bash
  3. docker commit -a 'zj' -m 'centos7 with common tools' 5ed60794e0e4

其中5ed60794e0e4是容器centos7的ID。
(5)将打包好的镜像上传到阿里云镜像仓库中

  1. # 登录阿里云镜像仓库
  2. sudo docker login --username=158****4705 registry.cn-chengdu.aliyuncs.com
  3. # 将生成好的centos镜像打标签
  4. sudo docker tag 42840af0d2a4 registry.cn-chengdu.aliyuncs.com/docker_study_jerry/mycentos7:1.0
  5. # 将打过标签的镜像上传到阿里云镜像仓库
  6. sudo docker push registry.cn-chengdu.aliyuncs.com/docker_study_jerry/mycentos7:1.0

上传好的镜像在阿里云的镜像仓库中如下:
image.png

有关发布镜像到阿里云镜像仓库的步骤可以参考我这篇文章的8.2小节:https://www.yuque.com/docs/share/d9d0ff52-2917-45e2-b1c3-46adb6bb083f?# 《狂神Docker学习笔记》

(6)删除之前创建的容器centos1,以上面创建的新镜像创建容器

  1. docker rm -f centos1
  2. # 容器内需要使用systemctl服务,需要加上/usr/sbin/init
  3. docker run -it --name centos_temp -d --privileged 42840af0d2a4 /usr/sbin/init
  4. docker exec -it centos_temp bash

(7)安装Nginx

  1. # 使用yum安装nginx,需要包括Nginx的库
  2. rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
  3. # yum install 安装nginx
  4. yum install -y nginx
  5. # 启动nginx
  6. systemctl start nginx.service
  7. # 查看是否启动成功,出现nginx欢迎界面表示安装成功
  8. curl localhost:80

Nginx启动成功如图所示:
image.png
(8)安装keepalived

  1. # 下载keepalived
  2. wget http://www.keepalived.org/software/keepalived-1.2.18.tar.gz
  3. # 解压安装
  4. tar -zxvf keepalived-1.2.18.tar.gz -C /usr/local/
  5. # 下载插件openssl
  6. yum install -y openssl openssl-devel
  7. # 编译keepalivedcd
  8. cd /usr/local/keepalived-1.2.18/ && ./configure --prefix=/usr/local/keepalived
  9. # make
  10. make && make install

(9)将keepalived安装成系统服务

  1. mkdir /etc/keepalived
  2. cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/
  3. cp /usr/local/keepalived/etc/rc.d/init.d/keepalived /etc/init.d/
  4. cp /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/
  5. chkconfig keepalived on

(10)修改keepalived.conf文件

  1. # 备份一下keepalived.conf
  2. cp /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.backup
  3. # 修改keepalived.conf,关键配置如下:
  4. vrrp_script chk_nginx {
  5. script "/etc/keepalived/nginx_check.sh"
  6. interval 2
  7. weight -20
  8. }
  9. vrrp_instance VI_1 {
  10. state MASTER
  11. interface eth0
  12. virtual_router_id 51
  13. priority 100
  14. advert_int 1
  15. authentication {
  16. auth_type PASS
  17. auth_pass 1111
  18. }
  19. virtual_ipaddress {
  20. 192.168.200.16
  21. }
  22. }

我们将虚拟IP设置成192.168.200.16(后来发现需要设置成docker0一个网段的ip:172.17.0.100)
(11)添加keepalived心跳检测脚本

  1. # 新建脚本
  2. touch nginx_check.sh
  3. # 添加以下内容
  4. #!/bin/bash
  5. A=`ps -C nginx –no-header |wc -l`
  6. if [ $A -eq 0 ];then
  7. /usr/local/nginx/sbin/nginx
  8. sleep 2
  9. if [ `ps -C nginx --no-header |wc -l` -eq 0 ];then
  10. killall keepalived
  11. fi
  12. fi
  13. # 给nginx_check.sh脚本赋予执行权限
  14. chmod +x nginx_check.sh

(12)将keepalived设置成开机启动

  1. systemctl enable keepalived.service
  2. # 开启keepalived
  3. systemctl start keepalived.service

如果启动失败,执行如下命令:

  1. cd /usr/sbin/
  2. rm -f keepalived
  3. cp /usr/local/keepalived/sbin/keepalived /usr/sbin/

keepalived服务相关命令:

  1. # 重新加载
  2. systemctl daemon-reload
  3. # 设置开机自动启动
  4. systemctl enable keepalived.service
  5. # 取消开机自动启动
  6. systemctl disable keepalived.service
  7. # 启动
  8. systemctl start keepalived.service
  9. # 停止
  10. systemctl stop keepalived.service
  11. # 查看服务状态
  12. systemctl status keepalived.service

13)验证虚拟IP是否成功,keepalived与nginx是否连通

  1. curl 192.168.200.16

出现如下nginx欢迎页面,说明成功:
image.png
(14)将上面安装了nginx、keepalived以及一些基本工具的centos镜像再次打包成一个新的镜像,之后要用这个镜像生成主备节点的容器

  1. # 生成镜像
  2. docker commit -a 'zj' -m 'centos7 with common tools nginx keepalived' 9e7bc142e2f1
  3. # 打tag
  4. sudo docker tag 4af9e6bf2a5a registry.cn-chengdu.aliyuncs.com/docker_study_jerry/mycentos7:2.0
  5. # push到阿里云镜像仓库
  6. sudo docker push registry.cn-chengdu.aliyuncs.com/docker_study_jerry/mycentos7:2.0

(15)删除之前的容器centos_temp

  1. docker rm -f 9e7bc142e2f1

8.2.3 热备试验修改配置文件

我们准备配置一个主节点centos(nginx + keepalived),2个从节点,试验过程我们验证以下几个点:

  • 访问虚IP 192.168.200.16,请求会先转发给Master节点;
  • 停止Master节点的centos容器,再次访问虚IP 192.168.200.16(后来发现需要设置成docker0一个网段的ip:172.17.0.100),请求会转发给Slave节点;
  • 重启Master节点的centos容器,访问访问虚IP 192.168.200.16,请求会重新转发给Master节点。

(1)用上面创建好的nginx + keepalived镜像创建Master节点

  1. docker run --privileged -tid --name centos_master --restart=always 4af9e6bf2a5a /usr/sbin/init
  2. docker exec -it centos_master bash

(2)修改centos_master节点的nginx的欢迎页,并配置centos_master的keepalived.conf

  1. vim /usr/share/nginx/html/index.html

image.png

  1. vim /etc/keepalived/keepalived.conf

image.png
(3)重新加载keepalived

  1. systemctl daemon-reload
  2. systemctl restart keepalived.service

(4)创建两个slave节点的centos容器,修改nginx首页和keepalived.conf,并重新加载keepalived
image.png
image.png

8.2.4 验证工作

验证过程中开始时没有成功,排查了下原因,还需要做以下动作:

  • 实验开始时发现主备节点的nginx进程没有起来,需要手动启动nginx进程: ```bash

    查看nginx进程是否启动

    ps -ef | grep nginx

手动启动nginx进程

systemctl start nginx

  1. 如果nginx进程在,则输入`ps -ef | grep nginx`命令时结果如下:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1536187/1616169217277-5d9c61bf-7b32-4f9a-8a8e-13b11f9648c2.png#align=left&display=inline&height=120&margin=%5Bobject%20Object%5D&name=image.png&originHeight=120&originWidth=1023&size=14853&status=done&style=none&width=1023)
  2. - 开始时在宿主机curl 192.168.200.16发现网不通,在宿主机输入`ip addr`,发现docker0网桥的ip为:172.17.0.1,如下:
  3. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/1536187/1616167654898-0411a7bf-9e09-402b-9744-36c95928719e.png#align=left&display=inline&height=403&margin=%5Bobject%20Object%5D&name=image.png&originHeight=403&originWidth=1022&size=58658&status=done&style=none&width=1022)<br />由于上面设置的虚ip地址为192.168.200.16,与docker0网桥都不在一个网段,自然ping不通,因此我们要将虚IP设置成跟docker0网关的ip是一个网段的,这里我们将虚IP重新改成:172.17.0.100。
  4. > 有关docker网络的基本介绍请参考:[https://www.yuque.com/docs/share/d9d0ff52-2917-45e2-b1c3-46adb6bb083f?#](https://www.yuque.com/docs/share/d9d0ff52-2917-45e2-b1c3-46adb6bb083f?#) 《狂神Docker学习笔记》,第9节。
  5. **(1)验证宿主机curlIp,请求是否能转给Mastercentos节点**
  6. ```bash
  7. # 在宿主机上curl
  8. curl 172.17.0.100

结果成功请求到Master的centos节点,如下:
image.png
(2)停止Master的centos容器,验证请求是否能转发到从节点的centos容器

  1. # 停止主节点的centos
  2. docker stop centos_master

此时再在宿主机上curl虚ip,看到请求可以发送给备节点的centos上,如下图所示:
image.png
如果没有成功,检查centos容器里的nginx进程是否还在。
(3)重启Master的centos容器,验证请求是否能重新转发到Master节点上

  1. # 重启Master容器
  2. docker start centos_master
  3. # 进入容器
  4. docker exec -it centos_master bash
  5. # 启动nginx进程
  6. systemctl start nginx

结果是请求又重新转发到Master节点上,说明Master节点恢复后,keepalived会将从节点退出之前占有的主节点的资源,让主节点重新获取资源,结果如下图所示:
image.png
如果启动nginx进程出现一直卡着没反应的情况,输入以下命令:

  1. systemctl status nginx.service

到这里Docker + Nginx + Keepalived的高可用集群方案就介绍完了,其实试验按照我们8.2.1的实现效果是没有做完的,我们仅是实现了nginx + keepalived的高可用,没有配置每一个nginx转发到后端3个tomcat容器,这一部分在第5节已经介绍了,这里就没有继续配置这个反向代理了。

9、Nginx原理浅析

9.1 Nginx的进程模型

Nginx启动后,会有一个Master进程和多个Worker进程,Worker进程的数目是在nginx.conf配置中的全局块里的worker process配置项设置的,默认是1,查看Ninx相关进程,如下图所示:
image.png
Nginx的进程模型如下图所示:

Nginx入门教程 - 图42

9.1.1 Master进程

Master进程只有一个,Master进程充当整个进程组与用户的交互接口,请求先过Master进程,再到下面的Worker进程。Master进程不负责处理网络事件,而是交给下面的Worker进程来处理,Master进程来管理这些worker进程,Master进程有以下5个功能:

  • 接收来自外界的请求;
  • 向各Worker进程发送请求信号;
  • 监控Worker进程的运行状态;
  • 当Worker进程在异常情况下退出后,Master进程来重启新的Worker进程;
  • 进行Nginx配置的热加载。

    9.1.2 Worker进程

    基本的网络事件(请求)是放在Worker进程中处理的,每个Worker进程之间是平等且独立的,一个请求只能在一个Worker进程中处理,一个Worker进程不能处理其他进程的请求。Worker进程的数目一般设置为服务器CPU核数一致。一个Worker进程可以处理多个请求,一个Worker进程可以处理的请求数目是在nginx.conf中的Event块的worker_connections配置项设置的,默认为512。
    一个请求仅由一个Worker进程处理,采用这种方式的好处是?

  • 节省锁带来的开销。独立的进程意味着不需要加锁,省掉了加锁带来的开销,同时在定位问题看日志也会方便;

  • 高可用。采用独立的进程处理请求,可以让不同的Worker进程间互不影响,一个Worker进程因为异常退出后其他Worker进程还在工作,服务不会终止,且Master进程会很快重启新的Worker进程;
  • 无需进程切换。一个请求仅有一个进程处理,不需要进行进程间的切换,也是节省了开销。

    9.2 Nginx处理请求的流程

    Nginx的IO使用的是epoll,epoll使用了I/O复用模型,与I/O阻塞模型比较,I/O复用模型的优势在于可以同时等待多个(而不只是一个)套接字描述符就绪(这就触碰到知识盲区了,没研究过Socket),Nginx的epoll工作流程如下:
  1. Master进程接收到信号(nginx -s reload)后,读取nginx.conf配置文件,建立需要listen的socket后,根据nginx.conf中的worker process配置fork出多个Worker进程,这样每个Worker进程都可以去accept这个socket;
  2. 当一个client链接到来时,所有accept的Worker进程都会收到通知,但只有一个Worker进程可以accept成功,其他的则会accept失败。Nginx提供了一把进程间的共享锁accept_mutex来保证同一时刻只有一个Worker进程在accept链接,从而解决了惊群问题;
  3. 当一个占有共享锁的Workeraccept这个链接后,就开始读取请求、解析请求、处理请求、获取数据并返回给客户端,最后断开连接,这样一个完整的请求就结束了。

    9.3 IO多路复用 + 异步非阻塞

    这块不咋理解,引用参考博客:Nginx:基本原理篇 中的解释:

    一个worker进程可以同时处理多个请求,每个worker进程只有一个主线程,而是采用异步非阻塞的方式来处理并发请求。比如同时有多个http request的时候,worker主线程与第一条request建议连接将其处理转发给下游fast cgi后,并不会挂起等待,而是立马处理下一条,可以理解轮询处理。与多线程相比,这种事件处理方式是有很大的优势的,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量级。并发数再多也不会导致无谓的资源浪费(上下文切换),更多的并发数,只是会占用更多的内存而已。因此nginx 是非常适合处理高并发请求的。

9.4 Nginx常见问题

9.4.1 Nginx是如何实现热加载的?

所谓热加载是指一个进程在改变了其相关配置后,无需重启进程,修改的配置就会生效。

  • 修改了nginx.conf后,Master进程会重新加载配置文件;
  • 对于正在处理请求的Worker进程,Master进程会向其发送信号,等Worker进程处理完请求后就可以结束kill掉了;
  • Master进程使用新的配置创建新的Worker进程。

    9.4.2 Nginx能支持的最大请求数?

    先上答案:

  • 当Nginx作为静态资源服务器时:worker process * worker_connections / 2;

  • 当Nginx作为反向代理服务器时:worker process * worker_connections / 4;

原因:
当作为静态资源服务器时,客户端向Nginx发送一个请求,Nginx将静态资源返回回去发送一个请求,因此要除以2,如下图:
未命名文件.png
当作为反向代理服务器时,由于Nginx是异步转发,Nginx与client建立连接的同时,还会和后端要转发的服务器建立连接,因此要除以4,如下图:
未命名文件 (1).png

9.4.3 Nginx的惊群现象是什么?

惊群现象是指:当一个fd事件被触发时,所有等待这个fd的线程或者进程都会被唤醒。
Nginx的惊群现象是指:一般都是在socket的accept()会导致惊群,很多个Worker进程处于阻塞状态,此时请求从客户端进到Nginx,所有Worker进程的accept()都会返回,但是只有一个Worker进程会读取到数据,这就是Nginx的惊群。

Nginx采用共享锁accept-mutex来解决惊群问题:当一个请求到达时,只有竞争到共享锁的Worker进程才会处理请求,其他Worker进程会继续等待。结合timer_solution配置的最大超时时间来继续尝试获取共享锁。

9.4.4 Nginx为什么能处理这么多请求?