nginx(三十)变量终谈

一    域名端口相关的变量

nginx变量的实现原理

最终的目的:

  1)变量的含义'清晰易懂',日志中'使用'这些变量,能提取'哪些'信息?

  2) 帮助我们'如何'快速定位信息?

之前写的辅助理解

①    $host

备注:$host是'ngx_http_core_module模块'内部的一个'变量'

++++++++++++"$host 的值解读"++++++++++++

优先级1: 如果'请求行 [Request Line]' 中有 'host name' -->"不常见"

  1) 请求行形式 'GET http://example.com/ HTTP/1.1' 

  2) 其中'example.com'就是'$host'值

  3) 可以用'telnet'模拟

优先级2: 

  1) 如果'请求头 [Request Header]' 内有'Host 字段'的值

  2) 并且'$Host与server_name'匹配

  3) 则'$host=Host请求头值'

优先级3: 

  1) 如果'Host头与server_name'都不匹配(包含'空值'场景)

  2) 此时使用'默认'的虚拟主机,该虚拟主机的'$host'值就是该请求匹配到的'server_name'

重点:$host 总是'小写',且'值'不带'端口'号

参考链接   关于nginx中的host变量

curl带一个'空'的Host请求头,会覆盖'url中主机头'的

 ②    $http_host  请求头中Host头

$http_host 的值:其实就是'请求头'中 Host 字段的值

备注: 

  1) HTTP/1.0'不带'host

  2) 'HTTP/1.1' 的所有请求报文中'必须包含'一个Host头字段,且只能'设置'一个

 

1)记录常见的请求头信息

关注:

  1) $http_host、$http_user_agent"场景:反爬虫"

  2) $http_referer"场景:盗链"、$http_cookie

测试方式:'curl'、'浏览器'、postman测试

 

 2)客户端自定义请求头不透传

参考链接

说明:下面是'对比'访问的效果

nginx策略:

  1) 默认是对'请求和响应header'的key'不支持下划线("忽略"该头)',包括'cookie头中的key'

  2) 需要在http|server模块'添加'如下配置来'支持':

     underscores_in_headers on;

解决1:header属性名'删除'下划线,或者连接符'改'为中划线"-"

解决2:修改nginx的配置文件 nginx.conf:underscores_in_headers on; '(默认值是off)'

++++++++++++++++"分割线"++++++++++++++++

关键::nginx会拦截http请求中自定义的'(有下划线)'的请求头,'不会'透传

思考:制定更'规范'的header名称,尽量'避免'使用带下划线的内容

细节点:
  
  1) 如果自定义的请求头有'下划线',但是需要透传给后端;

  2) 则underscores_in_headers on['默认是off'];

 ③    $server_name  重点

说明:是最终请求'匹配到'的'server_name',请求会被'route'到该server块对应的'location'

强调:

  1) 建议'日志'中增加这个'变量'

  2) 一目了然的看到实际'哪个server_name'处理客户请求,与'用户预期'做对比

④  $proxy_host

+++++++++$server_port 跟$proxy_port 的'区别'+++++++++

1) 前者为客户端访问 nginx 使用的端口,也即'nginx'监听的端口,如 80

1) 后者为 '上游服务器的端口',通常会'隐藏'起来,如 8080

 ⑤  upstream_addr

 ⑥    remote_addr

1: 表面含义是'客户端的'真实ip -->如果'中间层'没有代理的话,获取的是'客户端的真实ip'

2: 实际场景

 1) 经过反向代理后,由于在客户端和'web服务器(nginx、apache)之类'之间增加其它'中间代理层'

 2) 因此web服务器'无法直接'拿到客户端的ip

 3) 通过$remote_addr变量拿到的将是'上层'反向代理服务器的ip地址

3: $remote_addr 是与nginx'建立连接'的'上游客户端'的值

++++++++++++'了解即可'++++++++++++

$remote_port	'客户端'的端口

$server_name	服务器名称

$server_port	服务器'nginx'的端口号

⑦    proxy_add_x_forwarded_for

含义: nginx把'$remote_addr'变量值'追加'到'XFF'头中形成'最终'的字符串

其它参考

+++++++++++"常见场景"+++++++++++

1.  一般'最原始'的客户端请求不带'XFF'请求头

