动静分离是指在 web 服务器架构中,将静态页面与动态页面或者静态内容接口和动态内容接口分开不同系统访问的架构设计方法,进而提示整个服务的访问性和可维护性。
一般来说,都需要将动态资源和静态资源分开,由于 Nginx 的高并发和静态资源缓存等特性,经常将静态资源部署在 Nginx 上。如果请求的是静态资源,直接到静态资源目录获取资源,如果是动态资源的请求,则利用反向代理的原理,把请求转发给对应后台应用去处理,从而实现动静分离。
使用前后端分离后,可以很大程度提升静态资源的访问速度,即使动态服务不可用,静态资源的访问也不会受到影响。
负载均衡
一般情况下,客户端发送多个请求到服务器,服务器处理请求,其中一部分可能要操作一些资源比如数据库、静态资源等,服务器处理完毕后,再将结果返回给客户端。
这种模式对于早期的系统来说,功能要求不复杂,且并发请求相对较少的情况下还能胜任,成本也低。随着信息数量不断增长,访问量和数据量飞速增长,以及系统业务复杂度持续增加,这种做法已无法满足要求,并发量特别大时,服务器容易崩。
很明显这是由于服务器性能的瓶颈造成的问题,除了堆机器之外,最重要的做法就是负载均衡。
请求爆发式增长的情况下,单个机器性能再强劲也无法满足要求了,这个时候集群的概念产生了,单个服务器解决不了的问题,可以使用多个服务器,然后将请求分发到各个服务器上,将负载分发到不同的服务器,这就是负载均衡,核心是「分摊压力」。Nginx 实现负载均衡,一般来说指的是将请求转发给服务器集群。
举个具体的例子,晚高峰乘坐地铁的时候,入站口经常会有地铁工作人员大喇叭“请走 B 口, B 口人少车空……”,这个工作人员的作用就是负载均衡。
Nginx 实现负载均衡的策略:
轮询策略:默认情况下采用的策略,将所有客户端请求轮询分配给服务端。这种策略是可以正常工作的,但是如果其中某一台服务器压力太大,出现延迟,会影响所有分配在这台服务器下的用户。
最小连接数策略:将请求优先分配给压力较小的服务器,它可以平衡每个队列的长度,并避免向压力大的服务器添加更多的请求。
最快响应时间策略:优先分配给响应时间最短的服务器。
客户端 IP 绑定策略:来自同一个 IP 的请求永远只分配一台服务器,有效解决了动态网页存在的 session 共享问题。
Nginx 实战配置
=========================================================================
在配置反向代理和负载均衡等等功能之前,有两个核心模块是我们必须要掌握的,这两个模块应该说是 Nginx 应用配置中的核心,它们分别是: upstream、proxy_pass。
upstream
用于定义上游服务器(指的就是后台提供的应用服务器)的相关信息。
语法:upstream name {
…
}
上下文:http
示例:
upstream back_end_server{
server 192.168.100.33:8081
}
在 upstream 内可使用的指令:
server 定义上游服务器地址;
zone 定义共享内存,用于跨 worker 子进程;
keepalive 对上游服务启用长连接;
keepalive_requests 一个长连接最多请求 HTTP 的个数;
keepalive_timeout 空闲情形下,一个长连接的超时时长;
hash 哈希负载均衡算法;
ip_hash 依据 IP 进行哈希计算的负载均衡算法;
least_conn 最少连接数负载均衡算法;
least_time 最短响应时间负载均衡算法;
random 随机负载均衡算法;
server
定义上游服务器地址。
语法:server address [parameters]
上下文:upstream
parameters 可选值:
weight=number 权重值,默认为 1;
max_conns=number 上游服务器的最大并发连接数;
fail_timeout=time 服务器不可用的判定时间;
max_fails=numer 服务器不可用的检查次数;
backup 备份服务器,仅当其他服务器都不可用时才会启用;
down 标记服务器长期不可用,离线维护;
keepalive
限制每个 worker 子进程与上游服务器空闲长连接的最大数量。
keepalive connections;
上下文:upstream
示例:keepalive 16;
keepalive_requests
单个长连接可以处理的最多 HTTP 请求个数。
语法:keepalive_requests number;
默认值:keepalive_requests 100;
上下文:upstream
keepalive_timeout
空闲长连接的最长保持时间。
语法:keepalive_timeout time;
默认值:keepalive_timeout 60s;
上下文:upstream
配置实例
upstream back_end{
server 127.0.0.1:8081 weight=3 max_conns=1000 fail_timeout=10s max_fails=2;
keepalive 32;
keepalive_requests 50;
keepalive_timeout 30s;
}
proxy_pass
用于配置代理服务器。
语法:proxy_pass URL;
上下文:location、if、limit_except
示例:
proxy_pass http://127.0.0.1:8081
proxy_pass http://127.0.0.1:8081/proxy
URL 参数原则:
URL 必须以 http 或 https 开头;
URL 中可以携带变量;
URL 中是否带 URI ,会直接影响发往上游请求的 URL;
接下来让我们来看看两种常见的 URL 用法:
proxy_pass http://192.168.100.33:8081
proxy_pass http://192.168.100.33:8081/
这两种用法的区别就是带 / 和不带 / ,在配置代理时它们的区别可大了:
不带 / 意味着 Nginx 不会修改用户 URL,而是直接透传给上游的应用服务器;
带 / 意味着 Nginx 会修改用户 URL ,修改方法是将 location 后的 URL 从用户 URL 中删除;
不带 / 的用法:
location /bbs/{
proxy_pass http://127.0.0.1:8080;
}
用户请求 URL:/bbs/abc/test.html
请求到达 Nginx 的 URL:/bbs/abc/test.html
请求到达上游应用服务器的 URL :/bbs/abc/test.html
带 / 的用法:
location /bbs/{
proxy_pass http://127.0.0.1:8080/;
}
用户请求 URL:/bbs/abc/test.html
请求到达 Nginx 的 URL:/bbs/abc/test.html
请求到达上游应用服务器的 URL:/abc/test.html
并没有拼接上 /bbs,这点和 root与 alias 之间的区别是保持一致的。
配置反向代理
这里为了演示更加接近实际,作者准备了两台云服务器,它们的公网 IP 分别是:121.42.11.34 与 121.5.180.193。
我们把 121.42.11.34 服务器作为上游服务器,做如下配置:
server{
listen 8080;
server_name localhost;
location /proxy/ {
root /usr/share/nginx/html/proxy;
index index.html;
}
}
配置完成后重启 Nginx 服务器 nginx -s reload。
把 121.5.180.193 服务器作为代理服务器,做如下配置:
upstream back_end {
server 121.42.11.34:8080 weight=2 max_conns=1000 fail_timeout=10s max_fails=3;
keepalive 32;
keepalive_requests 80;
keepalive_timeout 20s;
}
server {
listen 80;
server_name proxy.lion.club;
location /proxy {
proxy_pass http://back_end/proxy;
}
}
本地机器要访问 proxy.lion.club 域名,因此需要配置本地 hosts,通过命令:vim /etc/hosts
进入配置文件,添加如下内容:
121.5.180.193 proxy.lion.club
分析:
当访问 proxy.lion.club/proxy 时通过 upstream 的配置找到 121.42.11.34:8080;
因此访问地址变为 http://121.42.11.34:8080/proxy;
连接到 121.42.11.34 服务器,找到 8080 端口提供的 server;
通过 server 找到 /usr/share/nginx/html/proxy/index.html 资源,最终展示出来。
配置负载均衡
配置负载均衡主要是要使用 upstream 指令。
我们把 121.42.11.34 服务器作为上游服务器,做如下配置(/etc/nginx/conf.d/balance.conf):
server{
listen 8020;
location / {
return 200 ‘return 8020 \n’;
}
}
server{
listen 8030;
location / {
return 200 ‘return 8030 \n’;
}
}
server{
listen 8040;
location / {
return 200 ‘return 8040 \n’;
}
}
配置完成后:
nginx -t
检测配置是否正确;
nginx -s reload
重启 Nginx 服务器;
执行ss -nlt
命令查看端口是否被占用,从而判断 Nginx 服务是否正确启动。
把 121.5.180.193 服务器作为代理服务器,做如下配置(/etc/nginx/conf.d/balance.conf):
upstream demo_server {
server 121.42.11.34:8020;
server 121.42.11.34:8030;
server 121.42.11.34:8040;
}
server {
listen 80;
server_name balance.lion.club;
location /balance/ {
proxy_pass http://demo_server;
}
}
配置完成后重启 Nginx 服务器。并且在需要访问的客户端配置好 ip 和域名的映射关系。
121.5.180.193 balance.lion.club
在客户端机器执行 curl http://balance.lion.club/balance/
命令:
不难看出,负载均衡的配置已经生效了,每次给我们分发的上游服务器都不一样。就是通过简单的轮询策略进行上游服务器分发。
接下来,我们再来了解下 Nginx 的其它分发策略。
hash 算法
通过制定关键字作为 hash key,基于 hash 算法映射到特定的上游服务器中。关键字可以包含有变量、字符串。
upstream demo_server {
hash $request_uri;
server 121.42.11.34:8020;
server 121.42.11.34:8030;
server 121.42.11.34:8040;
}
server {
listen 80;
server_name balance.lion.club;
location /balance/ {
proxy_pass http://demo_server;
}
}
hash $request_uri 表示使用 request_uri 变量作为 hash 的 key 值,只要访问的 URI 保持不变,就会一直分发给同一台服务器。
ip_hash
根据客户端的请求 ip 进行判断,只要 ip 地址不变就永远分配到同一台主机。它可以有效解决后台服务器 session 保持的问题。
upstream demo_server {
ip_hash;
server 121.42.11.34:8020;
server 121.42.11.34:8030;
server 121.42.11.34:8040;
}
server {
listen 80;
server_name balance.lion.club;
location /balance/ {
proxy_pass http://demo_server;
}
}
最少连接数算法
各个 worker 子进程通过读取共享内存的数据,来获取后端服务器的信息。来挑选一台当前已建立连接数最少的服务器进行分配请求。
语法:least_conn;
上下文:upstream;
示例:
upstream demo_server {
zone test 10M; # zone可以设置共享内存空间的名字和大小
least_conn;
server 121.42.11.34:8020;
server 121.42.11.34:8030;
server 121.42.11.34:8040;
}
server {
listen 80;
server_name balance.lion.club;
location /balance/ {
proxy_pass http://demo_server;
}
}
最后你会发现,负载均衡的配置其实一点都不复杂。
配置缓存
===================================================================
缓存可以非常有效的提升性能,因此不论是客户端(浏览器),还是代理服务器(Nginx),乃至上游服务器都多少会涉及到缓存。可见缓存在每个环节都是非常重要的。下面让我们来学习 Nginx 中如何设置缓存策略。
proxy_cache
存储一些之前被访问过、而且可能将要被再次访问的资源,使用户可以直接从代理服务器获得,从而减少上游服务器的压力,加快整个访问速度。
语法:proxy_cache zone | off ; # zone 是共享内存的名称
默认值:proxy_cache off;
上下文:http、server、location
proxy_cache_path
设置缓存文件的存放路径。
语法:proxy_cache_path path [level=levels] …可选参数省略,下面会详细列举
默认值:proxy_cache_path off
上下文:http
参数含义:
path 缓存文件的存放路径;
level path 的目录层级;
keys_zone 设置共享内存;
inactive 在指定时间内没有被访问,缓存会被清理,默认10分钟;
proxy_cache_key
设置缓存文件的 key 。
语法:proxy_cache_key
默认值:proxy_cache_key s c h e m e scheme schemeproxy_host$request_uri;
上下文:http、server、location
proxy_cache_valid
配置什么状态码可以被缓存,以及缓存时长。
语法:proxy_cache_valid [code…] time;
上下文:http、server、location
配置示例:proxy_cache_valid 200 304 2m;; # 说明对于状态为 200 和 304 的缓存文件的缓存时间是 2 分钟
proxy_no_cache
定义相应保存到缓存的条件,如果字符串参数的至少一个值不为空且不等于“ 0”,则将不保存该响应到缓存。
语法:proxy_no_cache string;
上下文:http、server、location
示例:proxy_no_cache $http_pragma $http_authorization;
proxy_cache_bypass
定义条件,在该条件下将不会从缓存中获取响应。
语法:proxy_cache_bypass string;
上下文:http、server、location
示例:proxy_cache_bypass $http_pragma $http_authorization;
upstream_cache_status 变量
它存储了缓存是否命中的信息,会设置在响应头信息中,在调试中非常有用。
MISS:未命中缓存
HIT:命中缓存
EXPIRED:缓存过期
STALE:命中了陈旧缓存
REVALIDDATED:Nginx 验证陈旧缓存依然有效
UPDATING:内容陈旧,但正在更新
BYPASS:X响应从原始服务器获取
配置实例
我们把 121.42.11.34 服务器作为上游服务器,做如下配置(/etc/nginx/conf.d/cache.conf):
server {
listen 1010;
root /usr/share/nginx/html/1010;
location / {
index index.html;
}
}
server {
listen 1020;
root /usr/share/nginx/html/1020;
location / {
index index.html;
}
}
把 121.5.180.193 服务器作为代理服务器,做如下配置(/etc/nginx/conf.d/cache.conf):
server {
listen 1010;
root /usr/share/nginx/html/1010;
location / {
index index.html;
}
}
server {
listen 1020;
root /usr/share/nginx/html/1020;
location / {
index index.html;
}
}
缓存就是这样配置,我们可以在 /etc/nginx/cache_temp 路径下找到相应的缓存文件。
对于一些实时性要求非常高的页面或数据来说,就不应该去设置缓存,下面来看看如何配置不缓存的内容。
…
server {
listen 80;
server_name cache.lion.club;
if ( r e q u e s t u r i ( ˙ t x t ∣ t e x t ) request_uri ~ \.(txt|text) requesturi (˙txt∣text)) {
set $cache_name “no cache”
}
location / {
proxy_no_cache $cache_name; # 判断该变量是否有值,如果有值则不进行缓存,如果没有值则进行缓存
proxy_cache cache_zone; # 设置缓存内存
proxy_cache_valid 200 5m; # 缓存状态为 200的请求,缓存时长为 5 分钟
proxy_cache_key $request_uri; # 缓存文件的 key 为请求的 URI
add_header Nginx-Cache-Status $upstream_cache_status # 把缓存状态设置为头部信息,响应给客户端
proxy_pass http://cache_server; # 代理转发
}
}
HTTPS
====================================================================
在学习如何配置 HTTPS 之前,我们先来简单回顾下 HTTPS 的工作流程是怎么样的?它是如何进行加密保证安全的?
HTTPS 工作流程
客户端(浏览器)访问 https://www.baidu.com 百度网站;
百度服务器返回 HTTPS 使用的 CA 证书;
浏览器验证 CA 证书是否为合法证书;
验证通过,证书合法,生成一串随机数并使用公钥(证书中提供的)进行加密;
发送公钥加密后的随机数给百度服务器;
百度服务器拿到密文,通过私钥进行解密,获取到随机数(公钥加密,私钥解密,反之也可以);
百度服务器把要发送给浏览器的内容,使用随机数进行加密后传输给浏览器;
此时浏览器可以使用随机数进行解密,获取到服务器的真实传输内容。
这就是 HTTPS 的基本运作原理,使用对称加密和非对称机密配合使用,保证传输内容的安全性。
配置证书
下载证书的压缩文件,里面有个 Nginx 文件夹,把 xxx.crt 和 xxx.key 文件拷贝到服务器目录,再进行如下配置:
server {
listen 443 ssl http2 default_server; # SSL 访问端口号为 443
server_name lion.club; # 填写绑定证书的域名(我这里是随便写的)
ssl_certificate /etc/nginx/https/lion.club_bundle.crt; # 证书地址
ssl_certificate_key /etc/nginx/https/lion.club.key; # 私钥地址
ssl_session_timeout 10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # 支持ssl协议版本,默认为后三个,主流版本是[TLSv1.2]
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
如此配置后就能正常访问 HTTPS 版的网站了。
配置跨域 CORS
========================================================================
先简单回顾下跨域究竟是怎么回事。
跨域的定义
同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。通常不允许不同源间的读操作。
同源的定义
如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。
下面给出了与 URL http://store.company.com/dir/page.html 的源进行对比的示例:
http://store.company.com/dir2/other.html 同源
https://store.company.com/secure.html 不同源,协议不同
http://store.company.com:81/dir/etc.html 不同源,端口不同
http://news.company.com/dir/other.html 不同源,主机不同
不同源会有如下限制:
Web 数据层面,同源策略限制了不同源的站点读取当前站点的 Cookie、IndexDB、LocalStorage 等数据。
DOM 层面,同源策略限制了来自不同源的 JavaScript 脚本对当前 DOM 对象读和写的操作。
网络层面,同源策略限制了通过 XMLHttpRequest 等方式将站点的数据发送给不同源的站点。
Nginx 解决跨域的原理
例如:
前端 server 的域名为:fe.server.com
后端服务的域名为:dev.server.com
现在我在 fe.server.com 对 dev.server.com 发起请求一定会出现跨域。
现在我们只需要启动一个 Nginx 服务器,将 server_name 设置为 fe.server.com 然后设置相应的 location 以拦截前端需要跨域的请求,最后将请求代理回 dev.server.com。如下面的配置:
server {
listen 80;