亿级流量Nginx架构设计

10,710 字 · 约 36 分钟Nginx#Nginx#反向代理#负载均衡#高并发

Nginx从零到一

一、Nginx简介

Nginx是一个具有高性能的HTTP和反向代理的Web服务器,同时也是一个POP3、SMTP、IMAP代理服务器

Nginx使用C语言开发,2004年发布

Web服务器主要功能是为服务提供网上信息浏览服务

HTTP是超文本传输协议,是用于从Web服务器传输超文本到本地浏览器的传输协议,是互联网上应用最为广泛的一种网络协议

POP3、SMTP、IMAP:都是邮件相关信息

正向代理是指客户端通过代理服务器向服务器请求资源

反向代理是指服务器通过代理将资源交给客户端

常见服务器

  • IIS:微软
  • Tomcat:运行Servlet和JSP的Web应用软件
  • Apache
  • Lighttped
  • GoogleServers

Nginx优点

  • 速度更快,并发更高:多进程和IO多路复用(epoll)的底层实现
  • 配置简单,扩展性强:本身就有很多模块组成
  • 高可靠性:Nginx采用多进程运行,一个Master进程和N个worker进程
  • 热部署:可以在不停止服务器的情况下,对Nginx进行文件升级、更新配置和更换日志文件
  • 成本低、BSD许可证

常用功能特性

  • 静态资源部署
  • Rewrite地址重写
  • 反向代理
  • 负载均衡
  • Web缓存
  • 环境部署
  • 用户认证模块

核心组成

  • Nginx二进制可执行文件
  • nginx.conf核心配置文件
  • error.log:错误日志记录
  • acces.log访问日志记录

二、安装

目录结构分析

  • conf:配置文件目录:包含了fastcgi、scgi、uwcgi参数的配置文件,所谓CGI是通用网关接口,就是服务端获取到请求和数据后调用CGI程序处理及响应结果给客户端的一种标准规范;编码转换相关;mime.types文件类型相关;nginx.conf
  • html:存放站点文件
  • logs:日志文件目录;访问目录、错误日志、Nginx进程PID
  • sbin:二进制可执行文件

Nginx服务启停命令

Nginx的启停在Linux系统中有很种方式,比较常用的是使用Nginx信号控制和命令行控制

Nginx默认采用多进程方式运行,一个Master进程和多个worker进程

管理员通过信号控制Master,Master根据信号调度worker。而发送信号就需要知道Master的进程ID,这可以通过两种方式获取:

  1. ps -ef | grep nginx
  2. more ./logs/nginx.pid

有了进程ID之后,可以发送的信号有如下几种:

  • TERM/INT:立即关闭整个服务
  • QUIT:将所有资源释放后再关闭
  • HUP:重读配置文件并使用服务对新配置项生效
  • USR1:重新打开日志文件,可以用来进来日志切割
  • USR2:平滑升级到最新版Nginx:会开启两个Master进程,新Master进程ID被写入nginx.pid中,另外创建一个nginx.pid.oldbin文件存储旧Master进程ID,当升级完成后,新Master进程会发送QUIT信号给旧Master进程,让其处理完当前请求后就关闭
  • WINCH:所有子进程不再接收处理新链接,相当于给worker进程发送Quit指令

调用命令格式为:

kill -singal PID

命令行控制就是使用sbin目录下的nginx可执行文件进行Nginx状态的控制

可以通过nginx -h查看有哪些参数可用

Usage: nginx [-?hvVtTq] [-s signal] [-p prefix] [-e filename] [-c filename] [-g directives]

Options: -?,-h : this help -v : show version and exit -V : show version and configure options then exit -t : test configuration and exit -T : test configuration, dump it and exit -q : suppress non-error messages during configuration testing -s signal : send signal to a master process: stop, quit, reopen, reload -p prefix : set prefix path (default: /usr/local/nginx/) -e filename : set error log file (default: logs/error.log) -c filename : set configuration file (default: conf/nginx.conf) -g directives : set global directives out of configuration file

Nginx服务和新增模块

不停机更新

两种方案:

  • 信号
  • make命令

两种方案的都需要做的准备工作:

将新版本Nginx安装包上传上去,然后解压缩,并执行configurer和make命令,切忌不要make install

使用信号方式:

  1. 将版本nginx可执行文件做一个备份
  2. 将新版本Nginx objs目录下的nginx可执行文件拷贝到旧版本的位置
  3. 发送信号USR2给旧版本Nginx Master进程,会新建Master进程,该Master进程就是新版本Nginx的Master进程
  4. 发送QUIT信号给旧版本Nginx Master进程

使用Nginx安装目录下的make命令完成升级:

前两步与使用信号方式是一样的,即备份旧版本Nginx可执行文件,将新版本Nginx可执行文件复制到旧版本的位置,不同的是下面这一步:

  1. 在新版本Nginx安装目录下执行如下命令:

    make upgrade
    

这种方式底层也是使用的信号,只不过Nginx对其进行了封装

新增模块也可以使用这种方式

三、Nginx核心配置文件结构

#表示注释,删除掉这些注释掉的内容之后的nginx.conf文件内容如下:

worker_processes  1;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;

        location / {
            root   html;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

    }

}

这份配置文件可以分为三大块:

  1. 全局块:在events和http之外的配置:设置Nginx整体运行的指令
  2. events块:配置与用户网络连接相关的指令
  3. http块:最重要的配置核心;一个http块可以配置多个server块;server块中包含多个server_name和location的相关配置

