nginx 常用功能

反向代理

我们最常说的反向代理的是通过反向代理解决跨域问题。

其实反向代理还可以用来控制缓存(代理缓存 proxy cache),进行访问控制等等,以及后面说的负载均衡其实都是通过反向代理来实现的。

server {
    listen    8080;
        # 用户访问 ip:8080/test 下的所有路径代理到 github
        location /test {
         proxy_pass   https://github.com;
        }

        # 所有 /api 下的接口访问都代理到本地的 8888 端口
        # 例如你本地运行的 java 服务的端口是 8888,接口都是以 /api 开头
        location /api {
            proxy_pass   http://127.0.0.1:8888;
        }

}

访问控制

server {
   location ~ ^/index.html {
       # 匹配 index.html 页面 除了 127.0.0.1 以外都可以访问
       deny 192.168.1.1;
       deny 192.168.1.2;
       allow all;
 }
}

上面的命令表示禁止 192.168.1.1 和 192.168.1.2 两个 ip 访问,其它全部允许。从上到下的顺序,匹配到了便跳出,可以按你的需求设置。

负载均衡

通过负载均衡充利用服务器资源,nginx 目前支持自带 4 种负载均衡策略,还有 2 种常用的第三方策略。

轮询策略(默认)

每个请求按时间顺序逐一分配到不同的后端服务器,如果有后端服务器挂掉,能自动剔除。但是如果其中某一台服务器压力太大,出现延迟,会影响所有分配在这台服务器下的用户。

http {
    upstream test.com {
        server 192.168.1.12:8887;
        server 192.168.1.13:8888;
    }
    server {
        location /api {
            proxy_pass  http://test.com;
        }
    }
}

根据服务器权重

例如要配置:10 次请求中大概 1 次访问到 8888 端口,9 次访问到 8887 端口:

http {
    upstream test.com {
        server 192.168.1.12:8887 weight=9;
        server 192.168.1.13:8888 weight=1;
    }
    server {
        location /api {
            proxy_pass  http://test.com;
        }
    }
}

客户端 ip 绑定(ip_hash)

来自同一个 ip 的请求永远只分配一台服务器,有效解决了动态网页存在的 session 共享问题。例如:比如把登录信息保存到了 session 中,那么跳转到另外一台服务器的时候就需要重新登录了。

所以很多时候我们需要一个客户只访问一个服务器,那么就需要用 ip_hash 了。

http {
    upstream test.com {
     ip_hash;
        server 192.168.1.12:8887;
        server 192.168.1.13:8888;
    }
    server {
        location /api {
            proxy_pass  http://test.com;
        }
    }
}

最小连接数策略

将请求优先分配给压力较小的服务器,它可以平衡每个队列的长度,并避免向压力大的服务器添加更多的请求。

http {
    upstream test.com {
     least_conn;
        server 192.168.1.12:8887;
        server 192.168.1.13:8888;
    }
    server {
        location /api {
            proxy_pass  http://test.com;
        }
    }
}

最快响应时间策略(依赖于第三方 NGINX Plus)

依赖于 NGINX Plus,优先分配给响应时间最短的服务器。

http {
    upstream test.com {
     fair;
        server 192.168.1.12:8887;
        server 192.168.1.13:8888;
    }
    server {
        location /api {
            proxy_pass  http://test.com;
        }
    }
}

按访问 url 的 hash 结果(第三方)

按访问 url 的 hash 结果来分配请求,使每个 url 定向到同一个后端服务器,后端服务器为缓存时比较有效。在 upstream 中加入 hash 语句,server 语句中不能写入 weight 等其他的参数,hash_method 是使用的 hash 算法

http {
    upstream test.com {
     hash $request_uri;
     hash_method crc32;
     server 192.168.1.12:8887;
     server 192.168.1.13:8888;
    }
    server {
        location /api {
            proxy_pass  http://test.com;
        }
    }
}

采用 HAproxy 的 loadbalance uri 或者 nginx 的 upstream_hash 模块,都可以做到针对 url 进行哈希算法式的负载均衡转发。

gzip 压缩

开启 gzip 压缩可以大幅减少 http 传输过程中文件的大小,可以极大的提高网站的访问速度,基本是必不可少的优化操作:

