Nginx介绍

  • 开源、轻量、快速、可扩展的web服务器
  • 早期是配合apache服务使用,现在很多网站都在使用Nginx, 像Github、Netflix、Wordpress都基于nginx
  • 可配置能力弱于Apache, 性能强于apache
  • 适用于大流量、高并发环境,稳定快速、占用资源少

Nginx与Apache比较

Apache:

  • Apache是基于线程的
  • 基于进程消耗的资源多,所有apache改造为基于线程的模式
  • 而同进程下的多线程共享内存,其中一个线程出现问题,进程下所有线程都可能出现问题

Nginx:

  • nginx使用事件驱动的架构
  • 新客户端请求时不创建新的进程、线程
  • 事件处理器 处理请求任务
  • 更少的处理时间、更少的内存消耗

nginx使用了一个基于事件的软件架构。当有新的客户端请求发往服务器的时候,服务器不会为这个用户创建新的进程或新的线程,事件的软件架构会有一个核心软件的一个响应组件,称之为事件处理器,用来接受客户端的请求。把客户端对服务器的请求都变为一个TASK,来一个新的访问请求,就处理一个TASK,一个任务,处理完响应的资源就释放了。所以可以利用更少的资源、更少的系统时间,消耗更少的系统内存,提供更大的访问量的处理请求的并发能力。

因为这种基于事件的处理方式,也导致nginx更适合处理静态资源,但是处理动态内容不如Apache。

Apache与Nginx共用

  • Apache处理动态内容
  • Nginx处理静态内容

Nginx架构分析之事件驱动模型

事件驱动模型介绍

事件驱动模型是实现异步非阻塞的一个手段。事件驱动模型中,一个进程(线程)就可以了。

对于web服务器来说,客户端A的请求连接到服务端时,服务端的某个进程(Nginx worker process)会处理该请求,
此进程在没有返回给客户端A结果时,它又去处理了客户端B的请求。
服务端把客户端A以及客户端B发来的请求作为事件交给了“事件收集器”,
而“事件收集器”再把收集到的事件交由“事件发送器”发送给“事件处理器”进行处理。
最后“事件处理器”处理完该事件后,通知服务端进程,服务端进程再把结果返回给客户端A、客户端B。

在这个过程中,服务端进程做的事情属于用户级别的,而事件处理这部分工作属于内核级别的。
也就是说这个事件驱动模型是需要操作系统内核来作为支撑的。

Nginx事件驱动模型

image.png

Nginx的事件驱动模型,支持select、poll、epoll、rtsig、kqueue、/dev/poll、eventport等。
最常用的是前三种,其中kqueue模型用于支持BSD系列平台的事件驱动模型。kqueue是poll模型的一个变种,本质上和epoll一样。
/dev/poll是Unix平台的事件驱动模型,其主要在Solaris7及以上版本、HP/UX11.22及以上版本、IRIX6.5.15及以上版本、Tru64 Unix 5.1A及以上版本的平台使用。
eventport是用于支持Solaris10及以上版本的事件驱动模型。

文件描述符

文件描述符是计算机科学中的一个术语,是一个用于表述只想文件的引用的抽象化概念。

在linux当中,每个进程会在进程控制块(PCB)当中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表都有一个指向已经打开文件的指针。

每个linux进程都应该有三个标准的文件描述符,对应三个标准流。

整数值 名称 (unistd.h)符号常量 (stdio.h)文件流
0 Standard input STDIN_FILENO stdin
1 Standard output STDOUT_FILENO stdout
2 Standard error STDERR_FILENO stderr

文件描述符在形式上是一个非负整数,实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

正因为对于linux来说,一切皆是文件的思想,所以,文件描述符为该系列平台上进行设备相关的变成实际上提供了统一的方法。

注意:文件描述符只有在linux下记为0,1,2,在其他系统是不一样的,例如在windows系统下,文件描述符和信号量、互斥锁等内核对象一样都记作HANDLE。所以文件描述符的概念只在Linux和UNIX系统上可以用来使用。

select模型