2.  此时'请求'经过第一层'代理[nginx]',如果日志中使用'$proxy_add_x_forwarded_for',则记录的是'$remote_addr'的值,真实客户端的ip

3.  此时第一层'proxy'设置如下

    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    含义:把'当前proxy代理的ip'追加到'XFF头'中

    作用:其实就像是'链路反追踪',从客户的'真实ip'为起点,穿过'多层级'的proxy,最终到达web 服务

器,'都会'记录下来

    特点:每一层代理会将'连接它的客户端'IP追加在X-Forwarded-For'右边'

4.  此时'请求'经过第二层'代理[nginx]',如果日志中使用'$proxy_add_x_forwarded_for',则记录的是'client_ip, proxy1_ip'的值

5.  依次'类推' ...

思考:变量的'数值'是'-',表示什么含义?

nginx获取客户端的真实ip

 二    ngx_http_upstream_module模块相关变量

参考链接

明确一点:是'nginx侧'和'后端服务器'之间'相关'数据

分析内容:传输'大小'、'字节'、'状态'

①    $upstream_addr

能'反映'的信息:nginx是否'转发请求'了["是否有值-->nginx是否拦截,没有转发"],nginx转发了'几次'["值是几个"]?

备注:如果返回非'2xx和3xx'-->返回'4xx'或'5xx',除了查看'nginx'日志['access.log'和error.log]、还要查看'upstream日志'

细节:'4xx'一般是'后端的问题'、'502'一般关注nginx到upstream的连接、'504'一般关注'后端'的日志

常见现象:如果'值'是'-',一般可以反映nginx'没有'转发请求到'后端',nginx'拦截'了'客户端'请求

②   相关时间轴

打印日志是在'最后'一个步骤:也就是nginx完全'响应完毕给client'后,才进行'日志'记录

++++++++++++请求的'整套'时间线++++++++++++

1、客户端------request---->nginx

2、nginx------connect---->服务端

3、服务端------connect success---->nginx

4、nginx------send data----->服务端

5、服务端------response begin----->nginx

6、服务端------response end ------>nginx

7、nginx------response----->客户端

8、nginx'记录'日志

++++++++++++捕获的'重要'信息++++++++++++

上游服务'运行(程序处理)'时间=$upstream_header_time - $upstream_connect_time

数据'从nginx'响应到'client'耗时 = $request_time - $upstream_response_time

$request_time 包含'所有'内容的时间,包含'数据返回'时间+'日志打印'时间

目的:可以通过以上各种时间,来计算'每个链路的耗时',日志记录,这个非常重要,便于后续'链路分析'

 图片参考来源

③    $upstream_connect_time

                +++++++++++'理解'+++++++++++

1) nginx与'后端'是'http'的话,则是记录'tcp'连接的时间,也即双方从'closed'进入'established'时间

2) nginx与'后端'是'https'的话,则在前面'tcp建立连接'的基础上+'SSL握手'时间

++++++++++'关联的参数'++++++++++

语法:proxy_connect_timeout 60s;

nginx与'后端连接'的'超时'时间,单位为'秒',默认为60秒

备注:我们在nginx错误日志里面看到的'(110: Connection timed out)',就是指nginx与后端连接已经'超时'

④    $upstream_response_time

理解: 后端'upstream上游'发数据到'nginx完全接收响应'所'花费'的时间

场景: 来分析'后端程序接口响应'的时候,需要在nginx的'log_format'中添加'$upstream_response_time'字段

补充: 如果数值'比较大',考虑'网络通信质量'和'发送数据量'问题
 

 ⑤   $upstream_header_time

含义:接收'后端返回数据中响应头'的时间

 ⑥    $upstream_queue_time

++++++++++'队列(queue)理解'++++++++++

1. 如果在'处理请求'时'无法'立即选择upstream服务器,则该请求将被'放入队列'中

2. 如果队列'已满'或者在'timeout参数指定的时间段'内无法选择'将请求传递'给后端的服务器,则会将'502(错误网关)错误'返回给客户端

3. nginx的listen 指令有'参数backlog' 用来指定'队列大小',默认的值为'511'

参考链接

 ⑦   性能调优探讨

重要参考