gzip  on; # 开启gzip 压缩
# gzip_types
# gzip_static on;
# gzip_proxied expired no-cache no-store private auth;
# gzip_buffers 16 8k;
gzip_min_length 1k;
gzip_comp_level 4;
gzip_http_version 1.0;
gzip_vary off;
gzip_disable "MSIE [1-6]\.";

解释一下:

  1. gzip_types:要采用 gzip 压缩的 MIME 文件类型,其中 text/html 被系统强制启用;
  2. gzip_static:默认 off,该模块启用后,Nginx 首先检查是否存在请求静态文件的 gz 结尾的文件,如果有则直接返回该 .gz 文件内容;
  3. gzip_proxied:默认 off,nginx 做为反向代理时启用,用于设置启用或禁用从代理服务器上收到相应内容 gzip 压缩;
  4. gzip_buffers:获取多少内存用于缓存压缩结果,16 8k 表示以 8k*16 为单位获得;
  5. gzip_min_length:允许压缩的页面最小字节数,页面字节数从 header 头中的 Content-Length 中进行获取。默认值是 0,不管页面多大都压缩。建议设置成大于 1k 的字节数,小于 1k 可能会越压越大;
  6. gzip_comp_level:gzip 压缩比,压缩级别是 1-9,1 压缩级别最低,9 最高,级别越高压缩率越大,压缩时间越长,建议 4-6;
  7. gzip_http_version:默认 1.1,启用 gzip 所需的 HTTP 最低版本;
  8. gzip_vary:用于在响应消息头中添加 Vary:Accept-Encoding,使代理服务器根据请求头中的 Accept-Encoding 识别是否启用 gzip 压缩;
  9. gzip_disable 指定哪些不需要 gzip 压缩的浏览器

其中第 2 点,普遍是结合前端打包的时候打包成 gzip 文件后部署到服务器上,这样服务器就可以直接使用 gzip 的文件了,并且可以把压缩比例提高,这样 nginx 就不用压缩,也就不会影响速度。一般不追求极致的情况下,前端不用做任何配置就可以使用啦~

附前端 webpack 开启 gzip 压缩配置,在 vue-cli3 的 vue.config.js 配置文件中:

const CompressionWebpackPlugin = require('compression-webpack-plugin')

module.exports = {
  // gzip 配置
  configureWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      // 生产环境
      return {
        plugins: [new CompressionWebpackPlugin({
          test: /\.js$|\.html$|\.css/,    // 匹配文件名
          threshold: 1024,               // 文件压缩阈值,对超过 1k 的进行压缩
          deleteOriginalAssets: false     // 是否删除源文件
        })]
      }
    }
  },
  ...
}

HTTP 服务器

nginx 本身也是一个静态资源的服务器,当只有静态资源的时候,就可以使用 nginx 来做服务器:

server {
  listen       80;
  server_name  localhost;

  location / {
      root   /usr/local/app;
      index  index.html;
  }
}

这样如果访问 http://ip 就会默认访问到 /usr/local/app 目录下面的 index.html,如果一个网站只是静态页面的话,那么就可以通过这种方式来实现部署,比如一个静态官网。

动静分离

就是把动态和静态的请求分开。方式主要有两种:

  • 一种是纯粹把静态文件独立成单独的域名,放在独立的服务器上,也是目前主流推崇的方案
  • 一种方法就是动态跟静态文件混合在一起发布, 通过 nginx 配置来分开
# 所有静态请求都由nginx处理,存放目录为 html
location ~ \.(gif|jpg|jpeg|png|bmp|swf|css|js)$ {
    root    /usr/local/resource;
    expires     10h; # 设置过期时间为10小时
}

# 所有动态请求都转发给 tomcat 处理
location ~ \.(jsp|do)$ {
    proxy_pass  127.0.0.1:8888;
}

注意上面设置了 expires,当 nginx 设置了 expires 后,例如设置为:expires 10d; 那么,所在的 location 或 if 的内容,用户在 10 天内请求的时候,都只会访问浏览器中的缓存,而不会去请求 nginx 。

请求限制

对于大流量恶意的访问,会造成带宽的浪费,给服务器增加压力。可以通过 nginx 对于同一 IP 的连接数以及并发数进行限制。合理的控制还可以用来防止 DDos 和 CC 攻击。

