第二章【NGINX 开源功能】—— 四层反向代理

本节目录

处理 TCP/UDP 的七个阶段

nginx 自 1.9.0 起引入了 stream 模块(ngx_stream_core_module),用于实现 TCP/UDP 层的反向代理(四层反向代理),与 HTTP 模块不同,stream 模块并不解析应用层,而是直接转发原始字节流。


nginx 四层反向代理 7 个阶段的结构体

Post-Accept

此阶段(NGX_STREAM_POST_ACCEPT_PHASE)是 nginx 四层反向代理处理引擎中的第一个阶段。当 nginx 的监听进程成功接收到一个新的 TCP 连接或 UDP 数据包,并初步创建了 ngx_stream_session_t 会话结构后(对于 TCP nginx在 accept() 之后创建绘画结构,对于 UDP nginx 会基于客户端五元组虚拟创建会话结构),程序立将即进入此阶段。

此阶段的核心任务就是,在握手之后、正式决策之前,对连接进行最基础的元数据预处理(ip 和 端口等),它的存在也主要是为了解决一个矛盾 ------ 拿到某些必须支业务处理之前的信息。(典型场景 proxy protocol)

在标准 nginx stream 的模块中,挂载在此阶段的常用指令非常少,常用到的就俩(set_real_ip_fromproxy_protocol)。所以如果你想要获得更多指令,就需要使用第三方写好的模块或者自己定制化开发。下面就是官方的 ngx_stream_realip_module 模块的注册案例:

将 realip 模块注册到此阶段

如果你想在此阶段开发三方模块,必须遵守如下规矩:

  • 绝对不能执行任何会导致进程阻塞的操作(如同步磁盘 I/O 、数据库查询),因为这会卡住整个 nginx 事件循环;
  • 此阶段数据尚未就绪,preread 缓冲区基本是空的,所以你无法在此阶段读取业务数据(如 header 头或 mysql 登录包等);
  • 三方模块通常返回 NGX_DECLINED,以便让后续模块(如 realip)继续运行。除非检测到致命错误(返回 NGX_ERROR)或者你明确此模块执行后中止当前 handler 进入下一阶段(返回 NGX_OK)。

Pre-Access

此阶段(NGX_STREAM_PREACCESS_PHASE)是 nginx 四层反向代理处理引擎中的第二个阶段。

其主要任务是进行访问控制前的初步检查,由于它处于 Access 阶段之前,所以通常用于处理那些不需要读取应用层数据、仅根据连接本身属性就能决定的限制逻辑。

