Nginx从入门到放弃.........

目录

Nginx安装部署

虚拟机安装Centos7.4

Centos下载地址:https://vault.centos.org/centos/7.4.1708/isos/x86_64/CentOS-7-x86_64-Minimal-1708.iso

新建虚拟机

image

选择典型即可

选择CentOS镜像

我们在这次学习时使用mini班操作系统镜像,安装速度快,也去除了我们用不到的软件。
image

选择一个非中文路径的虚拟机安装位置

image

虚拟机磁盘配置

image

自定义其他配置

在自定义硬件中,我们可以再次配置虚拟机的内存、cpu等硬件属性,在当前Nginx学习阶段硬件配置不需要过高,
默认单核cpu、1G内存即可。
image

Centos7系统安装

image
出现此界面后敲“回车”进入安装程序

选择安装语言

image
中文英文任你选

分区选择

手动确认一下即可
image
image

开始安装

image

设置密码

image

开启网卡并配置静态ip

  • 修改配置网卡配置文件
    vi /etc/sysconfig/network-scripts/ifcfg-ens33

  • 设置BOOTPROTO=static

  • 设置ONBOOT=yes

  • 手动配置ip地址,注意网关要跟当前虚拟机的网关一致

# ip地址
IPADDR=192.168.44.101
# 子网掩码
NETMASK=255.255.255.0
# 网关
GATEWAY=192.168.44.1
# DNS服务器
DNS1=8.8.8.8
  • 重启网络
    systemctl restart network

完整配置(仅供参考)

TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=static
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=ens33
UUID=10ac735e-0b8f-4b19-9747-ff28b58a1547
DEVICE=ens33
ONBOOT=yes
IPADDR=192.168.44.101
NETMASK=255.255.255.0
GATEWAY=192.168.44.1
DNS1=8.8.8.8

不能上网的错误排查

1、VMware中网关是否正确

2、直接ping ip是否能通(物理连接排查)

一些公网DNS服务器

阿里

223.5.5.5
223.6.6.6

腾讯

119.29.29.29
182.254.118.118

百度

180.76.76.76

114DNS

114.114.114.114
114.114.115.115

谷歌

8.8.8.8
8.8.4.4

Nginx的安装

版本区别

常用版本分为四大阵营

编译安装

下载地址:https://nginx.org/en/download.html

下载.tar.gz的编译包
image

安装gcc、perl、zlib

yum install -y gcc
yum install -y pcre pcre-devel
yum install -y zlib zlib-devel

解压下载的编译包

tar -zxcf nginx-1.21.6.tar.gz

进入到解压的目录,然后执行如下命令

# 配置安装的目录
./configure --prefix=/usr/local/nginx

# 执行编译跟安装
make && make install

Nginx的常用命令

进入安装好的目录 /usr/local/nginx/sbin

./nginx 启动
./nginx -s stop 快速停止
./nginx -s quit 优雅关闭,在退出前完成已经接受的连接请求
./nginx -s reload 重新加载配置

安装成系统服务

创建服务脚本
vi /usr/lib/systemd/system/nginx.service

服务脚本内容(注意路径)

[Unit]
Description=nginx - web server
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
PIDFile=/usr/local/nginx/logs/nginx.pid
ExecStartPre=/usr/local/nginx/sbin/nginx -t -c /usr/local/nginx/conf/nginx.conf
ExecStart=/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
ExecReload=/usr/local/nginx/sbin/nginx -s reload
ExecStop=/usr/local/nginx/sbin/nginx -s stop
ExecQuit=/usr/local/nginx/sbin/nginx -s quit
PrivateTmp=true
[Install]
WantedBy=multi-user.target

重新加载系统服务
systemctl daemon-reload

启动服务
systemctl start nginx.service

开机启动
systemctl enable nginx.service

Docker安装(本次练习使用)

  • 拉取nginx镜像
docker pull nginx
  • 做好数据挂载的准备
# 先随便创建一个Nginx容器
docker run --name nginx -d nginx

# 拷贝创建几个目录用于挂载Nginx的数据
mkdir -p /opt/docker/nginx/conf
mkdir -p /opt/docker/nginx/log
mkdir -p /opt/docker/nginx/html

# 拷贝数据
# 将容器nginx.conf文件复制到宿主机
docker cp nginx:/etc/nginx/nginx.conf /opt/docker/nginx/conf/nginx.conf
# 将容器conf.d文件夹下内容复制到宿主机
docker cp nginx:/etc/nginx/conf.d /opt/docker/nginx/conf/conf.d
# 将容器中的html文件夹复制到宿主机
docker cp nginx:/usr/share/nginx/html /opt/docker/nginx/

# 删除现有的容器
docker rm -f nginx
  • 运行容器
docker run \
-p 80:80 \
--name nginx \
-v /opt/docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \
-v /opt/docker/nginx/conf/conf.d:/etc/nginx/conf.d \
-v /opt/docker/nginx/log:/var/log/nginx \
-v /opt/docker/nginx/html:/usr/share/nginx/html \
-d nginx:latest

Nginx的基础使用

目录结构

进入Nginx的主目录我们可以看到这些文件夹

client_body_temp conf fastcgi_temp html logs proxy_temp sbin scgi_temp uwsgi_temp

其中这几个文件夹在刚安装后是没有的,主要用来存放运行过程中的临时文件

client_body_temp fastcgi_temp proxy_temp scgi_temp

conf

用来存放配置文件相关

html

用来存放静态文件的默认目录 html、css等

sbin

nginx的主程序

基本运行原理

image

Nginx配置与应用场景

最小配置

# 表示开启一个工作的任务进程
worker_processes  1;

# 表示一个工作的任务进程可以开启1024个连接
events {
    worker_connections  1024;
}

http {
    # 包含一些文件,这里包含的是mimeType,其实就是指什么contentType等于什么类型的资源
    include       mime.types;
	
    # 默认的contentType,如果mime.types没匹配上,则使用二进制流的方式传输
    default_type  application/octet-stream;
	
    # 开启零拷贝功能,拷贝数据时不再拷贝给用户态,而是直接拷贝到内核缓冲区,再拷贝给网卡,其中会拷贝一部分关键信息到网卡缓冲区中
    sendfile        on;

    # 与一个客户端保持连接点超时时常
    keepalive_timeout  65;

    server {
        listen       80; # 监听端口号
        server_name  localhost; # 主机名

        location /api {  # 相当于匹配 localhost:80/api路径
            root   html; # 会到nginx服务器的html/api/找东西
            index  index.html index.htm; # 默认页,可配置多个
        }

        location / {  # 相当于匹配 localhost:80/路径
            root   html;   # 会到nginx服务器的html/找东西
            index  index.html index.htm; # 默认页,可配置多个
        }

        error_page   500 502 503 504  /50x.html; # 当出现500、502等状态码时,
	# 跳转到/50x.html,但是localhost:80/50x.html并不存在
	# 因此还顺便location定位了一下,从html目录里找
        location = /50x.html {
            root   html;
        }
    }
}

虚拟主机

原本一台服务器只能对应一个站点,通过虚拟主机技术可以虚拟化成多个站点同时对外提供服务

注意: 在配置nginx的server时,其实是可以配置多个的,意味着nginx可以同时监听多个端口,并且可以配置不同的主机名称

修改本地的hosts文件(nginx包括windows,主要是这里的域名不能访问80端口,所以采用这种方式)

修改windows的hosts文件
C:\Windows\System32\drivers\etc\hosts
添加如下内容

81.68.120.66 codestars.com  (可以访问到www.codestars.com)
81.68.120.66 www.codestars.com
81.68.120.66 vip.codestars.com

修改linux的hosts文件
vi /etc/hosts
添加如下内容

127.0.0.1 www.codestars.com
127.0.0.1 vip.codestars.com

在nginx的html目录下添加如下2个文件夹,分别为www和vip,代表2个不同的站点(注意docker的路径问题)

  • 创建文件夹
    由于我使用的是docker安装,所以在映射的html目录中添加
    image

  • 修改nginx的配置文件(注意现在用的是docker,所以目录地址需要注意注意)

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

        location / {
          root /usr/share/nginx/html/www;
          index index.html;
        }
   }

   server {
        listen 80;
        server_name vip.codestars.com;

        location / {
          root /usr/share/nginx/html/vip;
          index index.html;
        }
   }

  • 此时通过浏览器访问,发现不同的域名会跳转到不同的站点

image


image

server_name的多种匹配方式

  • 完整匹配
server_name vod.mmban.com www1.mmban.com;
  • 通配符匹配
server_name *.mmban.com

  • 通配符结束匹配
server_name vod.*;
  • 正则匹配
server_name ~^[0-9]+\.mmban\.com$;

反向代理(外网内网均可代)

location / {
   proxy_pass http://www.baidu.com/;
}

隧道式代理与DR模型(lvs)

lvs:更高性能的负载均衡器,但是功能更少。

隧道式代理:客户端请求nginx,由nginx访问tomcat,将资源拿到,再转发给客户端。(不适用于大文件的IO操作)

DR模型: 客户端请求nginx,nginx访问tomcat,然后让tomcat直接将资源转发给客户端。

基于反向代理的负载均衡

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

    sendfile        on;
    #tcp_nopush     on;
    keepalive_timeout  65;
	
    # 配置负载均衡
    upstream nginx-cluster{
        server localhost:8081;
        server localhost:8082;
    }
    server {
        listen       80;
        server_name  localhost;

        location /api {
            # 配置反向代理,一旦使用了proxy_pass,那么root和index的配置就失效了
            proxy_pass http://nginx-cluster;
        }

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

负载均衡策略

轮询

默认情况下使用轮询方式,逐一转发,这种方式适用于无状态请求。

weight(权重)

指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。

upstream httpd {
	server 127.0.0.1:8050 weight=10 down;
	server 127.0.0.1:8060 weight=1;
	server 127.0.0.1:8060 weight=1 backup;
}
  • down: 表示当前的server暂时不参与负载
  • weight:默认为1,weight越大,负载的权重就越大(常用
  • backup: 其他所有的非backup机器down或者忙的时候,才会请求backup备用机器

ip_hash

根据客户端的ip地址转发同一台服务器,可以保持回话。

upstream httpd {
	ip_hash;
	server 127.0.0.1:8050 weight=10 down;
	server 127.0.0.1:8060 weight=1;
	server 127.0.0.1:8060 weight=1 backup;
}

least_conn

最少连接访问

url_hash

根据用户访问的url定向转发请求

fair

根据后端服务器的响应时间转发请求

动静分离

使用场景、原理

什么需要使用动静分离?
1、 不然所有的静态资源都由我们的tomcat来承担,会增加tomcat的负担
2、 需要多经过一层网络传输,因为是先访问的nginx,而再由nginx将静态资源再转发给客户端

动静分离的基本配置

先将tomcat中的静态资源,剪切到我们的nginx服务器上

位置就放在html目录当中就可以了。

image

修改nginx.conf配置文件

多个location的写法

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

    sendfile        on;
    #tcp_nopush     on;
    keepalive_timeout  65;
	
    # 配置负载均衡
    upstream nginx-cluster{
        server 81.68.120.66:8081;
        server 81.68.120.66:8082;
    }
    server {
        listen       80;
        server_name  localhost;

        location /api {
            # 配置反向代理,一旦使用了proxy_pass,那么root和index的配置就失效了
            proxy_pass http://nginx-cluster;
        }

        location /css {
          # 这里可以写绝对路径,也可以直接写html,会根据你写的路径再拼接上一个 /css,因此这里只需要在html目录下创建一个css文件即可
          root html; 
          index index.html index.htm;
        }

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

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

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

单个location的写法

location ~*/(css|img|js) {
    root /usr/local/nginx/static;
    index index.html index.htm;
}

location的匹配写法

location的前缀一共有4种

  • / 通用匹配,任何请求都会匹配到

  • = 精确匹配,只有完全匹配上才能生效

  • ~ 正则匹配,区分大小写

  • ~* 正则匹配,不区分大小写

  • ^~ 前缀匹配,只匹配指定前缀的uri

location匹配顺序

  • 多个正则location直接按书写顺序匹配,成功后就不会继续往后面匹配

  • 普通(非正则)location会一直往下,直到找到匹配度最高的(最大前缀匹配)

  • 当普通location与正则location同时存在,如果正则匹配成功,则不会再执行普通匹配

  • 所有类型location存在时,“=”匹配 > “^~”匹配 > 正则匹配 > 普通(最大前缀匹配)

alias与root的区别

location /css {
    alias /usr/local/nginx/static/css;
    index index.html index.htm;
}

root用来设置根目录,而alias在接受请求的时候在路径上不会加上location。

1)alias指定的目录是准确的,即location匹配访问的path目录下的文件直接是在alias目录下查找的;

2)root指定的目录是location匹配访问的path目录的上一级目录,这个path目录一定要是真实存在root指定目录下的;

3)使用alias标签的目录块中不能使用rewrite的break(具体原因不明);另外,alias指定的目录后面必须要加上"/"符号!!

4)alias虚拟目录配置中,location匹配的path目录如果后面不带"/",那么访问的url地址中这个path目录后面加不加"/"不影响访问,访问时它会自动加上"/"; 但是如果location匹配的path目录后面加上"/",那么访问的url地址中这个path目录必须要加上"/",访问时它不会自动加上"/"。如果不加上"/",访问就会失败!

5)root目录配置中,location匹配的path目录后面带不带"/",都不会影响访问。

URLRewrite 路径重写

使用场景: 屏蔽真实访问地址

rewrite的基本语法

基本语法

rewrite   <regex>    <replacement>   [flag];
关键字     正则表达式    替换的内容     flag标记

关键字:不能改变

正则表达式:注意开头需要带上 ^ 结尾需要带上$,其中的分组需要小括号扩起来

替换的内容: 随便写,如果需要获取到正则表达式中匹配到的值,需要使用$n,其中的n为正则表达式的分组,分组从1开始

flag标记

  • last # 本条规则匹配完成后,继续向下匹配新的location URI规则
  • break # 本条规则匹配完成即终止,不再匹配后面任何规则
  • redirect # 返回302临时重定向,浏览器地址会显示跳转后的URL地址
  • permanent # 返回301永久重定向,浏览器地址会显示跳转后的URL地址

能够使用的地方

  • server
  • location
  • if

rewrite伪静态配置

例如在localtion当中进行路径的重写,如果路径出现了xx.html,则将路径进行重写

此时一旦匹配到 /api/1.html 则真实访问的地址为: http://nginx-cluster/api/index.jsp?pageNum=1