每个块中有指令名和指令值组成

全局块

user指令

user指令用于配置运行Nginx服务器的worker进程的用户和用户组

语法:user user[group]

默认值:nobody

该属性也可以在编译的时候指令:./configure --user=user --group=group,如果两个地方都配置了,以配置文件中的为主

使用user指令可以指定启动运行工作进程的用户和用户组,这样对于系统的权限访问控制的更加精细,也更加安全

work process指令

master_process:用来指定是否开启工作进程

语法:master_process on | off

默认值:on

位置:全局块

worker_process:用来配置Nginx生成工作进程的数量,这个是Nginx实现并发处理服务的关键,理论上来说worker_process的值越大,可以支持的并发处理量就越多,但事实上这个值的设定受限于服务器自身的限制,建议将该值设置为CPU的内核数

语法:worker_processes num | auto

默认值:1

位置:全局块

其他指令

daemon:设置Nginx是否以守护进程的方式启动

语法:daemon on|off;

默认值:on

位置:全局块

pid:用来设置Nginx当前Master进程的进程ID存储的文件路径

语法:pid file;

默认值:/usr/local/nginx/logs/nginx.pid

位置:全局块

该属性可以通过./configure --pid-path=PATH来设定

如果两个地方都设置了,以配置文件为准

error_log:用来配置Nginx错误日志的存放路径

语法:error_log file[日志级别];

默认值:error_log logs/error.log error;

位置:全局块、http、server、location

该属性可以通过./configure --error-log-path=PATH来指定

其中日志级别有:debug、info、notice、warn、error、crit、alert、emerg

include:用来引入其他配置文件,使得Nginx的配置更加灵活

语法:include file;

默认值:无

位置:任意

events块

accept_mutex:用来设置Nginx网络连接序列化

语法:accept_mutex on|off;

默认值:on

位置:events

这个配置主要解决常说的惊群问题,即某一时刻,只有一个请求过程,但是有多个worker进程被唤醒,最后只有一个worker进程处理该请求,如果将上述设置为on,则会对多个Nginx进程接收设置序列号,一个一个唤醒,避免了多进程对连接的争抢

multi_accept:用来设置是否允许同时多个网络连接

语法:multi_accept on|off;

默认值:off;

位置:events

如果multi_accept被禁止了,Nginx一个工作进程只能同时接收一个新的连接,否则一个工作进程可以同时接收所有新连接

work_connections:用来配置单个worker进程最大的连接数

语法:worker_connections number;

默认值:512

位置:events

这里的连接数不仅仅包含与客户端的连接,还包含所有可能的连接,另外,number值不能超过操作系统所能打开的最大文件句柄数量

use:用来设置Nginx服务器选择哪种事件驱动来处理网络消息

语法:use method;

默认值:根据操作系统而定

位置:events

注意,此处的事件驱动类型是Nginx优化的一个重要类型,Method的可选值有:select、poll、epoll、kqueue等,这些值也可以在编译的时候指定

http块

定义mime-type

MIME-Type是网络资源的媒体类型,服务器将该该资源的类型加入到响应头中,浏览器依据此类型选择合适的处理方式,比如某些类型不支持打开,那打开就变成了下载

在Nginx的配置文件中,默认有两行配置:

include       mime.types;
default_type  application/octet-stream;

这两行配置就是指定浏览器该如何处理请求的资源,当某些资源类型没有在mime.types文件中时,默认处理为二进制流文件

default_type:用来配置Nginx响应前端请求默认的MIME类型

语法:default_type mime-type;

默认值:text/plain;

位置:http、server、location

如下示例展示了我们要求浏览器如何处理资源:

location /get_text {
  default_type text/html;
  return 200 "<h1>This is Nginx text!</h1>";
}

location /get_json {
  default_type application/json;
  return 200 "{'username': 'tom', 'passwd':'***'}";
}
自定义服务日志

Nginx中日志类型分为access.log和error.log两种

一个是访问日志,一个是错误日志

Nginx支持对服务器日志的格式、大小、输出等进行设置,需要使用到两个指令:access_log和log_format

access_log:用来设置用户访问日志的相关属性

语法:access_log path[log_format[buffer=size]]

默认值:logs/access.log combined;

位置:http、server、location

log_format:用来指定日志的输出格式

语法:log_format name[escape=default|json|non] string...;

默认值:log_format combined “...”;

位置:http

其他指令

send_file:用来设置Nginx服务器是否使用sendfile()传输文件,该属性可以大大提高Nginx处理静态资源的性能

语法:sendfile on|off;

默认值:off

位置:http、server、location

keepavlive_timeout:用来设置长连接的超时时间

提升请求的处理效率

语法:keepalive_timeout time;

默认值:75s

位置:http、server、location

keep_requests:用来设置一个keep-alive连接使用的次数

语法:keep_requests number;

默认值:100

位置:http、server、location

server块和location块

一个http块中可以包含多个server块和location块,虽然这两个块隶属于http块,但因为它们是很重要的一块内容,因此将它们单独放在一节

server块

listen:指定监听的端口

server_name:虚拟主机名,可以设置主机名、域名等

listen和server_name共同构成了访问路径的前半部分,即URL-URI的部分

location块

location 后面加的是所要访问的资源的路径

root:资源对应的目录

index:该路径访问的默认页

error_page:后面加状态码 即对应的跳转路径

块的优先级

某些配置既可以放在http块中,也可以放在server块或者location块中,如果三个块中都放置了,那么以谁为准呢?

