变量是 NGINX 配置的一个重要但有时会被忽视的方面。 有大约150 个可用变量,可以增强配置的每个部分。 在这篇博文中,我们讨论了如何使用 NGINX 变量进行应用跟踪和应用性能管理(APM),重点是揭示应用中的性能瓶颈。 这篇文章适用于 NGINX Open Source 和 NGINX Plus。 为了简洁起见,除非两个版本之间存在差异,否则我们将始终引用 NGINX Plus。
在我们的示例应用交付环境中,NGINX Plus 作为我们应用的反向代理。 该应用本身由一个 Web 前端组成,其背后有多个微服务。
NGINX Plus R10(和 NGINX 1.11.0)引入了$request_id
变量,它是一个随机生成的 32 个十六进制字符串,在每个 HTTP 请求到达时自动分配给它(例如, 444535f9378a3dfa1b8604bc9e05a303
)。 这种看似简单的机制却解锁了一种强大的跟踪和故障排除工具。 通过配置 NGINX Plus 和所有后端服务来传递$request_id
值,您可以端到端跟踪每个请求。 此示例配置适用于我们的前端 NGINX Plus 服务器。
上游 app_server { server 10.0.0.1:80;
}
server {
listen 80;
add_header X-Request-ID $request_id; # 返回客户端
location / {
proxy_pass http://app_server;
proxy_set_header X-Request-ID $request_id; # 传递给应用服务器
}
}
要配置 NGINX Plus 进行请求跟踪,我们首先在上游
块中定义应用服务器的网络位置。 为了简单起见,我们在这里仅显示单个应用服务器
,但为了实现高可用性和负载均衡目的,我们通常会使用多个应用程序服务器。
应用服务器
块定义 NGINX Plus 如何处理传入的 HTTP 请求。 listen
指令告诉 NGINX Plus 监听端口 80 - HTTP 流量的默认端口,但生产配置通常使用 SSL/TLS 来保护传输中的数据。
add_header
指令将$request_id
值作为响应中的自定义标头发送回客户端。 这对于测试以及生成自己的日志的客户端应用(例如移动应用程序)非常有用,这样客户端错误就可以与服务器日志精确匹配。
最后,位置
块适用于整个应用空间( / ),而proxy_pass
指令只是将所有请求代理到应用服务器。 proxy_set_header
指令通过添加传递给应用的HTTP 标头来修改代理请求。 在这种情况下,我们创建一个名为X-Request-ID
的新标题,并将$request_id
变量的值分配给它。 因此我们的应用接收由 NGINX Plus 生成的请求 ID。
$request_id
我们进行应用跟踪的目标是作为应用性能管理的一部分来识别请求处理生命周期中的性能瓶颈。 我们通过在处理过程中记录重要事件来做到这一点,以便我们稍后可以分析它们以发现意外或不合理的延迟。
我们首先配置前端 NGINX Plus 服务器,将$request_id
包含在自定义日志格式trace
中,用于access_trace.log文件。
log_format trace '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" "$http_user_agent" ' '"$http_x_forwarded_for" $request_id '; upper app_server { server 10.0.0.1; } server { listen 80; add_header X-Request-ID $request_id; # 返回客户端位置 / { proxy_pass http://app_server; proxy_set_header X-Request-ID $request_id; # 传递给应用服务器access_log /var/log/nginx/access_trace.log trace ; # 记录 $request_id } }
将请求 ID 传递给您的应用固然很好,但实际上并不能帮助应用跟踪,除非应用对其执行某些操作。 在此示例中,我们有一个由uWSGI管理的 Python应用。让我们修改应用入口点以将请求 ID 作为日志变量获取。
从uwsgi导入set_logvar def main (environ,start_response): set_logvar ( 'requestid' ,environ[ 'X_REQUEST_ID' ])
然后我们可以修改 uWSGI 配置以将请求 ID 包含在标准日志文件中。
日志格式 = %(addr) - %(user) [%(ltime)] "%(method) %(uri) %(proto)" %(status) %(size) "%(referer)" "%(uagent)" %(requestid)
有了这个配置,我们现在可以生成可以链接到跨多个系统的单个请求的日志文件。
来自 NGINX 的日志条目:
172.17.0.1 - - [02/Aug/2016:14:26:50 +0000] "GET / HTTP/1.1" 200 90 "-" "-" "-" 5f222ae5938482c32a822dbf15e19f0f
来自应用的日志条目:
192.168.91.1 - - [02/8月/2016:14:26:50 +0000] "GET / HTTP/1.0" 200 123 "-" "-" 5f222ae5938482c32a822dbf15e19f0f
通过将事务与请求 ID 字段进行匹配, Splunk和Kibana等工具允许我们识别应用服务器中的性能瓶颈。 例如,我们可以搜索花费两秒以上才能完成的请求。 然而,常规时间戳中一秒的默认时间分辨率不足以满足大多数实际分析的需求。
为了准确地测量端到端请求,我们需要毫秒级精度的时间戳。 通过在日志条目中包含$msec
变量,我们可以获得每个条目的时间戳的毫秒分辨率。 在我们的应用日志中添加毫秒时间戳使我们能够查找花费超过 200 毫秒而不是 2 秒才能完成的请求。
但即便如此,我们也无法获得完整的情况,因为 NGINX Plus 仅在处理每个请求结束时写入$msec
时间戳。 幸运的是,还有其他几个具有毫秒精度的 NGINX Plus 计时变量,可以让我们更深入地了解处理本身:
$request_time
– 完整请求时间,从 NGINX Plus 从客户端读取第一个字节开始,到 NGINX Plus 发送响应主体的最后一个字节结束$upstream_connect_time
– 与上游服务器建立连接所花费的时间$upstream_header_time
– 与上游服务器建立连接和接收响应头的第一个字节之间的时间$upstream_response_time
– 与上游服务器建立连接和接收响应主体的最后一个字节之间的时间有关这些时间变量的详细信息,请参阅使用 NGINX 日志记录进行application性能监控。
让我们扩展我们的log_format
指令以将所有这些高精度计时变量包含在我们的跟踪
日志格式中。
log_format 跟踪 '$remote_addr - $remote_user [$time_local] "$request" $status ' '$body_bytes_sent "$http_referer" "$http_user_agent" ' '"$http_x_forwarded_for" $request_id $msec $request_time ' '$upstream_connect_time $upstream_header_time $upstream_response_time' ;
使用我们首选的日志分析工具,我们可以提取变量值并执行以下计算,以查看 NGINX Plus 在连接到应用服务器之前处理请求需要多长时间:
NGINX Plus 处理时间 = $request_time - $upstream_connect_time - $upstream_response_time
我们还可以搜索$upstream_response_time
的最高值,看看它们是否与特定的 URI 或上游服务器相关联。 然后可以根据具有相同请求 ID 的应用日志条目进一步检查这些内容。
利用新的$request_id
变量和部分或全部毫秒精度变量可以深入了解应用中的性能瓶颈,从而无需借助重量级代理和插件即可改善应用性能管理。
亲自尝试使用 NGINX Plus 进行应用跟踪:立即开始30 天免费试用或联系我们讨论您的用例。
“这篇博文可能引用了不再可用和/或不再支持的产品。 有关 F5 NGINX 产品和解决方案的最新信息,请探索我们的NGINX 产品系列。 NGINX 现在是 F5 的一部分。 所有之前的 NGINX.com 链接都将重定向至 F5.com 上的类似 NGINX 内容。”