"场景1":nginx日志出现大量'超时'报警,这个时候发现$upstream_header_time'正常',但是$request_time、$upstream_response_time很大

分析:根据上面的'示意图',这个时候便反映出是'上游程序执行较慢'、或'发送数据量大',需要排查'执行程序'的相关'慢'日志

+++++++++++++'分割线'+++++++++++++

"场景2":同样是ngxin日志出现大量'超时'报警,这个时候发现'$request_time很大',但是$upstream_response_time'正常'

分析:$upstream_response_time正常,说明'程序执行完毕且正常返回',那么这个时候需要验证是'数据返回过慢[nginx到client侧]'、还是'日志打印'出现了阻塞.

可能1:数据返回慢可以通过'抓包'分析,通常来说是'用户网络原因'引起的;

可能2:日志'打印'出现阻塞,可能是'机器io(写磁盘)'出现了问题,这个一般很容易发现;

可能3:还有可能是'nginx配置'了相关参数,导致了'延迟关闭',这里只要根据问题现象一步一步排查即可.

可能4:也可能返回给客户端是https,大数据'加解密'耗时,同时参考'error.log'日志

+++++++++++++'分割线'+++++++++++++

"场景3": $upstream_connect_time很大,可能是'nginx到后端网络通信'出现了问题,一般会出现'502|504'

+++++++++++++'分割线'+++++++++++++

"场景4":$upstream_header_time很小,但是$upstream_response_time很大,可能是'数据回写(暂时缓存在nginx侧)'nginx出现了问题.

小结:不难看出,通过这些变量,便可以'快速定位'到'问题环节',而不至于'毫无头绪'的到处排查,可以说是'事半功倍'
 
+++++++++++++'request_time与upstream_response_time比较'+++++++++++++
 
[1]、一般'request_time'比upstream_response_time'大'

如果'用户端'网络状况'较差'或者'传递数据本身较大',再考虑到 当使用 POST 方式传参时 nginx 会先把 request body '缓存'起来,而这些耗时都会'累积'到 '$request_time' 头上去

 ⑧    $upstream_bytes_sent

 ⑨    $upstream_bytes_received

三    nginx核心模块提供的变量

ngx_http_log_module模块

参考链接

nginx调试变量模块

①  args & arg_name

$args:这个变量在'读取'时'返回当前请求行'的 URL 参数串,即请求 URL 中'问号(?)'后面的部分,如果有的话

案例: https:/www.wzj.com/a.html?name=wzj&age=18 

则:$args这个变量等于请求行中'一般是GET请求'的参数,'也即'name=wzj&age=18,'专业点'叫作'查询字符串'

细节点:curl 发送请求'带参数',url需要加'单引号'或"双引号"

  

②  connection相关连的三个变量

+++++++++++++"不常用,了解即可"+++++++++++++

如果'关闭'HTTP keep-alive(长连接)以'禁止'TCP连接重用,$request_time将始终'等于'$connection_time

$connection_time是http请求使用的'TCP连接'的活动时间

HTTP和HTTP/2允许'单个TCP连接'发送和接收'多个'HTTP请求/响应,所以$connection_time应该总是'大于'$request_time;

③  uri相关

+++++++++++ "$uri和$request_uri的区别"+++++++++++

1) request_uri是'浏览器发'起的'不做'任何修改的'原生URI':'不包括'协议及主机名,包含'查询'参数(中文也会原样输出,而不是编码后的),不包含'锚点'信息

2) uri这个变量指当前的请求URI,'不包括任何参数',这个变量反映任何'内部重定向'或index模块所做的修改

location嵌套

④  request相关

+++++++++++"统计客户端发过来的request信息"+++++++++++

1)$request

2)$request_method

3)$request_body

限制:只有在'location'中用到'proxy_pass、fastcgi_pass、 uwsgi_pass或者scgi_pass'指令时,request body才会被'读取到内存缓冲区'中,$request_body变量才'有值'

通俗'理解':request_body在'未运行'上面所说的proxy_pass等指令配置时,'默认'会被赋值为'空'

备注:直接在'log_format中添加'$request_body,经常会得到'空值'

+++++++++++++"请求方式"+++++++++++++

[1]. 对于'GET'请求,request_body始终是"空"