Linux和Windows都支持,使用select模型的步骤是:

  • 创建所关注事件的描述符集合,对于一个描述符,可以关注其上面的读(Read)事件、写(Write)事件以及异常发生(Exception)事件。在select模型中,要创建这3类事件描述符集合。
  • 调用底层提供的select()函数,等待事件发生。
  • 轮询所有事件描述符集合中的每一个事件描述符,检查是否有相应的事件发生,如果有就进行处理。

poll模型

  • poll模型是Linux平台上的事件驱动模型,在Linux2.1.23中引入的,Windows平台不支持该模型。
  • poll模型和select模型工作方式基本相同,区别在于,select模型创建了3个文件描述符集合,而poll模型只创建一个文件描述符集合。

epoll模型

  • epoll模型属于poll模型的变种,在Linux2.5.44中引入。epoll比poll更加高效,原因在于它不需要轮询整个描述符集合,而是Linux内核会关注事件集合,当有变动时,内核会发来通知。
  • 也就是说,epoll不产生事件,但它监听并报告事件。

安装与配置

安装:

1
sudo apt install nginx

配置文件介绍

基于虚拟主机的配置

1
2
3
4
5
6
/etc/nginx/nginx.conf            # 主配置文件(包含其他配置文件)
/etc/nginx/sites-available/ # 可用的虚拟主机 (server blocks)
/etc/nginx/sites-enabled/ # 启用的虚拟主机
/etc/nginx/snippets/ # 需要复用的配置片段
/var/log/nginx/access.log # 访问日志
/var/log/nginx/error.log # 错误日志

介绍主配置文件/etc/nginx/nginx.conf

/etc/nginx/nginx.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
user www-data              # nginx进程账号
worker_processes # 进程数, 默认是auto,由nginx自己决定

events {
worker_connections # 每个进程支持最大的连接数,默认是768个,一个进程支持768个用户并发访问
}


http {
sendfile on; # 这个参数很重要,通过内核来实现对静态资源的快速访问,是静态新性能的主要来源

tcp_nopush on; # 优化了操作系统层面对tcp协议的基本实现, 通过优化数据包大小来实现。 基于TCP/IP协议请求时, 以太网最大的数据包是1518,排除二层的头 是1500,1500是IP头+TCP头+数据段总共的大小, 一般IP头和TCP头会各占20个字节,剩下的1460才是一个数据包最大传输的数据量。 如果每次发的数据都很小,每次请求数据量小,那么服务器每次就要相应很多次。为了进行优化,可以把请求的多个小数据一起打包,一次性发送过去。

tcp_nodelay on; # 优化发包延时,与上一个参数相反。 IP/TCP的特性,客户端请求之后,服务端总是要等待大概0.2秒的时间看看客户端是否还有数据发过来没有。 如果不将nodelay开启,就会要等待0.2秒, 开启就不需要等待了。

# 上面这两个参数 看上去是矛盾的, 可以理解成: 当有大量访问请求来的时候,可以攒成一个大包发送,但是如果传输的数据总量不是1460的整数倍,最后还剩下几个字节的时候,就用nodelay参数

keepalive_timeout 65; # 每次连接保持时间

types_hash_max_size 2048; # mime类静态内容hash表大小, nignx对静态内容的优化做到了极致,像文件的名称、http的头部等都属于静态内容。nginx对静态内容做了一个hash表的方法,通过对每一个静态内容计算一个hash值,存储在缓存里的一个hash表。利用缓存快速读取,从hash表中尽快匹配客户端查询的内容。一般不手动修改

gzip on; # 压缩,加快传输速度

}

虚拟主机配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/etc/nginx/sites-enabled/       # 已启用的站点

/var/www/html # web目录

/etc/nginx/sites-available/ # 可用站点

sudo vim /etc/nginx/sites-available/default

# 一些配置的介绍
server {
listen 80 default_server; # 通过端口、主机名、ip提供站点服务

root /var/www/html; # web服务端的根目录

index index.html index.htm; # 访问页面的主页索引

server_name; # 主机名

location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
# 访问ip或主机名,显示的是index,如果没有就展示目录结构,还没有就404

}