在标准版 nginx stream 中,可用于此阶段的指令也几乎没有,常用的就 limit_conn 它一个,用于限制连接数量,当超过规定数量 nginx 会关闭连接。如果需要开发第三方模块请参照 nginx/src/stream/ngx_stream_limit_conn_module.c 这个内置模块,将你的模块追加到 &cmcf->phases[NGX_STREAM_PREACCESS_PHASE].handlers[1](#1) 这个动态数组中即可。

Access

此阶段(NGX_STREAM_ACCESS_PHASE)是紧随 pre-access 之后的第三个阶段,如果说 pre-access 是初步安检(如限制并发连接数),那么 Access 阶段就是正式准入审查。

其主要任务是根据预设的规则(ip 黑白名单、认证状态等)来决定当前连接是否允许继续向后转发到上游服务器。

在 nginx stream 模块中,此阶段主要由 ngx_stream_access_module 负责,通过指令 allowdeny 来允许或拒绝特定 ip 或网段访问。

如果你要开发一个复杂的准入控制模块(对接 redis 校验 token、动态防火墙等),则此阶段是最佳落脚点。强烈建议参考 src/stream/ngx_stream_access_module.c 模块的实现。

如果你想在此阶段开发三方模块,必须遵守如下规矩:

  • 此阶段具有局限性,你拿不到 HTTP 的内容,只能通过 s->connection->sockaddr 拿到客户端的 ip 和 端口号;
  • 此阶段如果要拒绝访问,可以在此阶段设置标志变量,并在 content 阶段由 ngx_stream_return_module 统一返回提示信息(ngx_stream_access_module 默认打印一条错误日志后就会直接关闭 socket),这样对用户相对友好些;
  • 此阶段为每个连接的必经之路,还是老生常谈的问题,务必使用非阻塞的方式来实现它。

SSL

此阶段(NGX_STREAM_SSL_PHASE)是紧随 access 之后的第四个阶段,也是一个极其特殊的阶段,它的核心任务只是完成 TLS/SSL 握手即可。在四层代理中,此阶段要处理加密流量的解密(如果 nginx 作为 SSL 的终点)或简单的属性提取。

注意:

  • 如果 nginx 没有配置证书,它通常是不会进入此阶段的,而是直接将加密包透传给后端;
  • 此阶段是四层代理中计算量最大的,非对称加密握手会消耗大量 CPU 资源,所以建议在全局配置中开启 ssl_session_cache 以复用会话,减少重复握手,提高性能。

与之前的 access 阶段不同,很少人会在 NGX_STREAM_SSL_PHASE 上注册自定义 handler 来实现业务,这其中涉及到了 openssl 库和SSL 核心处理逻辑等。如果非要在此阶段挂 handler,请挂载到 SSL_set_verify 的回调函数上,或者使用 ngx_stream_ssl_conf_t 提供的钩子,不要直接在 cmcf->phase 数组里塞函数[2](#2)

如果你想在此阶段开发三方模块,必须遵守如下规矩:

  • 此阶段完成之前,你无法读取到加密报文中的任何业务数据,如果你需要根据原始报文内容来决定是否开启 SSL,你需要去下一阶段利用变量判断;
  • 一旦进入 SSL 阶段并握手成功,大量的 $ssl_* 变量(如 $ssl_protocol, $ssl_cipher, $ssl_client_s_dn)才会变得可用;
  • 对于高性能场景,nginx 支持异步 openssl 操作。如果你的模块涉及外部硬件加速器,需要特别处理 NGX_AGAIN 返回值,确保握手挂起时不会阻塞 Worker。

Preread

此阶段(NGX_STREAM_PREREAD_PHASE)是紧随 ssl 之后的第五个阶段,也是最核心、最具有技术含量的一个阶段。在四层代理中,nginx 默认是不会读取应用层数据的,它只是简单地把包从左手倒右手,但此阶段是允许 nginx 在不真正消耗数据的情况下,偷摸的看一眼客户端发送的前几个字节。

此阶段的核心意义在于判断流量是 HTTP、数据库还是 SSH 并进行只能路由转发,它读取的数据会保留在缓冲区中,当连接发往后端时,这些原始数据依然会完整的发送。

在 nginx stream 模块中,此阶段主要是由 ngx_stream_ssl_preread_module 等模块支持。其指令 ssl_preread 可以实现根据域名转发 TCP 流量,指令 preread_buffer_size 可以设置预读缓冲区的大小用于分析结果,指令 preread_timeout 可以设置预读数据到达的超时时间,当客户端只建立连接而不发送数据时,避免 nginx 死等着。

作为开发者来说,这里将是重灾区,推荐参考 src/stream/ngx_stream_ssl_preread_module.c 模块来进行开发。

如果你想在此阶段开发三方模块,必须遵守如下规矩:

  • 想要获取 peek 数据,请通过 c->recv(需谨慎,避免多读)或检查 s->preread_buf(优先)来获取;
  • 因为 TCP 是流式数据,所以你必须要遵循状态机机制,正确处理 handler 的 NGX_DONENGX_AGAINNGX_DECLINED
  • 对于缓冲区指针的操作,你只能移动指针或读取内容,坚决不能把数据从缓冲区中删除,否则后端收到的包将是不完整的;
  • 预读会增加首包延迟 TTFB(nginx 必须等缓冲区填满或匹配到特征码才往下走),所以建议将 preread_buffer_size 设置得尽量小且精准;
  • 注意前一阶段是否存在 SSL 解密,如果存在则本阶段读到的是明文,反之是密文。

Coutent

此阶段(NGX_STREAM_CONTENT_PHASE)是紧随 preread 之后的第六阶段,也是 nginx 四层代理的终点站,其唯一任务就是产生响应或将流量转发到上游服务器。 在四层代理中,这通常意味着建立与后端服务器的 TCP/UDP 连接,并在客户端和后端之间开启双向数据透明传输。

此阶段具有独占性,在一个 server 块中只能有一个模块负责此阶段,一旦某个模块接管了内容处理,后续的 content 处理程序将不会被触发。此外,此阶段不止负责数据的发送,还管理着整个连接的生命周期。

在 nginx stream 模块中,此阶段主要是由 ngx_stream_proxy_modulengx_stream_return_module 两模块支持,前一个负责将流量转发到后端服务器,后一个负责维护状态或非法访问的友好提示(向客户端发送指定字符串并关闭连接)。

如果你想在此阶段开发三方模块,有下列注意事项:

  • 与上述的其他阶段不同,此阶段通常是通过配置指令直接设置 ngx_stream_core_srv_conf_t 中的 handler 指针,让你的模块来接管内容处理,而不是推入全局的 phase 数组;(参见下图)
  • 如果你实现的 handler 只是想发点数据就行了,那么可以参见 nginx/src/stream/ngx_stream_return_module.c 模块;
  • 如果你实现的 handler 还想要做代理,那么你需要自行调用 ngx_stream_proxy_handler 和管理上游连接;
  • 作为内容的产生者,你必须确保在连接结束时释放所有分配的资源,不过一般不需要自己管理,而是通过内存池 s->connection->pool 自动管理;
  • 你自定义的 handler 一旦被调用,就必须负责整个会话的生命周期,且禁止返回 NGX_DECLINED,否则会导致未定义的行为。


return 模块的单点挂载

Log

此阶段(NGX_STREAM_LOG_PHASE)是最后一个阶段,当上一阶段处理完成、连接准备关闭或已经关闭的时候,nginx 会进入此阶段记录这次会话的所有相关信息。

此阶段是事后审计的阶段,无论连接是正常结束、被中途拒绝(access 直接跳过来的),还是因为超时断开,nginx 都会调用该阶段的处理器。

如果你需要将日志实时推送到 kafka、发送到特定的监控系统或者进行自定义的流量审计,你需要在此阶段注册 handler。可以参考 src/stream/ngx_stream_log_module.c 模块来写。

Nginx Stream 如何串联七个阶段

在 nginx stream 源码中,串联这七个阶段的核心逻辑被称为阶段引擎,它并不是靠简单的函数嵌套,而是通过一个连续的函数数组(阶段数组)和一个索引指针来回拨动状态机实现的。下面是一个连接的全流程追踪:(基于 nginx-1.26.x 及以后的最新源码)

  1. 打开工厂大门,连接从 event 核心进入 stream 模块的第一个函数,需要分配 ngx_stream_session_t 会话结构,先判断连接是否是 SSL,以决定是否进入 NGX_STREAM_SSL_PHASE;(本次假设不是 SSL,不进入 SSL 阶段)

收到货物,送入工厂准备启动阶段引擎处理它

  1. 阶段引擎启动,这是阶段引擎运行起点,调用 ngx_stream_core_run_phases(s) 正式让连接进入流水线;

引擎启动函数:拉闸开电,准备干活

  1. 阶段驱动引擎,这是整个流水线的核心,它根据 s->phase_handler 查找对应阶段的 checker,并循环调用每个阶段 checker 函数。只有 checker 能决定是停下来等事件(返回 NGX_OK),还是继续走下一个(索引自增并继续循环);

引擎驱动函数:状态机逐步推进,去找牛马们(handler)来处理流水线上的货物

  1. 阶段 python,ngx_stream_core_generic_phase() 函数就像胶水一样,负责调用用户注册的函数。如果你的 handler 返回 NGX_OK,它会在当前阶段完成后直接进入下一阶段,忽略此阶段后续 handler;如果返回 NGX_DECLINED,它会放弃处理当前 handler,并自增 s->phase_handler 在循环中立即执行下一模块;如果返回 NGX_AGAIN,它会等待当前 handller 被事件触发,此时阶段引擎暂时挂起(等待事件回调再次驱动);

包工头函数:接收状态机找的牛马们,然后有序组织它们干活

  1. 阶段货仓,这是整个流水线的终点站,它不再寻找下一个阶段,而是查找 cscf->handler,如果存在(比如 proxy_pass),就直接跳过去执行,阶段引擎的循环到此终止;

快递员函数:根据货物目的地发送出去,独占性发货,后续 handler 不再触发

  1. 统计员,无论连接成功还是失败最后都会手动调用此函数,它会触发 NGX_STREAM_LOG_PHASE 进入 log 阶段。

    log 阶段不在 run_phases 的主循环里跑,而是由这个销毁函数在关闭 socket 前最后拉起来跑一次。

记录所有货物的处理情况

四层反向代理配置

在生产环境中,nginx 四层代理常用于数据库集群(MySQL/Redis)、中继加速(SSH/RDP)、DNS 转发以及非 HTTP 协议的负载均衡。

nginx 复制代码
limit_conn_zone $binary_remote_addr zone=addr:10m;

stream {
    log_format proxy '$remote_addr [$time_local] '		# 定义日志格式,记录连接耗时、流量和后端响应情况
                     '$protocol $status $bytes_sent $bytes_received '
                     '$session_time "$upstream_addr" '
                     '"$upstream_bytes_sent" "$upstream_bytes_received" "$upstream_connect_time"';
    access_log /var/log/nginx/stream_access.log proxy;

    upstream mysql_backend {
        hash $remote_addr consistent; 					# 采用通用 hash,确保同一 ip 尽量落到同一台库,减少 session 漂移
        server 192.168.1.10:3306 max_fails=3 fail_timeout=30s;
        server 192.168.1.11:3306 max_fails=3 fail_timeout=30s;
        server 192.168.1.12:3306 backup;
    }

    server {
        listen 33060;
        
        allow 10.0.0.0/8;    				# 仅允许内网网段访问(access 阶段)
        deny all;            				# 拒绝其他所有连接
        limit_conn addr 100;				# 限制单个 ip 同时最多 100 个连接(pre-access 阶段)
        proxy_connect_timeout 5s;  			# 连接后端超时,生产建议设短一点,快速重试
        proxy_timeout 10m;         			# 数据传输超时,对于长连接(数据库),设长一些防止被意外切断
        proxy_buffer_size 16k;     			# 存放后端响应数据的缓冲区
        proxy_pass mysql_backend;			# 转发
        proxy_bind 192.168.100.100:3306;	# 通过本地接口 192.168.100.100 的端口 3306 转发到上游
    }

	server {
		listen 12345;
		...
	}
}

配置 nginx 四层反向代理的注意事项:

  • nginx 作为中转,作为客户端去连后端时会消耗临时端口,可以根据需要去调优内核参数 net.ipv4.ip_local_port_range 扩大端口范围,并开启 net.ipv4.tcp_tw_reuse 以快速回收端口;

    /etc/sysctl.conf 复制代码
    net.ipv4.ip_local_port_range = 1024 65535			# 扩大临时端口范围
    net.ipv4.tcp_tw_reuse = 1							# 开启快速回收和重用处于 time_wait 的连接
    net.core.somaxconn = 4096							# 调高全连接队列大小,防止高并发下丢包
  • 上游服务器(如 mysql)看到的连接 ip 全是 nginx 的内网 ip 时是无法进行审计的,可以设置 proxy_protocol on(需要上游软件支持,如 mysql 8.0 或 pgsql);

    nginx 复制代码
    stream {
    	server {
    		listen 3306;
    		proxy_pass mysql_backend;
    		proxy_protocol on;					# 开启 proxy protocol 发送,将客户端真实 ip 封装在报文头传给后端
    	}
    }
  • 四层代理一个会话要占两个 socket(一个客户端,一个后端),所以必须修改系统 ulimit -n 和 nginx 的 worker_rlimit_nofile(如果单机并发 10 万,句柄数至少要设为 20 万以上);

    nginx 复制代码
    user nginx;									
    worker_processes auto;
    worker_rlimit_nofile 100000; 				# 限制每个 worker 能打开的最大文件数
    
    events {
     	worker_connections 40000;				# 每个 worker 允许的并发连接
     	use epoll;								# 开启 epoll 事件模型提高效率
     	multi_accept on;			 			# 允许一个 worker 同时接收多个新连接
    }
  • UDP 是无连接的,如果代理 UDP,必须配置 proxy_responses 指令告知 nginx 收到多少个包后就认为本次会话结束,否则会话会一直占用直到超时。

    nginx 复制代码
    stream {
     	server {
         	listen 53 udp;
         	proxy_pass dns_backends;
         	proxy_responses 1; 					# 期望从后端收到 1 个包后就关闭会话(适用于 DNS 查询)
         	proxy_timeout 10s; 					# 如果后端没回包,10秒后强制释放连接资源
     	}
    }

附加

下面是一个 nginx stream 中各阶段的 handler 开发模板:(不适用于内容处理阶段)

c 复制代码
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_stream.h>

/**
 * 业务逻辑处理的模板
 * @param s: 当前 stream 会话上下文,包含连接、内存池、变量等所有信息
 */
static ngx_int_t ngx_stream_my_filter_logic_handler(ngx_stream_session_t *s) {
    ngx_connection_t  *c;
    c = s->connection;

    // 1. 记录一条调试日志
    ngx_log_error(NGX_LOG_DEBUG, c->log, 0, "my filter: f*`*k nginx");

    // 2. 处理业务逻辑
    // 在四层代理中,获取客户端 ip 地址进行判断(c->sockaddr 包含了原始的二进制地址信息)
    if (c->sockaddr->sa_family == AF_INET) {
        // 如果是 ipv4,执行一些逻辑
	   	// ...
    }

    // 3. 决定连接命运
    // 验证通过,允许进入下一个 handler 或下一个阶段
    // return NGX_DECLINED; 
    // 验证通过,且不需要再执行本阶段后续的 handler,直接跳到下一阶段
    // return NGX_OK;
    // 验证失败,出现异常,立即中断并关闭连接
    // return NGX_ABORT;
    // 默认行为:交给下一个处理器
    return NGX_DECLINED;
}

/**
 * 在 nginx 内存池中申请内存的模板
 * @param s: 当前 stream 会话上下文,包含连接、内存池、变量等所有信息
 */
static ngx_int_t ngx_stream_my_filter_memory_handler(ngx_stream_session_t *s) {
    ngx_connection_t  *c;
    void              *my_data;
    ngx_str_t         *custom_msg;

    c = s->connection;

    // 1:申请一块普通的内存(类似 malloc)
    // 使用 s->connection->pool,这块内存在 TCP 连接断开时自动释放
    // ngx_pallo 分配对齐的内存,用于性能要求极高的结构体分配
    my_data = ngx_palloc(c->pool, 1024); 
    if (my_data == NULL) {
        return NGX_ERROR; // 内存分配失败,返回错误
    }

    // 2:申请并清零内存(类似 calloc)
    // ngx_pcalloc	分配并初始化为 0,防止读到内存中的脏数据
    custom_msg = ngx_pcalloc(c->pool, sizeof(ngx_str_t));
    if (custom_msg == NULL) {
        return NGX_ERROR;
    }

    // 3:拷贝字符串
    // nginx 的字符串不是以 \0 结尾的,手动拷贝容易出错
    // ngx_pnalloc 分配不对齐的内存,可以节省内存对齐造成的空隙
    u_char *src = (u_char *) "cnmd nginx";
    size_t len = ngx_strlen(src);
    custom_msg->data = ngx_pnalloc(c->pool, len);
    if (custom_msg->data == NULL) {
        return NGX_ERROR;
    }
    ngx_memcpy(custom_msg->data, src, len);
    custom_msg->len = len;

    ngx_log_error(NGX_LOG_DEBUG, c->log, 0, "fenpei memory for: %V", custom_msg);

    return NGX_DECLINED;
}

/**
 * 模块初始化注册函数的模板(不适用于内容处理阶段)
 * 通常赋值给 ngx_stream_module_t 中的 postconfiguration 钩子
 */
static ngx_int_t ngx_stream_my_filter_init(ngx_conf_t *cf) {
    ngx_stream_handler_pt        *h;
    ngx_stream_core_main_conf_t  *cmcf;

    // 1:获取 stream 核心模块的 main 配置
    cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module);

    // 2:在对应的阶段(以 preaccess 为例)推入一个新的 handler 指针
    h = ngx_array_push(&cmcf->phases[NGX_STREAM_PREACCESS_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    // 3:将占位符指向我们编写的函数地址
    *h = ngx_stream_my_filter_logic_handler;
    return NGX_OK;
}

下面是一份适用于内容处理阶段的模板:

c 复制代码
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_stream.h>

static void ngx_stream_my_echo_handler(ngx_stream_session_t *s);
static void ngx_stream_my_echo_event_handler(ngx_event_t *ev);

/**
 * 注册函数,在配置解析时将本模块设为内容处理阶段的处理器
 * 函数统一这样签名,否则编译器报错
 */
static char *ngx_stream_my_echo_directive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
    ngx_stream_core_srv_conf_t  *cscf;

    // 获取当前 server 块的核心配置
    cscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_core_module);

    // 直接将 handler 指针挂载到核心配置上(第六阶段独有)
    cscf->handler = ngx_stream_my_echo_handler;

    return NGX_CONF_OK;
}

/**
 * 入口 handler,当连接经过前五个阶段后,会跳转到这里
 */
static void ngx_stream_my_echo_handler(ngx_stream_session_t *s) {
    ngx_connection_t  *c;

    c = s->connection;
    ngx_log_error(NGX_LOG_INFO, c->log, 0, "enter my echo content phase");

    // 设置读写事件的回调函数(接管状态机)
    c->read->handler = ngx_stream_my_echo_event_handler;
    c->write->handler = ngx_stream_my_echo_event_handler;

    // 将 session 指针存入 connection 的 data,方便在事件触发时找回 session
    c->data = s;

    // 立即触发一次读逻辑
    ngx_stream_my_echo_event_handler(c->read);
}

/**
 * 事件循环,处理实际的数据收发
 */
static void ngx_stream_my_echo_event_handler(ngx_event_t *ev) {
    ngx_connection_t      *c;
    ngx_stream_session_t  *s;
    u_char                 buf[1024];
    ssize_t                n;
    c = ev->data;
    s = c->data;

    // 如果连接已经出错或超时,直接终结
    if (ev->timedout) {
        ngx_stream_finalize_session(s, NGX_STREAM_OK);
        return;
    }

    // 读循环逻辑
    for ( ;; ) {
        n = c->recv(c, buf, 1024);
        if (n == NGX_AGAIN) {
            // 内核缓冲区暂时没数据,等待下一次 epoll 通知
            if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
                ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
            }
            return;
        }

        if (n == NGX_ERROR || n == 0) {
            // n=0 表示客户端主动断开连接
            ngx_stream_finalize_session(s, NGX_STREAM_OK);
            return;
        }

        // 业务逻辑:将读到的内容原样发回
        // 实际开发中应检查 c->send 的返回值和缓冲区状态
        c->send(c, buf, n);
      
        break;
    }
}

  1. &cmcf->phases 是一个超级数组,其中每个元素都是 stream 的一个处理阶段;
    NGX_STREAM_PREACCESS_PHASE 是具体的阶段名称;
    .handlers 是一个阶段下的动态数组,存储了一个阶段下所有需要执行的拓展。 ↩︎

  2. 因为 SSL 阶段的执行并非严格的 phase-handler 顺序,而是被 openssl 状态机驱动,直接插入 phase handler 可能导致未初始化的 SSL 结构被访问。 ↩︎