配置如下

    location /api {
        rewrite ^/([0-9]+).html$ /index.jsp?pageNum=$1 break;
        # 配置反向代理,一旦使用了proxy_pass,那么root和index的配置就失效了
        proxy_pass http://nginx-cluster;
    }

希望我们的tomcat仅能被nginx内网访问,无法被其他人访问

开放8080端口,并仅对指定ip 27.38.202.73开放

firewall-cmd --permanent --add-rich-rule="rule family="ipv4" source address="192.168.44.101"
port protocol="tcp" port="8080" accept"

查看已配置的规则

firewall-cmd --list-all

移除规则

firewall-cmd --permanent --remove-rich-rule="rule family="ipv4" source
address="27.38.202.73" port port="8080" protocol="tcp" accept"

防盗链

防盗链原理(及疑惑点,以后再来解)

1、我们的站点在第一次访问时,不会携带Referer
2、但是我们的站点再去访问我们的其他静态资源时,就会携带Referer
3、因此我们可以在nginx中进行配置,要求客户端在访问我们的静态资源时,必须携带Referer,并且需要判断Referer为我们的站点

nginx的防盗链配置

valid_referers none | blocked | server_names | strings ....;
  • none,检测Referer头域不存在的情况(此时如果不携带Referer就可以访问)
  • blocked,在Header中的Referer不为空,但是该值被防火墙或代理进行伪装过,如不带"http://" 、"https://"等协议头的资源允许访问。
  • server_names 设置一个或多个URL,检测Referer头域的值是否是这些URL中的某一个

在需要防盗链的location中配置

# 需要携带Referer并且只能为 27.38.202.73,注意应该配置域名
valid_referers 27.38.202.73;
    if ($invalid_referer) {
       return 403;
    }

使用浏览器或者curl检测

# -I 只显示响应头
curl -I http://192.168.44.101/img/logo.png

# -e 携带Referer
curl -e "http://baidu.com" -I http://192.168.44.101/img/logo.png

返回错误码、返回错误页面、返回错误图片

上面已经演示过返回错误码的情况了。

返回错误页面
1、在html下创建一个401.html

2、修改配置文件(写法1)

valid_referers 27.38.202.73;
    if ($invalid_referer) {
       return 401;
    }

error_page 401 /401.html;
location = /401.html {
    root html;
}

3、修改配置文件(写法2)

valid_referers 27.38.202.73;
    if ($invalid_referer) {
       return /401.html;
    }

返回报错图片(配合rewrite)

valid_referers 27.38.202.73;
    if ($invalid_referer) {
       rewrite ^/ /img/x.png break;
    }

Nginx高可用配置,配合keepalived

keepalived的两种安装方式

编译安装
下载地址:https://www.keepalived.org/download.html#

安装依赖
yum install openssl-devel

使用 ./configure 编译安装

使用yum安装

yum install keepalived

使用yum安装后配置文件在
/etc/keepalived/keepalived.conf

最小配置

第一台机器的/etc/keepalived/keepalived.conf配置文件

! Configuration File for keepalived
global_defs {
  # 每台机器需要唯一的路由id
  router_id lb111
}

vrrp_instance atguigu {
  # 配置为主机
  state MASTER
  # 使用的网卡
  interface ens33
  # 虚拟的路由id,多台机器需要一致
  virtual_router_id 51
  # 优先级
  priority 100
  advert_int 1

  # 配置同一个keepalived分组的认证
  authentication {
    auth_type PASS
    auth_pass 1111
  }
  
  # 配置虚拟ip
  virtual_ipaddress {
    192.168.44.200
  }
}

第二台机器的/etc/keepalived/keepalived.conf配置文件

! Configuration File for keepalived
global_defs {
  router_id lb110
}

vrrp_instance atguigu {
  # 配置为从机
  state BACKUP
  interface ens33
  virtual_router_id 51
  priority 50
  advert_int 1

  authentication {
    auth_type PASS
    auth_pass 1111
  }

  virtual_ipaddress {
    192.168.44.200
  }
}

重启服务即可
systemctl start keepalived

SSL证书

不安全的Http协议

http协议是不安全的,因为在数据传输的过程中,数据可能会被篡改。

Https原理

其实就是添加了一个第三方的CA机构,该机构会在客户端存储公钥,并且服务器端在接收到数据后,还会经过CA机构进行私钥解密。

证书

如上介绍的那种方式,发放的就称之为证书。

证书自签名(太难了)

openssl

包含SSL协议库、应用程序以及密码算法库

图形化工具 XCA

https://www.hohnstaedt.de/xca/index.php/download

在线证书申请(腾云云免费申请)

打开腾讯云,搜索ssl服务

image

点击左侧: 我的证书,再点击页面的申请免费证书

image

填写申请免费证书的表单

image

下载需要的证书

image

将下载好的证书文件解压出来,将其放到nginx的conf.d目录(注意我这里使用的是docker)

image
image

修改nginx的配置文件

  server {
    listen 80;
    server_name www.codestars.xyz;
    rewrite ^(.*)$ https://${server_name}$1 permanent;
  }

  server {
    listen 443 ssl;
    server_name www.codestars.xyz;

    #请填写证书文件的相对路径或绝对路径
    ssl_certificate /etc/nginx/conf.d/www.codestars.xyz_bundle.crt; 
    #请填写私钥文件的相对路径或绝对路径
    ssl_certificate_key /etc/nginx/conf.d/www.codestars.xyz.key; 
    ssl_session_timeout 5m;

    #请按照以下协议配置
    ssl_protocols TLSv1.2 TLSv1.3; 

    #请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; 
    ssl_prefer_server_ciphers on;

    location / {
      root /usr/share/nginx/html/www;
      index index.html;
    }
  }
部署参考

腾讯云官方文档: https://cloud.tencent.com/document/product/400/35244

Nginx高级篇

会化管理

Nginx高级负载均衡

ip_hash

作用: 根据用户请求的ip来进行hash,保证同一个ip都会访问到同一台服务器
提示:现在的移动用户,使用手机网络中都很可能在不断的移动,换了个基站可能ip就不一样了,这会导致会话失效。

upstream httpds {
   ip_hash;
   server 192.168.44.102 ;
   server 192.168.44.103 ;
}

作用:根据cookie中的jessionid来进行hash后转发到指定的服务器上

注意: 这是tomcat给我们下发的cookie_jsessionid,同时也可以使用其他自定义的cookie
这种方式可以保证只要是同一个jssesionid,那么就都会转发到同一台服务器上

upstream httpds {
   hash $cookie_jsessionid;
   server 192.168.44.102 ;
   server 192.168.44.103 ;
}

hash $request_uri;

作用:根据请求中的uri来进行hash
例如: https://www.baidu.com/abc/aa?name=zhangsan中的uri就是:/abc/aa?name=zhangsan

upstream httpds {
   hash $request_uri;
   server 192.168.44.102 ;
   server 192.168.44.103 ;
}

使用stick模块进行负载均衡(动态静态服务器都能用)

安装stick(平滑升级原有的nginx)

stick模块: 可以基于完成对于会话的保持,并且cookie不需要上游服务器来生成,而是通过stick来为我们生成对应的cookie来完成hash

下载stick模块:下载最新版的即可
https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng/downloads/?tab=tags

将下载好的tar.gz压缩包给解压
tar -zxvf nginx-goodies-nginx-sticky-module-ng-c78b7dd79d0d.tar.gz

进入到解压后的目录,修改ngx_http_sticky_misc.c文件,在12行添加如下内容

#include <openssl/sha.h>
#include <openssl/md5.h>

image

安装所需的openssl-devel依赖
yum install -y openssl-devel

进入到下载好的nginx目录,进行安装

./configure --prefix=/usr/local/nginx --add-module=/root/nginx-goodies-nginx-sticky-module-ng-c78b7dd79d0d

执行编译make,可以不执行make install,因为make install其实就是将编译好的文件目录objs覆盖到--prefix指定的目录中
make

执行make upgrade可以查看文件编译的是否有问题
make upgrade

进入到objs文件目录下,将nginx复制到/usr/local/nginx/sbin/nginx,将原本的nginx命令替换掉

cd objs
cp nginx /usr/local/nginx/sbin/

查看是否安装成功

cd /usr/local/nginx/sbin
nginx -v

image

stick的基础使用

name:默认值就是route,是stick为我们生成的cookie的名称,stick会根据这个名称进行hash后为我们做负载均衡(注意别设置为jsessionid,不要与tomcat下发的cookie重名,不然会导致负载均衡失效)

expires:过期时间,设置为6小时,默认为永不过期

upstream httpget {
  sticky name=route expires=6h;

  server 192.168.44.102;
  server 192.168.44.103;
}

KeepAlive

通过查看www.baidu.com观察是否使用了KeepAlive

http提供的一种特性,能够保持浏览器与服务端的连接。

查看www.baidu.com中发送请求中的请求头与响应头

可以看到www.baidu.com 这个请求当中,请求头与响应头中都携带了一个参数
Connection: keep-alive

而我们再看看其他的js、css、png等请求:发现都没有携带该参数

什么时候使用KeepAlive

使用: 访问首页之类的,访问了首页之后肯定还需要访问首页其他的链接,而这个时候可以考虑使用KeepAlive来保持连接

不使用:类似于访问首页这个骨架后所访问的其他子请求,例如css、js等静态资源,我们期望达到的是将这些资源缓存下来,而不是通过保持连接而不断的重新请求

一句话: 明显的预知用户会在当前连接上有下一步操作使用KeepAlive来保证连接复用。访问内联资源一般用缓存,不需要keepalive,因为长时间的tcp连接容易导致系统资源无效占用。

在nginx中可以对客户端配置,也可以对上游服务器配置

客户端配置(在http中配置)

官方文档: http://nginx.org/en/docs/http/ngx_http_core_module.html

# 限制keepalive保持一次tcp连接的最大时间,超过使该keepalive失效
keepalive_time 1h;

# 如果有一些高频访问接口,不需要缓存,很多人每人都来一下,例如时间同步服务器,并发量高,但是都是短连接
# 如果设置成0,则相当于关闭keepalive,响应的Connection: close
# 指的是65秒不活动的连接,会被强制断开,如果65内客户端进行了操作,那么该时间会重置
keepalive_timeout 65;
# 会告知客户端timeout超时时间,额外添加一个Header为Keep-Alive:timeout 65(http1.0必须要,但是http1.1之后已经不需要了)
keepalive_timeout 65 65;

# 关闭对于ie6的长连接,并不是指关闭keepalive
keepalive_disable msie6;

# 一个tcp连接复用后可以发起的请求次数
keepalive_requests 1000;

# 两次向客户端写操作之间的间隔,如果大于这个时间则关闭连接(指的是服务器端向客户端进行写操作的间隔)。
# 一定要注意: 不能比keepalive_timeout少,不然如果客户进行文件下载时,服务器端准备文件的事件超过了该时间,则会直接断开连接
send_timeout 65s;

上游服务器配置(在upstream中配置)

官方文档: http://nginx.org/en/docs/http/ngx_http_upstream_module.html

# nginx与上游服务器之间保持连接最大个数
keepalive 100;

# nginx与上游服务器的连接保持超时时间,指的是空闲时间
keepalive_timeout 60s;

# 保持的一个连接,最大可以发起的请求数量
keepalive_requests 1000;

# 保持一个连接的最大时间,nginx1.18.0好像不支持
# keepalive_time 1h;
由于默认nginx向上游服务器发起连接时使用keepalive,nginx默认向上游服务器发送请求时使用的是http1.0协议