新建虚拟主机(Server Blocks)

在一台物理服务器上,通过不同的主机名区分不同的服务器。在不同的站点创建一个虚拟主机,每个虚拟主机对应一个配置文件。

在Apache称之为虚拟主机,Nginx称之为 Server Blocks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 

sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/lab.com # 拷贝配置文件,进行修改

sudo vi /etc/nginx/sites-available/lab.com
# 修改配置
server {
listen 80;

root /var/www/lab.com/;

server_name www.lab.com;
}


# 在/var/www/下创建
sudo mkdir /var/www/lab.com

# 在qq.com下创建主页
sudo vim index.html

# 创建链接,指向可用站点文件
sudo ln -s /etc/nginx/sites-available/lab.com /etc/nginx/sites-enabled/

# 重启nginx
sudo systemctl restart nginx

然后就可以请求www.lab.com

Nginx支持PHP

安装fastCGI process manager

1
2
3
sudo apt install php-fpm           # 处理nginx转发的php请求

/var/run/php/php7.2-fpm.sock # 验证 php -v

配置nginx

1
sudo vim /etc/nginx/sites-available/lab.com

更该配置如下:

1
2
# 检测nginx修改是否正确
sudo nginx -t
1
sudo vim /var/www/lab.com/index.php      # 当访问www.qq.com 主机名的时候,会显示index.php 页面内容

写测试的php代码:

1
<?php phpinfo(); ?>
1
2
3
4
5
# 重启nginx服务
sudo systemctl restart nginx

# 重启php服务
sudo systemctl restart php7.2-fpm.service

此时访问 www.lab.com,显示如下:

配置php

1
2
3
4
5
6
7
8
9
10
sudo vim /etc/php/7.2/fpm/php.ini         # php的主配置文件


# 几条配置说明
cgi.fix_pathinfo=0 # 这是有关安全的选项,改为0
file_uploads = On # 允许上传文件
upload_max_filesize = 100M # 上传文件的大小,单个文件的大小
allow_url_fopen = On # 如果想包含站点以外url的地址,可以开启
allow_url_include = Off # 是否包含本地文件
memory_limit = 128M # 指定内存大小

重启php服务:

1
sudo systemctl restart php7.2-fpm.service

Nginx支持SSL

自签名证书:

1
2
3
sudo openssl req -x509 -days 365 -sha256 -newkey rsa:2048 -nodes -keyout /etc/ssl/private/lab.key -out /etc/ssl/certs/lab.pem

# 使用openssl命令去request请求一张 x509的证书,证书的有效期为365天,使用hash算法sha256,生成一个key文件,这个key文件使用非对称加密算法,是2048位的rsa算法。 这是一张节点(服务器)证书,输出的key文件保存在/etc/ssl/private/lab.key,最终生成的公钥证书在/etc/ssl/certs/lab.pem

服务器配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sudo vim /etc/nginx/sites-available/lab.com

# 修改配置
server {
listen 443 ssl;

server_name www.lab.com;
ssl_certificate /etc/ssl/certs/lab.pem;
ssl_certificate_key /etc/ssl/private/lab.key;

}

# 保存配置并重启服务
sudo systemctl restart nginx.service

访问 https://www,lab.com

使用Let's encrypt免费证书

1
2
3
4
5
6
7
8
9
# 安装
sudo apt install update && sudo apt install certbot python-certbot-nginx

# 给nginx申请证书
sudo certbot --nginx -m admin@lab.com -d www.lab.com -d lab.com

# 域名必须能够正常解析

# 证书存放于 /etc/letsencrypt/live/ 下

这是免费证书,有效期90天

1
2
#  证书更新:
sudo certbot renew --dry-run

测试ssl:https://www.ssllabs.com/ssltest/ 这个网站可以扫描你的证书配置

正向代理和反向代理

正向代理

