乐趣区

关于nginx:Nginx-实战核心知识点整理下

Nginx 作为一款高性能的代理软件,在工作中常被用做负载均衡器、正向反向代理。最近在看了一个对于 Nginx 的视频,老师讲得很不错。笔者也记录了一下整套课程的一些重点和本人的了解。

上篇:https://segmentfault.com/a/11…

上篇次要跟大家讲述了一下如果应用 Nginx,也是为下篇进行铺垫。接下来的内容能让读者手上的 Nginx 施展最大作用。

那咱们开始吧!

sticky 维持状态

上篇介绍了几种调度算法,咱们一起回顾一下外面的 ip_hash 和 url_hash

  • ip_hash:对于紧急扩容场景,应用这种策略可能疾速引入新节点,但如果后端服务忽然宕机会造成状态失落,能够应用 Global Session 的形式解决(引入 Spring Session + Redis)。但后端服务数量太多也会对外置的 Redis 造成压力。

    upstream  flask  {
        ip_hash;
        server  172.20.0.11:80;
        server  172.20.0.12:80;
    }

    比方 ERP 零碎应用 URL 哈希比拟适合,因为对于一个企业,多个用户收回的申请达到路由器后都会转换成同一个 IP,容易造成流量歪斜。考试零碎也是雷同情理。

  • url_hash:对于不反对 Cookies 的场景,个别会把 JSESSION_ID 放到 URL 中,这时候能够应用 url_hash 进行调度

    • $cookie_jsessioni 基于 SSESION ID 的 hash
    • $request_uri; 基于 url 的 hash
    upstream  flask  {
        # hash  $cookie_jsession;
        hash  $request_uri;
        server  172.20.0.11:80;
        server  172.20.0.12:80;
    }

    不难发现,如果心愿一个用户的所有申请都路由到一台上游服务时,必须依赖于 SESSION_ID。如果客户端不反对 Cookie、URL 长度还受限制,或者上游服务器压根是一台动态服务器,那么记录用户和上游服务器映射关系的工作就要前置到 Nginx 上了。sticky 模块就是专门用作这样的场景。

  • Bitbucket:https://bitbucket.org/nginx-g…

下载后解压,在 ngx_http_sticky_misc.c 增加两个头文件定义

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

从新编译、尝试运行、备份、替换

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

mnake

make upgrade

cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak
cp objs/nginx /usr/local/nginx/sbin/nginx

批改配置并从新加载配置文件

upstream  flask  {
    sticky name=route expires=6h;
    server  172.20.0.11:80;
    server  172.20.0.12:80;
}

刷新一下页面,发现同一个用户的屡次拜访会被调度到同一台主机上,且 Cookie 中多出了一个 route 字段。

sticky 的字段不能与后端服务器的 cookie 字段抵触

keepalive 晋升性能

应用 Nginx 作为网关可能很好的爱护内网服务的安全性,引入了代理节点难免会带来转发的损耗,对于须要继续转发的场景,开启 keepalive 可能缩小频繁三次握手的开销。

工夫同步的场景就不太适宜开启 keepalive,动态资源服务也不适宜开启 keepalive

通常状况下,浏览器发动申请时会在每一个申请中被动加上 Connection: keep-alive,服务器依据本人的志愿响应 Connection: keep-alive 或者 Connection: close

比方上面这个配置,

server {
    keepalive_time  65;
    keepalive_requests   15;
}

upstream  demo {
    keepalive_time  65;
    ...
}
  • keepalive_time:在某次申请后开始计时,这要在这段时间内有申请,会持续放弃连贯,并重置定时器
  • keepalive_timeout:连贯维持的总时长
  • send_timeout:keepavlive 放弃工夫
  • keepalive_disable:对某些浏览器禁用 keepalive,默认 msie6
  • keepalive_requests:一次连贯能够解决多少个申请
  • send_timeout:两次申请距离,默认值为 60 秒,大于该值就会敞开连贯。对于一些耗时较长的动作须要额定留神这个配置

这里须要留神 send_timeout 和 keepalive_timeout 的区别:

keepavlive 开启后,客户端会向服务端收回心跳包,服务端会接管心跳包并重置定时器(send_timeout)。当定时器超时后服务端会敞开连贯。而 send_timeout 其实和 keepavlue 没什么关系,当用户发动一次申请后开始计时,超过 send_timeout 后敞开连贯。

配置实现后能够应用 Charles 进行测试,看看连贯是否被复用和被复用的次数。

这里应用 abtest 进行一下压测,前面的压测只是做一个参考,在虚拟机中压测后果不是很精确。

# 装置 ABtest
$ yum install -y httpd-tools

# 进行一次压力测试
$ ab -n 100000 -c 30 http://192.168.80.154/

abtest 参数阐明:

  • -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。

大家能够测试一下上面几种状况,

  1. 间接压测上游服务器
  2. 压测代理服务器
  3. 敞开代理服务器和上游服务的 keepavlie

依据后果比照一下这两个参数

  • Requests per second:每秒解决的申请(客户端接管到响应)
  • Transfer rate:吞吐量

会发现开启 keepalive QPS 会更高。

链路上的可配参数

从代理接管客户端申请数据,到代理服务区将数据转发到上游服务器,最初接管响应数据转发返回客户端,两头过程能够进行

  1. 申请头批改
  2. Buffer 缓冲
  3. 超时管制

缓冲和超市管制

  • set_header:设置 header
  • proxy_connect_timeout:与上游服务器连贯超时工夫、疾速失败
  • proxy_send_timeout:定义 nginx 向后端服务发送申请的间隔时间(不是耗时)。默认 60 秒,超过这个工夫会敞开连贯
  • proxy_read_timeout:后端服务给 nginx 响应的工夫,规定工夫内后端服务没有给 nginx 响应,连贯会被敞开,nginx 返回 504 Gateway Time-out。默认 60 秒

缓冲区

  • proxy_requset_buffering:是否齐全读到申请体之后再向上游服务器发送申请
  • proxy_buffering:是否缓冲上游服务器数据
  • proxy_buffers 32 64k;:缓冲区大小 32 个 64k 大小内存缓冲块
  • proxy_buffer_size:header 缓冲区大小
proxy_requset_buffering on;
proxy_buffering on;

proxy_buffer_size 64k;