[2]. 对于'POST'请求,request_body是'参数'信息

+++++++++request_body的'获取'有两种方式+++++++++

1) 使用nginx ngx_http_core模块的$request_body

2) openresty中使用'lua脚本'

参考链接

 4)$request_length

 

含义:nginx统计'cleint'发送'请求数据'长度

注意:由于$request_length是请求解析过程中'不断累加'的,如果解析请求时出现'异常'或'提前完成',则$request_length只是'已经累加部分'的长度,并'不是nginx从客户端收到的完整请求'的总字节数(包括请求行、请求头、请求体)

例如:向nginx的'静态文件'的URL POST数据,则'POST的数据(即请求体)不会计算'在内。

5)案例讲解 

说明:'浏览器'和'curl命令'测试'request_length'差异体现在'客户端会自带一些信息',所以'浏览器'发送请求信息会'多一点'

6)获取$request_body

POST 请求携带请求体 $request_body为什么是空 

思考:为什么会出现\x22这种格式,如何增加可读性?

7)补充$request_body_file  了解

⑤  $sent_http_name

目的:'临时'调试,看后端响应头到底'透传'过来没有,是'被nginx拦截'了还是'就没发送'过来

常用:$sent_http_content_type --> nginx记录'后端返回消息主体'的编码方式

content_length content_type

$content_length

$content_length:客户端发送"Content-Length"请求头的值,'等同于'$http_content_length

含义:'nginx从客户端收到'的请求头中Content-Length字段的值,'不是nginx返回'给客户端响应中的Content-Length字段

HTTP协议中的Content-Length  Content-Type详解  nginx的媒体类型

⑦    $bytes_sent   $body_bytes_sent

$bytes_sent含义:'真正'发送给客户端的'全部的字节数'

场景:对比'client接收数据'和'nginx接收后端数据'是否一致,client下载'中断'场景

++++++++++++"分割线"++++++++++++
​
$body_bytes_sent: nginx返回给客户端的'响应体'的字节数

 ⑧  $request_time

备注:$request_time包括'一次完整HTTP请求'

具体:nginx'接收'客户端请求数据的时间、后端程序'响应'给nginx的时间、nginx'发送'响应数据给客户端的时间(包含写日志的时间),看上面的'拓扑图'

⑨  日志

msec:'1970 年 1 月 1 日'到如今的时间,单位为秒,小数点后精确到'毫秒'

time_local:以'本地时间标准'输出的当前时间,例如 14/Nov/2019:15:55:37 +0800

time_iso8601:使用 'ISO8601 标准输出'的当前时间,例如 2019-11-14T15:55:37+08:00

⑩  其它

应用场景:因此在 limit_req 和 limit_conn 中一般能够'用做 key' 

 

nginx_version:Nginx '版本号'

pid:所属 'worker 进程'的进程 pid

反映:TCP '内核层'参数

应用场景:利用'变量'可以map、可以set'自定义'、可以if'逻辑'判断

⑪   $request_id

说明: 之前'没有'关注过这个变量

特点: 整个'代理'过程中,一个请求'对应'一个唯一id

细节: 在发生'内部跳转'的时候可能会'改变',建议使用'uuid',或者'客户端'传递

++++++++++++++++  "生成唯一id"  ++++++++++++++++ 

需求:在微服务架构中,多个微应用'相互'调用,'系统日志排错'显得尤为重要

通过$request_id 可是实现 '客户端->网关服务器->微服务集群A->>微服务集群B ... '实现日志串联

目的:通过trace_id回显,显示'跟踪'每次调用路由,便于'联调'分析

后续:如果日志服务器接'EFK',可通过'trace_id'快速实现单次请求,各微服务间路由日志'复盘'

  1、proxy_set_header trace_id  $request_id;

  2、add_header trace_id $request_id;

  3、同时在nginx.conf的'log_format'中加入'$request_id'变量

++++++++++++++++  'openresty实现'  ++++++++++++++++

通过'读取'文件 "/proc/sys/kernel/random/uuid"

local uuid = io.open("/proc/sys/kernel/random/uuid", "r"):read()

$request_id在微服务架构中的应用   openresty实现uuid

通过读取文件  /proc/sys/kernel/random/uuid 来生成一个uuid

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值