也就是代理客户端访问外网服务器。

  • 公司企业对员工上网进行过滤,过滤的主流方法就是用代理服务器。公司内部员工上网的时候所有的请求都不是发给外网的目标服务器的,所有的请求都是发送给公司内部搭建的代理服务器的。

  • 因为客户端所有的请求都发给代理服务器,那么代理服务器必须看见、了解要请求的外网服务器的具体的内容资源、请求的参数等,所有详细的信息都必须向代理服务器申明,否则代理服务器就无法代替你去到外网服务器拿请求的资源。

  • 所以代理服务器是能够看到应用层的东西的,客户端发给服务端应用层的东西要让代理服务器知道,这样代理服务器就可以原封不动的将请求的资源拿过来给你。

  • 代理是把对应用层的请求告诉给代理服务器,代理服务器知道后,会按照你的要求重新生成一个请求,向外网真正的服务器发送。然后代理服务器把外网服务器返回的结果拿到之后,重新打包封装,再返回给客户端。

这种工作方式就决定着代理服务器必须要了解应用层内容,这样代理服务器就可以对应用层的内容进行控制,当你发送一个视频请求,代理服务器可以拒绝请求。

这种代理方式是 代理服务器代替企业内部客户端发送请求,再将响应内容返回给客户端。

nginx做反向代理

什么是反向代理:

把nginx部署在企业web服务器的前面,让它先接受客户端的请求。客户端的请求发送给nginx的反向代理服务器,nginx的反向代理服务器根据客户端发来的请求,再代理转发给真正的web服务器。真正的服务器接收到nginx转发的请求之后,进行响应,把内容交给nginx服务器,nginx服务器再把响应交给客户端。

这个过程中,代理服务器是代替服务端接收客户端的请求,而不是代替客户端,它是代替真正的web服务端向客户端交付web服务的。称之为反向代理。

这种反向代理服务器可以部署在企业内部,在防火墙里面;更常见的是把nginx部署在互联网上,在企业防火墙外面,甚至nginx的反向代理服务器可以是云平台。

nginx反向代理过程:

通过nginx做反向代理,nginx可以承受高并发、大流量、高负载的访问请求,即使有大流量访问,也可以保证web服务器瘫痪,不会让应用程序宕机。通过nginx的过滤,把剩余的干净的请求转发给web服务器,由真正的web服务器进行处理,而真正的web服务器可以由Apache进行搭建。

结合Apache / Nginx的优势:

Apache适合处理动态内容,可以把需要程序逻辑处理的动态的内容部署在apache服务器集群上,然后通过前端的nginx做反向代理。大流量、高并发打向nginx,nginx可以抗住,把有害的、恶意的请求过滤之后,干净的请求转发给后面的apache服务器集群做处理,处理一些动态内容,响应的内容给nginx,nginx再交付给客户端。

  • Apache不适用于高并发、高负载的环境
  • Nginx没有内建支持动态内容处理能力
  • 结合Apache / Nginx的优势
    • Nginx接受入站的请求和静态内容缓存,动态请求发给Apache
    • Apache完成动态内容处理
    • Nginx作为反向代理,动态请求转发给Apache

Nginx做反向代理部署

本机安装Apache

由于apache和nginx都会默认侦听80端口,会有冲突,所以先停掉nginx

1
2
3
4
5
# 先停掉nginx
sudo systemctl stop nginx.service

# 安装apache
sudo apt install apache2

修改Apache 侦听端口

让nginx侦听80端口,修改apache侦听端口

1
sudo /etc/apache2/ports.conf

修改如下:

修改apache虚拟主机的站点配置

1
sudo vim /etc/apache2/sites-available/000-default.conf

修改:

1
2
# 重启apache服务
sudo systemctl restart apache2.service

修改ngixn的配置

需要将nginx 80端口转发给apache默认的页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sudo vim /etc/nginx/sites-available/default

# 修改配置,只转发动态的内容,静态的内容留在本地
location ~ \.php$ {
proxy_pass http://127.0.0.1:8080; # 本机apache
proxy_set_header X-Real-IP $remote_addr; # 不声明的话,apache认为所有的请求都来自本机的nginx,增加这些头就可以知道外部请求的ip

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Forward-Proto $scheme;
}
location ~* \.(js|css|jpg|png|svg|html|htm)$ {
expires 10d; # 设置有效时间
}