proxy_buffers 32 128k;
proxy_busy_buffers_size 8k;
proxy_max_temp_file_size 1024m;
  • proxy_temp_file_write_size 8k:当启用从代理服务器到临时文件的响应的缓冲时,一次限度写入临时文件的数据的大小。默认状况下,大小由 proxy_buffer_size 和 proxy_buffers 指令设置的两个缓冲区限度。临时文件的最大大小由 proxy_max_temp_file_size 指令设置。
  • proxy_max_temp_file_size 1024m;:临时文件最大值
  • proxy_temp_path:临时文件门路

    proxy_temp_path /spool/nginx/proxy_temp 1 2;

    a temporary file might look like this:

    /spool/nginx/proxy_temp/7/45/00000123457

对客户端的限度

  • client_body_buffer_size:对客户端申请中的 body 缓冲区大小,看操作系统,默认 32 位 8k 64 位 16k,如果申请体大于配置,则写入临时文件
  • client_header_buffer_size:设置读取客户端申请体的缓冲区大小。如果申请体大于缓冲区,则将整个申请体或仅将其局部写入临时文件。默认 32 位 8K。64 位平台 16K。
  • client_max_body_size 1000M;:默认 1m,如果一个申请的大小超过配置的值,会返回 413 (request Entity Too Large)谬误给客户端。将 size 设置为 0 将禁用对客户端申请注释大小的查看。
  • client_body_timeout:指定客户端与服务端建设连贯后发送 request body 的超时工夫。如果客户端在指定工夫内没有发送任何内容,Nginx 返回 HTTP 408(Request Timed Out)
  • client_header_timeout:客户端向服务端发送一个残缺的 request header 的超时工夫。如果客户端在指定工夫内没有发送一个残缺的 request header,Nginx 返回 HTTP 408(Request Timed Out)。
  • client_body_temp_path path [level1 [level2 [level3`]]]:在磁盘上客户端的 body 长期缓冲区地位
  • client_body_in_file_only on:把 body 写入磁盘文件,申请完结也不会删除
  • client_body_in_single_buffer:尽量缓冲 body 的时候在内存中应用间断繁多缓冲区,在二次开发时应用 $request_body 读取数据时性能会有所提高
  • large_client_header_buffers:如果一个申请行或者一个申请头字段不能放入 client_header_buffer_size 大小的缓冲区,那么就会应用 large_client_header_buffers,默认 8k

压缩响应体

压缩响应体可能能够放慢传输效率,但须要客户端和服务端反对独特的压缩算法。罕用的算法有 gzip 和 brotil

GZIP 动静压缩

咱们应用 Chrome 浏览器拜访某站点时会主动带上一个 Header

Accept-Encoding: gzip

示意浏览器反对 gzip 解压缩算法,服务器能够将响音体用 gzip 压缩后返回,在咱们没有开启 GZIP 时,响应体的 Header 中 Content-Length 示意字符数量

Content-Length: 654

当咱们在 Nginx 中配置 GZIP

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)";

再次申请发现没有 Content-Length,Content-Encoding 变成了

Transfer-Encoding: chunked

Nginx 一边压缩一边响应,导致 Nginx 也不晓得压缩后的文件大小,所以以 chunked 形式返回,chunked 示意以块为单位传输数据。当最初一个块为 0 字节时示意完结。

咱们能够在 Chrome 中设置一下浏览器反对的压缩算法

相干配置的阐明:

  • gzip:是否开启 gzip
  • gzip_buffers:buffer 大小
  • gzip_comp_level:压缩等级,等级越高对 CPU 损耗越大
  • gzip_http_version:http 最低版本
  • gzip_proxied:依据上游服务器返回的 Header 决定是否进行压缩

    • 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 – 无条件启用压缩
  • gzip_vary:减少一个 header,适配老的浏览器 Vary: Accept-Encoding
  • gzip_types:减少一个 header,适配老的浏览器 Vary: Accept-Encoding
  • gzip_disable:禁止某些浏览器应用 gzip,尽量不要在配置文件中应用正则表达式,这个尽量不配置

下面的配置形式属于动静压缩,有一个致命问题是 无奈应用 send_file 零拷贝 。send_file 只能针对磁盘上的文件进行零拷贝。咱们能够先把资源文件 事后 进行压缩,当用户申请时间接返回压缩后的文件

动态压缩

装置一下 http_gzip_static_module 和 http_gunzip_module

# 从新编译,加上上面参数
--with-http_gzip_static_module  
--with-http_gunzip_module

对于心愿事后进行压缩的文件。应用上面命令进行压缩

# 压缩
$ gzip -r ./
# 解压缩
$ gunzip -r /

在配置文件中增加配置

gzip_static  always;
  • always:间接返回 gz 文件
  • on:查看客户端是否反对 gzip,如果反对则返回 gz 文件,不反对则返回源文件
  • off:敞开该模块

咱们齐全能够将源文件全副删除,只保留压缩文件。但当面对局部客户端不反对 gzip 时,在没有保留源文件的状况下咱们能够配合 gunzip 模块,在服务端解压缩后再将数据返回。

gunzip on;
gunzip_buffers 16 8k;
gzip_static  always;

在理论生产环境中,对于动态文本文件如 js、css(代码源文件的压缩比能够很高),咱们能够开启动态压缩取代动静压缩。当你心愿移除源文件只保留压缩文件时,须要思考到不反对 GZIP 的客户端,所有须要引入 gunzip 模块

Brotli 压缩

Brotli 是谷歌的一种压缩算法,压缩率比 GZIP 高 20%。

  • ngx_brotli:https://github.com/google/ngx…
  • brotli:https://github.com/google/brotli

下载最新版本,依照上面步骤进行编译

# 解压两个安装包,将 brotli 里的所有内容复制到 ngx_brotli/deps/brotli
$ rotli-1.0.9
$  -r  ./* ../ngx_brotli-1.0.0rc/deps/brotli/

# 回到 Nginx 源码目录,编译
$ ./configure --with-compat --add-dynamic-module=/root/ngx_brotli-1.0.0rc --prefix=/usr/local/nginx/ --add-module=/root/nginx-goodies-nginx-sticky-module-ng-c78b7dd79d0d --with-http_gzip_static_module --with-http_gunzip_module

$ make
$ cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak 
$ cp objs/nginx /usr/local/nginx/sbin/nginx

$ mkdir -p /usr/local/nginx/modules
$ cp ./objs/ngx_http_brotli_static_module.so /usr/local/nginx/modules/
$ cp .objs/ngx_http_brotli_filter_module.so  /usr/local/nginx/modules/ 

批改配置文件

# 加载顶部
load_module "/usr/local/nginx/modules/ngx_http_brotli_filter_module.so";
load_module "/usr/local/nginx/modules/ngx_http_brotli_static_module.so";

# 加到某个 server
brotli on;
brotli_static on;
brotli_comp_level 6;
brotli_buffers 16 8k;
brotli_min_length 20;
brotli_types text/plain text/css text/javascript application/javascript text/xml application/xml application/xml+rss application/json image/jpeg image/gif image/png;

brotli 能够和 gzip 一起应用,当浏览器不反对 br 时会主动应用 gzip

Chrome 浏览器只会在 HTTPS 状况下应用 br,这里能够应用 curl 来测试一下

$ curl -H "Accept-Encoding: br" -I http://192.168.80.154/static/test.txt
HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Sun, 26 Jun 2022 13:55:45 GMT
Content-Type: text/plain
Last-Modified: Sun, 26 Jun 2022 10:08:17 GMT
Connection: keep-alive
Vary: Accept-Encoding
ETag: W/"62b83011-268"
Content-Encoding: br

concat 申请合并

Concat 是淘宝开源的一个模块,他能够将多个动态资源申请合并,比方某个页面须要上面两个 css

<link src="font.css">
<link src="background.css">

为了缩小申请次数,咱们能够将两个申请合并

<link src="font.css,background.css">

淘宝官网就有相似的操作

咱们能够下载安装一下 Concat 模块,尝试合并一下 CSS

  • Github:https://github.com/alibaba/ng…
# 动态编译

$ ./configure --with-compat --add-dynamic-module=/root/ngx_brotli-1.0.0rc --prefix=/usr/local/nginx/ --add-module=/root/nginx-goodies-nginx-sticky-module-ng-c78b7dd79d0d --add-module=/root/nginx-http-concat-master --with-http_gzip_static_module --with-http_gunzip_module 

$ make
$ cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak 
$ cp objs/nginx /usr/local/nginx/sbin/nginx

增加配置

location /static/css/ {
    concat on;
    concat_max_files 20;
}

创立两个 CSS 文件,尝试申请

curl http://192.168.80.154/static/??test02.css,test02.css

能够或则两个文件合并的后果。因为这里的合并须要将两个文件加载到内存,所以无奈应用 send_file。

事后进行资源动态化

对于一些并发数较高的申请,个别优化思路有三个:

  1. 多级缓存
  2. 事后资源事后动态化
  3. 将数据放到间隔用户更近的中央

资源动态化是借助一些模板引擎,将动态数据和动态页面进行渲染并生成文件,将该文件搁置到 Nginx 中。

  1. 缓存一致性问题:对于一些相似评论、价格等数据咱们能够应用异步形式获取
  2. 动态页面同步问题:能够应用 rsync + inotify 进行文件同步
  3. 文件合并输入:对于一个页面,比方淘宝的任意一个商品详情,能够将他拆分成三个局部:

    • 固定的文本:例如导航栏、页头页尾
    • 绝对固定的文本:比方商品的形容、参数等
    • 动态数据:比方价格、购物车等等

    对于固定的文本,咱们能够将其永恒放在资源服务器上,应用 Nginx 的一些页面嵌套的技术与非固定页面进行合并。对于绝对固定的文本,咱们能够应用一些模板引擎进行页面渲染,实现将选而后的文件放在某台服务器,让该服务器将页面同步到各台 Nginx 中,对于动静的数据咱们就应用异步的形式获取即可。

SSI 合并输入

SSI 是 Nginx 进行 HTML 文件合并的模块,用法相似于模板引擎:

  • 文档:http://nginx.org/en/docs/http…

应用该模块就可能将固定的文件内容抽离,相干的配置如下:

  • ssi on:是否开启 SSI
  • ssi_min_file_chunk:向磁盘存储并应用 sendfile 发送,文件大小最小值
  • ssi_last_modified:是否保留 lastmodified
  • ssi_silent_errors:不显示逻辑谬误
  • ssi_value_length:限度脚本参数最大长度
  • ssi_types:默认 text/html; 如果须要其余 mime 类型 须要设置

比方咱们能够抽离出页头页尾:

<!--# include file="header.html" -->
Hello word
<!--# include file="footer.html" -->

两头的局部交给模板引擎生成,再 incluede 到页面中。这种形式同样无奈应用 send_file

文件同步计划

文件同步能够应用推模式或者拉模式,借助工具 rsync 能够实现文件的比照、压缩和传输

咱们能够在源服务器启动 rsync,在指标服务器定时拉取资源文件。

# 装置 rsync
$ yum install -y rsync
# 筹备密码文件
$ echo "hello:123" >> /etc/rsyncd.pwd
$ chmod 600 /etc/rsyncd.pwd

# 批改配置
$ vim /etc/rsyncd.conf

auth users = hello
secrets file = /etc/rsyncd.pwd

[ftp]
        path = /usr/local/nginx/flask-demo
        comment = ftp export area


$ rsync --daemon

$ firewall-cmd --add-port=873/tcp --permanent
$ firewall-cmd --reload

指标服务器查看

$ rsync --list-only hello@192.168.80:154::ftp/
passwordL 123
drwxr-xr-x             39 2022/06/18 12:01:31 .
-rw-r--r--          2,406 2022/06/18 12:01:31 invalid.png
drwxr-xr-x             72 2022/06/26 23:40:45 static

拉模式

拉取最新文件

$ rsync -avz /usr/local/nginx/flask-demo  hello@192.168.80:154::ftp/

应用 SSH 进行文件同步

$  rsync -avz --delete hello@192.168.80.154::ftp/ /usr/local/nginx/flask-demo/

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 传输时压缩

推模式

这时须要在指标服务器启动 rysnc,源服务器进行推送。先批改指标服务器的配置文件

uid = root
gid = root
readonly = no

auth users = hello
secrets file = /etc/rsyncd.pwd

[ftp]
        path = /usr/local/nginx/flask-demo
        comment = ftp export area

生产环境要独自创立用户和用户组哈。启动 rsync,将文件推送到指标服务器

# 在源服务器记录指标服务器认证明码
$ echo "123" > /etc/rsyncd.passwd.client
$ chmod 600 /etc/rsyncd.passwd.client

$ firewall-cmd --add-port=873/tcp --permanent
$ firewall-cmd --reload

$ rsync -az --delete --password-file=/etc/rsyncd.passwd.client /usr/local/nginx/flask-demo/ hello@192.168.80.155::ftp/

须要借助 inotify 进行目录监控,当存在文件变更时进行推送

  • Github:https://github.com/inotify-to…
  • 下载地址:http://github.com/downloads/r…
$ ./configure
$ make
$ make install

监控指定目录

$ /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 //usr/local/nginx/flask-demo/

2022-06-27 14:12:27 //usr/local/nginx/flask-demo/New File CREATE
2022-06-27 14:12:27 //usr/local/nginx/flask-demo/New File CLOSE_WRITE,CLOSE
2022-06-27 14:12:29 //usr/local/nginx/flask-demo/heelo CREATE
2022-06-27 14:12:29 //usr/local/nginx/flask-demo/New File DELETE

这样咱们就能够编写一个自动化脚本:

#!/bin/bash

/usr/local/inotify/bin/inotifywait -mrq --timefmt '%d/%m/%y %H:%M' --format '%T %w%f %e' -e close_write,modify,delete,create,attrib,move //usr/local/nginx/html/ | while read file
do 
        rsync -az --delete --password-file=/etc/rsyncd.passwd.client /usr/local/nginx/html/ sgg@192.168.44.102::ftp/
done

部署到间隔用户更近的中央

后面提到的资源动态化,应用了 rsync 进行文件同步。在公网上咱们心愿可能将资源部署到间隔用户最近的中央,这时候就须要 CDN 分布式内容散发网络。

两头的 CDN 服务器能够应用 Nginx 搭建,个别 DNS 解析都比拟智能,可能依据你的 ISP 返回指定的 CDN 服务器地址(这个须要去云平台配置)。

比拟不智能的形式是 GEOIP,应用场景比拟少。

获取用户 IP 地址

因为引入了代理服务器,上游服务无奈获取客户端 IP 地址。咱们能够在 Nginx 中为 HTTP Header 加上 X-Forwarded-For

proxy_set_header X-Forwarded-For $remote_addr;

GEOIP

Nginx 借助 GEOIP 也能够做 IP 归属地查看,并依据查看后果返回不同的资源,比方 IP 来自于中国就返回中文站点。

  • GEOIP 数据库:https://dev.maxmind.com/geoip…
  • MMDB 依赖:https://github.com/maxmind/li…

    $ ./configure
    $ make && make install
    
    # 增加动态链接库
    $ echo /usr/local/lib  >> /etc/ld.so.conf.d/local.conf 
    $ ldconfig
  • Nginx 模块:https://github.com/leev/ngx_h…

    $ ./configure --with-compat --add-dynamic-module=/root/ngx_brotli-1.0.0rc --prefix=/usr/local/nginx/ --add-module=/root/nginx-goodies-nginx-sticky-module-ng-c78b7dd79d0d --add-module=/root/nginx-http-concat-master --with-http_gzip_static_module --with-http_gunzip_module --add-module=/root/ngx_http_geoip2_module-3.4
    
    $ make
    $ cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak 
    $ cp objs/nginx /usr/local/nginx/sbin/nginx

疾速配置

geoip2 /root/GeoLite2-Country_20220628/GeoLite2-Country.mmdb {$geoip2_country_code country iso_code;}
add_header country $geoip2_country_code;

更多配置:http://nginx.org/en/docs/http…

多级缓存

下面介绍了资源动态化和 CDN,都是 让经量少的申请达到上游服务器。

除了下面的两种形式,还能够在客户端做手脚:
比方一次大促流动,因为用户一直拜访热门商品的详情页,服务器是能够提前预知那些商品是热卖商品。例如某商品只有 100 件,那么设计者齐全能够在用户中随机筛选 200 位用户让他们参加抢购,其余用户间接展现抢完了(不发动申请)

利用缓存能够进步用户响应速度的同时升高服务器的负载。依照与用户的间隔划分,能够分为上面几个层级的缓存

  • 浏览器缓存
  • CNS 缓存
  • 正向代理缓存
  • 反向代理缓存
  • Nginx 内存缓存
  • Nginx 外置缓存
  • 上游服务器缓存

浏览器缓存

比方 JD,能够看到不禁用缓存的状况下,有很多申请来自于 memory 或者 disk,加载速率比拟高。怎么样的申请能力应用浏览器的缓存咧?

  1. 强制缓存:指定有效期的形式:当响应头存在 Expire 字段时,浏览器会将申请缓存至无效工夫。这里的无效工夫依赖于客户端零碎工夫,如果客户端工夫没有同步则会产生偏差。

    expires 30s;   #缓存 30 秒
    expires 30m;  #缓存 30 分钟   
    expires 2h;     #缓存 2 小时
    expires 30d;    #缓存 30 天

    http1.1 的标准,应用 max-age 示意文件能够在浏览器中缓存的工夫以秒为单位

    标记 类型 性能
    public 响应头 响应的数据能够被缓存,客户端和代理层都能够缓存
    private 响应头 可公有缓存,客户端能够缓存,代理层不能缓存(CDN,proxy_pass)
    no-cache 申请头 能够应用本地缓存,然而必须发送申请到服务器回源验证
    no-store 申请和响应 应禁用缓存
    max-age 申请和响应 文件能够在浏览器中缓存的工夫以秒为单位
    s-maxage 申请和响应 用户代理层缓存,CDN 下发,当客户端数据过期时会从新校验
    max-stale 申请和响应 缓存最大应用工夫,如果缓存过期,但还在这个工夫范畴内则能够应用缓存数据
    min-fresh 申请和响应 缓存最小应用工夫,
    must-revalidate 申请和响应 当缓存过期后,必须回源从新申请资源。比 no-cache 更严格。因为 HTTP 标准是容许客户端在某些非凡状况下间接应用过期缓存的,比方校验申请发送失败的时候。那么带有 must-revalidate 的缓存必须校验,其余条件全副生效。
    proxy-revalidate 申请和响应 和 must-revalidate 相似,只对 CDN 这种代理服务器无效,客户端遇到此头,须要回源验证
    stale-while-revalidate 响应 示意在指定工夫内能够先应用本地缓存,后盾进行异步校验
    stale-if-error 响应 在指定工夫内,从新验证时返回状态码为 5XX 的时候,能够用本地缓存
    only-if-cached 响应 那么只应用缓存内容,如果没有缓存 则 504 getway timeout
  1. 协商缓存:判断文件批改状态的形式(Nginx 返回动态资源的默认形式):第一次拜访动态资源时,响应头蕴含了 Etag(文件名、大小、批改工夫生成的标识)和 Last-Modified(文件上次更新工夫),第二次申请该资源时申请头须要带上这两个头(Last-Modified 改名成 If-Modified-Since),如果 Nginx 判断文件没有被批改则返回 304 No Modified,不会返回文件

    如何敞开

    etag  off;
    
    add_header  "Last-Modified" "";
    # 或者
    if_modified_since  off;

在理论利用中,当咱们应用 Chrome 关上一个新的标签页输出某个网站,会触发强制缓存的逻辑,查看内存或者磁盘是否存在缓存,如果存在则通过 Expire 和 Cache-Control 判断一下是否过期。当用户按下 F5 刷新时则走协商缓存的逻辑,通过 Etag 和 If-Modified-Since 协商是否可能应用缓存。

有几点须要留神:

  1. 不只是动态资源,动静资源也很可能进行缓存。上游服务器须要本人生成 Etag(能够应用织入的形式进行),如果数据没有变动则返回 304
  2. 对于一些不心愿应用缓存的申请,能够在 URL 上加上一些随机数
  3. 缓存的作用不经能够爱护上游服务,还能够再没有联网时进步用户体验。但应用时须要看看客户端是否反对规范 HTTP

反向代理文件缓存

当咱们通过代理服务器拜访上游服务器时,上游服务器返回的后果能够被 Nginx 缓存,当下一次有雷同的拜访时就能够间接返回缓存中的数据。

只须要进行以下配置即可

http 模块:proxy_cache_path /ngx_tmp levels=1:2 keys_zone=test_cache:100m inactive=1d max_size=10g ;
location 模块:add_header  Nginx-Cache "$upstream_cache_status";
proxy_cache test_cache;
proxy_cache_valid 168h;

其中 proxy_cache_path 中,第一个参数是门路,level 示意层级(比方这里的 1:2 示意第一层目录名称是一个字符,2 示意第二层目录以两个字符命名),keys_zone 是一个标识符,前面援用该配置时须要用到。100m 示意 KV(每一个被缓存的文件都有一个 Key,Value 是缓存文件所在门路)在内存中的大小。inactive 示意文件的删除工夫,到点了会主动删除。max_size 示意缓存文件总大小最大值。

  • proxy_cache_use_stale:默认 off。在什么时候能够应用过期缓存,可选error | timeout | invalid_header | updating | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | http_429 | off
  • proxy_cache_background_update:默认 off,运行开启子申请更新过期的内容。同时会把过期的内容返回给客户端
  • proxy_no_cache:什么时候不应用缓存,比方上面设置中的变量为 0 则不应用

    proxy_no_cache $cookie_nocache $arg_nocache$arg_comment;
    proxy_no_cache $http_pragma    $http_authorization;
  • proxy_cache_bypass:缓存大小
  • proxy_cache_convert_head:默认为 on,示意是否把 head 申请转换为 get 申请获取数据并缓存,如果敞开 须要在 cache key 中增加 $request_method 以便辨别缓存内容
  • proxy_cache_lock:因为可能存在多个申请实现数据更新,这里能够应用锁来保证数据一致性。(这里是一个读 - 写场景)
  • proxy_cache_lock_age:锁超时工夫
  • proxy_cache_key:默认$scheme$proxy_host$request_uri,缓存的 Key
  • proxy_cache_revalidate:向上游服务器发送 If-Modified-Since 来校验是否须要从新拉取缓存
  • proxy_cache_valid:缓存过期工夫,超出工夫后会从新获取。

    proxy_cache_valid 200 302 10m;
    proxy_cache_valid 301      1h;
    proxy_cache_valid any      1m;

咱们能够尝试配置后,应用浏览器拜访,页面会被缓存在 Nginx 下,轻易关上一个能够看到文件中蕴含缓存的 Key、Date 和缓存文件内容。但目录后果浏览性差,当咱们须要删除某个页面的缓存时比拟麻烦,如果心愿革除指定缓存,须要依赖另一个插件。

  • purge:https://github.com/FRiCKLE/ng…

下载安装后配置

location ~ /purge(/.*) {proxy_cache_purge  test_cache  $1;}

留神这里配置的 key 是 uri,所以对应的 proxy_cache 配置的 proxy_cache_key 也要是 uri

proxy_cache_key $uri;

配置实现后尝试通过 GET 或者 PURGE 申请革除缓存

range

对于数据大小已知,Response 蕴含 Content-Length、服务器反对 Http Range 的状况,咱们能够在申请头上带上

Range: bytes=0-5

指定须要获取的数据范畴

如果 Content-Type 是 Stream,则不反对 range

能实现片段读取的前提有两个:

  1. 上游服务器反对 Range
  2. 代理服务器将 Range Header 传递到后端服务器

    proxy_set_header Range $http_range;

在应用 Range 时,如果心愿应用上 Proxy Cache 则须要留神上面的配置

  • proxy_cache_max_range_offset:range 最大值,超过之后不做缓存,默认状况下 不须要对单文件较大的资源做缓存
  • proxy_cache_methods:默认为 get、header
  • proxy_cache_min_uses:申请多少次才进行缓存
  • proxy_cache_path:指定存储根目录
  • levels=1:2:存储层级,当须要进行磁盘扩容是能够应用软连贯逾越文件系统
  • use_temp_path:默认创立缓存文件时,先向缓冲区创立临时文件,再挪动到缓存目录
  • inactive:指定工夫内未被拜访过的缓存将被删除

配置:

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

Nginx 过程缓存

Nginx 提供了 shared_dict,多 Worker 共享,在咱们应用 lua 进行二次开发的时候有用,稍后会赘述。这里先说一个可优化的点

open_file_cache 优化

当 Nginx 须要将某个动态资源返回给用户时,须要经验上面的零碎调用

$ strace -p {Nginx Worker PID}

strace: Process 1135 attached
epoll_wait(8, [{EPOLLIN, {u32=90669072, u64=139642362363920}}], 512, -1) = 1
accept4(6, {sa_family=AF_INET, sin_port=htons(13822), sin_addr=inet_addr("192.168.80.1")}, [112->16], SOCK_NONBLOCK) = 3
epoll_ctl(8, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=90669792, u64=139642362364640}}) = 0
epoll_wait(8, [{EPOLLIN, {u32=90669072, u64=139642362363920}}], 512, 60000) = 1
accept4(6, {sa_family=AF_INET, sin_port=htons(13823), sin_addr=inet_addr("192.168.80.1")}, [112->16], SOCK_NONBLOCK) = 10
epoll_ctl(8, EPOLL_CTL_ADD, 10, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=90669552, u64=139642362364400}}) = 0
epoll_wait(8, [{EPOLLIN, {u32=90669792, u64=139642362364640}}], 512, 59999) = 1
recvfrom(3, "GET /static/test.txt HTTP/1.1\r\nH"..., 1024, 0, NULL, NULL) = 492
open("/usr/local/nginx//flask-demo/static/test.txt.gz", O_RDONLY|O_NONBLOCK) = 11
fstat(11, {st_mode=S_IFREG|0644, st_size=616, ...}) = 0
writev(3, [{iov_base="HTTP/1.1 200 OK\r\nServer: nginx/1"..., iov_len=264}], 1) = 264
sendfile(3, 11, [0] => [616], 616)      = 616
write(4, "192.168.80.1 - - [30/Jun/2022:10"..., 204) = 204
close(11)                               = 0
setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0

每一次申请都会先 open 文件,再 sendfile 进来。这里能够进行缓存一下文件句柄,防止重复创立销毁(配置到 Server 上面)

open_file_cache max=500 inactive=60s
open_file_cache_min_uses 1; 
open_file_cache_valid 60s; 
open_file_cache_errors on
  • max:缓存最多多少个句柄,inactive:什么时候删除句柄
  • open_file_cache_min_uses:拜访多少次后才进行缓存
  • open_file_cache_valid:缓存无效工夫
  • open_file_cache_errors:是否对错误信息进行缓存

配置实现后间断发动申请

epoll_wait(8, [{EPOLLIN, {u32=4025118736, u64=140621254389776}}], 512, -1) = 1
accept4(6, {sa_family=AF_INET, sin_port=htons(3464), sin_addr=inet_addr("192.168.80.1")}, [112->16], SOCK_NONBLOCK) = 3
epoll_ctl(8, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=4025119216, u64=140621254390256}}) = 0
epoll_wait(8, [{EPOLLIN, {u32=4025119216, u64=140621254390256}}], 512, 60000) = 1
recvfrom(3, "GET /static/test.txt HTTP/1.1\r\nH"..., 1024, 0, NULL, NULL) = 418
open("/usr/local/nginx//flask-demo/static/test.txt", O_RDONLY|O_NONBLOCK) = 10
fstat(10, {st_mode=S_IFREG|0644, st_size=1208, ...}) = 0
writev(3, [{iov_base="HTTP/1.1 200 OK\r\nServer: nginx/1"..., iov_len=240}], 1) = 240
sendfile(3, 10, [0] => [1208], 1208)    = 1208
write(4, "192.168.80.1 - - [30/Jun/2022:12"..., 205) = 205
setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
epoll_wait(8, [{EPOLLIN, {u32=4025119216, u64=140621254390256}}], 512, 65000) = 1
recvfrom(3, "GET /static/test.txt HTTP/1.1\r\nH"..., 1024, 0, NULL, NULL) = 418
writev(3, [{iov_base="HTTP/1.1 200 OK\r\nServer: nginx/1"..., iov_len=240}], 1) = 240
sendfile(3, 10, [0] => [1208], 1208)    = 1208
write(4, "192.168.80.1 - - [30/Jun/2022:12"..., 205) = 205

可见没有再 open file 了

外置缓存

咱们须要先理解两个知识点:匿名 location 和 return。

  • 匿名 location 相当于被 private 润饰的 location,内部无奈间接拜访,
  • return:响应 Response

比方上面这个例子

error_page   404 = @fallback;
location @fallback {
    add_header  "Content-Type" text/plain";
    return 200 "page no found";
}

随便拜访一个不存在的文件不再会返回 404。

Memcached

当 Nginx 所在主机的内存不够用时,能够应用 memcached 或者 redis 扩容。先看 memcached,Nginx 中提供的模块只反对 get 操作,所以须要内部应用程序当时进行 set 操作。

咱们能够疾速体验一下

# 装置
$ yum -y install memcached
# 查看状态
$ memcached-tool 127.0.0.1:11211  stats

增加配置

location /memcached  {
    set            $memcached_key "$uri?$args";
    memcached_pass 127.0.0.1:11211;
    add_header X-Cache-Satus HIT;
    add_header Content-Type 'text/html; charset=utf-8'; # 强制响应数据格式为 html
}

先用 telnet 模仿一下应用程序 set

$ telent localhost 11211
set /memcached/test? 0 0 5
hello
$ curl localhost/memcached/test?
hello

参数中 set 第一个是 flags,第二个是有效期,第三个内容字节数

Redis

Nginx 提供的 Redis 模块既能够进行 get/set 操作,所以 redis 齐全能够充当 proxy cache 应用

  • Github:https://github.com/openresty/…
  • Openresty:http://openresty.org/cn/

因为 redis2-nginx-module 须要依赖 ngx_set_misc 等依赖,ngx_set_misc 又依赖于其余模块,所以罗唆间接应用 OpenResty 了

编译形式和 Nginx 完全一致

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

装置好后编写一下 service file

$ tee /usr/lib/systemd/system/openresty.service  <<EOF
[Unit]
Description=The nginx HTTP and reverse proxy server
After=syslog.target network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=/usr/local/openresty/nginx/logs/nginx.pid
ExecStartPre=/usr/local/openresty/nginx/sbin/nginx -t
ExecStart=/usr/local/openresty/nginx/sbin/nginx
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF

通过这个模块能够通过 URL 进行一些 Redis 操作。

location = /get {
    default_type  text/plain;
    set_unescape_uri $key $arg_key;  # this requires ngx_set_misc
    redis2_query get $key;
    redis2_pass localhost:6379;
}
location = /set {
    default_type  text/plain;
    set_unescape_uri $key $arg_key;  # this requires ngx_set_misc
    set_unescape_uri $val $arg_val;  # this requires ngx_set_misc
    redis2_query set $key $val;
    redis2_pass localhsot:6379;
}
location = /incr {
    default_type  text/plain;
    set_unescape_uri $key $arg_key;  # this requires ngx_set_misc
    redis2_query incr  $keyl;
    redis2_pass localhsot:6379;
}

较为简单的操作能够通过前面学习的 lua 脚本实现。

Stream 模块

一个四层代理模块,能够代理 TCP 和 UDP。例如 MySQL 多主模式中,能够借助 Nginx 做简略的负载平衡,利用更新上线时也能够做灰度公布。

stream {
    server {
        listen  6666;
        proxy_pass  mysql;
    }
    
    upstream mysql {
        server   192.168.0.2:3306;
        server   192.168.0.3:3306;
    }
}

文档:http://nginx.org/en/docs/stre…

QPS 限度

先介绍一下压缩工具 jmatter

官网:https://jmeter.apache.org/

步骤:

  1. 创立线程组,三个外围参数:

    1. Number of threads:线程数
    2. Range-up period:启动距离
    3. Loop Count:循环次数
  2. 创立 HTTP 申请
  3. 创立后果展现,上面三个视图比拟重要

    1. Summary Results:

      • Label:取样器 / 监听器名称
      • Samples:事务数量
      • Average:均匀一个实现一个事务耗费的工夫(均匀响应工夫)
      • Median:所有响应工夫的两头值,也就是 50% 用户的响应工夫,大略是这个意思
      • Min:最小响应工夫
      • Max:最大响应工夫以上单位都是 ms
      • Std.Dev:偏离量,越小示意越稳固
      • Error %:谬误事务率
      • Throughtput:每秒事务数,即 tps
      • Recieve、Sent、Arg KB/sec:网络吞吐量

Nginx 反对三种限流形式:

  1. 漏桶(切合实际网络环境)

    limit_req_zone   $binary_remote_addr  zone=one:10m rate=1r/s; 
    
    location / {
        limit_req  zone=one burst=5;
        root  html;
    }

    binary_remote_addr 示意以二进制近程地址(只是 IP 地址不蕴含端口)作为限度单位(也能够应用 server_name),zone 为该规定的名称,10m 是在内存保护了客户吨发动申请的速率。rate 是速率。localtion 中的 burst 是同大小。

    一一切都是为了 Nginx 依照 rate 速度解决申请,

    • 当没有设置 burst 时,一旦申请速率超过 rate 就会返回 503
    • burst 示意解决突发流量的能力,能够了解为桶。速度过快的申请会临时寄存在这里,依照 rate 的速度缓缓解决,当桶满了会拒绝请求。
    • nodelay 可能勾销 burst 缓冲,让 burst 刹时解决完所有 i 申请,并回绝所有不合乎速率的申请,直到客户端速率复原为止,

    下面三点能够通过 jmatter 进行测试

  2. 令牌桶(为不同用户进行限速,资源下载软件的 VIP、视频播放先全速再限速)

    令牌桶中保留 n 个令牌,每个令牌可能代表着带宽,不同等级的用户能够获取到不同个数的令牌以失去不同的下载速度。Nginx 官网没有实现该性能。

  3. 带宽限度

    location  /  {
        limit_rate_after  1m;
        limit_rate  1k;
    }

    这个性能在视频播放中有用,先让用户看的难受一点再限速,第一能够让用户充值二是用户可能对视频不感兴趣没必要加载这么多内容,

  4. 并发连贯(服务端形式客户端多线程下载,基于计数器)

    limit_conn_zone   $binary_remote_addr  zone=two:10m;
    
    location / {
        limit_rate   20;
        limit_conn  two  1;
        root  html;
    }

    limit_conn 中的第二个鲸吞是限度的连接数

日志

低价值的日志通过采集到大数据平台后,通过机器学习等算法解决可能取得用户画像、用户行为轨迹等高价值数据,Nginx 相干配置

  • binary_remote_addr:http://nginx.org/en/docs/http…
  • ngx_http_empty_gif_module:http://nginx.org/en/docs/http…

先说 ngx_http_empty_gif_module 模块,拜访 ngx_http_empty_gif_module 会返回一个 1×1 像素的图片,咱们能够通过 img 标签内嵌到主页上,传递用户的行为信息,配置如下

location = /_.gif {empty_gif;}

拜访后能够看到 access.log

$ tail -f  /usr/local/nginx/logs/access.log 

192.168.80.1 - - [01/Jul/2022:10:51:39 +0800] "GET //_.gif HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"

access.log 相干配置

access_log /path/to/log.gz combined gzip flush=5m buffer=32k;

combined 是零碎定义的一个 log_format,flush 是 log_buffer 刷盘机会

log_format combined '$remote_addr - $remote_user [$time_local]'
                    '"$request" $status $body_bytes_sent ''"$http_referer""$http_user_agent"';

如果采集器要求咱们输入 json,能够设置对应的 format

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

咱们还能够缓存一下 log file 的句柄

open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time];

error.log

谬误日志则只能设置日志门路和日志等级

error_log** *file* [*level*];

logrotale:Linux 自带的日志绕接工具。这里简略说一下如何在不影响利用程序运行状况 ia 进行日志宰割

  • mv + create:应用程序关上一个文件后,对文件的读写落实到物理块,即便在此过程中咱们批改了文件名称。所以 logrotale 能够间接重命名日志文件,而后发送信号给应用程序,应用程序本人 reload 配置文件
  • copy + truncate:当应用程序不反对 reload 时,logrotale 能够先将局部内容复制进去,再放大原来的文件

具体:https://wsgzao.github.io/post…

健康检查机制

被动查看机制

文档:http://nginx.org/en/docs/http…

  • proxy_next_upstream:触发下次重试的状况

    • error与服务器建设连贯,向其传递申请或读取响应头时产生谬误;
    • timeout在与服务器建设连贯,向其传递申请或读取响应头时产生超时;
    • invalid_header服务器返回空的或有效的响应;
    • http_500服务器返回代码为 500 的响应;
    • http_502服务器返回代码为 502 的响应;/
    • ttp_503服务器返回代码为 503 的响应;
    • http_504服务器返回代码 504 的响应;
    • http_403服务器返回代码为 403 的响应;
    • http_404服务器返回代码为 404 的响应;
    • http_429服务器返回代码为 429 的响应;

    不理解这个机制,在日常开发 web 服务的时候,就可能会踩坑。

    比方有这么一个场景:一个用于导入数据的 web 页面,上传一个 excel,通过读取、解决 excel,向数据库中插入数据,解决工夫较长(如 1 分钟),且为同步操作(即解决实现后才返回后果)。暂且不管这种形式的好坏,若 nginx 配置的响应等待时间(proxy_read_timeout)为 30 秒,就会触发超时重试,将申请又打到另一台。如果解决中没有思考到反复数据的场景,就会产生数据多次重复插入!(当然,这种场景,内网能够通过机器名拜访该服务器进行操作,就能够绕过 nginx 了,不过外网就没方法了)

  • max_fails:最大失败次数,0 为标记始终可用,不查看衰弱状态
  • fail_timeout:生效工夫,当 fail_timeout 工夫内失败了 max_fails 次,标记服务不可用,fail_timeout 工夫后会再次激活次服务
  • proxy_next_upstream_timeout:重试最大超时工夫
  • proxy_next_upstream_tries:重试次数

被动查看机制:

  • tengine 版本:https://github.com/yaoweibin/

如果要应用开源版本,须要装置 1.20 的 Nginx,并 patch 一下 module 中的补丁

能够尝试将他合并到开源版的 Nginx,它能够被动探测

upstream backend {
    #   server 192.168.44.102 weight=8 down;
    server 172.20.0.11;
    server 172.20.0.11;
    check interval=3000 rise=2 fall=5 timeout=1000 type=http;
    check_http_send "HEAD / HTTP/1.0\r\n\r\n";
    check_http_expect_alive http_2xx http_3xx;
}

location /status {
    check_status;
        access_log off;
}

location / {
    proxy_pass http://backend;
    root   html;
}

拜访 http://192.168.80.154/status 能够看到上游服务的衰弱状况

Linux 中打补丁的办法:https://blog.csdn.net/longint…

  • 制作补丁

    $ diff -Nur src src_new >src.patch
  • 打补丁

    $ patch -p0 <src.patch

Lua 脚本

  • IDE:https://www.eclipse.org/ldt/
  • 语法:https://www.runoob.com/lua/lu…

装置 Openresty 后,能够通过上面这些路径应用 lua 脚本

间接输入

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

援用文件

# 这里关上热编译,即每次申请都会从新编译 lua 脚本,不便测试
lua_code_cache off;

location /lua {
    default_type text/html;
    content_by_lua_file lua/show_headers.lua;
}

lua_code_cache off 示意进行热部署,每次申请都会加载一次 lua

show_headers.lua 内容如下

local url_args = ngx.req.get_headers()

for k, v in pairs(url_args) do

    if type(v) == 'table' then
        ngx.say(k, ":", table.concat(v, ","), "<br/>")
    else
        ngx.say(k, ":", v, "<br/>")
    end

end

还能够在配置文件中引入代码块,但不是很举荐

content_by_lua_block {ngx.say(ngx.var.arg_a)
}
  • lua_ngx_api:https://openresty-reference.r…

应用 Nginx 缓存

  • nginx 原生的多过程共享内存:share dict,拜访须要上锁

    配置文件

    lua_shared_dict shared_data 1m;

    shared_dict.lua

    local shared_data = ngx.shared.shared_data  
    
    local key = "demo"
    local val = shared_data:get(key)  
    local default_val = 0
    
    if not val then  
        shared_data:set(key, default_val)  
        ngx.say("lazy set key", "<br/>")  
        return
    end  
    
    val = shared_data:incr(key, 1)  
    ngx.say("val =", val, "<br/>")
  • lua-lrucache,是 lua 实现的单过程内存

    content_by_lua_block {require("lua/lurcache.lua").go()}

    留神默认的加载门路是 openresty/lualib,能够通过 lua_package_path 设置,特地留神两点:

    1. 应用须要开启 lua_code_cache
    2. 内存为 worker 独享,尽管会产生多份数据但过程独享比拟高效

连贯 Redis

lua-resty-redis:https://github.com/openresty/…

local cjson = require "cjson"
local redis = require "resty.redis"
-- register the module prefix "bf" for RedisBloom
redis.register_module_prefix("bf")

local red = redis:new()

local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
    ngx.say("failed to connect:", err)
    return
end

-- call BF.ADD command with the prefix 'bf'
res, err = red:bf():add("dog", 1)
if not res then
    ngx.say(err)
    return
end
ngx.say("receive:", cjson.encode(res))

-- call BF.EXISTS command
res, err = red:bf():exists("dog")
if not res then
    ngx.say(err)
    return
end
ngx.say("receive:", cjson.encode(res))

连贯 MySQL

lua-resty-mysql:https://github.com/openresty/…

local cjson = require "cjson"
local mysql = require "resty.mysql"

local db = mysql:new()
local ok, err, errcode, sqlstate = db:connect({
    host = "127.0.0.1",
    port = 3306,
    database = "world",
    user = "monty",
    password = "pass"})

if not ok then
    ngx.log(ngx.ERR, "failed to connect:", err, ":", errcode, " ", sqlstate)
    return ngx.exit(500)
end

res, err, errcode, sqlstate = db:query("select 1; select 2; select 3;")
if not res then
    ngx.log(ngx.ERR, "bad result #1:", err, ":", errcode, ":", sqlstate, ".")
    return ngx.exit(500)
end

ngx.say("result #1:", cjson.encode(res))

local i = 2
while err == "again" do
    res, err, errcode, sqlstate = db:read_result()
    if not res then
        ngx.log(ngx.ERR, "bad result #", i, ":", err, ":", errcode, ":", sqlstate, ".")
        return ngx.exit(500)
    end

    ngx.say("result #", i, ":", cjson.encode(res))
    i = i + 1
end

local ok, err = db:set_keepalive(10000, 50)
if not ok then
    ngx.log(ngx.ERR, "failed to set keepalive:", err)
    ngx.exit(500)
end

应用模板引擎

lua-resty-template:https://github.com/bungle/lua…

装置模板时只须要将 lib 复制到 lualib 中即可

local template = require "resty.template"

template.render("include.html", {
    users = {{
        name = "Jane",
        age = 29
    }, {
        name = "John",
        age = 25
    }}
})

模板门路配置

set $template_root /usr/local/openresty/nginx/templates;

在测试阶段咱们能够敞开模板进行缓存,但生产上要将他开启

template.caching(false)

模板如下

include.html

<html>
<body>
<ul>
{% for _, user in ipairs(users) do %}
    {(user.html, user)}
{% end %}
</ul>
</body>
</html>

user.html

<li>User {{name}} is of age {{age}}</li>

更多的模板语法能够在工作中应用到了再关注。

更多简单性能

基于 Lua 脚本 + openresty 的开源我的项目有 Kong、APISX、WAF 等,比方 Kong 预设了很多流量管制、申请批改等 lua 脚本,也反对自定义,lua 脚本能够作为插件进行热插拔。比方咱们的一些鉴权操作(对接公司的 SSO 等)能够应用 lua 高效实现。

  • WAF,软防火墙

    • https://github.com/unixhot/waf
    • https://github.com/loveshell/…
  • API 网关:

    • Kong:https://github.com/apache/api…
    • APISIX:https://github.com/apache/api…
  • 灰度公布:

    • ABTestingGateway:https://github.com/CNSRE/ABTe…

以上就是 Nginx 实战的所有内容,前面会补上实践,敬请期待!

退出移动版