注意: 需要配置在进行了proxy_pass的localtion中进行相对应的配置

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

    sendfile        on;
    #tcp_nopush     on;
    keepalive_timeout  0;

    upstream httpget {
        # nginx与上游服务器之间保持连接最大个数
        keepalive 100;
        # nginx与上游服务器的连接保持超时时间,指的是空闲时间
        keepalive_timeout 60s;
        # 保持的一个连接,最大可以发起的请求数量
        keepalive_requests 1000;
    
        server 127.0.0.1:81;
    }
    server {
        listen       80;
        server_name  localhost;

        location / {
            # 配置http版本号
            # 默认使用http1.0协议,需要在request中增加”Connection: keep-alive“ header才能够支持,而HTTP1.1默认支持。
            proxy_http_version 1.1;
        
            # 清除默认的Connection:close信息,清理了之后默认就使用keep-alive
            proxy_set_header Connection "";
            proxy_pass http://httpget;
        }

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

charles抓包工具

下载地址
https://www.charlesproxy.com/assets/release/4.6.2/charles-proxy-4.6.2-win64.msi?k=fc1457e312

官网
https://www.charlesproxy.com

使用示例
image

重复发起请求复现结果
image

查看当前连接的复用情况
image
image
开启:
image

关闭:
image

AB压力测试keepalive效果

apache benchmark 安装与基本使用

安装
yum install -y httpd-tools

参数说明:

  • -n 即requests,用于指定压力测试总共的执行次数。
  • -c 即concurrency,用于指定的并发数。
  • -t 即timelimit,等待响应的最大时间(单位:秒)。
  • -b 即windowsize,TCP发送/接收的缓冲大小(单位:字节)。
  • -p 即postfile,发送POST请求时需要上传的文件,此外还必须设置-T参数。
  • -u 即putfile,发送PUT请求时需要上传的文件,此外还必须设置-T参数。
  • -T 即content-type,用于设置Content-Type请求头信息,例如:application/x-www-form-urlencoded,默认值为text/plain。
  • -v 即verbosity,指定打印帮助信息的冗余级别。
  • -w 以HTML表格形式打印结果。
  • -i 使用HEAD请求代替GET请求。
  • -x 插入字符串作为table标签的属性。
  • -y 插入字符串作为tr标签的属性。
  • -z 插入字符串作为td标签的属性。
  • -C 添加cookie信息,例如:"Apache=1234"(可以重复该参数选项以添加多个)。
  • -H 添加任意的请求头,例如:"Accept-Encoding: gzip",请求头将会添加在现有的多个请求头之后(可以重复该参数选项以添加多个)。
  • -A 添加一个基本的网络认证信息,用户名和密码之间用英文冒号隔开。
  • -P 添加一个基本的代理认证信息,用户名和密码之间用英文冒号隔开。
  • -X 指定使用的和端口号,例如:"126.10.10.3:88"。
  • -V 打印版本号并退出。
  • -k 使用HTTP的KeepAlive特性。
  • -d 不显示百分比。
  • -S 不显示预估和警告信息。
  • -g 输出结果信息到gnuplot格式的文件中。
  • -e 输出结果信息到CSV格式的文件中。
  • -r 指定接收到错误信息时不退出程序。
  • -h 显示用法信息,其实就是ab -help。

测试ngixn直连(性能最好)

测试环境: 直接访问某一台nginx服务器即可

测试命令ab -n 100000 -c 30 http://192.168.44.102/

测试结果:
image

测试nginx代理(性能明显下降)

测试环境: 2台nginx,其中一台nginx代理另外1台nginx,将代理服务器中有关上游服务器的keepalive设置注释掉

测试命令ab -n 100000 -c 30 http://192.168.44.101/

测试结果:
image
原因:nginx代理到另外一台nginx本身也需要完成TCP三次握手。

测试nginx代理 + keepalive(性能有所提升,一般来说至少40%的提升,这里的测试是本机,不是实际生产环境)

测试环境: 2台nginx,其中一台nginx代理另外1台nginx,将代理服务器中有关上游服务器的keepalive设置开启,具体参考文章中对于上游服务器的keepalive设置
image

测试tomcat直连(AB客户端没有连接复用)

测试环境: 准备一台tomcat,然后先手动访问一次即可

测试命令ab -n 100000 -c 30 http://192.168.44.105:8080/

测试结果
image

测试nginx代理tomcat并使用keepalive(相当于有了连接复用,效率反而更高)

测试环境: 准备一台tomcat、一台nginx,使用nginx代理tomcat,并配置nginx对于上游服务器的keepalive设置

测试命令ab -n 100000 -c 30 http://192.168.44.101/

测试结果
image

提示:如果不开启keepalive,效率就会被直连tomcat更低了。

什么时候在tomcat前置nginx性能有明显提升?

当客户端不支持连接复用且直接访问tomcat时,每次都会使用新的连接。在这种情况下前置一个nginx来进行keepalive性能会有明显提升。

Nginx反向代理核心流程

Upstream proxy_pass工作流程

proxy_pass 向上游服务器请求数据共有6个阶段

  • 初始化
  • 与上游服务器建立连接
  • 向上游服务器发送请求
  • 处理响应头
  • 处理响应体
  • 结束

接收到客户端请求时的处理(包含对于上游服务器的缓冲配置)

1、处理请求头

2、处理请求体,此时会有如下配置

  • client_body_buffer_size :客户端请求体或响应体的缓冲区最大大小(如果大于该配置,写入临时文件)
  • proxy_request_buffering on | off : 是完全读取完毕请求体再向服务器发请求,还是边读边发(并行操作)

3、默认链表形式轮询选择upstream中的服务器,其中会使用epoll的方式加入连接请求的事件,并设置回调函数来处理该连接请求

4、第3点中的回调函数带着请求数据去访问上游服务器,这里也会通过epoll来异步获取结果处理

5、执行第4步添加的回调函数,接收上游服务器传递的数据,其中会涉及如下参数

  • proxy_buffering on 缓冲读取到的数据
  • proxy_buffering off 不缓冲读取到的数据(上下游速率不一致),如果设置为off,那么将会边读取上游服务器发来的数据,边向客户端传输数据(这样可以保证速率一致,但是会导致连接长时间占用),当然,header部分是无论如何都会缓冲的(也会缓冲body数据,但是一般来说大小都不够,此时也不会写入临时文件),关闭后只是不缓冲body部分
  • proxy_buffers 32 64k; 设置缓冲区32个64kb的内存块
  • proxy_max_temp_file_size; 当proxy_buffers缓冲区不够时会写入临时文件到磁盘,设置临时文件的最大值
  • proxy_temp_path; 设置临时文件的多层目录,避免一个目录下文件过多,查找更耗时
  • proxy_temp_file_write_size 8k; 设置一次向磁盘写入临时文件的大小

如下配置需要配置在

# 可以配置在http中
proxy_requset_buffering on;

# 可配置http、server、location
proxy_buffering on;
# 可配置http、server、location
proxy_buffer_size 64k;

# 可配置http、server、location
proxy_buffers 32 128k;
# 可配置http、server、location
proxy_busy_buffers_size 8k;
# 可配置http、server、location
proxy_max_temp_file_size 1024m;

对客户端的限制(http、server、location,其中包含内存与文件缓冲区)

可以配置的位置有如下三个

  • http
  • server
  • location

client_body_buffer_size : 读取客户端body缓冲区时的大小配置(内存缓冲),默认32位8k,64位16k

client_body_temp_path : 磁盘缓冲存储位置,当内存缓冲区大小不够时,则会存储到磁盘(文件缓冲)

client_body_in_file_only on : 把body写入磁盘文件,请求结束也不会删除(一般不开启)

client_max_body_size: 客户端发送的请求体最大大小。根据http请求头中的content-length来判断

client_body_timeout:接收客户端的请求体时,客户端突然不发送数据了,那么超过该时间则断开连接

client_header_timeout:接收客户端的请求头时,客户端突然不发送数据了,那么超过该时间则断开连接

client_body_in_single_buffer : 确定 nginx 是否应将整个客户端请求主体保存在单个缓冲区中。建议在使用变量时使用该指令 $request_body ,以节省涉及的复制操作次数。

client_header_buffer_size : 设置读取客户端请求头的缓冲区大小,如果没有足够的内存放入请求头或请求行的数据,那么将会使用large_client_header_buffers

large_client_header_buffers : 默认8k

使用反向代理后无法获取客户端IP地址

问题分析: 1台tomcat服务器 + 1台nginx服务器,使用nginx反向代理到tomcat服务器上,而tomcat服务器需要获取到ip地址。此时tomcat获取到的ip地址就是nginx的ip地址。

原因分析: tomcat中获取ip地址的方式是通过request.getRemoteAddr(),此种方式只能获取到实际访问了该tomcat的连接,而此时这个连接是由nginx发起的,所以获取到的是nginx的ip地址。

解决方案

通过X-Real-IP

额外模块,不推荐使用

通过setHeader的方式来使用nginx设置一个请求头,请求头携带真实的ip地址

nginx的配置(配置在进行了proxy_pass的location当中)

proxy_set_header X-Forwarded-For $remote_addr;

tomcat中的代码修改

String ipAddr = request.getHeader("X-Forwarded-For");
System.out.println(ipAddr);
该方案可能出现的问题

1、ip地址是否可能被通过header伪造
答:不能,因为虽然通过请求工具来伪造了X-Forwarded-For这个header,但是在经过我们反向代理服务器时,nginx还是会通过remoteAddr来获取客户端的地址,然后将原有的X-Forwarded-For给覆盖掉

2、多层nginx会导致最终IP地址不正确的情况怎么办?答: 一旦有多层nginx,每台nginx上都配置了proxy_set_header X-Forwarded-For $remote_addr;那么将会导致真实的IP地址被覆盖掉。因此有如下2个解决方案:

  • 只在第一层nginx上添加proxy_set_header X-Forwarded-For $remote_addr;,往后的nginx都不添加该header
  • 不覆盖原有的X-Forwarded-For,而是多层的ngixn分别往该header上追加获取到的ip地址。最终的结果应该为: X-Forwarded-For: 192.168.11.1,192.168.11.2

一些默认有用的header(host、pragma、cache-control、upgrade-insecure-requests、accept-encoding)

# nginx在帮我们做反向代理的时候,会默认覆盖掉原有的host,换成我们upstream配置的名称
host:httpget

# 浏览器缓存
pragma:no-cache
cache-control:no-cache

# 告知服务器,当前的连接是不安全的,可以考虑切换为https
upgrade-insecure-requests:1

# 当前客户端的信息
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36

# 客户端能够接受到数据类型
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

# 能够接受的响应数据的压缩格式
accept-encoding:gzip, deflate, br

# 能够接受的语言类型
accept-language: zh-CN,zh;q=0.9

Gzip进行数据传输时的压缩

Gzip原理

实际上就是客户端在进行http请求时,携带的一个header, accept-encoding当中会告知服务器端能够接受的压缩类型。

服务器端在接收到客户端的请求后,可以将数据压缩后再发送给客户端,而后客户端再将其解压处理。

使用gzip的站点

访问京东时的响应头中: content-encoding: gzip
访问淘宝时的响应头中: content-encoding: gzip
访问阿里云时的响应头中:content-encoding: gzip
访问谷歌时的响应头中: content-encoding: br

由此可见,在很多的互联网大厂中都使用到了gzip来压缩数据,因此还是很有必要学习的

Gzip动态压缩(无法使用sendfile,建议不要对音视频、图片等文件进行压缩)

配置gzip之前的请求响应头

Accept-Ranges: bytes
Connection: keep-alive
Content-Length: 14
Content-Type: text/html
Date: Wed, 23 Nov 2022 07:37:07 GMT
ETag: "637d92c0-e"
Last-Modified: Wed, 23 Nov 2022 03:25:52 GMT
Server: nginx/1.18.0

Gzip的配置解析

gzip压缩可以配置在如下3种地方

  • http
  • server
  • location

配置详解

# 开启gzip功能,默认是关闭的
gzip on;

# gzip的缓冲区大小,默认32位操作系统是 32个4k的内存块,64位操作系统是16个8k的内存块
gzip_buffers 16 8k;

# 压缩的比例,比例越高,压缩的文件越小,但是也更耗费资源,压缩等级默认为1,可以设置1~9
gzip_comp_level 6;

# 匹配不使用gzip压缩的类型,没有默认值
gzip_disable "MSIE [1-6]\.(?!.*SV1)";

# 使用gzip的最小http协议版本,可以配置1.0或1.1,默认为1.1
gzip_http_version 1.1;

# 设置将被gzip压缩的响应的最小长度。 长度仅由“Content-Length”响应报头字段确定。
gzip_min_length 256;

# gzip_proxied 多选,该配置放在下面单独说
gzip_proxied off;

# 增加一个header:  Vary: Accept-Encoding,适配老的浏览器
gzip_vary on;

# 哪些mime类型的文件进行压缩
gzip_types text/html;

gzip_proxied的配置选项解析:作为反向代理时,针对上游服务器返回的响应头信息进行压缩(如果不使用反向代理,那么该配置项是没有用的。)

  • off 为不做限制

  • expired - 启用压缩,如果header头中包含 "Expires" 头信息

  • no-cache - 启用压缩,如果header头中包含 "Cache-Control:no-cache" 头信息

  • no-store - 启用压缩,如果header头中包含 "Cache-Control:no-store" 头信息

  • private - 启用压缩,如果header头中包含 "Cache-Control:private" 头信息

  • no_last_modified - 启用压缩,如果header头中不包含 "Last-Modified" 头信息

  • no_etag - 启用压缩 ,如果header头中不包含 "ETag" 头信息

  • auth - 启用压缩 , 如果header头中包含 "Authorization" 头信息

  • any - 无条件启用压缩(跟off一个意思)

完整实例(放在需要反向代理的location中)

  gzip on;
  gzip_buffers 16 8k;
  gzip_comp_level 6;
  gzip_http_version 1.1;
  gzip_min_length 256;
  gzip_proxied any;
  gzip_vary on;
  gzip_types text/plain application/x-javascript text/css application/xml;
  gzip_types
    text/xml application/xml application/atom+xml application/rss+xml application/xhtml+xml image/svg+xml
    text/javascript application/javascript application/x-javascript
    text/x-json application/json application/x-web-app-manifest+json
    text/css text/plain text/x-component
    font/opentype application/x-font-ttf application/vnd.ms-fontobject
    image/x-icon;
  # gzip_disable "MSIE [1-6]\.(?!.*SV1)";

配置gzip之后的请求响应头

Connection: keep-alive
# 内容压缩格式为gzip
Content-Encoding: gzip
Content-Type: text/html
Date: Wed, 23 Nov 2022 07:43:34 GMT
ETag: W/"637d92c0-e"
Last-Modified: Wed, 23 Nov 2022 03:25:52 GMT
Server: nginx/1.18.0

# 传输的压缩格式,将数据以包的形式一点点的传输给客户端,会额外加一个最后的包,最后的包为0字节大小
# 当包为0字节大小时,代表传输结束。相当于Content-Length的代替
# nginx异步导致不知道最终发过来的数据有多大,所以使用该方式
Transfer-Encoding: chunked

# 因为配置了gzip_vary on; 一般不需要配置,当然配置了也无所谓,主要是为了兼容一些老的浏览器
Vary: Accept-Encoding

调整Chrome浏览器的接受压缩格式

image

Gzip静态压缩(相当于动态压缩的扩展)

静态压缩的原理(预先对文件处理、nginx找文件时的区别、适用场景、sendfile)

  • 我们可以把nginx上的一些html、css等文件预先打包成一个压缩包

  • 例如nginx的html目录下,不仅有一个index.html 还有一个 index.html.gz

  • 当nginx开启了静态压缩后,寻找文件时,不会直接先找index.html,而是先找index.html.gz,找到后直接使用sendfile零拷贝的方式传输到网卡缓冲区当中,减少一次拷贝(内核态到用户态),这里的用户态是指nginx的缓冲区

  • 这样做的好处就是,nginx不再拿到文件后进行动态压缩了,而是把已经压缩好的文件直接通过零拷贝的方式发送给客户端。

  • 值得注意的是: 不适合反向代理,只适用于作为资源服务器的时候

重新编译nginx,加上静态gzip的模块

进入nginx的目录后重新配置
./configure --prefix=/usr/local/nginx --with-http_gzip_static_module

编译
make

进入objs目录下, 拷贝走nginx命令到/usr/local/nginx/sbin/
cp nginx /usr/local/nginx/sbin/

nginx当中的配置

仅有一个配置:gzip_static on | off | always;,可以配置在http, server, location

  • on:检查客户端是否支持gzip,如果支持就发送gzip包,不支持就找源文件发给客户端
  • off:关闭static模块
  • always:不管客户端是否支持gzip,都将gzip直接发送给客户端(这个一般需要配合gun_zip模块使用)

ngx_http_gunzip_module

在文件发送给客户端的时候,将其解压后发送给客户端。

作用: 为我们节省磁盘空间,我们可以把nginx上所有的源文件都删了,只剩下压缩文件,此时需要配合静态压缩模块配置成只向客户端发送gzip压缩包时。 一般用不上。

使用方法

# 配置nginx
./configure --prefix=/usr/local/nginx --with-http_gzip_static_module --with-http_gzip_gunzip_module

# 编译
make

# 进入到编译好的目录,然后把nginx命令挪过去
cd objs
cp nginx /usr/local/nginx/sbin/

配置,可以配置在http, server, location

  • gunzip on | off; 开关
  • gunzip_buffers 16 8k; 配置gunzip缓冲区为16个8k的内存块

查看京东对于图片、js、css、html等文件的压缩情况

html
image


css
image


js
image


png(发现并没有进行压缩)
image

从上面能看到,数据是经过gzip压缩后,发送给浏览器的,而我们还能够看到字节大小,代表京东对于这些静态资源,也使用了静态压缩的方式。

如果想要实现像京东一样的压缩方案

1、编译nginx,编译的时候添加ngx_http_gzip_static_module模块

2、配置nginx的配置文件

location / {
    # 开启静态压缩
    gzip_static on;
    gzip on;
    gzip_buffers 16 8k;
    gzip_comp_level 6;
    gzip_http_version 1.1;
    gzip_min_length 10;
    gzip_proxied any;
    gzip_vary on;
    gzip_types text/plain application/x-javascript text/css application/xml;
    gzip_types
        text/xml application/xml application/atom+xml application/rss+xml application/xhtml+xml image/svg+xml
        text/javascript application/javascript application/x-javascript
        text/x-json application/json application/x-web-app-manifest+json
        text/css text/plain text/x-component
        font/opentype application/x-font-ttf application/vnd.ms-fontobject
        image/x-icon;
    root   html;
    index  index.html index.htm;
}

3、 然后我们再访问自己的nginx

  • 能看到Content-Encoding: gzip,代表返回的是压缩后的文件
  • Content-Length: 36 ,能够看到响应的字节长度

4、如果是动态压缩,结果如下

  • 能看到Content-Encoding: gzip
  • Transfer-Encoding: chunked

第三方zip模块Brotile与模块化加载

Brotli什么时候才能使用、为什么效率高、比gzip高多少、与gzip配合使用

使用场景:https情况下才能使用,不然浏览器发送的请求头中是不会携带accept-encoding: br

效率高: 内置了上万的字典,字典文件中包含一些常见的文本、html、css这些标签,将标签归类后按照序号解压缩文件,尤其对文本文档压缩解压缩效率极高

效率大概比gzip高20%左右

gzip配合使用: 当客户端支持Brotile时,就使用Brotli进行压缩后发送给客户端,不行的话还有gzip,gzip客户端也不支持的话,那就只能给源文件了

下载和安装Brotli

1、下载brotli主程序文件 https://github.com/google/ngx_brotli

2、下载brotle算法包 https://github.com/google/brotli

3、将2个包放在/root目录下后分别解压

-zxf ngx_brotli-1.0.0rc.tar.gz
-zxf brotli-1.0.9.tar.gz

4、进入到解压后的brotli-1.0.9算法包目录下

cd brotli-1.0.9

5、将该目录下的所有文件,全部移动到/root/ngx_brotli-1.0.0rc/deps/brotli/目录下

mv ./* /root/ngx_brotli-1.0.0rc/deps/brotli/

6、进入nginx目录对nginx进行配置并编译,别忘了加上之前配置nginx时的命令

./configure --prefix=/local/usr/nginx --with-http_gzip_static_module --with-http_gunzip_module --with-compat --add-dynamic-module=/root/ngx_brotli-1.0.0rc

其实想要静态编译也可以,那就是不用 --with-compat --add-dynamic-module=/root/ngx_brotli-1.0.0rc ,而使用--add-module=/root/ngx_brotli-1.0.0rc

7、进行编译

make

8、进入到编译好的objs文件夹下,此时可以看到多出的可执行文件

cd objs

image

9、在原本已经安装好的nginx目录下创建一个module文件夹

mkdir /local/usr/nginx/modules

10、将这2个可执行文件复制过去

cp ngx_http_brotli_filter_module.so /local/usr/nginx/modules/
cp ngx_http_brotli_static_module.so /local/usr/nginx/modules/

11、备份原有的nginx命令,再将该nginx命令复制过去

mv /local/usr/nginx/sbin/nginx /local/usr/nginx/sbin/nginx.old1
cp nginx /local/usr/nginx/sbin/

使用brotli进行压缩

1、 打开nginx的配置文件,将如下两句话加在配置文件的最上方

load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;

image


2、对配置文件进行配置,配置在location中,当然也可以配置在http、server当中

location / {
    # 所有gzip的配置
    gzip_static on;
    gzip on;
    gzip_buffers 16 8k;
    gzip_comp_level 6;
    gzip_http_version 1.1;
    gzip_min_length 10;
    gzip_proxied any;
    gzip_vary on;
    gzip_types text/plain application/x-javascript text/css application/xml;
    gzip_types
        text/xml application/xml application/atom+xml application/rss+xml application/xhtml+xml image/svg+xml
        text/javascript application/javascript application/x-javascript
        text/x-json application/json application/x-web-app-manifest+json
        text/css text/plain text/x-component
        font/opentype application/x-font-ttf application/vnd.ms-fontobject
        image/x-icon;

    # 所有brotli的配置
    # 启动br的静态压缩功能,找文件时,会优先寻找.br结尾的文件,找不到才会找源文件
    brotli_static on;
    # 启用br的动态压缩
    brotli on;
    # 需要进行压缩的mime格式
    brotli_types
        application/atom+xml application/javascript application/json application/rss+xml
        application/vnd.ms-fontobject application/x-font-opentype application/x-font-truetype
        application/x-font-ttf application/x-javascript application/xhtml+xml application/xml
        font/eot font/opentype font/otf font/truetype image/svg+xml image/vnd.microsoft.icon
        image/x-icon image/x-win-bitmap text/css text/javascript text/plain text/xml;
    # br压缩时的缓冲区大小
    brotli_buffers 16 8k;
    # br压缩时的比例
    brotli_comp_level 6;
    # 最小会被压缩的content-length
    brotli_min_length 10;
    
    root   html;
    index  index.html index.htm;
}
gzip 与 br两个压缩算法共存时的微妙变化

1、具体使用哪个压缩算法,与配置的顺序无关

2、优先使用br压缩算法,如果br压缩算法无法解决,会尝试使用gzip压缩算法

3、由于gzip和br都配置了静态压缩,因此都会优先寻找对应的压缩文件,gzip的压缩文件后缀为.gz、brotli压缩文件后的后缀为.br

关于nginx的html目录下50x.html文件无法使用压缩算法的问题

请求时
image

可以看到,响应头中并没有content-encoding,而会多出一个Accept-Ranges响应头

根据不断的测试得到如下结果:

  • 该情况仅与文件名有关
  • 与不同的压缩算法无关,不管使用brotli还是gzip,结果都是一样的

linux安装brotli命令进行文件的压缩与解压

1、下载安装

# 添加yum的下载源
yum install -y  epel-release

# 下载安装brotli
yum install -y  brotli

2、压缩

# 以 Brotli 压缩文件
brotli MyFile.txt

# 以 Brotli 压缩文件并删除源文件
brotli --rm MyFile.txt

# 压缩层级,可以是0到11,其中0到9可以用数字直接表示,数字越大代表压缩率越高
brotli --quality=5 MyFile.txt
# 设置压缩层级为最高(11)
brotli --best MyFile.txt

3、解压缩

# 解压缩 Brotli 压缩文件
brotli --decompress MyFile.txt.br

淘宝网案例: 压缩客户端请求数(合并文件)

什么是压缩客户端请求数量: 例如我们需要向服务器获取1.css、2.css、3.css一共3个文件,一般情况下我们都需要发送3个请求,才可以获取到这3个文件。压缩请求数的意思其实就是用更少的请求数量来获取到多个文件。

以前的做法:会发起三个请求

<link rel="stylesheet" type="text/css" href="1.css" />
<link rel="stylesheet" type="text/css" href="2.css" />
<link rel="stylesheet" type="text/css" href="3.css" />

压缩请求数量:文件返回的时候,需要将3个文件先合并再返回

<link rel="stylesheet" type="text/css" href=" http://192.168.22.10/??1.css ,2.css,3.css" />

作用: 有效减少并发请求数量

Tengine concat模块安装到nginx开源版当中(其实大部分Tengine中的模块在开源的nginx上是用不了的)

大部分模块用不了的原因: Tengine是基于开源的nginx进行开发的,其中修改了nginx的源码,因此很多其实是不能直接拿过来用的,要么就直接用Tengine

Nginx官方介绍: https://www.nginx.com/resources/wiki/modules/concat/
git地址: https://github.com/alibaba/nginx-http-concat

1、进入git地址,将文件下载下来,可以下载tar.gz格式的

2、下载后,将其放到服务器的/root目录下并解压
tar -zxf nginx-http-concat-1.2.2.tar.gz

3、进入nginx源码目录,重新编译nginx,加上该模块

./configure --prefix=/local/usr/nginx --with-http_gzip_static_module --with-http_gunzip_module --with-compat --add-dynamic-module=/root/ngx_brotli-1.0.0rc --add-module=/root/nginx-http-concat-1.2.2

4、执行make进行编译

make

5、进入到编译好的objs目录下,将nginx命令拷贝到nginx安装目录的sbin目录下

cd objs/
cp nginx /local/usr/nginx/sbin/

concat模块的使用

1、在nginx的html目录下创建一个css文件夹,再创建2个css文件
image

2、修改nginx的配置文件,可以在location中配置,也可以配置在 http, server
这里就在location中配置

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            # 开启文件合并功能
            concat on;

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

3、修改在html文件夹下的index.html文件

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" type="text/css" href="??css/1.css,css/2.css" />
  <title>Welcome to nginx!</title>
</head>
<body>
  hello world! abcasdfasdfasdfasdfasdfasdfasdfasdf
</body>
</html>

高并发系统资源静态化方案

处理高并发无非就是缓存、资源静态化、服务器的节点拉进用户DNS

资源静态化的概述

什么是资源静态化

资源静态化: 把需要上游服务器进行计算的资源静态化,存储到nginx服务器上,实现资源静态化。资源静态化实际上也是缓存的一种方式。

大致环境构建

大致环境

  • 3台nginx构建集群环境
  • 使用LVS分配2台nginx生成一个虚拟IP供客户端访问
  • 剩下一个nginx不被客户端访问,专门用来进行资源静态化的数据同步,将数据同步给另外2台机器

适用场景以及不适合生成静态页面的数据

适用场景以及不适合生成静态页面的数据

  • 变化不会特别大的数据
  • 容易变的内容,一般不生成到静态页面当中,静态页面加载后,可以通过js去异步的请求数据
  • 原则:和动态有关的数据,不要生成到静态页面当中

可能出现的问题

  • 数据一致性问题(如果类似与价格之类的写入了静态页面,那么我们可以在用户提交订单时,去动态获取一次价格,相当于对一致性问题做了一个补偿
  • 集群环境下,数据同步的过程
  • 合并文件输出问题

为什么需要合并文件输出,以及合并文件输出的两种方式

  • 一个页面有内容
  • 有关联内容
  • 还有固定内容,比如头跟尾
    如果单单把多个文件单单搞成一个大文件,一旦固定的内容发生了修改,那岂不是非常崩溃了。

电商系统,并发最高的是商品的详情页

合并文件的两种方式

  • 前端合并: 前端获取到数据后,通过innerHtml或Iframe等方式来加载页面

    • 好处:减少了服务器端的计算压力
    • 弊端:增加了请求数量
  • 后端合并

    • 采用Nginx的SSI完成文件合并
    • openresty中也有对应的模板引擎,真的要用Nginx做应用服务器的话,可以考虑这个

SSI(Server Side Include)合并服务器端文件(使用后,nginx响应的资源响应头不会携带last_modify,除非再进行相对应的配置)

官方文档: http://nginx.org/en/docs/http/ngx_http_ssi_module.html

SSI的常见配置(均可配置在http、server、location当中)

  • ssi on | off; 开启或关闭ssi

  • ssi_last_modified on | off; 指的是响应给客户端时,响应头是否需要携带last_modified,注意 : 如果设置成了开启,即便访问的页面中没有编写SSI语法也会最后修改时间

  • ssi_min_file_chunk 1k; 向磁盘存储并使用sendfile发送时的文件大小最小值

  • ssi_silent_errors on | off; 当出现ssi的语法错误时,屏蔽显示的错误信息

  • ssi_types text/html; 能够进行ssi文件合并的mime-type类型

  • ssi_value_length 256; 限制使用的脚本(ssi的命令)参数最大长度

SSI的常见命令

include

  • 包含一个文件
    <!--# include file="top.html" -->

  • 包含一个远程文件
    <!--# include virtual="/503.html" -->

  • 包含一个远程文件并且同步等待
    <!--# include virtual="/503.html" wait="yes" -->

config

  • 设置语法错误的提示信息,以及日期的格式化(指的是使用$date_gmt时)
    <!--# config errmsg="ni chu cuo la~" timefmt="%A, %d-%b-%Y %H:%M:%S %Z" -->

其他的就去看官方文档把...

使用示例

SSI是默认编译好的Nginx模块,Nginx可以直接使用。

在nginx的配置文件中开启ssi功能,这里演示会放在location中

location / {
    # 开启SSI文件合并功能
    ssi on;
    # 屏蔽语法错误信息
    ssi_silent_errors on;
    # 响应头中携带访问资源的最后修改时间
    ssi_last_modified on;

    root   html;
    index  index.html index.htm;
}

修改一个index.html页面

<!--# include file="top.html" -->
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" type="text/css" href="??css/1.css,css/2.css" />
  <title>Welcome to nginx!</title>
</head>
<body>
  hello world! abcasdfasdfasdfasdfasdfasdfasdfasdf
</body>
</html>
<!--# include file="bottom.html" -->
<!--# include virtual="/503.html" -->

需要额外注意最后这一行<!--# include virtual="/503.html" -->,相当于直接访问当前nginx,例如: http://192.168.22.10:80/503.html

集群环境下,数据同步的解决方案rsync

实际我们的环境
1、我们需要有专门生成静态文件的服务器,我们称之为源服务器
2、还需要用于给客户端提供服务的服务器,这些服务器需要接收到源服务器同步过来的静态文件,我们称之为目标服务器

rsync工具
remote synchronized 远程同步工具,可以让我们实现文件的推送和拉取。一般我们还会配合另外一个工具inotify使用,inotify用于监控文件是否发生变化。

例如我们在源服务器上需要同步的文件目录上使用inotify来监控文件是否发生变化,一旦发生了变化,就执行rsync的推送功能,将源服务器上的文件推送到目标服务器当中。

rsync 是用于替代 rcp 的一个工具,rsync 使用所谓的 rsync算法 进行数据同步,这种算法只传送两个文件的不同部分,而不是每次都整份传送,因此速度相当快。

rsync 基于inotify 开发

Rsync有三种模式:

  • 本地模式(类似于cp命令)
  • 远程模式(类似于scp命令)
  • 守护进程(socket进程:是rsync的重要功能)

rsync的安装与基本使用

安装rsync

yum install -y rsync;
注意: 源服务器和目标服务器都需要安装,因此需要准备多台服务器

rsync配置开机启动

/etc/rc.local文件中添加

rsync --daemon
rsync的常用选项解析
选项 含义
-a 包含-rtplgoD
-r 同步目录时要加上,类似cp时的-r选项
-v 同步时显示一些信息,让我们知道同步的过程
-l 保留软连接
-L 加上该选项后,同步软链接时会把源文件给同步
-p 保持文件的权限属性
-o 保持文件的属主
-g 保持文件的属组
-D 保持设备文件信息
-t 保持文件的时间属性
–delete 删除DEST中SRC没有的文件
–exclude 过滤指定文件,如–exclude “logs”会把文件名包含logs的文件或者目录过滤掉,不同步
-P 显示同步过程,比如速率,比-v更加详细
-u 加上该选项后,如果DEST中的文件比SRC新,则不同步
-z 传输时压缩
使用rsync手动同步源文件(目标服务器拉取方案)

原理: 源服务器开启rsync服务,其他的目标服务器则直接可以使用rsync命令来获取数据

源服务器的操作

  • 修改源服务器上的vim /etc/rsyncd.conf文件,加上2行
# 这里的source代表一组资源的名称,待会目标服务器需要用到
[source]
  path = /local/usr/nginx/html
  • 启动服务
rsync --daemon

目标服务器的操作

  • 查看当前可以同步的文件列表
rsync --list-only 192.168.22.10::source
  • 进行文件的同步,需要注意的是,此时的同步,如果被同步的目录中有多余文件或目录,不会进行删除
rsync -avz 192.168.22.10::source /local/usr/nginx/html/
  • 进行文件的同步,如果被同步到目录中有多余文件或目录,则直接进行删除
rsync -avz --delete 192.168.22.10::source /local/usr/nginx/html/

源服务器添加安全认证以及目标服务器免密登录

源服务器的操作

  • 在etc目录下创建一个rsync.pwd的文件,里面用户存放key:value形式的用户名和密码
# 创建并编辑文件
vi /etc/rsync.pwd

# 文件内容
zhagnsan:123
lisi:321

# 再进行降权
chmod 600 /etc/rsync.pwd
  • 修改/etc/rsyncd.conf配置文件,追加如下内容
auth users = zhangsan,lisi
secrets file=/etc/rsync.pwd

目标服务器操作

  • 直接通过用户名密码的方式连接源服务器的rsync服务
# 列出文件
rsync --list-only zhangsan@192.168.22.10::source
123

# 同步文件并删除本机目录中多余的文件
rsync -avz --delete zhangsan@192.168.22.10::source /local/usr/nginx/html/
  • 设置免密登录
    • 在/etc目录下创建一个rsync.pwd.client文件,在里面写入密码(现在假设当前是zhangsan用户)
      echo "123" > /etc/rsync.pwd.client
    • 降低该文件的权限
      chmod 600 /etc/rsync.pwd.client
    • 再使用rsync时,添加--password-file指令,指定密码的位置
rsync --list-only --password-file=/etc/rsync.pwd.client zhangsan@192.168.22.10::source
使用rsync配合inotify实现自动同步文件(源服务器推送方案)
目标服务器配置
  • 修改/etc/rsyncd.conf配置文件
# 设置rsync启动时的用户,和用户组,不然可能会出现源服务器推送时报错权限不足
uid = root
gid = root
# 关闭是否只读
read only = no
# 配置可以进行认证的用户
auth users = manager
# 账号密码
secrets file=/etc/rsync.pwd
# 资源的名称以及资源对应的目录
[dest]
  path = /local/usr/nginx/html
  • 添加/etc/rsync.pwd文件,设置用户名和密码,并降权
echo "manager:123" > /etc/rsync.pwd
chmod 600 /etc/rsync.pwd
  • 启动目标服务器 并 查看是否启动成功
rsync --daemon
ps -ef | grep rsync
源服务器配置
  • 添加密码文件并降权,用于连接到目标服务器
echo "123" > /etc/rsync.pwd.source.client
chmod 600 /etc/rsync.pwd.source.client
  • 测试是否能够连接上目标服务器
rsync --list-only --password-file=/etc/rsync.pwd.source.client manager@192.168.22.11::dest
  • 推送到目标服务器的命令,其实推送与拉取差不多,就是目标地址与文件路径反过来而已。。
rsync -avz --delete --password-file=/etc/rsync.pwd.source.client /local/usr/nginx/html/ manager@192.168.22.11::dest
源服务器下载安装inotify配置自动化推送

1、安装必备依赖
yum install -y automake

2、下载inotify,并传到服务器的/root目录下
https://github.com/downloads/rvoicilas/inotify-tools/inotify-tools-3.14.tar.gz

3、 进入/root目录并解压,再进入解压后的目录

cd /root
tar -zxf inotify-tools-3.14.tar.gz
cd inotify-tools-3.14

4、配置及编译安装

# 配置
./configure --prefix=/usr/local/inotify
# 编译及安装
make && make install

5、接下来可以在/usr/local/inotify/bin目录下看到对应的命令
image

6、测试监控目录

/usr/local/inotify/bin/inotifywait -mrq --timefmt '%Y-%m-%d %H:%M:%S' --format '%T %w%f %e' -e close_write,modify,delete,create,attrib,move /local/usr/nginx/html/

7、编写一个简单的自动化脚本,实现自动化推送

#!/bin/bash

/usr/local/inotify/bin/inotifywait -mrq --timefmt '%Y-%m-%d %H:%M:%S' --format '%T %w%f %e' -e close_write,modify,delete,create,attrib,move /local/usr/nginx/html/ | while read file
do
       
        rsync -az --delete --password-file=/etc/rsync.pwd.source.client /local/usr/nginx/html/ manager@192.168.22.11::dest
done

使用步骤
1、 在/root目录下创建一个文件,填充如上的脚本内容
touch autoSyncNginxFiles.sh

2、为该文件添加可执行权限
chmod u+x autoSyncNginxFiles.sh

3、启动脚本
./autoSyncNginxFiles.sh

多级缓存

静态资源缓存(其实就是上方讲的资源静态化)

浏览器缓存的原理(2类)

协商缓存(每次都需要发送请求) : Nginx默认就开启了协商缓存
  原理:浏览器在向Nginx服务器发送资源请求时,浏览器向服务器发起请求时,服务器端会在响应头中添加Last-ModifiedETag
  那么如果是第二次请求了,浏览器会在请求头中携带上If-Modified-SinceIf-None-Match 分别来对应服务器第一次请求时响应请求头中的Last-ModifiedETag

  服务器端在接收到浏览器的请求后,判断当前服务器上对应资源的last_modify与浏览器携带的请求头中的last_modify做比较,如果不同则返回当前资源,相同则直接返回304状态码,让浏览器使用缓存。

  • ETag: 使用文件名、文件大小、最后修改时间进行的hash,一旦文件内容发生改变,那么ETag就不一样了

强制缓存(第一次发送请求之后就不再发起请求了)
  浏览器在向服务器端发送请求获取到一个资源时,服务器端会返回一个响应头expire,告知浏览器当前资源的过期时间,之后浏览器每次需要当前资源时,就查看过期时间expire,如果没有过期则直接使用缓存。(如果使用浏览器的主机时间不正确的话,可能会导致缓存失效)

协商缓存的开启与关闭

Nginx默认就开启了协商缓存.

可能导致协商缓存失效的原因

  • 使用了SSI文件合并模块,并且没有配置ssi_last_modified on;

协商缓存实现靠的是Nginx服务器在响应数据的时候,响应头中添加了ETag 和 last_modify,所以我们其实有如下2种方案关闭协商缓存(如下配置均在location当中

  • 关闭ETag、重写响应头中的last_modify,将其设置为空字符串
etag off;
add_header Last-Modified "";
  • 关闭ETag、再关闭if_modified_since(相当于不管浏览器发送的if_modified_since请求头)
etag off;
add_header Last-Modified "";

强制缓存的使用(不发起请求,看到有memory cache或者 disk cache,并且没有请求头的都是强制缓存)

Nginx对于强制缓存,要根据浏览器而定,好像默认都是开启了强制缓存的,不过强制缓存通常只是浏览器刚打开时候,第一次访问页面,会使用强制缓存,一旦页面刷新就不会使用强制缓存,而是发起请求。

因此我们对于强制缓存的设置,其实就是设置强制缓存的过期时间,我们有2个可以配置的选项,分别为

  • cache-control 优先级更高
  • expires 优先级比较低,是http1.0版本之前的

在nginx中进行配置,注意两个不要同时配置,不然会出现两个Cache-Control响应头,这里是配置在location当中

expires 300s;
add_header Cache-Control "max-age=300";

image

浏览器缓存的使用建议

  • 多级集群负载时,返回资源的响应头last_modified必须保持一致,所以需要做好时间的同步

  • 还有一些场景下我们希望禁用浏览器缓存,比如京东的主页就只开启了10秒的强制缓存,没有开启协商缓存。比如轮询api上报数据

  • 浏览器缓存很难彻底禁用,大家都做法是加版本号,随机数等方法。

  • 只缓存200响应头的数据,像3xx这类跳转到页面不需要缓存。

  • 对于js、css这类可以缓存很久的数据,可以通过加版本号的方式更新内容

  • 不需要强一致性的数据,可以缓存几秒钟

  • 异步加载接口数据,可以使用ETag来校验,在Nginx中可以开启ETag

  • 在服务器端添加Server头,有利于排查错误,在京东可以看到使用了nginx、淘宝可以看到使用的是Tengine

  • 当项目中开发的是手机APP的后台接口时,需要询问下前端是否遵循http协议,很多框架可能会改一些请求

  • 没有联网的状态下也可以展示数据(强制缓存)

  • 提前下发数据 能做缓存的就把缓存做了,避免秒杀活动时,同时下发数据造成流量段时间暴增

  • 兜底数据 在服务器崩溃和网络不可用的时候展示友好数据

  • 临时缓存 退出即清理

  • 固定缓存 展示框架这种,可能长时间不会更新,可以长时间缓存
    首页应该禁用缓存,以保证加载的资源都是最新的。参考京东

  • 父子连接 页面跳转时有一部分内容不需要重复加载,可以从父菜单带过来(浏览器实现了)

  • 预加载(前端实现), 某些逻辑可用判定用户接下来的操作,那么可用异步来预加载那些资源,例如我们需要访问微信公众号的一些文章,我们首先看到的是列表标题,那么可以异步加载把内容搞定,实现数据的预加载。可以避免主线程阻塞

CDN缓存(基本不可能自己开发,所以都是直接购买)

CDN产品的三大组成部分。全网静态资源分发

  • DNS系统:进行域名解析,得到离用户最近的web服务器的ip地址,把域名解析成不同的ip地址。根据ip的来源来找到最近的服务器的ip地址。

  • web服务器: 承载客户的请求,进行请求资源的响应。

  • IT系统: 后端管理系统,此时就需要有一个数据源,一旦源发生改变,就由这个IT系统将资源分发给不同的web服务器以达到一致性问题。

正向代理与反向代理缓存

Nginx的正向代理配置(不过一般不用)

代理http请求
1、配置nginx的配置文件,添加如下内容

# 配置在server中,用于DNS解析
resolver 8.8.8.8;
# 配置在location当中
proxy_pass $scheme://$host$request_uri;

注意: 此时如果直接访问nginx会报错,因为相当于自己代理到自己身上

2、需要配置电脑的代理服务器
image


代理https请求

1、下载第三方模块
https://github.com/chobits/ngx_http_proxy_connect_module

2、重新编译安装nginx

3、配置如下

server {
     listen                         3128;

     # dns resolver used by forward proxying
     resolver                       8.8.8.8;

     # forward proxy for CONNECT request
     proxy_connect;
     proxy_connect_allow            443 563;
     proxy_connect_connect_timeout  10s;
     proxy_connect_read_timeout     10s;
     proxy_connect_send_timeout     10s;

     # forward proxy for non-CONNECT request
     location / {
         proxy_pass http://$host;
         proxy_set_header Host $host;
     }
 }

proxy_pass代理时的缓存(减少向上游服务器发起的请求)

原理:
  实际上就是nginx在进行反向代理,向上游服务器获取资源后,将获取到的资源缓存到反向代理的nginx服务器上,当用户再次请求时,直接从反向代理服务器中将数据响应给用户,减少一次向上游服务器发送的请求。

测试环境
1、 准备2台nginx服务器
2、一台作为反向代理服务器,一台作为上游服务器提供资源
3、配置反向代理服务器的配置文件

配置反向代理的配置文件nginx.conf

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

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;


    upstream httpget {
        server 192.168.22.11:81;
    }

    # proxy_cache_path /ngx_tmp 配置临时缓存文件的目录
    # levels 配置了目录级别,表示1级目录以一个字符命名,一共设置2级目录,最多3级
    # keys_zone=test_cache:100m 配置该缓存的名称,申请100m的内存,用于存储key索引
    # inactive=1d 指的是临时文件只存放一天
    # max_size=10g 最大分配空间为10g
    proxy_cache_path /ngx_tmp levels=1:2 keys_zone=test_cache:100m inactive=1d max_size=10g;
    
    server {
        listen       80;
        server_name  localhost;
    
        location / {
            # 添加一个响应请求头,主要是$upstream_cache_status这个变量可以看到是否使用了缓存
            add_header  Nginx-Cache "$upstream_cache_status";
            # 与keys_zone保持一致
            proxy_cache test_cache;
            # 指的是文件什么时候失效,失效了将会去上游服务器取数据
            proxy_cache_valid 1h;
            # 请求代理
            proxy_pass http://httpget;
        }
        
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

当浏览器缓存与上游服务器缓存同时存在时,其实使用的就是浏览器缓存。(注意:关闭浏览器缓存,并不会影响proxy缓存,因为proxy缓存是存储在反向代理服务器上的)

缓存未命中时
image

缓存命中时
image

缓存清理,purge模块的下载安装及使用

purge模块用于帮助我们清理proxy缓存。
1、下载 并 将其解压到/root目录下
https://github.com/FRiCKLE/ngx_cache_purge

2、编译安装nginx

./configure --prefix=/local/usr/nginx --add-module=/root/ngx_cache_purge-2.3
# 编译
make

3、 将编译后的命令拷贝到原有nginx的sbin目录下

cd objs/
cp nginx /local/usr/nginx/sbin/

purge模块的基本使用
1、在nginx配置文件中进行如下配置

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

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;


    upstream httpget {
        server 192.168.22.11:81;
    }

    # proxy_cache_path /ngx_tmp 配置临时缓存文件的目录
    # levels 配置了目录级别,表示1级目录以一个字符命名,一共设置2级目录,最多3级
    # keys_zone=test_cache:100m 配置该缓存的名称,申请100m的内存,用于存储key索引
    # inactive=1d 1天内未被访问过的缓存将被删除
    # max_size=10g 最大分配空间为10g
    proxy_cache_path /ngx_tmp levels=1:2 keys_zone=test_cache:100m inactive=1d max_size=10g;
    
    server {
        listen       80;
        server_name  localhost;
    
        location ~ /purge(/.*) {
            proxy_cache_purge test_cache $1;
        }
        location / {
            # 添加一个响应请求头,主要是$upstream_cache_status这个变量可以看到是否使用了缓存
            add_header  Nginx-Cache "$upstream_cache_status";
            # 与keys_zone保持一致
            proxy_cache test_cache;
            # 指的是文件什么时候失效,失效了将会去上游服务器取数据
            proxy_cache_valid 1h;

            # 配置缓存后key的名称,默认是:$scheme$proxy_host$request_uri,协议 + httpget + 请求的uri
            proxy_cache_key $uri;
            # 请求代理
            proxy_pass http://httpget;
        }
        
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

2、在浏览器访问资源构建缓存

http://192.168.22.10/

3、访问指定的接口删除指定缓存

http://192.168.22.10/purge/

4、如果想配置动态资源缓存,请参考如下网站
https://github.com/FRiCKLE/ngx_cache_purge/tree/2.3
image

proxy_cache的配置详解(如下配置均可配置在http, server, location)

  • proxy_cache_use_stale 何时使用过期缓存,默认是关闭,如果当缓存过期时向上游服务器获取资源时出现错误,默认不使用过期缓存,但是可以经过如下配置让其可以使用过期缓存。
proxy_cache_use_stale error | timeout | invalid_header | updating | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | http_429 | off ...;
  • proxy_cache_backgroud_update off; 当缓存过期时,是否直接响应过期数据,然后开启一条子线程去找上游服务器获取资源来更新缓存。

  • proxy_no_cache 以及 proxy_cache_bypass 指定什么时候不使用缓存而访问上游服务器(一般动态资源需要配置),注意这2个配置作用是一样的。

# 如果这些变量存在,且不为空也不为0,则不使用缓存
proxy_no_cache $cookie_nocache $arg_nocache$arg_comment;
proxy_no_cache $http_pragma $http_authorization;
  • proxy_cache_convert_head on; 是否需要让head请求像get请求一样能够返回body。默认head请求是只返回响应头,不返回响应体的。通常用于试试请求能否正常发送。,如果设置为关闭,需要在cache key中添加$request_method以便区分缓存内容

  • proxy_cache_lock off; 在更新缓存时是否加锁。其实不加锁也是比较合理的,减少了线程之间抢锁浪费的资源,毕竟缓存一般一致性要求也没那么高。

  • proxy_cache_lock_age 5s; 与如上配置配合使用,表示更新缓存时,加锁的超时时间。

  • proxy_cache_max_range_offset number; 设置range的最大值,超过指定的字节大小,则不进行缓存。

  • proxy_cache_methods GET HEAD; 缓存哪些请求类型的资源,默认是缓存get和head,需要的话post也可以加上

  • proxy_cache_min_uses 1; 一个资源被请求多少次才缓存下来,默认1次就缓存了,如果是高并发系统,可以设置的稍微高一些,比如100次或1000次之后才做缓存。

  • proxy_cache_path /data/nginx/cache; 指定缓存在磁盘上的存储目录,如果存储目录大小不够,可以配合levels的目录层级,然后不同的目录层级挂载到不同的磁盘上。

  • levels=1:2; 缓存目录的名称位数为1,目录层级为2

  • 如果磁盘内存富裕,磁盘不够用的话,可以考虑使用上 TMPFS这个工具

  • use_temp_path off; 默认创建缓存文件时,先向缓冲区创建临时文件,再移动到缓存目录。注意这里的缓冲区是磁盘。

  • inactive 1h; 指定时间内未被访问过的缓存将被删除

  • proxy_cache_key $scheme$proxy_host$request_uri; 缓存key的设置规则,删除缓存就需要根据key来删除

  • proxy_cache_revalidate on;如果缓存过期了,向上游服务器发送“IF-Modified-Since” 和 “If-None-Match”;来验证是否改变,如果没有就不需要重新拉取资源了。on代表开启该功能。

  • proxy_cache_valid 1h; 缓存的过期时间,这样相当于对所有缓存都使用一样的过期时间,我们可以根据不同的http状态码设置不同的过期时间。

# 200、302状态码
proxy_cache_valid 200 302 10m;
# 404状态码
proxy_cache_valid 404 1m;
# 指其他任意状态码
proxy_cache_valid any 1m;
使用proxy代理缓存时需要注意的问题

1、数据一致性问题,对于静态请求,我们可以设置较长的失效时间。对于动态请求,我们可以适当缓存前几页的数据,并且缓存的时间为5~20秒之间,不能太长。

2、删除proxy缓存时,如果批量删除,我们一般都直接把整个缓存目录删了。

Nginx断点续传(tomcat与nginx是否支持)

断点续传的原理

  客户端: 请求头携带range
   服务器端: 响应的时候携带上一个响应头: Accept-Ranges
  响应的状态码: 206代表响应了部分内容

默认tomcat是不支持range的,而nginx默认是支持range的

当我们下载的文件看不到大小时,就说明服务器端是以流的方式响应的数据。我们看的直播通常就是以这种方式响应。此时说明服务器端响应的时候,将content-type换成了application/octet-stream二进制流的方式

使用示例(客户端,服务器端)

  客户端: 客户端向nginx服务器发送请求时,携带如下请求头,如果携带的range范围超过最大值会进行合理化修改,但是低于最小值则会抛出416错误,表示range为不可满足的,下标从0开始

key: range
value: bytes=5-15

  服务器端:服务器端响应的状态码为206,并且还会携带content-range的响应头,其中记录了针对本此访问的资源响应了怎样范围的字节数,以及该资源总共有多少字节。
image

  如果是视频直播,那么其实响应的资源字节数量是不固定的,因此nginx服务器可以设置响应头,将content-type替换成:application/octet-stream(有歧义,待验证)

提示
  如果想要让上游服务器实现断点续传,首先上游服务器必须支持range,并且反向代理服务器需要配置向上游发送请求时的请求头。把客户端的range数据发送给上游服务器。

proxy_set_header range $http_range;

Nginx内存缓存(占用Nginx所在服务器的内存空间)

静态文件元数据缓存:Nginx内部访问本机磁盘上静态文件时,对于这些文件建立索引级别的缓存,操作系统在打开文件时是需要操作句柄的,我们可以把这部分操作给缓存起来,在内存中建立对于文件的索引(文件的位置)。(不缓存具体的文件内容)。比较适用于磁盘文件比较多,内容又比较大(一旦关闭sendfile那就没啥意义了,开启就是为了进一步提升我们sendfile的性能)

多进程共享缓存:Nginx启动之后会创建多个进程,在进程直接到共享级别缓存(缓存具体文件内容)。不仅仅可以存储文件的源数据信息,还可以存储文件内容。比较适用于热点接口数据,并且内容不是特别大的,一般缓存的是一些被高频访问到的静态资源,以及一些热点接口数据。

静态文件元数据缓存使用示例,配合strace追踪内核

前提该要:
1、linxu上需要安装好strace工具
yum install -y strace

2、查看当前运行的nginx进程,找到worker进程
ps -ef | grep nginx

3、 使用strace来监控nginx的worker进程
strace -p pid

4、在nginx当中进行如下配置(可以配置在一个location下)

# 最多缓存多少个文件的元数据信息,60秒没有被访问则删除
open_file_cache max=500 inactive=60s;
# 最少访问多少次资源才建立缓存
open_file_cache_min_uses 1;
# 缓存的过期时间
open_file_cache_valid 60s;
# 是否缓存错误信息,例如访问文件出现404错误,是否缓存下来,如果为off,则会再次去尝试建立缓存
open_file_cache_errors on;

演示效果

  • 开启了sendfile和开启了静态文件元数据缓存效果:建立了缓存之后,接收到请求后,不会再看到open命令来打开文件了。

  • 开启了sendfile但是关闭了静态文件元数据缓存效果

# 等待连接的建立
epoll_wait(8, [{EPOLLIN, {u32=1904243152, u64=140709327827408}}, {EPOLLIN, {u32=1904242704, u64=140709327826960}}], 512, 25215) = 2

# 获取到客户端的请求,以及请求的来源信息等
recvfrom(10, "GET / HTTP/1.1\r\nHost: 192.168.44"..., 1024, 0, NULL, NULL) = 475

# 找到对应的文件资源
stat("/usr/local/nginx//html/index.html", {st_mode=S_IFREG|0644, st_size=1429, ...}) = 0

# 通过句柄访问来打开对应的文件
open("/usr/local/nginx//html/index.html", O_RDONLY|O_NONBLOCK) = 11

# 获取当前打开文件的状态信息等
fstat(11, {st_mode=S_IFREG|0644, st_size=1429, ...}) = 0

# 将响应头等信息写给客户端
writev(10, [{iov_base="HTTP/1.1 200 OK\r\nServer: nginx/1"..., iov_len=263}], 1) = 263

# 使用sendfile的方式将文件传输交给操作系统,不会将文件copy到我们的nginx用户态当中,而是copy到内核缓冲区后将数据直接发送到客户端的网卡缓冲区
sendfile(10, 11, [0] => [1429], 1429)   = 1429

# 实际写入数据给客户端
write(4, "192.168.44.1 - - [27/May/2022:14"..., 193) = 193

# 关闭
close(11) 

外置内存缓存(通常都不是由Nginx来维护)

需要脱离nginx

原理: 当有数据确实需要缓存,但是Nginx服务器上的内存以及不足以进行缓存时,我们可以考虑外置一台服务器用来缓存数据,然后让nginx去访问这台外置服务器获取缓存数据。比如说一台redis服务器

nginx + memcached 外置缓存使用(了解即可)

官方nginx对于该module的介绍: http://nginx.org/en/docs/http/ngx_http_memcached_module.html

部署memcached
1、在服务器上安装memcached
yum install -y memcached

2、如果想要修改其配置文件,配置文件的默认地址为
/etc/sysconfig/memcached

3、查看memcached的状态,以及启动memcached

# 查看状态
memcached-tool 127.0.0.1:11211  stats

# 启动
systemctl start memcached;

4、使用telnet来连接上memcached,添加一条缓存数据

# 输入命令,按回车
telnet 127.0.0.1 11211

# 输入命令,按回车
set /? 0 0 8

# 输入命令,按回车
zhangsan

修改nginx的配置文件

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

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;
    
    server {
        listen       80;
        server_name  localhost;
    
        location / {
            # 设置变量,以uri及其参数来作为key
            set  $memcached_key "$uri?$args";
            # 反向代理到memcached上获取资源
            memcached_pass 127.0.0.1:11211;
            # 添加响应头,标志当前使用了缓存
            add_header X-Cache-Satus HIT;
            #  # 强制响应数据格式为html
            add_header Content-Type 'text/html; charset=utf-8';
        }
        
        # 如果当前nginx出现了404资源找不到时
        # 1、跳转到匿名location,客户端是无法直接访问到匿名location的
        error_page 404 = @fallback;

        location @fallback {
            # 跳转到tomcat服务器上,并且携带key过去,由上游服务器向memcached中建立缓存
            proxy_set_header memcached_key $memcached_key;
            proxy_pass http://192.168.22.11:8080;
        }
    }
}

nginx + redis 外置缓存使用(了解即可)

部署nginx

yum install epel-release
yum install -y redis

部署nginx
1、下载如下nginx模块编译安装
https://github.com/openresty/redis2-nginx-module

2、具体查看官方文档使用...

原因: 实际上我们都直接使用openresty来实现外置内存缓存。

服务器安装telnet连接redis使用

前提: redis没有设置密码,并且允许远程访问

1、下载安装telnet
yum install -y telnet

2、直接连接远程的服务器
telnet 82.18.130.66 6379

3、接下来就可以使用命令来操作redis了

适用情况: 当需要连接到某台服务器,但是却没有客户端时,可以考虑使用

上游服务器应用缓存

应用缓存与多级缓存整体架构

image

ErrorPage、匿名location和return

ErrorPage逐行解读

# 如果当前nginx出现了404资源找不到时
# 1、响应302状态码给用户,直接强制跳转到另一个页面
#error_page 404 =302 http://www.baidu.com;

# 2、响应200状态码给用户,跳转到nginx上的资源,此时需要注意location需要存在
error_page 404 =200 /40x.html;

# 3、这个location与上面一行配合使用,会在html中找 40x.html
location = /40x.html {
    root html;
}

# 4、配置多个状态码,统一跳转到同一个资源下
error_page   500 502 503 504  /50x.html;
location = /50x.html {
    root  html;
}

匿名location(不想让用户直接访问我们的错误界面,只供内部调用)

# 如果当前nginx出现了404资源找不到时
# 1、响应302状态码给用户,直接强制跳转到另一个页面
#error_page 404 =302 http://www.baidu.com;

# 1、跳转到匿名location,客户端是无法直接访问到匿名location的
error_page 404 = @page404;

# 注意: 这里直接路径重写了,所以其实响应的状态码为200
location @page404 {
    # 将路径重写为 /40x.html,响应个错误界面
    rewrite ^/ /40x.html break;
    root html;
}

return的使用

error_page 404 = @page404;

location @page404 {
    # 响应200状态码,以及一段文本
    add_header content-type "text/html";
    return 200 "hello";
}

Nginx使用Stream模块完成对于Mysql的透明化负载均衡(实现访问nginx相当于访问mysql)

部署2台mysql,实现主从

1、创建一台虚拟机,安装mysql

2、复制该虚拟机,新的一台虚拟机需要修改:MAC地址、IP地址、MYSQL的UUID、主机名[可选]

vim /etc/hostname
vim /var/lib/mysql/auto.cnf
vim /etc/sysconfig/network-scripts/ifcfg-ens33

3、主机的配置文件

[mysqld]
#启用二进制日志
# 为每个mysql服务器配置的id
server-id=1
# binlog日志的前缀
log-bin=codestarts-bin
#[可选] 0(默认)表示读写(主机),1表示只读(从机)
read-only=0
# 设置日志文件保留的时长,单位是秒,注意:MYSQL8才支持的
# binlog_expire_logs_seconds=6000
##控制单个二进制日志大小。此参数的最大和默认值是1GB
max_binlog_size=200M
##[可选]设置不要复制的数据库
binlog-ignore-db=test
##[可选]设置需要复制的数据库,默认全部记录。比如:binlog-do-db=atguigu_master_slave
binlog-do-db=codestarts_db
##[可选]设置binlog格式
binlog_format=STATEMENT

4、从机配置文件

[mysqld]
#[必须]从服务器唯一ID
server-id=2
#[可选]启用中继日志
relay-log=mysql-relay

#[可选] 0(默认)表示读写(主机),1表示只读(从机)
read-only=1

5、使用主机建立账户并授权

  • mysql5.7
#在主机MySQL里执行授权主从复制的命令
GRANT REPLICATION SLAVE ON *.* TO 'slave1'@'从机器数据库IP' IDENTIFIED BY 'abc123';

# 例如
GRANT REPLICATION SLAVE ON *.* TO 'slave1'@'%' IDENTIFIED BY '123123';
  • mysql8
CREATE USER 'slave1'@'%' IDENTIFIED BY '123123';
GRANT REPLICATION SLAVE ON *.* TO 'slave1'@'%';

#此语句必须执行。否则无法访问。
ALTER USER 'slave1'@'%' IDENTIFIED WITH mysql_native_password BY '123123';
flush privileges;

# 查看当前主机状态
show master status;

6、从机连接到mysql

CHANGE MASTER TO
MASTER_HOST='主机的IP地址',
MASTER_USER='用户名',
MASTER_PASSWORD='用户名的密码',
MASTER_LOG_FILE='mysql-bin.具体数字',
MASTER_LOG_POS=具体值;


CHANGE MASTER TO MASTER_HOST='192.168.22.22',MASTER_USER='slave1',MASTER_PASSWORD='123123',MASTER_LOG_FILE='codestarts-bin.000005',MASTER_LOG_POS=154;

7、从机常用命令
image

部署nginx,额外添加stream模块

stream模块官方文档:http://nginx.org/en/docs/stream/ngx_stream_core_module.html

nginx编译安装

./configure --prefix=/local/usr/nginx --with-stream
make
make install

修改nginx的配置文件

worker_processes  1;

events {
    worker_connections  1024;
}

stream {
    upstream mysql {
       server 192.168.22.13:3306;
       server 192.168.22.14:3306;
    }

    server {
        listen     80;
        proxy_pass mysql;
    }
}

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

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;
}

使用navicat连接测试
image

Nginx对于客户端请求的QPS限制

对于QPS限制的模块官方文档: http://nginx.org/en/docs/http/ngx_http_limit_req_module.html

漏桶算法进行限流(针对客户端ip、针对单个server)

漏桶算法: 该桶是作为缓冲区存在的,用户请求的连接如果暂时无法响应,会进入该桶当中等待处理。而请求如果无法处理并且漏桶已经满了,那么根据不同的策略是进入等待队列还是快速失败。

nginx的配置文件

http {
    # $binary_remote_addr 根据客户端的ip来进行限流
    # $server_name 如果使用server_name,则根据不同的server监听的服务来进行限流
    # zone 限流规则的名称,以及申请10m的内存空间用于存储客户端的ip
    # rate 代表速率,这里代表QPS为1个,也就是每秒只能访问一次
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
    

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

    sendfile        on;

    keepalive_timeout  65;
    
    server {
        listen       80;
        server_name  localhost;
    
        location / {
            # 配置限流,指定刚刚配置好的限流规则
            # burst 开启漏桶算法,其中桶的大小为5个请求,如果不配置nodelay。那么没有进入桶的连接将会进入等待队列。
            # nodelay 不进行等待,直接处理接收到的请求
            limit_req zone=one burst=5 nodelay;
            root html;
            index index.html;
        }
    
    }
}

令牌桶算法进行传输带宽限制

令牌桶算法: 依然有一个容器,服务器按照一定的速率往容器中放入令牌,客户端请求只有获取到了令牌才可以继续处理请求。适用场景: 想想百度网盘,普通用户的带宽以及超级会员的带宽。

nginx配置文件(可以配置在location当中)

# 传输了1M的资源后再进行限速
limit_rate_after 1m;
# 限速为每秒只能传输1kb的资源,参考某盘,想不限速就加钱
limit_rate 1k;

计数器算法进行(连接数)的限制(客户端并发请求数,1个人)

计算器算法:记录当前连接客户端的ip,一个ip只能访问限制数量的请求。

官方文档:http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html

nginx配置文件(可以在location配置)

# http模块
# 配置一个限制连接的规则,以客户端的ip来限制,规则名称为 addr,使用的内存大小为10m
limit_conn_zone $binary_remote_addr zone=addr:10m;

# http, server, location
# 限制一个客户端的连接数量只能为1个,相当于禁止并发了
limit_conn addr 1;

QPS与连接数的区别

QPS: 指的是每秒访问的数量
连接数: 指的是服务器端与客户端保持连接的数量。

小提示: 每秒访问的次数多,但是不代表使用的连接多,可能都是同一个连接。因此两者是有本质区别的。

Nginx的日志

用户访问日志模块以及测试访问模块

用户访问模块官方文档: http://nginx.org/en/docs/http/ngx_http_log_module.html

测试用于访问的模块: http://nginx.org/en/docs/http/ngx_http_empty_gif_module.html

测试gif访问模块

nginx配置

location = /_.gif {
   empty_gif;
}

浏览器测试访问nginx: http://192.168.22.10/_.gif

nginx日志模块的配置(解压缩、格式化json格式)

nginx配置(可以放在http模块下)

提示:别忘了先创建 /ngx_logs 这个目录mkdir /ngx_logs

# 设置log日志的格式,compression是日志格式名称
log_format compression '$remote_addr - $remote_user [$time_local] '
                       '"$request" $status $bytes_sent '
                       '"$http_referer" "$http_user_agent" "$gzip_ratio"';

# 设置log日志
# compression 代表使用的日志格式名称
# buffer 设置日志的缓冲区大小,当缓冲区写满了才写入磁盘,不然的话每次都会直接写入磁盘
# gzip 代表使用gzip在缓冲区中将文件压缩后再写入日志文件
# flush 每隔30s强制将缓冲区中的输入写入磁盘
access_log /ngx_logs/nginx-access.log compression buffer=32k gzip=9 flush=30s;

如果使用了gzip,那么想要读取压缩前的数据,需要先将文件设置为gz后缀,再解压
例如:

# 复制
cp nginx-access.log accessLog.gz
# 解压
gunzip accessLog.gz
# 查看
cat accessLog

格式化json格式

log_format  ngxlog json '{"timestamp":"$time_iso8601",'
                    '"source":"$server_addr",'
                    '"hostname":"$hostname",'
                    '"remote_user":"$remote_user",'
                    '"ip":"$http_x_forwarded_for",'
                    '"client":"$remote_addr",'
                    '"request_method":"$request_method",'
                    '"scheme":"$scheme",'
                    '"domain":"$server_name",'
                    '"referer":"$http_referer",'
                    '"request":"$request_uri",'
                    '"requesturl":"$request",'
                    '"args":"$args",'
                    '"size":$body_bytes_sent,'
                    '"status": $status,'
                    '"responsetime":$request_time,'
                    '"upstreamtime":"$upstream_response_time",'
                    '"upstreamaddr":"$upstream_addr",'
                    '"http_user_agent":"$http_user_agent",'
                    '"http_cookie":"$http_cookie",'
                    '"https":"$https"'
                    '}';

error错误日志模块的配置

官方文档:http://nginx.org/en/docs/ngx_core_module.html#error_log

# 可配置在http等模块,默认的级别为error
error_log file [level];

# 默认配置
error_log logs/error.log error;

# 可以配置的项
debug, info, notice, warn, error, crit

日志分割

1.脚本

2.Logrotate (linux平台下的工具)

以后自己去学习了

Nginx进行反向代理时的失败重试机制

通过nginx的配置,实现被动健康检查

反向代理配置官方文档: http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream

upstream配置官方文档:
http://nginx.org/en/docs/http/ngx_http_upstream_conf_module.html

nginx配置如下

worker_processes  1;

events {
    worker_connections  1024;
}


http {

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

    sendfile        on;

    keepalive_timeout  65;
    
    upstream httpget {
        # max_fails 最大失败次数
        # fail_timeout 失败的时间区间。
        # 解析: 10秒内如果出现了5次错误,那么将会让该服务下机10s,10s后再次上线
        server 192.168.22.11:81 max_fails=5 fail_timeout=10s;
        server 192.168.22.12:82;
    }

    server {
        listen       80;
        server_name  localhost;

        location / {
            # 当请求一台服务器时,遇到什么情况才会将请求交给下一台服务器
            proxy_next_upstream error timeout;
            # 一个用户的请求,多久没有成功就直接响应失败
            proxy_next_upstream_timeout 5s;
            # 重试次数,包括第一次。一般配合 timeout 使用,相当于一个请求在5秒内只能重试5次
            # 如果都没成功,就响应错误信息了
            proxy_next_upstream_tries 5;
            
            # 反向代理
            proxy_pass http://httpget;
        }
    }
}

通过开源版的nginx,配合tengine的主动健康检查,减少无效请求

原理: 开启一条线程去间隔性的给上游服务器发一个心跳包,检测是否存活。并且在接受客户端请求时,只会根据主动健康检查的情况来进行反向代理。非常重要!很多企业就是因为想用这个功能,才使用的tengine

部署nginx,下载1.20.2的版本
1、tengine主动健康检查的模块下载:
https://github.com/yaoweibin/nginx_upstream_check_module

2、 下载nginx地址:http://nginx.org/download/nginx-1.20.2.tar.gz

3、 下载修改nginx源码的脚本:https://github.com/yaoweibin/nginx_upstream_check_module/blob/master/check_1.20.1%2B.patch

4、 将下载好的nginx安装包以及脚本都放到服务器的/root目录下
image

4、解压下载好的check模块,解压nginx并进入解压后的nginx目录

tar -zxf nginx_upstream_check_module-0.4.0.tar.gz
tar -zxf nginx-1.20.2.tar.gz
cd nginx-1.20.2

5、安装patch命令

yum install -y patch

6、执行脚本修改nginx源码

# -p1 代表跳过第一行,然后指定刚刚的脚本文件
patch -p1 < /root/check1.20.1.patch

7、配置并编译安装nginx,别忘了添加模块

./configure --prefix=/local/usr/nginx20 --add-module=/root/nginx_http_upstream_check_module

make && make install

8、修改配置文件vi /local/usr/nginx20/conf/nginx.conf

worker_processes  1;

events {
    worker_connections  1024;
}

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

    sendfile        on;
    keepalive_timeout  65;


    upstream httpget {
        server 192.168.22.11:81;
        server 192.168.22.12:82;

        # interval 每个3秒钟发送一次心跳包
        # rese 2次都请求成功就标记服务器为up
        # fall 5次都请求失败就标记服务器为down
        # timeout 检测心跳的超时时间,如果超过时间未接收到结果,则直接认定为本次心跳检测失败
        # type 请求的协议使用http,发送http请求包
        check interval=3000 rise=2 fall=5 timeout=1000 type=http;
        # 当设置type为http时,发送(HEAD请求,请求路径为: / ,请求协议为HTTP1.0) 作为请求包
        check_http_send "HEAD / HTTP/1.0\r\n\r\n";
        # 这些状态码表示上游服务器的http响应正常,后端还活着。
        check_http_expect_alive http_2xx http_3xx;
    }

    server {
        listen       80;
        server_name  localhost;

        location / {
            proxy_pass http://httpget;
        }

        location /status {
            check_status;
            # 访问健康状态的时候,访问日志就别记录了
            access_log off;
        }
    }

}

9、启动nginx并指定配置文件

/local/usr/nginx20/sbin/nginx -c /local/usr/nginx20/conf/nginx.conf

10、测试访问 http://192.168.22.10/status
image

OpenResty

Lua Luajit nginx openresty之间的关系

lua是一门轻量级的脚本语言,同时也是一门解释性语言

Luajit 是lua脚本语言的解释器,基于C进行的开发

nginx 是服务器,其中可以内嵌lua脚本来完成一些定制化的开发

openresty 是对nginx的扩展,其中就集成了luajit以及很多第三方的包

Lua开发环境搭建

IDEA软件开发时的Lua插件

可以安装如下插件
英文版:https://github.com/EmmyLua/IntelliJ-EmmyLua

中文版:https://emmylua.github.io/zh_CN/

下载与安装OpenResty

预编译安装
参考文档:http://openresty.org/cn/linux-packages.html

yum install -y yum-utils
yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
yum install -y openresty
# 如果你想安装命令行工具 resty,那么可以像下面这样安装 openresty-resty 包:
yum install -y openresty-resty

源码编译安装
1、下载安装包
下载地址:http://openresty.org/cn/download.html
最新版安装包: https://openresty.org/download/openresty-1.21.4.1.tar.gz

2、拷贝到服务器的任意目录下解压,然后编译安装,步骤与安装开源nginx没有区别

3、如果缺少一些依赖,可以安装一下

yum install gcc openssl-devel pcre-devel zlib-devel postgresql-devel

4、配置后编译安装

./configure --prefix=/local/usr/openresty

make && make install

5、可以像使用开源版nginx一样使用openresty

openresty的最基础使用(编写一个location响应lua脚本的数据,注意编码)

可选: 关闭对于lua的缓存,不关闭的话,每次修改都需要重启nginx

# 关闭lua的缓存,仅做测试使用,放在http模块下
lua_code_cache off;

响应lua脚本内容(写在location与写在lua文件当中)

直接响应lua脚本的内容

location /lua {
    charset utf8;
    default_type  text/html;
    content_by_lua 'ngx.say("<p>Hello, World!你说啥?</p>")';
}

响应lua脚本内容,但是内容从文件中获取

location /lua {
    charset utf8;
    default_type  text/html;
    # 注意这里的相对路径,会直接从nginx目录下找,也就是跟conf、html平级的那个目录
    # 因此需要在nginx目录下创建一个lua文件夹
    content_by_lua_file lua/hello.lua;
}

获取Nginx URI的单一变量

-- 代表获取请求参数中的age变量的值
-- 完整url:http://192.168.22.10/lua?name=%E5%BC%A0%E4%B8%89&age=18
-- 因此这里可以获取到18
ngx.say("<p>Hello, World!我是小笨蛋</p>" .. ngx.var.arg_age);

获取Nginx URI的全部变量

-- 完整URL:http://192.168.22.10/lua?name=zhangsan&age=18&hoppy=ball&hoppy=study

local uri_args = ngx.req.get_uri_args()

for k, v in pairs(uri_args) do
  -- 判断如果value还是一个table的话,那么就会把value的值拼接起来,具体可以去看看教程
  if type(v) == "table" then
      ngx.say(k, " : ", table.concat(v, ", "), "<br/>")  
  else
      ngx.say(k, ": ", v, "<br/>") 
  end
end

image

在处理http请求时还可以使用

  • set_by_lua

修改nginx变量

  • rewrite_by_lua

修改uri

  • access_by_lua

访问控制

  • header_filter_by_lua

修改响应头

  • boy_filter_by_lua

修改响应体

  • log_by_lua

获取Nginx请求头信息

local headers = ngx.req.get_headers()

ngx.say("Host : ", headers["Host"], "<br/>")
ngx.say("Host : ", headers.host, "<br/>")
ngx.say("user-agent : ", headers["user-agent"], "<br/>")
ngx.say("user-agent : ", headers.user_agent, "<br/>")

for k, v in pairs(headers) do
  -- 判断如果value还是一个table的话,那么就会把value的值拼接起来,具体可以去看看教程
  if type(v) == "table" then
      ngx.say(k, " : ", table.concat(v, ", "), "<br/>")  
  else
      ngx.say(k, ": ", v, "<br/>") 
  end
end

获取post请求参数(form data 与 请求体)

-- 获取请求体
ngx.req.read_body()
local post_args = ngx.req.get_post_args()

--[[
  -- 当请求的数据是json时,可以使用该方式处理 cjson会将其转换为一个table对象
  local cjson = require "cjson"
  ngx.say("result: ", cjson.encode(ngx.req.get_body_data()))
--]]

for k, v in pairs(post_args) do
  -- 判断如果value还是一个table的话,那么就会把value的值拼接起来,具体可以去看看教程
  if type(v) == "table" then
      ngx.say(k, " : ", table.concat(v, ", "), "<br/>")
  else
      ngx.say(k, ": ", v, "<br/>")
  end
end

获取http协议版本、请求方式、原始请求头、body内容体

ngx.req.read_body();
ngx.say("ngx.req.http_version : ", ngx.req.http_version(), "<br/>")
ngx.say("ngx.req.get_method : ", ngx.req.get_method(), "<br/>")
ngx.say("ngx.req.raw_header : ",  ngx.req.raw_header(), "<br/>")
-- 获取到的是string类型
ngx.say("ngx.req.get_body_data() : ", ngx.req.get_body_data(), "<br/>")

Nginx多进程共享缓存(也是基于内存,算是nginx内存缓存的补充)

使用共享字典的方式(会加锁保证原子性,而且能限制大小)

配置如下:
1、开启共享字典,在nginx.conf的http下添加配置

# 共享字典,也就是本地缓存,名称叫做:shared_data,大小150m
lua_shared_dict shared_data 150m;

2、基础使用

-- 获取本地缓存对象
local item_cache = ngx.shared.item_cache
-- 存储, 指定key、value、过期时间,单位s,默认为0代表永不过期
item_cache:set('key', 'value', 1000)
-- 读取
local val = item_cache:get('key')
ngx.say(val)
Lua-resty-lrucache(不加锁,只能限制缓存的个数)

Lua 实现的一个简单的 LRU 缓存,适合在 Lua 空间里直接缓存较为复杂的 Lua 数据结构:它相比 ngx_lua 共享内存字典可以省去较昂贵的序列化操作,相比 memcached 这样的外部服务又能省去较昂贵的 socket 操作。而且不会加锁。

官方文档: https://github.com/openresty/lua-resty-lrucache

使用示例:
1、在openresty的安装目录,找到lualib目录,在这里创建一个my文件夹,里面放一个cache.lua文件
image

提示:如果找不到文件的话,在错误日志中可以看到会从如下路径中寻找lua文件
image

2、修改nginx的配置文件

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;
        }

        location /lua {
            charset utf8;
            default_type  text/html;
            # 相当于以一个lua脚本中的方法来作为响应结果
            content_by_lua_block {
                require("my/cache").go();
            }
        }
    }
}

3、修改cache.lua脚本文件

-- 定义了一个table
local _M = {}

-- 引入lru缓存模块
lrucache = require "resty.lrucache"

-- 设置lru缓存的个数为200
lruCache, err = lrucache.new(200)
ngx.say("count=init")


if not lruCache then
    error("lur缓存创建失败 " .. (err or "unknown"))
end

-- 为第一行创建的table添加了一个方法,方法名为go
function _M.go()
  count = lruCache:get("count")

  lruCache:set("count",100)
  ngx.say("count=", count, " --<br/>")


  if not count then  
    -- 计数器还不存在,设置一个默认值为1
    lruCache:set("count",1)
    ngx.say("lazy set count ", lruCache:get("count"), "<br/>")  
  else
    -- 设置缓存中的计数器加一
    lruCache:set("count",count+1)
    ngx.say("count=", count, "<br/>")
  end
end
-- 返回table对象
return _M

openresty连接redis

连接redis官方文档: https://github.com/openresty/lua-resty-redis
集群支持: https://github.com/steve0511/resty-redis-cluster

模板渲染lua-resty-template

官方文档: https://github.com/bungle/lua-resty-template

如果学习过JavaEE中的servlet和JSP的话,应该知道JSP模板最终会被翻译成Servlet来执行;

而lua-resty-template模板引擎可以认为是JSP,其最终会被翻译成Lua代码,然后通过ngx.print输出。

lua-resty-template大体内容有:

l 模板位置:从哪里查找模板;

l 变量输出/转义:变量值输出;

l 代码片段:执行代码片段,完成如if/else、for等复杂逻辑,调用对象函数/方法;

l 注释:解释代码片段含义;

l include:包含另一个模板片段;

l 其他:lua-resty-template还提供了不需要解析片段、简单布局、可复用的代码块、宏指令等支持。

基础语法

l {(include_file)}:包含另一个模板文件;

l {* var *}:变量输出;

l {{ var }}:变量转义输出;

l {% code %}:代码片段;

l {# comment #}:注释;

l {-raw-}:中间的内容不会解析,作为纯文本输出;

使用示例

1、下载模板引擎
https://github.com/bungle/lua-resty-template

2、解压后把如下目录拷贝走
image

3、把拷贝的目录放在openresty的lualib/resty目录下
image
cp -p template template.lua /local/usr/openresty/lualib/resty/

4、在openresty的目录下创建一个文件夹templates,然后在这里写一个模板,hello.html,顺便创建一个header.html和footer.html随便写点内容
hello.html模板文件

{(header.html)}
   <body>
      {# 不转义变量输出 #}
      姓名:{* string.upper(name) *}<br/>
      {# 转义变量输出 #}
      简介:{{description}}
           简介:{* description *}<br/>
      {# 可以做一些运算 #}
      年龄: {* age + 10 *}<br/>
      {# 循环输出 #}
      爱好:
      {% for i, v in ipairs(hobby) do %}
         {% if v == '电影' then  %} - xxoo

              {%else%}  - {* v *}
{% end %}

      {% end %}<br/>

      成绩:
      {% local i = 1; %}
      {% for k, v in pairs(score) do %}
         {% if i > 1 then %},{% end %}
         {* k *} = {* v *}
         {% i = i + 1 %}
      {% end %}<br/>
      成绩2:
      {% for i = 1, #score2 do local t = score2[i] %}
         {% if i > 1 then %},{% end %}
          {* t.name *} = {* t.score *}
      {% end %}<br/>
      {# 中间内容不解析 #}
      {-raw-}{(file)}{-raw-}
{(footer.html)}

5、修改nginx的配置文件

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;
        }

        location /lua {
            charset utf8;
            default_type  text/html;
            # 设置模板文件的位置,目的是为了让lua脚本中能够获取到该变量
            set $template_root /local/usr/openresty/templates;
            # 以一个脚本文件来作为响应结果,这里是相对路径,也就是从nginx目录开始找,与html、conf等目录同级
            content_by_lua_file lua/hello.lua;
        }
    }
}

6、修改lua脚本文件

local template = require("resty.template")
-- 生产环境下需要开启缓存
template.caching(false)
local context = {
    title = "测试",
    name = "lucy",
    description = "<script>alert(1);</script>",
    age = 40,
    hobby = {"电影", "音乐", "阅读"},
    score = {语文 = 90, 数学 = 80, 英语 = 70},
    score2 = {
        {name = "语文", score = 90},
        {name = "数学", score = 80},
        {name = "英语", score = 70},
    }
}

template.render("hello.html", context)

GEOIP2根据ip的来源获取到该ip的归属地

需求1: 根据用户的ip归属地,获取到用户的归属地,以此来将域名解析到离用户最近的服务器的ip
需求2: 一旦用户的ip归属地是外国的,就直接拒绝提供服务。

下载和安装GEOIP2,以及基础使用配置

更完整的配置可参考官方文档
http://nginx.org/en/docs/http/ngx_http_geoip_module.html#geoip_proxy

1、下载数据库,GeoLite2 Country、City、ASN 一共有三个版本,细粒度从低到高
https://dev.maxmind.com/geoip/geolite2-free-geolocation-data?lang=en
下载后解压放在/root目录下

2、下载安装依赖
https://github.com/maxmind/libmaxminddb
下载后解压,执行如下命令进行安装,如下5个命令依次执行

./configure
make
make install
echo /usr/local/lib  >> /etc/ld.so.conf.d/local.conf 
ldconfig

3、下载nginx的geoip2模块
https://github.com/leev/ngx_http_geoip2_module
下载后解压到/root目录下

4、拿一个nginx的源码包,进行编译,编译时加上该geoip2模块

./configure --prefix=/usr/local/nginx --add-module=/root/ngx_http_geoip2_module
make
make install

5、基础使用,在nginx配置文件添加如下配置

# 添加在http模块下
geoip2 /root/GeoLite2-ASN_20220524/GeoLite2-ASN.mmdb {
    $geoip2_country_code country iso_code;
}

# 添加到location模块下,在进行反向代理时,可以把用户归属地给上游服务器,也可以自行进行判断
# 这里仅仅是添加响应头进行测试
add_header country $geoip2_country_code;

CDN进行域名解析时的归属地控制

image

Lua 开源项目(未来再说了...)

WAF

https://github.com/unixhot/waf

https://github.com/loveshell/ngx_lua_waf

l 防止 SQL 注入,本地包含,部分溢出,fuzzing 测试,XSS/SSRF 等 Web 攻击

l 防止 Apache Bench 之类压力测试工具的攻击

l 屏蔽常见的扫描黑客工具,扫描器

l 屏蔽图片附件类目录执行权限、防止 webshell 上传

l 支持 IP 白名单和黑名单功能,直接将黑名单的 IP 访问拒绝

l 支持 URL 白名单,将不需要过滤的 URL 进行定义

l 支持 User-Agent 的过滤、支持 CC 攻击防护、限制单个 URL 指定时间的访问次数

l 支持支持 Cookie 过滤,URL 与 URL 参数过滤

l 支持日志记录,将所有拒绝的操作,记录到日志中去

Kong 基于Openresty的流量网关

https://konghq.com/

https://github.com/kong/kong

Kong 基于 OpenResty,是一个云原生、快速、可扩展、分布式的微服务抽象层(Microservice Abstraction Layer),也叫 API 网关(API Gateway),在 Service Mesh 里也叫 API 中间件(API Middleware)。

Kong 开源于 2015 年,核心价值在于高性能和扩展性。从全球 5000 强的组织统计数据来看,Kong 是现在依然在维护的,在生产环境使用最广泛的 API 网关。

Kong 宣称自己是世界上最流行的开源微服务 API 网关(The World’s Most Popular Open Source Microservice API Gateway)。

核心优势:

l 可扩展:可以方便的通过添加节点水平扩展,这意味着可以在很低的延迟下支持很大的系统负载。

l 模块化:可以通过添加新的插件来扩展 Kong 的能力,这些插件可以通过 RESTful Admin API 来安装和配置。

l 在任何基础架构上运行:Kong 可以在任何地方都能运行,比如在云或混合环境中部署 Kong,单个或全球的数据中心。

APISIX

ABTestingGateway

https://github.com/CNSRE/ABTestingGateway

ABTestingGateway 是一个可以动态设置分流策略的网关,关注与灰度发布相关领域,基于 Nginx 和 ngx-lua 开发,使用 Redis 作为分流策略数据库,可以实现动态调度功能。

ABTestingGateway 是新浪微博内部的动态路由系统 dygateway 的一部分,目前已经开源。在以往的基于 Nginx 实现的灰度系统中,分流逻辑往往通过 rewrite 阶段的 if 和 rewrite 指令等实现,优点是性能较高,缺点是功能受限、容易出错,以及转发规则固定,只能静态分流。ABTestingGateway 则采用 ngx-lua,通过启用 lua-shared-dict 和 lua-resty-lock 作为系统缓存和缓存锁,系统获得了较为接近原生 Nginx 转发的性能。

l 支持多种分流方式,目前包括 iprange、uidrange、uid 尾数和指定uid分流

l 支持多级分流,动态设置分流策略,即时生效,无需重启

l 可扩展性,提供了开发框架,开发者可以灵活添加新的分流方式,实现二次开发

l 高性能,压测数据接近原生 Nginx 转发

l 灰度系统配置写在 Nginx 配置文件中,方便管理员配置

l 适用于多种场景:灰度发布、AB 测试和负载均衡等

使用nginx的一些建议

  • 尽量不要在nginx的配置文件中出现正则表达式,因为匹配正则表达式还是比较耗费资源的。
posted @ 2022-11-18 20:14  CodeStars  阅读(31)  评论(0)    收藏  举报