重启nginx服务

Apache支持php

这样在访问 192.168.32.128:80时,动态的内容如php 会转发给 本机的8080端口

这就需要apache支持php服务,处理php的内容

安装:

1
sudo apt install php7.2 libapache2-mod-php7.2

验证:

1
php -v

启用模块:

1
a2enmod php7.2

修改索引文件:

1
sudo vim /etc/apache2/mods-available/dir.conf

测试php

1
2
3
4
cd /var/www/html/
sudo vim index.php
# 添加测试函数
<?php phpinfo(); ?>

Nginx与Apache不在一个服务器部署

准备的实验环境:

一台Ubuntu虚拟机安装Apache服务,另外两台安装Nginx服务,由宿主机向第一台nginx服务器发送访问请求,第一台ngixn服务器把访问请求转发给第二台nginx服务器,第二台nginx服务器再把请求转发给apache服务器。

就是通过宿主机,中间跨两道nginx代理,最终访问到apache上的web页面

  • apache 192.168.8.101
  • nginx1 192.168.8.102
  • nginx2 192.168.8.103

在nginx1中修改:

1
2
3
4
5
6
7
8
9
sudo vim /etc/nginx/sites-available/default

# 修改配置:
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
proxy_pass http://192.168.8.103 # 转发到nginx2
}

nginx2:

1
2
3
4
5
6
7
8
9
sudo vim /etc/nginx/sites-available/default

# 修改配置:
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
proxy_pass http://192.168.8.101 # 转发到apache
}

都重启服务之后

宿主机访问 nginx1 http://192.168.8.102 结果访问到了apache的web页面

Nginx负载均衡

负载均衡介绍

apache不适合做大流量、高并发的工作,如果 客户端访问请求量比较大,都是正常的请求,nginx能抗住,但一台apache可能扛不住,这样可以做apache服务器集群。由前面的一个nginx接收到客户端所有的访问请求之后,由nginx反向代理把请求转发给后面诸多的apache服务器。

而且nginx可以做负载均衡,比如说1秒有一万个请求发给nginx,nginx可以承受住,但如果把这一万个请求发给apache,apache就受不了了。多台apache服务器集群,nginx可以负载均衡,按照一定权重把请求分摊给多个apache服务器。

先在nginx服务器里创建一个组,把后面的apache的ip写到这个组里面,然后在nginx配置里面指定,凡是访问nginx的请求,都要按一定权重比例分给后面的apache里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sudo vim /etc/nginx/sites-available/default

# 在server前面定义组
upstream srvs {
least_conn; # 负载均衡算法:最少连接数优先
server 10.1.1.1:80 weight=1;
server 10.1.1.2:80 weight=2;
}

# 转发组
server {
location / {
proxy_pass http://srvs;
}
}

负载均衡方法

round-robin

轮询,默认使用方法,按照权重

least_conn

最少连接数优先

客户端的所有请求都是发给nginx,由nginx把请求分摊给后面的web服务器。后端的服务器如果有增有减,有的服务器可能刚上线,刚加入这个组,它维持的连接数还比较少。比如说这个组由5台服务器,前面4台都各自维持了100个连接了,第5台服务器刚练上来,还没有连接数,这意味着它的负载最小,最空闲,就把新的请求发给它处理。

least_time

响应速度最快

你可能连接数很少,只有50个,人家有100个连接。但是但从连接数上不能反映出工作能力的强弱。人家的配置比你好,新一代cpu、固态硬盘,而你老旧的cpu、机械硬盘。人家100个连接响应都很快,而你50个就不行了。

hash

基于请求的hash值

对每一个请求做一个hash值,如果hash值相同,就永远发给这个目标的服务器。

更适合来自同样的计算机的相同请求,每次都由同一个后端服务器来响应。

ip_hash

基于IP地址的hash值

以这个客户端IP地址发给nginx请求,nginx算完hash值之后,凡是来自与你这个IP的所有请求都会被转发给同一个服务器,除非这个服务器宕机,那么会重新计算hash值分配。

这种场景适合需要长时间的 session会话连接请求。