Nginx日志规则以及根据日志进行性能问题判断的思路

发布时间 2023-12-28 14:13:24作者: 济南小老虎

Nginx日志规则以及根据日志进行性能问题判断的思路


背景

Nginx是开源方案里面能实现反向代理 负载均衡的首选.
但是有时候性能出问题比较难以分析和定位, 不知道是不是nginx的瓶颈

性能问题的种类其实非常多,核心其实就是等待事件和等待事件. 
回到nginx的主题, 其实本质就是 nginx自己处理和 后端服务器处理时间以及网络延迟等事项

最近看了一些文档,想着总结一下nginx的日志处理便于分析和定位问题原因. 

日志格式

log_format 用来定义日志的格式
access_log 用于指定日志的具体路径. 

这里主要是想一些简单的处理,便于完善, 暂时不考虑安全相关

log_format 的处理

log_format main  '远程地址: $remote_addr 请求url: $request 请求发生时间: [$time_local] 状态: $status 请求体大小: $body_bytes_sent 浏览器信息: $http_user_agent 请求总用时: $request_time 后端服务总耗时: upstream_response_time 代理建立连接用时: $upstream_connect_time 代理发送header的用时:upstream_header_time 客户端浏览器种类: $http_user_agent  后端服务器地址: $upstream_addr 后端服务器状态: $upstream_status '

注意格式可以自己选择定义, 其实不建议这么长, 可能影响性能. 
用中文的目的是便于非专业人士查看,

简单的模板

worker_processes  auto;
events {
    worker_connections  10250;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    gzip            on;
    keepalive_timeout  65;

    log_format main  '远程地址: $remote_addr 请求发生时间: [$time_iso8601] 状态: $status  请求总用时: $request_time 后端服务总耗时: $upstream_response_time 代理建立连接用时: $upstream_connect_time 代理发送header的用时: $upstream_header_time  后端服务器地址: $upstream_addr  链接序列号: $connection 当前链接的请求数: $connection_requests 请求体大小: $body_bytes_sent 后端服务器状态: $upstream_status 请求url: $request ' ;

    access_log /usr/local/nginx/logs/access_log.log  main;

    server {
        listen       80;
        server_name  localhost ;
        location /{
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://127.0.0.1:5200/;
        }
    }
}

请求状态数分析

可以简单查询不同状态的数量. 
cat logs/access_log.log  |awk '{print $6}' |sort |uniq -c |sort -k1hr

包含请求url查看状态等信息
cat logs/access_log.log  |awk '{printf "%-20s%-20s%-20.60s\n",  $6,$26,$27}' |sort |uniq -c |sort -k1hr |head -n 10

查看时间最长请求的url  注意是倒序排列.
cat logs/access_log.log  |awk '{printf "%-20s%-20s%-20s%-20.60s\n", $8,$22,$26,$27}'  |sort -k1h

查看body最大的请求
cat logs/access_log.log  |awk '{printf "%-20s%-20s%-20.60s\n", $22,$26,$27}'  |sort -k1h

几个参数的理解-connection

第一部分: 
链接序列号: $connection 
当前链接的请求数: $connection_requests
我理解这里的链接, 应该是TCP的链接信息. 
不明白 ngnx 还是 http的协议限制. 
好像最多使用六个tcp链接
cat access_log.log |awk '{print $18}'  |sort |uniq -c |sort -k1h
      3 7
      3 9
      5 1
      7 6
     10 4
     12 10
对应另外一个netstat的信息为:
netstat -ano |grep 80 |grep 192.168.1.2
tcp        0      0 10.110.xx.xx:80        192.168.1.2:27481        TIME_WAIT   timewait (35.04/0/0)
tcp        0      0 10.110.xx.xx:80        192.168.1.2:27479        TIME_WAIT   timewait (35.04/0/0)
tcp        0      0 10.110.xx.xx:80        192.168.1.2:27478        TIME_WAIT   timewait (35.04/0/0)
tcp        0      0 10.110.xx.xx:80        192.168.1.2:27482        TIME_WAIT   timewait (35.04/0/0)
tcp        0      0 10.110.xx.xx:80        192.168.1.2:27480        TIME_WAIT   timewait (35.04/0/0)
tcp        0      0 10.110.xx.xx:80        192.168.1.2:27476        TIME_WAIT   timewait (35.04/0/0)

经过一段时间的运行会发现 
链接会超时之后自动 进入timewait的状态
然后每一个链接最多发送 100个请求, 然后就会关闭重新进行三步握手. 

所以理论上一个tcp最多服用100次..
应该有时间进行一下简单处理

几个参数的理解-time

博客园上面的大佬:
https://www.cnblogs.com/AcAc-t/p/nginx_request_time_upstream_respone_time_analysis.html
里面文章讲的非常详细, 这里参考学习一下:

首先详细拆分一下一个完整HTTP请求(非keep alive)生命周期的多个阶段(以下C指代客户端,N指代nginx服务器,S指代上游server):

1.C向N发起TCP三次握手建立连接成功,C开始向N通过TCP发送HTTP请求具体数据(header/body...)
2.N开始接收到C发送的数据到全部接收完成
3.N作为代理向S发起TCP三次握手并建立连接成功,N开始向S发送HTTP数据
4.S开始接收N发送的数据并全部接收完成
5.S业务代码根据业务规则进行处理完成并生成HTTP响应结果
6.S开始将响应结果发送给N
7.N开始接收响应结果并接收header部分完成
8.N接收S返回的全部响应结果完成,准备关闭该连接
9.N开始向C返回全部的HTTP响应结果并发送完成
10.C开始接收N返回的数据并全部接收完成
11.N向C发起四次挥手关闭TCP连接

经过源码追溯最终可以得出request_time、upstream_response_time、upstream_connect_time与upstream_header_time四个指标的关系为:
1. upstream_header_time = upstream_connect_time(阶段3) + N向S发送数据被接收完成时间(阶段4) + S业务代码处理数据返回并被N接收完header部分数据的时间(阶段5~7)
2. upstream_response_time = upstream_header_time + N接收完S除header部分剩余全部数据的时间(阶段8)
3. request_time = N开始接收C全部数据并完成的时间(阶段2) + upstream_response_time + N向C send响应数据并完成(阶段9)

至于一开始对于文档解释request_time 接收第一个字节的、发送完最后一个字节的具体定义,在阅读过程中也有了答案:
HTTP是应用层协议,其建立于传输层的TCP协议之上,而TCP是保证有序和可靠的--
    其每一个有效数据包都必须收到对端的ACK确认才算发送成功,因此站在N的角度看待数据接收与发送完成,可以得出以下结论:
1. 其所谓的接收第一个字节时刻必然是属于C发向N的第一个TCP有效数据包被接收时刻--
    不会包括三次握手纯SYN/ACK包--除非第三个握手包已经带了有效数据。
2. 所谓的发送完最后一个字节时刻,则由于N使用send发送响应给C,指的是调用send函数发送完数据的时刻
    仅代表数据已成功写入协议栈buffer,并不代表N已经接收到了C确认收到所有数据的ack。