你好,哈布尔!
在本文中,我们将了解 NGINX 如何处理 TCP/UDP 连接:从接受请求到日志记录。
NGINX 中的 TCP/UDP 处理架构
NGINX 是使用事件循环(也称为事件循环)建立在非阻塞 I/O 模型之上的。这允许您同时处理数以万计的连接。在 TCP/UDP 级别,NGINX 使用所谓的流模块,这些模块独立于 HTTP 块工作。TCP/UDP 会话处理的主要阶段如下:
-
接受后
-
预访问
-
访问
-
SSL认证
-
预读
-
内容
-
日志
这些阶段中的每一个都完成自己的任务。
Post-accept:每个连接的 path 的开头
在 NGINX 内核接受新连接(通过非阻塞 accept() 和系统调用(如 epoll))后,Post-accept 阶段立即开始。这是启动模块的地方,如果客户端的 IP 地址来自中间设备(例如,负载均衡器或反向代理),则会更正客户端的 IP 地址。ngx_stream_realip_module
连接是异步接收的,这避免了大量并发连接的延迟,并且 ngx_stream_realip_module 模块本身使用特殊 Headers(例如 X-Real-IP)中的值将原始 IP 替换为真实 IP。
配置示例:
stream {
server {
listen 12345;
# Используем модуль realip для определения реального IP
real_ip_header X-Real-IP;
set_real_ip_from 192.168.1.0/24;
# Продолжаем маршрутизацию трафика
proxy_pass backend;
}
}
即使客户使用 NAT,您也始终可以找到他的真实地址。
预访问:过滤
在 Pre-access 阶段,NGINX 会检查连接的 pre-parameters 。这是触发 type modules 以限制连接数量以及设置变量和配置参数的地方。ngx_stream_limit_conn_module
ngx_stream_set_module
共享内存区域允许您按键 (例如 IP 地址) 跟踪活动连接。您还可以为连接的后续处理指定条件和规则。
配置示例:
stream {
# Определяем shared memory зону для хранения информации о соединениях
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
listen 12345;
# Ограничиваем количество соединений с одного IP до 10
limit_conn addr 10;
# Здесь можно задать дополнительные параметры через ngx_stream_set_module
# Например, установка кастомных переменных для дальнейшей обработки
proxy_pass backend;
}
}
因此,即使在这个阶段,您也可以扔掉坏人并节省资源。
访问:访问控制
Access 阶段是一个检查点,用于决定客户是否有资格访问服务。该模块检查访问规则,如果您使用它,则应用该指令。ngx_stream_access_module
njs
js_access
筛选配置示例:
stream {
server {
listen 12345;
# Разрешаем доступ только из определённой подсети
allow 192.168.1.0/24;
deny all;
proxy_pass backend;
}
}
使用 njs 进行动态控制的示例:
stream {
js_import my_access from js/my_access.js;
server {
listen 12345;
js_access my_access.check;
proxy_pass backend;
}
}
在 js/my_access.js 文件中:
function check(ctx) {
// Если IP не начинается с нужного префикса, отклоняем соединение
if (!ctx.remoteAddress.startsWith("192.168.1.")) {
return ctx.error(403);
}
}
export default { check };
所以你可以想出某种适应需求的逻辑。
SSL认证
如果为 TCP/UDP 连接配置了 TLS/SSL,则会激活 SSL 阶段。该模块建立安全通道,执行 TLS 握手,验证证书并协商加密算法。ngx_stream_ssl_module
SSL 配置示例:
stream {
server {
listen 443 ssl;
# Пути к SSL-сертификату и приватному ключу
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# Настройки протоколов и шифров для обеспечения безопасности
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
proxy_pass backend;
}
}
普雷亚
Preread 阶段是最有趣的阶段之一。在这里,NGINX 将传入流的第一个字节读取到一个特殊的缓冲区中。为什么?以便 Journal 等模块可以在数据完全处理之前对其进行分析。对于 njs 用户,这是 js_preread 指令。ngx_stream_ssl_preread_module
读取并存储前 N 个字节(通常为 1-2 KB)以进行初步分析。
启用 preread 的示例配置:
stream {
server {
listen 443;
# Включаем предварительное чтение для анализа первых байт
ssl_preread on;
proxy_pass backend;
}
}
使用 njs 进行自定义分析的示例:
stream {
js_import my_preread from js/my_preread.js;
server {
listen 443;
js_preread my_preread.analyze;
proxy_pass backend;
}
}
在 js/my_preread.js 文件中:
function analyze(ctx) {
// Если первые байты содержат ключевое слово, задаем переменную для последующей маршрутизации
if (ctx.buffer && ctx.buffer.startsWith("SPECIAL")) {
ctx.variables.special = true;
}
}
export default { analyze };
这样您就可以提前了解流量的本质,甚至在主要处理之前就做出决定。
内容
内容阶段是完成所有数据工作的地方。在这里,NGINX 要么通过 直接将数据传递给后端,要么使用 njs 脚本(js_filter 指令)对其进行修改。proxy_pass
简单代理示例:
stream {
upstream backend {
server 127.0.0.1:8000;
server 127.0.0.1:8001;
}
server {
listen 12345;
proxy_pass backend;
}
}
通过 njs 进行动态过滤的示例:
stream {
js_import my_filter from js/my_filter.js;
upstream backend {
server 127.0.0.1:8000;
server 127.0.0.1:8001;
}
server {
listen 12345;
js_filter my_filter.process;
proxy_pass backend;
}
}
在 js/my_filter.js 文件中:
function process(ctx) {
// Пример: заменяем все вхождения "foo" на "bar" в передаваемом буфере
if (ctx.buffer) {
ctx.buffer = ctx.buffer.replace(/foo/g, "bar");
}
return ctx;
}
export default { process };
日志
最后一个阶段 Log 负责记录服务器的工作结果。该模块记录处理时间、连接状态、客户端 IP 和其他有用信息。此外,您可以通过添加必要的变量来设置自定义格式,并且日志本身可以发送到 ELK 堆栈、Prometheus、Grafana 和其他工具。ngx_stream_log_module
简单日志记录设置的示例:
stream {
server {
listen 12345;
access_log /var/log/nginx/stream_access.log;
error_log /var/log/nginx/stream_error.log;
proxy_pass backend;
}
}
使用 njs 进行自定义日志记录的示例:
stream {
js_import my_logger from js/my_logger.js;
server {
listen 12345;
js_log my_logger.customLog;
proxy_pass backend;
}
}
在 js/my_logger.js 文件中:
function customLog(ctx) {
// Логируем важные параметры: IP клиента, длительность соединения, статус обработки
console.log(`Client IP: ${ctx.remoteAddress}, Duration: ${ctx.duration}ms, Status: ${ctx.status}`);
}
export default { customLog };
作为行业专家提供的实用在线课程的一部分,您可以获得更相关的 IT 基础架构技能。 在目录中,您可以看到所有程序的列表,在日历中,您可以注册公开课程。