相关推荐
iconball2 小时前
个人用云计算学习笔记 --33 Containerd
运维·笔记·学习·云计算
工程师华哥2 小时前
2026新版华为数通认证HCIP-CT题库试卷,涵盖拖拽题、判断题、填空题、多选题、单选题等多种考试题型和题目(附答案解析)
运维·网络工程师·华为认证·华为hcip·华为数通认证·核心路由·hcip题库
lbb 小魔仙2 小时前
【Linux】Linux 安全实战:防火墙配置 + 漏洞修复,符合企业合规标准
linux·运维·安全
_OP_CHEN2 小时前
【测试理论与实践】(三)测试BUG篇:从 BUG 本质到实战博弈,带你吃透软件测试的核心逻辑
运维·测试开发·产品运营·bug·压力测试·测试
iconball2 小时前
个人用云计算学习笔记 --35 Ceph 分布式存储
运维·笔记·ceph·学习·云计算
oMcLin2 小时前
如何在 Linux 上打开和编辑 Apple iWork 文件(增强版)
linux·运维·服务器
Ares-Wang2 小时前
网络》》FTP、TFTP、Telnet DHCP
运维·服务器·网络
艾莉丝努力练剑2 小时前
【Linux进程(七)】进程虚拟地址空间详解:从概念到实现与设计哲学
java·linux·运维·服务器·人工智能·安全·进程
曼诺尔雷迪亚兹2 小时前
微服务启动失败:Nacos 403(unknown user)与配置拉取失败故障双排查
java·运维·微服务