其实它采用的是就近原则,它会寻找离它最近的那个配置所配置的内容

比如access_log在三个块中都配置了,那么当访问一个固定的URI时,如果该URI对应的location中有access_log配置,那么就采用它的,如果没有,就看server块中有没有配置,有就采用,没有就使用http块中的配置

四、配置实例案例

需求

  1. 四个访问路径:server1、2/location1、2;
  2. 访问资源不存在返回自定义的404页面
  3. 将server1和server2使用不同的配置文件分割
  4. 为server1和server2各自建立访问日志

配置过程

mkdir -p custom/html
mkdir conf.d

准备静态资源,一个404页面和4个html文件

配置nginx.conf文件:

worker_processes  1;
error_log logs/error.log;
pid logs/nginx.pid;

events {
    accept_mutex on;
    multi_accept on;
    worker_connections  1024;
    use epoll;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;
    
    log_format server1 '===> server1 access log format';
    log_format server2 '===> server2 access log format';
    
    include /usr/local/nginx/conf.d/*.conf;

    server {
        listen       80;
        server_name  localhost;

        location / {
            root   html;
            index  index.html index.htm;
        }
        
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

    }

}

在conf.d目录下新建server1.conf和server2.conf,配置如下:

server {
  
  listen 8081;
  server_name localhost;
  access_log /usr/local/nginx/custom/html/server1/access.log server1;
  
  location /server1/location1 {
    root /usr/local/nginx/custom/html;
    index sr1_lo1.html;
  }
  
  location /server1/location2 {
    root /usr/local/nginx/custom/html;
    index sr1_lo2.html;
  }
  
  error_page 404 /404.html;
  location = /404.html {
    root /usr/local/nginx/custom/html;
    index 404.html;
  }
}

server2的配置文件与此类型,就不多赘述了

测试

先运行nginx -t测试配置文件是否正确

重新加载配置文件:nginx -s reload

测试通过

五、Nginx静态资源部署

概述

所谓静态资源就是指在服务端真实存在且能拿来展示的一些文件,比如常见的HTML、CSS、JavaScript、图片、视频等资源

动态资源即指在服务器上真实存在但是想要获取需要经过一定的业务处理逻辑,根据不同条件展示不同的内容

Nginx处理静态资源,需要考虑:

  1. 静态资源的配置指令
  2. 静态资源的配置优化
  3. 静态资源的压缩配置指令
  4. 静态资源的缓存处理
  5. 静态资源的访问控制,包括跨域和防盗链问题

静态资源配置指令

listen指令

用来配置监听端口

语法:listen address:[port][default_server]...;

默认值:listent *:80|*:8000

位置:server

listen的设置比较灵活,比如:

listen 127.0.0.1:80; //监听指定IP的指定端口

listent 127.0.0.1; //监听指定IP的所有端口

listen 80; //监听指定端口

listen *:80; //监听指定端口

listen有很多可以配置的属性

default_server属性是标识符,用来将此虚拟主机设置为默认主机,所谓的默认主机是指如果没有匹配到对应的IP和端口,则会默认执行;如果不指定默认使用的是第一个个server;指定了之后,没有找到对应的,则会使用指定的server

server_name指令

用来设置虚拟主机服务名称

语法:server_name name ...;

name可以提供多个,中间用空格分隔

默认值:“”

位置:server

关于server_name有三种方式配置:分别是精确匹配、通配符匹配和正则表达式匹配

精确匹配:

server_name  localhost top.academic.top;

可以通过在hosts文件中添加一个虚拟域名来使用,这里用到的知识是:当浏览器解析域名时,先会在本地文件中寻找IP地址,找不到再向DNS服务器提交请求

通配符匹配

通配符可以放在域名首部或尾部,不能出现在中间

server_name *.academic.com dev.academic.*

正则表达式匹配:

正则表达式匹配需要使用~作为正则表达式开头

server_name的匹配执行顺序:

  • 精确匹配
  • 前置通配符匹配
  • 后置通配符匹配
  • 正则表达式匹配
  • 被默认的default_server处理,如果没有默认找第一个
location指令

用来设置请求的URI

语法:location [=|~|~*|^~|@] uri{...}

默认值:-

位置:server、location

URI是待匹配的请求字符串,可以不包含正则表达式,也可以包含正则表达式

Nginx在搜索匹配location的时候,先使用不含正则表达式的进行匹配,找到匹配度最高的那个,然后再通过包含正则表达式的进行匹配,如果能匹配到就直接访问,匹配不到,就使用刚才匹配度最高的那个location处理请求

不带任何符号:必须以指定模式开始

location /aaa {}

所谓以指定模式开始,即任何以aaa开头的URI都能匹配到这个location

=:精确匹配,用于不包含正则表达式的URI前,必须以指定的模式精确匹配

location = /aaa {}

~:用于表示当前URI中包含了正则表达式,并且区分大小写

~*:用于表示当前URI中包含了正则表达式,并且不区分大小写

location ~^www\.academic\.com$ {}

^~:用于不包含正则表达式的URI前,功能和不加符号的一致,唯一不同的是,如果模式匹配,那么停止搜索其他模式

location ^~/abc {}
root、alias指令

root指令用于设置请求的根目录

语法:root path;

默认值:root html;

位置:http、server、location

path为Nginx服务器收到请求以后查找资源的根目录路径

alias:用来更改location的URI

语法:alias path;

默认值:-

位置:location

path为修改后的根路径

以上两个指令都可以指定访问资源的路径,不同之处在于:

  • root的处理结果是:root路径+location路径
  • alias的处理结果是:使用alias路径替换location路径
index指令

用来设置网站的默认首页

语法:index file...;

默认值:index index.html;

位置:http、server、location

index后面可以跟随多个设置,如果访问的时候没有指定具体访问的资源,则会依次进行查找,找到第一个为止

error_page指令

用来设置网站的错误页面

语法:error_page code... [=[response]] uri;

默认值:-

位置:http、server、location

当出现对应的响应code后,如何来处理

有三种方式处理:

  1. 指定具体跳转的地址:error_page 404 https://baidu.com

  2. 可以指定重定向地址:

    error_page 404 /50x.html;
    location = /50x.html {
    	root html;
    }
    
  3. 使用location的@符号完成错误信息展示

    error_page 404 @jump_to_error;
    location @jump_to_error {
    	default_type text/plain;
    	return 404 "not found";
    }
    

    可选项=[reponse]的作用是将相应响应码替换为另外一个:

    error_page 404 =200 /50x.html;
    

静态资源优化配置

Nginx对静态资源如何进行优化,这里有三个属性配置进行优化:

  • sendfile on;
  • tcp_nopush on;
  • tcp_nodelay on;

sendfile用来开启高效的文件传输模式

请求静态资源的过程:客户端通过网络接口向服务端发送请求,操作系统将客户端请求传递给服务器端应用程序,服务器端应用程序会处理这些请求,请求处理完成之后,操作系统还需要将得到的结果(通常是静态资源)通过网络适配器传递回去

如果不开启:

  1. 客户端向服务端发送请求后,由应用程序向操作系统发送一个read指令,操作系统将指定的静态资源从磁盘中读取到内核缓冲区,这是第一次拷贝;
  2. 然后操作系统再将资源从内核缓冲区拷贝到应用程序缓冲区,这是第二次拷贝;
  3. 应用程序发送write指令,应用缓冲区中的资源被拷贝到socket缓冲区,这是第三次拷贝
  4. socket缓冲区向网卡又涉及到一次拷贝,这是第四次拷贝

使用sendfile:

  1. 客户端向服务端发送请求后,应用程序向操作系统发送sendfile指令,操作系统接收到指令后,将资源从磁盘读取到内核缓冲区,这是第一次拷贝
  2. 操作系统将资源从内核缓冲区拷贝到socket缓冲区,这是第二次拷贝
  3. socket缓冲区发送到网卡,由网卡发送到客户端,这是第三次拷贝

也即:使用了sendfile后,少了一次拷贝,并且减少了内核态与用户态之间的进程切换

tcp_nopush:该指令必须在sendfile打开状态下才会生效,主要是用来提升网络传输包的传输效率

语法:tcp_nopush on|off;

默认值:off

位置:http、server、location

tcp_nodepay:该指令必须在keep-alive开启的时候才生效,来提高网络传输包的传输的实时性

默认为on

服务器响应客户端数据有两种方式:

  1. 有数据就发:实时性好;效率低;
  2. 先将数据缓存到缓存区,存满后再发:效率高;但实时性低

这两个配置看起来是互斥的,但是从Linux2.5.9以后,这两个配置加上sendfile指令三者是可以共存的

以tcp_nopush为主,当最后一个包没填满时,忽略tcp_nopush,使用tcp_nodelay直接发送

静态资源压缩

经过上述内容的优化,再接下来的优化方向就是传输文件的大小

Nginx的配置文件中,可以通过配置gzip来对静态资源进行压缩,相关指令可以配置在http、server和location中,Nginx可以通过:

  • ngx_http_gzip_module
  • ngx_http_gzip_static_module
  • ngx_http_gunzip_module

三个模块来实现压缩

gzip模块配置指令

接下来的指令都来自与ngx_http_gzip_module模块,该模块会在nginx安装的时候内置到Nginx的安装环境中,也即可以直接使用

gzip指令:用于开启或关闭gzip功能

语法:gzip on|off;

默认:off

位置:http、server、location

只有该指令打开,下面介绍的指令才有效果

gzip_types指令:该指令可以根据响应页的MIME类型选择性地打开Gzip压缩功能

语法:gzip_types mime-type...;

默认值:text/html

位置:http、server、location

所选择值可以在mime.types中寻找,也可以使用*表示所有

gzip_comp_level指令:该指令用于设置Gzip压缩程度,级别从1-9,1表示最低,9表示最高

语法:gzip_comp_level level;

默认值:1

位置:http、server、location

建议设置到6

gzip_vary:用来设置是否发送数据已被压缩的标识:Vary:Accept-Encoding

语法:gzip_vary on|off;

默认值:off

位置:http、server、location

gzip和sendfile的共存问题

gzip需要Nignx来进行压缩,但开启sendfile之后,资源不经过应用程序缓存

可以使用ngx_http_gzip_static_module模块的gzip_static解决

gzip_static指令:检查与访问资源同名的.gz文件,response中以gzip相关的header返回.gz文件的内容

语法:gzip_static on|off|always;

默认值:off

位置:http、server、location

需要添加该模块

即将nginx可执行文件备份之后,执行./configure --with-http_gzip_static_module

再将新的二进制文件复制的原来的位置

使用gzip xxx将资源提前进行压缩

静态资源缓存处理

Web缓存是指一个Web资源存在于Web服务器和客户端之间的副本

Web缓存分为两类:客户端缓存(浏览器缓存)、服务端缓存(Nginx、Redis等)

浏览器缓存是为了节约网络资源,加速浏览,浏览器可以从用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面,浏览器可以从本地显示文档

HTTP协议中与页面缓存相关的字段有:

  • expires:缓存过期时间
  • cache-Control:设置和缓存相关的配置信息
  • Last-Modified:请求资源的最后修改时间
  • ETag:请求变量的实体标签的当前值,比如文件的MD5

浏览器缓存有强缓存和弱缓存之分:

  • 强缓存:不会向服务端发送请求,直接走本地缓存
  • 弱缓存:发送请求的服务端,服务端来判断Last-Modified和ETag两个字段,之后响应304给客户端,客户端再从本地缓存中取数据

Nginx跨域问题

浏览器为了保护资源,设置了同源策略

所谓同源,即协议、IP、端口都相同

使用Nginx来解决:

使用add_header指令,该指令可以用来添加一些头信息

语法:add_header name value;

默认值:-

位置:http、server、location

用来解决跨域问题,需要添加两个头信息:

  • Access-Control-Allow-Origin:允许跨域访问的源地址信息,可以配置多个,*表示所有
  • Access-Control-Allow-Methods:允许跨域访问的请求方式,可以配置多个

Nginx静态资源防盗链

资源盗链是指此内容不在自己服务器上,而是通过技术手段,将别人网站上的内容放到自己的网站上展示给用户,以此来盗取大网站的空间和流量

在了解防盗链原理前,需要先学习一个HTTP的头信息Referer,当浏览器向Web服务器发送请求的时候,一般会带上该信息,来告诉浏览器该网页是从哪个页面链接过来的

后台服务器可以根据获取到的这个Referer信息来判断是否为自己信任的网站地址,如果是则放行,如果不是则返回403的状态信息

具体实现:

valid_referers指令:Nginx会通过查看referer自动和valid_referers后面的内容进行匹配,如果匹配到了就将$invalid_referer变量置为0,如果没有匹配到,就置为1,匹配的过程不区分大小写

语法:valid_referers none | blocked | server_names | string..

默认值:-

位置:server、location

none:如果Header中的Referer为空,允许访问

Blocked:在Header中的Referer不为空,但是该值被防火墙或者代理进行伪装过,如果不带http或https等协议头的资源允许访问

server_names:指定的域名或者IP

string:可以支持正则表达式和*的字符串,如果是正则表达式,需要以~开头表示

六、Rewrite功能

Rewrite是Nginx服务器提供的一个重要功能

Nginx的Rewrite功能实现依赖于PCRE的支持,因此在编译安装Nginx服务器之前,需要按照PCRE库,Nginx使用ngx_http_rewrite_module模块来解析和处理Rewrite功能的相关配置

Rewrite的相关指令

七、反向代理

Nginx既可以实现正向代理,也可以实现反向代理

Nginx反向代理配置语法

Nginx反向代理模块的指令是由ngx_http_proxy_module模块进行解析,该模块在安装Nginx的时候已经安装到Nginx中

常用的反向代理指令:

  • proxy_pass
  • proxy_set_header
  • proxy_redirect
proxy_pass

该指令用来设置被代理服务器地址,可以是主机名称、IP地址+端口形式

语法:proxy_pass URL;

默认值:-

位置:location

URL:为要设置的被代理服务器地址,包含传输协议、主机名称或IP地址+端口号、URL等要素

  location / {
    proxy_pass http://192.168.231.134;
  }

这里需要注意的是URL后面要不要加斜杠:/

当location后面只有一个/的时候,URL后面加不加都行

但是当location后面是/server这种形式的时候,URL后面加斜杠和不加是不一样的

  • 加斜杠:proxy_pass http://192.168.231.134/:它不会将/server拼接上去
  • 不加斜杠:proxy_pass http://192.168.231.134:它会将/server拼接上去
proxy_set_header

该指令可以更改Nginx服务器接收到的客户端请求头信息,然后将心的请求头发送给代理的服务器

语法:proxy_set_header field value;

默认值:Host $proxy_host; Connection close;

位置:http、server、location

需要注意的是,如果想要看到结果,必须在被代理的服务器上获取添加的头信息

proxy_redirect

该指令用来重置头信息中的Location和Refresh的值

可以设置的值:redirect replacement、default、off

默认是default

位置:http、server、Location

这个指令主要用来解决当访问一个不存在的URI的时候,会报404,当不想显示该页面,而是将其重定向一个欢迎页面的时候,被代理服务器的真实地址会被暴露出来

为了解决这个问题,可以重写Location头信息

八、Nginx负载均衡

概述

将流量分发到不同的服务器上,分发流量和避免单点故障

系统的扩展可以分为纵向扩展和横向扩展

纵向扩展是从单机角度触发,通过增加系统的硬件处理能力来提升服务器的处理能力

横向扩展是通过增加机器来满足大型网站服务的处理能力

这里面涉及到两个重要的角色:应用集群和负载均衡器

应用集群就是将同一应用部署到多台服务器上,组成处理集群,接收负载均衡设备分发的请求,矜夸处理并返回响应的数据

负载均衡器:将用户的请求根据对应的负载均衡算法分发到应用集群中的某一台服务器上进行处理

常用处理方式

方式一:用户手动选择

用户自己选择具体的服务器

后台控制用户选择的能力比较弱

方式二:DNS轮询方式

DNS即域名和IP的相互转换协议

大多数域名注册商都支持同一主机名添加多条A记录,这就是DNS轮询,DNS服务器将解析请求按照A记录的顺序,随机分发到不同的IP上,这样就能完成简单的负载均衡,DNS轮询的成本非常低,在一些不重要的服务器上被经常使用

缺点是:

  • 可靠性低:如果出现单点故障,那么所有访问该服务器的请求都不会响应,同时DNS不会实时刷新,也就导致可能会出现一段时间访问不到的情况
  • 负载均衡不均衡:不能区分服务器差异,不能反映服务器当前的运行状态,不能做到为性能好的服务器多分配请求,另外本地计算机也会缓存已经解析的域名,这也就导致一段时间内访问的都是同一台服务器

方式三:四、七层负载均衡

这基于OSI七层网络模型,该模型将网络通信的工作分为七层:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层

四层负载均衡即OSI七层模型中的传输层,主要基于IP+Port的负载均衡

实现四层负载均衡的方式:

  • 硬件:F5 BIG-IP、Radware
  • 软件:Lvs、Nginx、Hayproxy等

所谓七层负载均衡指的是应用层,主要基于虚拟机的URL或主机的负载均衡

实现七层负载均衡的方式:

  • 软件:Nginx、Hayproxy等

四层和七层的区别:

  • 四层实在底层就进行了分发,而七层是在最顶端分发,所以四层的效率比七层的高
  • 四层不识别域名,七层可以识别域名
  • 四层成本比较高,七层成本低

实际环境一般采用四层+七层:Lvs+Nginx

Nginx七层负载均衡

Nginx七层负载均衡实在Nginx反向代理的基础上将用户的请求根据指定的算法分发到一组upstream虚拟服务池实现的

七层负载均衡指令

upstream指令:

该指针用于定义一组服务器,他们可以是监听不同端口的服务器,并且业可以是同时监听TCP和Unix socket的服务器,服务器可以指定不同的权重,默认为1

语法:upstream name {...}

默认值:

位置:http

server指令:

该指令用来指定后端服务器的名称和一些参数,可以使用域名、IP、端口或者Unix socket

语法:server name [parameters]

默认值:-

位置:upstream

这里配置四台主机,其中一台作为负载均衡器,另外三台当做App server

负载均衡器的配置如下:

upstream backend {
        server 192.168.231.132:8082;
        server 192.168.231.132:8083;
        server 192.168.231.132:8084;
}
    
server {
    listen 80;
    server_name localhost;
    location / {
            proxy_pass http://backend;
    }
}

默认的负载均衡策略是轮询

负载均衡状态

代理服务器在负责均衡调度中的状态有以下几个:

  • down:当前的server不参与负载均衡
  • backup:预留的备份服务器
  • max_fails:允许请求失败的次数
  • fail_timeout:经过最大请求失败次数后,服务暂停时间
  • max_conns:限制最大的接收连接数

down状态用来设置该服务器永久不可用,那么该代理服务器将不再参与负载均衡

比如:

upstream backend {
        server 192.168.231.132:8082 down;
        server 192.168.231.132:8083;
        server 192.168.231.132:8084;
    }
    
server {
    listen 80;
    server_name localhost;
    location / {
            proxy_pass http://backend;
    }
}

此时8082这台服务器就不再参与负载均衡

一般用于对某台机器进行停机维护

backup会将该服务器标记为备份服务器,当主服务器不可用时,将用来传递请求

upstream backend {
    server 192.168.231.132:8082 down;
    server 192.168.231.132:8083 backup;
    server 192.168.231.132:8084;
}

此时请求只会分发到8084这台主机上

当将8084也down掉后,8083才会启用

upstream backend {
    server 192.168.231.132:8082 down;
    server 192.168.231.132:8083 backup;
    server 192.168.231.132:8084 down;
}

max_conns用来限制并发量,默认为0,表示不限制

max_fails和fail_timeout用来设置请求代理服务器允许失败的次数,默认是1以及经过最大失败次数后服务暂停的时间,默认是10s

负责均衡策略

upstream支持6种算法:

  • 轮询:默认
  • weight:权重
  • ip_hash:根据IP哈希
  • least_conn:依据最少连接数
  • url_hash:根据URL哈希
  • fair:根据响应时间

轮询是upstream模块负载均衡的默认的策略,每个请求会按照时间顺序逐个分配到不同的后端服务器,轮询不需要额外的配置

weight(加权轮询):weight=number,用来设置服务器的权重,默认为1,权重数据越大,被分配到请求的几率越大,主要针对后端服务器硬件配置进行调整,这个策略比较适合服务器之间的配置差别比较大的情况

ip_hash:可以解决session不共享的问题,主要通过客户端IP进行请求,请求会固定请求某一台服务器

配置方式:

upstream {
	ip_hash;
	server xxx;
}

这种方式可能会导致请求不均衡

least_conn:这种算法会将请求分配到连接数较少的服务器,使用请求处理时间长短不一造成服务器过载的情况

url_hash:根据url的哈希结果来分配请求,使得每个url定向到同一台服务器,要配合缓存命中来使用;同一个资源多次请求,如果分配到不同机器,会导致缓存命中率不高,通过url哈希可以将固定请求的资源保存到一个固定的服务器上,提高缓存命中率

fair:采用不是内建的负载均衡策略,而是根据页面大小、加载时间长短智能地选择负载均衡,需要使用第三方模块:nginx_upstream-fair

四层负载均衡

Nginx在1.9之后,增加了一个Stream模块,用来实现四层协议的转发、代理负载均衡等

stream模块的用发跟http的用发类似

四层负载均衡的实现一般都会用到lvs、HaProxy、F5等,要么很贵,要么配置很麻烦,而Nginx的配置相对简单,更能快速完成工作

九、Nginx缓存集成

缓存就是数据交换的缓冲区,当用户要获取数据时候,会先从缓存中查询获取数据,如果缓存中有就直接返回给用户,如果缓存中没有再向服务器发送请求,将数据返回给用户的同时将数据缓存一份

缓存的使用场景:

  • 操作系统磁盘缓存
  • 数据库缓存
  • 应用程序缓存
  • Web服务器缓存
  • 浏览器缓存

缓存的优缺点:

  1. 减少数据传输、节省网络流量、加快响应速度、提升用户体验、减轻服务器压力、提供服务端的高可用性
  2. 导致数据的不一致,增加成本

Nginx作为Web服务器,它可以作为Web服务器进行缓存,可以充当客户端和应用服务器之间的一层缓存层

实现原理

Nginx缓存是基于Proxy Store来实现的

相关指令

使用的是ngx_http_proxy_module模块提供的相关指令

语法:proxy_cache_path path [levels=number] keys_zone=zone_name:zone_size [inactive=time][max_size=size];

默认值:-

位置:http

  • proxy_cache_path:用于指定缓存文件的存放路径
  • levels:指定缓存空间对应的目录,最多可以设置3层,每个取值为1或2,比如:levels=1:2表示缓存空间有两层目录,第一次是1个字母,第二次是2个字母
  • keys_zone:用来为这个缓存区设置名称和大小:keys_zone=proxy_cache:200m
  • inactive:指定缓存的数据多长时间未被访问就删除掉
  • max_size:设置最大缓存空间,如果缓存空间存满,默认会覆盖缓存时间最长的资源

proxy_cache:用来开启或关闭代理缓存,如果是开启则自定使用哪个缓存区来进行缓存

语法:proxy_cache zone_name | off;

默认值:off

位置:http、server、Location

zone_name:指定使用缓存区的名称

proxy_cache_key:该指令用来设置web缓存的key值,Nginx会根据key值MD5哈希存缓存

语法:prxy_cache_key key;

默认值:$scheme$proxy_host$request_uri:协议+主机+uri

位置:http、server、Location

proxy_cache_valid:该指令用于对不同状态码的URL设置不同的缓存时间

语法:proxy_cache_valid [code...] time;

默认值:-

位置:http、server、location

proxy_cache_min_uses:该指令用来设置资源被访问多少次后进行缓存

语法:number;

默认值:1

位置:http、server、location

proxy_cache_methods:该指令用来设置缓存哪些HTTP方法

语法:proxy_cache_methods GET|HEAD|POST;

默认值:GET|HEAD

位置:http、server、location

缓存清除

方式一:手动删除对应的缓存目录

方式二:使用第三方扩展模块:ngx_cache_purge

资源不缓存

不是所有的数据都适合缓存

proxy_no_cache:该指令用来定义不将数据进行缓存的条件

语法:string...;

默认值:-

位置:http、server、location

proxy_cache_bypass:该指令用来设置不从缓存中获取数据的条件

语法:string...;

默认值:-

位置:http、server、location

上述条件可以是多个,并且多个条件中至少有一个不为空切不等于0

十、Nginx服务端集群搭建

动静分离

动:后台应用程序的业务处理

静:网站的静态资源

分离:将二者分开部署访问,提供用户进行访问,举例说明就是将所有静态资源相关的内容都交给Nginx,非静态内容交给Tomcat

Nginx并发访问量非常大,官方称5w+,Tomcat的并发量有500就相当不错了

实现步骤:

将静态资源与动态资源剥离

upstream backend {
        server 192.168.231.132:8082;
        server 192.168.231.132:8083;
        server 192.168.231.132:8084;
}

server {
    listen 80;
    charset utf-8;
    server_name localhost;
    # 动态资源获取配置
    location / {
        proxy_pass http://backend;
    }
    # 静态资源获取配置
    location ~/.*\.(png|jpg|gif|js) {
    	root html/web;
    }
}

Nginx高可用

可以布置多个Nginx来解决高可用

多个Nginx访问的问题交给Keepalived来处理,该软件主要通过VRRP协议实现高可用

VRRP协议即虚拟路由冗余协议

VRRP虚拟出一个虚拟路由,分发到多个真实路由

真实路由会分为Master节点和Backup节点,这被称为选择

Master和Backup之间会有心跳检测

用了Keepalived之后,在多台Nginx主机上部署Keepalived,它们之间会选出Master节点和Backup节点,通常由Master节点对外提供服务,当Master节点出现问题,Backup节点会竞选成为Master节点,继续对外提供服务

Keepalived配置文件

这里分为三部分:global全局配置、vrrp相关配置以及LVS相关配置

这里重点关注前两个部分

# 全局配置
global_defs {
   # 邮件通知信息
   notification_email {
     # 定义收件人
     acassen@firewall.loc
   }
   # 定义发件人
   notification_email_from Alexandre.Cassen@firewall.loc
   # SMTP服务器地址
   smtp_server 192.168.200.1
   smtp_connect_timeout 30
   # 路由器标识,一般不用改,也可以写成每个主机自己的主机名
   router_id LVS_DEVEL
   # VRRP的ipv4和ipv6的广播地址,配置了VIP的网卡向这个地址广播来宣告自己的配置信息,下面是默认值
   vrrp_mcast_group4 224.0.0.18
   vrrp_mcast_group6 ff02::12
}

# 定义用于实例执行的脚本内容,比如可以在线降低优先级,用于强制切换
vrrp_script SCRIPT_NAME {

}

# 一个vrrp_instance就是定义一个虚拟路由器的,实例名称
vrrp_instance VI_1 {
    # 定义初始状态,可以是MASTER或者BACKUP
    state MASTER
    # 工作接口,通告选举使用哪个接口进行
    interface ens33
    # 虚拟路由ID,如果是一组虚拟路由就定义一个ID,如果是多组就要定义多个,而且这个虚拟
    # ID还是虚拟MAC最后一段地址的信息,取值范围0-255
    virtual_router_id 51
    # 使用哪个虚拟MAC地址
    use_vmac XX:XX:XX:XX:XX
    # 监控本机上的哪个网卡,网卡一旦故障则需要把VIP转移出去
    track_interface {
        eth0
        ens33
    }
    # 如果你上面定义了MASTER,这里的优先级就需要定义的比其他的高
    priority 100
    # 通告频率,单位为秒
    advert_int 1
    # 通信认证机制,这里是明文认证还有一种是加密认证
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    # 设置虚拟VIP地址,一般就设置一个,在LVS中这个就是为LVS主机设置VIP的,这样你就不用自己手动设置了
    virtual_ipaddress {
        # IP/掩码 dev 配置在哪个网卡
        192.168.200.16/24 dev eth1
        # IP/掩码 dev 配置在哪个网卡的哪个别名上
        192.168.200.17/24 dev label eth1:1
    }
    # 虚拟路由,在需要的情况下可以设置lvs主机 数据包在哪个网卡进来从哪个网卡出去
    virtual_routes {
        192.168.110.0/24 dev eth2
    }
    # 工作模式,nopreempt表示工作在非抢占模式,默认是抢占模式 preempt
    nopreempt|preempt
    # 如果是抢占默认则可以设置等多久再抢占,默认5分钟
    preempt delay 300
    # 追踪脚本,通常用于去执行上面的vrrp_script定义的脚本内容
    track_script {

    }
    # 三个指令,如果主机状态变成Master|Backup|Fault之后会去执行的通知脚本,脚本要自己写
    notify_master ""
    notify_backup ""
    notify_fault ""
}

# 定义LVS集群服务,可以是IP+PORT;也可以是fwmark 数字,也就是防火墙规则
# 所以通过这里就可以看出来keepalive天生就是为ipvs而设计的
virtual_server 10.10.10.2 1358 {
    delay_loop 6
    # 算法
    lb_algo rr|wrr|lc|wlc|lblc|sh|dh 
    # LVS的模式
    lb_kind NAT|DR|TUN
    # 子网掩码,这个掩码是VIP的掩码
    nat_mask 255.255.255.0
    # 持久连接超时时间
    persistence_timeout 50
    # 定义协议
    protocol TCP
    # 如果后端应用服务器都不可用,就会定向到那个服务器上
    sorry_server 192.168.200.200 1358

    # 后端应用服务器 IP PORT
    real_server 192.168.200.2 1358 {
        # 权重
        weight 1
        # MSIC_CHECK|SMTP_CHEKC|TCP_CHECK|SSL_GET|HTTP_GET这些都是
        # 针对应用服务器做健康检查的方法
        MISC_CHECK {}
        # 用于检查SMTP服务器的
        SMTP_CHEKC {}

        # 如果应用服务器不是WEB服务器,就用TCP_CHECK检查
        TCP_CHECK {
          # 向哪一个端口检查,如果不指定默认使用上面定义的端口
          connect_port <PORT>
          # 向哪一个IP检测,如果不指定默认使用上面定义的IP地址
          bindto <IP>
          # 连接超时时间
          connect_timeout 3
        }

        # 如果对方是HTTPS服务器就用SSL_GET方法去检查,里面配置的内容和HTTP_GET一样
        SSL_GET {}

        # 应用服务器UP或者DOWN,就执行那个脚本
        notify_up "这里写的是路径,如果脚本后有参数,整体路径+参数引起来"
        notify_down "/PATH/SCRIPTS.sh 参数"

        # 使用HTTP_GET方法去检查
        HTTP_GET {
            # 检测URL
            url { 
              # 具体检测哪一个URL
              path /testurl/test.jsp
              # 检测内容的哈希值
              digest 640205b7b0fc66c1ea91c463fac6334d
              # 除了检测哈希值还可以检测状态码,比如HTTP的200 表示正常,两种方法二选一即可
              status_code 200
            }
            url { 
              path /testurl2/test.jsp
              digest 640205b7b0fc66c1ea91c463fac6334d
            }
            url { 
              path /testurl3/test.jsp
              digest 640205b7b0fc66c1ea91c463fac6334d
            }
            # 向哪一个端口检查,如果不指定默认使用上面定义的端口
            connect_port <PORT>
            # 向哪一个IP检测,如果不指定默认使用上面定义的IP地址
            bindto <IP>
            # 连接超时时间
            connect_timeout 3
            # 尝试次数
            nb_get_retry 3
            # 每次尝试之间间隔几秒
            delay_before_retry 3
        }
    }

    real_server 192.168.200.3 1358 {
        weight 1
        HTTP_GET {
            url { 
              path /testurl/test.jsp
              digest 640205b7b0fc66c1ea91c463fac6334c
            }
            url { 
              path /testurl2/test.jsp
              digest 640205b7b0fc66c1ea91c463fac6334c
            }
            connect_timeout 3
            nb_get_retry 3
            delay_before_retry 3
        }
    }
}