关于请求限制主要使用 nginx 默认集成的 2 个模块:

  • limit_conn_module 连接频率限制模块
  • limit_req_module 请求频率限制模块

涉及到的配置主要是:

  • limit_req_zone 限制请求数
  • limit_conn_zone 限制并发连接数

通过 limit_req_zone 限制请求数

http{
    limit_conn_zone $binary_remote_addrzone=limit:10m; // 设置共享内存空间大
    server{
     location /{
            limit_conn addr 5; # 同一用户地址同一时间只允许有5个连接。
        }
    }
}

如果共享内存空间被耗尽,服务器将会对后续所有的请求返回 503 (Service Temporarily Unavailable) 错误。

当多个 limit_conn_zone 指令被配置时,所有的连接数限制都会生效。比如,下面配置不仅会限制单一 IP 来源的连接数,同时也会限制单一虚拟服务器的总连接数:

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
    limit_conn perip 10; # 限制每个 ip 连接到服务器的数量
    limit_conn perserver 2000; # 限制连接到服务器的总数
}

通过 limit_conn_zone 限制并发连接数

limit_req_zone $binary_remote_addr zone=creq:10 mrate=10r/s;
server{
    location /{
        limit_req zone=creq burst=5;
    }
}

限制平均每秒不超过一个请求,同时允许超过频率限制的请求数不多于 5 个。如果不希望超过的请求被延迟,可以用 nodelay 参数,如:

limit_req zone=creq burst=5 nodelay;

这里只是简单讲讲,配置的时候可以深入去找找资料。

正向代理

正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理,比如我们使用的 VPN 服务就是正向代理,直观区别:

配置正向代理:

resolver 8.8.8.8 # 谷歌的域名解析地址
server {
  resolver_timeout 5s; // 设超时时间
  location / {
      # 当客户端请求我的时候,我会把请求转发给它
      # $host 要访问的主机名 $request_uri 请求路径
      proxy_pass http://$host$request_uri;
  }
}

正向代理的对象是客户端,服务器端看不到真正的客户端。

图片防盗链

server {
  listen       80;
  server_name *.test;

  # 图片防盗链
  location ~* \.(gif|jpg|jpeg|png|bmp|swf)$ {
      valid_referers none blocked server_names ~\.google\. ~\.baidu\. *.qq.com; # 只允许本机 IP 外链引用,将百度和谷歌也加入白名单有利于 SEO
      if ($invalid_referer){
          return 403;
      }
  }
}

以上设置就能防止其它网站利用外链访问我们的图片,有利于节省流量

适配 PC 或移动设备

根据用户设备不同返回不同样式的站点,以前经常使用的是纯前端的自适应布局,但是复杂的网站并不适合响应式,无论是复杂性和易用性上面还是不如分开编写的好,比如我们常见的淘宝、京东。

根据用户请求的 user-agent 来判断是返回 PC 还是 H5 站点:

server {
  listen 80;
  server_name test.com;

  location / {
    root /usr/local/app/pc; # pc 的 html 路径
      if ($http_user_agent ~* '(Android|webOS|iPhone|iPod|BlackBerry)') {
          root /usr/local/app/mobile; # mobile 的 html 路径
      }
      index index.html;
  }
}

设置二级域名

新建一个 server 即可:

server {
  listen 80;
  server_name admin.test.com; // 二级域名

  location / {
      root /usr/local/app/admin; # 二级域名的 html 路径
      index index.html;
  }
}

配置 HTTPS

这里我使用的是 certbot 免费证书,但申请一次有效期只有 3 个月(好像可以用 crontab 尝试配置自动续期,我暂时没试过):

先安装 certbot

wget https://dl.eff.org/certbot-auto
chmod a+x certbot-auto

申请证书(注意:需要把要申请证书的域名先解析到这台服务器上,才能申请):

sudo ./certbot-auto certonly --standalone --email admin@abc.com -d test.com -d www.test.com

执行上面指令,按提示操作。

Certbot 会启动一个临时服务器来完成验证(会占用 80 端口或 443 端口,因此需要暂时关闭 Web 服务器),然后 Certbot 会把证书以文件的形式保存,包括完整的证书链文件和私钥文件。

文件保存在 /etc/letsencrypt/live/ 下面的域名目录下。

修改 nginx 配置:

server{
  listen 443 ssl http2; // 这里还启用了 http/2.0

  ssl_certificate /etc/letsencrypt/live/test.com/fullchain.pem; # 证书文件地址
  ssl_certificate_key /etc/letsencrypt/live/test.com/privkey.pem; # 私钥文件地址

  server_name test.com www.test.com; // 证书绑定的域名
}

配置 HTTP 转 HTTPS

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

  # 单域名重定向
  if ($host = 'www.sherlocked93.club'){
      return 301 https://www.sherlocked93.club$request_uri;
  }

  # 全局非 https 协议时重定向
  if ($scheme != 'https') {
      return 301 https://$server_name$request_uri;
  }

  # 或者全部重定向
  return 301 https://$server_name$request_uri;
}

以上配置选择自己需要的一条即可,不用全部加。

单页面项目 history 路由配置

server {
  listen       80;
  server_name fe.sherlocked93.club;

  location / {
      root       /usr/local/app/dist; # vue 打包后的文件夹
      index     index.html index.htm;
      try_files $uri $uri/ /index.html @rewrites; # 默认目录下的 index.html,如果都不存在则重定向

      expires -1;                         # 首页一般没有强制缓存
      add_header Cache-Control no-cache;
  }

  location @rewrites { // 重定向设置
      rewrite ^(.+)$ /index.html break;
  }
}

vue-router 官网只有一句话 try_files $uri $uri/ /index.html;,而上面做了一些重定向处理。

配置高可用集群(双机热备)

当主 nginx 服务器宕机之后,切换到备份的 nginx 服务器

首先安装 keepalived:

apt install keepalived -y

然后编辑 /etc/keepalived/keepalived.conf 配置文件,并在配置文件中增加 vrrp_script 定义一个外围检测机制,并在 vrrp_instance 中通过定义 track_script 来追踪脚本执行过程,实现节点转移:

global_defs{
  notification_email {
      cchroot@gmail.com
  }
  notification_email_from test@firewall.loc
  smtp_server 127.0.0.1
  smtp_connect_timeout 30 // 上面都是邮件配置
  router_id LVS_DEVEL     // 当前服务器名字,用 hostname 命令来查看
}
vrrp_script chk_maintainace { // 检测机制的脚本名称为chk_maintainace
  script "[[ -e/etc/keepalived/down ]] && exit 1 || exit 0" // 可以是脚本路径或脚本命令
  // script "/etc/keepalived/nginx_check.sh"   // 比如这样的脚本路径
  interval 2 // 每隔2秒检测一次
  weight -20 // 当脚本执行成立,那么把当前服务器优先级改为-20
}
vrrp_instanceVI_1 {   // 每一个vrrp_instance就是定义一个虚拟路由器
  state MASTER     // 主机为MASTER,备用机为BACKUP
  interface eth0   // 网卡名字,可以从ifconfig中查找
  virtual_router_id 51 // 虚拟路由的id号,一般小于255,主备机id需要一样
  priority 100     // 优先级,master的优先级比backup的大
  advert_int 1     // 默认心跳间隔
  authentication { // 认证机制
      auth_type PASS
      auth_pass 1111   // 密码
  }
  virtual_ipaddress { // 虚拟地址vip
      172.16.2.8
  }
}

其中检测脚本 nginx_check.sh,这里提供一个:

#!/bin/bash
A=`ps -C nginx --no-header | wc -l`
if [ $A -eq 0 ];then
  /usr/sbin/nginx # 尝试重新启动nginx
  sleep 2         # 睡眠2秒
  if [ `ps -C nginx --no-header | wc -l` -eq 0 ];then
      killall keepalived # 启动失败,将keepalived服务杀死。将vip漂移到其它备份节点
  fi
fi

复制一份到备份服务器,备份 nginx 的配置要将 state 后改为 BACKUPpriority 改为比主机小。设置完毕后各自 service keepalived start 启动,经过访问成功之后,可以把 Master 机的 keepalived 停掉,此时 Master 机就不再是主机了 service keepalived stop,看访问虚拟 IP 时是否能够自动切换到备机 ip addr。

再次启动 Master 的 keepalived,此时 vip 又变到了主机上。

配置高可用集群的内容来源于:Nginx 从入门到实践,万字详解!

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注