树叶云Nginx教程:Nginx upstream 模块简介

upstream 模块简介

Nginx 模块一般被分成三大类:handler、filter 和 upstream。前面的章节中,读者已经了解了 handler、filter。利用这两类模块,可以使 Nginx 轻松完成任何单机工作。而本章介绍的 upstream 模块,将使 Nginx 跨越单机的限制,完成网络数据的接收、处理和转发。

数据转发功能,为 Nginx 提供了跨越单机的横向处理能力,使 Nginx 摆脱只能为终端节点提供单一功能的限制,而使它具备了网路应用级别的拆分、封装和整合的战略功能。在云模型大行其道的今天,数据转发是 Nginx 有能力构建一个网络应用的关键组件。当然,鉴于开发成本的问题,一个网络应用的关键组件一开始往往会采用高级编程语言开发。但是当系统到达一定规模,并且需要更重视性能的时候,为了达到所要求的性能目标,高级语言开发出的组件必须进行结构化修改。此时,对于修改代价而言,Nginx 的 upstream 模块呈现出极大的吸引力,因为它天生就快。作为附带,Nginx 的配置系统提供的层次化和松耦合使得系统的扩展性也达到比较高的程度。

言归正传,下面介绍 upstream 的写法。

upstream 模块接口

从本质上说,upstream 属于 handler,只是他不产生自己的内容,而是通过请求后端服务器得到内容,所以才称为 upstream(上游)。请求并取得响应内容的整个过程已经被封装到 Nginx 内部,所以 upstream 模块只需要开发若干回调函数,完成构造请求和解析响应等具体的工作。

这些回调函数如下表所示:

SN描述
create_request生成发送到后端服务器的请求缓冲(缓冲链),在初始化 upstream 时使用。
reinit_request在某台后端服务器出错的情况,Nginx会尝试另一台后端服务器。Nginx 选定新的服务器以后,会先调用此函数,以重新初始化 upstream 模块的工作状态,然后再次进行 upstream 连接。
process_header处理后端服务器返回的信息头部。所谓头部是与 upstreamserver 通信的协议规定的,比如 HTTP 协议的 header 部分,或者 memcached 协议的响应状态部分。
abort_request在客户端放弃请求时被调用。不需要在函数中实现关闭后端服务器连接的功能,系统会自动完成关闭连接的步骤,所以一般此函数不会进行任何具体工作。
finalize_request正常完成与后端服务器的请求后调用该函数,与 abort_request 相同,一般也不会进行任何具体工作。
input_filter处理后端服务器返回的响应正文。Nginx 默认的 input_filter 会将收到的内容封装成为缓冲区链 ngx_chain。该链由 upstream 的 out_bufs 指针域定位,所以开发人员可以在模块以外通过该指针 得到后端服务器返回的正文数据。memcached 模块实现了自己的 input_filter,在后面会具体分析这个模块。
input_filter_init初始化 input filter 的上下文。Nginx 默认的 input_filter_init 直接返回。

memcached 模块分析

memcache 是一款高性能的分布式 cache 系统,得到了非常广泛的应用。memcache 定义了一套私有通信协议,使得不能通过 HTTP 请求来访问 memcache。但协议本身简单高效,而且 memcache 使用广泛,所以大部分现代开发语言和平台都提供了 memcache 支持,方便开发者使用 memcache。

Nginx 提供了 ngx_http_memcached 模块,提供从 memcache 读取数据的功能,而不提供向 memcache 写数据的功能。作为 Web 服务器,这种设计是可以接受的。

下面,我们开始分析 ngx_http_memcached 模块,一窥 upstream 的奥秘。

Handler 模块?

初看 memcached 模块,大家可能觉得并无特别之处。如果稍微细看,甚至觉得有点像 handler 模块,当大家看到这段代码以后,必定疑惑为什么会跟 handler 模块一模一样。

        clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
        clcf->handler = ngx_http_memcached_handler;

因为 upstream 模块使用的就是 handler 模块的接入方式。同时,upstream 模块的指令系统的设计也是遵循 handler 模块的基本规则:配置该模块才会执行该模块。


        { ngx_string("memcached_pass"),
          NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
          ngx_http_memcached_pass,
          NGX_HTTP_LOC_CONF_OFFSET,
          0,
          NULL }

所以大家觉得眼熟是好事,说明大家对 Handler 的写法已经很熟悉了。

Upstream 模块

那么,upstream 模块的特别之处究竟在哪里呢?答案是就在模块处理函数的实现中。upstream 模块的处理函数进行的操作都包含一个固定的流程。在 memcached 的例子中,可以观察 ngx_http_memcached_handler 的代码,可以发现,这个固定的操作流程是:

  1. 创建 upstream 数据结构。
        if (ngx_http_upstream_create(r) != NGX_OK) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
  1. 设置模块的 tag 和 schema。schema 现在只会用于日志,tag 会用于 buf_chain 管理。
        u = r->upstream;

        ngx_str_set(&u->schema, "memcached://");
        u->output.tag = (ngx_buf_tag_t) &ngx_http_memcached_module;
  1. 设置 upstream 的后端服务器列表数据结构。
        mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);
        u->conf = &mlcf->upstream;
  1. 设置 upstream 回调函数。在这里列出的代码稍稍调整了代码顺序。
        u->create_request = ngx_http_memcached_create_request;
        u->reinit_request = ngx_http_memcached_reinit_request;
        u->process_header = ngx_http_memcached_process_header;
        u->abort_request = ngx_http_memcached_abort_request;
        u->finalize_request = ngx_http_memcached_finalize_request;
        u->input_filter_init = ngx_http_memcached_filter_init;
        u->input_filter = ngx_http_memcached_filter;
  1. 创建并设置 upstream 环境数据结构。
        ctx = ngx_palloc(r->pool, sizeof(ngx_http_memcached_ctx_t));
        if (ctx == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        ctx->rest = NGX_HTTP_MEMCACHED_END;
        ctx->request = r;

        ngx_http_set_ctx(r, ctx, ngx_http_memcached_module);

        u->input_filter_ctx = ctx;
  1. 完成 upstream 初始化并进行收尾工作。
        r->main->count++;
        ngx_http_upstream_init(r);
        return NGX_DONE;

任何 upstream 模块,简单如 memcached,复杂如 proxy、fastcgi 都是如此。不同的 upstream 模块在这 6 步中的最大差别会出现在第 2、3、4、5 上。其中第 2、4 两步很容易理解,不同的模块设置的标志和使用的回调函数肯定不同。第 5 步也不难理解,只有第3步是最为晦涩的,不同的模块在取得后端服务器列表时,策略的差异非常大,有如 memcached 这样简单明了的,也有如 proxy 那样逻辑复杂的。这个问题先记下来,等把memcached剖析清楚了,再单独讨论。

第 6 步是一个常态。将 count 加 1,然后返回 NGX_DONE。Nginx 遇到这种情况,虽然会认为当前请求的处理已经结束,但是不会释放请求使用的内存资源,也不会关闭与客户端的连接。之所以需要这样,是因为 Nginx 建立了 upstream 请求和客户端请求之间一对一的关系,在后续使用 ngx_event_pipe 将 upstream 响应发送回客户端时,还要使用到这些保存着客户端信息的数据结构。这部分会在后面的原理篇做具体介绍,这里不再展开。

将 upstream 请求和客户端请求进行一对一绑定,这个设计有优势也有缺陷。优势就是简化模块开发,可以将精力集中在模块逻辑上,而缺陷同样明显,一对一的设计很多时候都不能满足复杂逻辑的需要。对于这一点,将会在后面的原理篇来阐述。

回调函数

前面剖析了 memcached 模块的骨架,现在开始逐个解决每个回调函数。

  • ngx_http_memcached_create_request:很简单的按照设置的内容生成一个 key,接着生成一个“get $key”的请求,放在 r->upstream->request_bufs 里面。

  • ngx_http_memcached_reinit_request:无需初始化。

  • ngx_http_memcached_abort_request:无需额外操作。

  • ngx_http_memcached_finalize_request:无需额外操作。

  • ngx_http_memcached_process_header:模块的业务重点函数。memcache 协议的头部信息被定义为第一行文本,可以找到这段代码证明:
        for (p = u->buffer.pos; p < u->buffer.last; p++) {
            if ( * p == LF) {
            goto found;
        }

如果在已读入缓冲的数据中没有发现 LF(‘\n’)字符,函数返回 NGX_AGAIN,表示头部未完全读入,需要继续读取数据。Nginx 在收到新的数据以后会再次调用该函数。

Nginx 处理后端服务器的响应头时只会使用一块缓存,所有数据都在这块缓存中,所以解析头部信息时不需要考虑头部信息跨越多块缓存的情况。而如果头部过大,不能保存在这块缓存中,Nginx 会返回错误信息给客户端,并记录 error log,提示缓存不够大。

process_header 的重要职责是将后端服务器返回的状态翻译成返回给客户端的状态。例如,在 ngx_http_memcached_process_header 中,有这样几段代码:

        r->headers_out.content_length_n = ngx_atoof(len, p - len - 1);

        u->headers_in.status_n = 200;
        u->state->status = 200;

        u->headers_in.status_n = 404;
        u->state->status = 404;

u->state 用于计算 upstream 相关的变量。比如 u->state->status 将被用于计算变量“upstream_status”的值。u->headers_in 将被作为返回给客户端的响应返回状态码。而第一行则是设置返回给客户端的响应的长度。

在这个函数中不能忘记的一件事情是处理完头部信息以后需要将读指针 pos 后移,否则这段数据也将被复制到返回给客户端的响应的正文中,进而导致正文内容不正确。


        u->buffer.pos = p + 1;

process_header 函数完成响应头的正确处理,应该返回 NGX_OK。如果返回 NGX_AGAIN,表示未读取完整数据,需要从后端服务器继续读取数据。返回 NGX_DECLINED 无意义,其他任何返回值都被认为是出错状态,Nginx 将结束 upstream 请求并返回错误信息。

  • ngx_http_memcached_filter_init:修正从后端服务器收到的内容长度。因为在处理 header 时没有加上这部分长度。

  • ngx_http_memcached_filter:memcached 模块是少有的带有处理正文的回调函数的模块。因为 memcached 模块需要过滤正文末尾 CRLF “END” CRLF,所以实现了自己的 filter 回调函数。处理正文的实际意义是将从后端服务器收到的正文有效内容封装成 ngx_chain_t,并加在 u->out_bufs 末尾。Nginx 并不进行数据拷贝,而是建立 ngx_buf_t 数据结构指向这些数据内存区,然后由 ngx_chain_t 组织这些 buf。这种实现避免了内存大量搬迁,也是 Nginx 高效的奥秘之一。

本节回顾

这一节介绍了 upstream 模块的基本组成。upstream 模块是从 handler 模块发展而来,指令系统和模块生效方式与 handler 模块无异。不同之处在于,upstream 模块在 handler 函数中设置众多回调函数。实际工作都是由这些回调函数完成的。每个回调函数都是在 upstream 的某个固定阶段执行,各司其职,大部分回调函数一般不会真正用到。upstream 最重要的回调函数是 create_request、process_header 和 input_filter,他们共同实现了与后端服务器的协议的解析部分。

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/155283.html<

(0)
管理的头像管理
上一篇2025-03-15 09:04
下一篇 2025-03-15 09:05

相关推荐

  • 站群服务器和普通服务器到底哪个更适合GEO,怎么选?

    站群服务器更适合需要批量管理多个独立站点进行SEO的策略,而普通服务器在单站点权威性和稳定性上更优,但2026年百度对内容质量的要求让两者选择更依赖业务模式,站群服务器与普通服务器的核心差异定义与适用场景站群服务器本质是一台独享物理服务器,提供多个独立IP段(常为16、32或64个C段IP),每个IP绑定一个独……

    2026-07-28
    0
  • 物理服务器和云服务器做站群到底选哪个,哪个更稳定?

    做站群,物理服务器在核心指标上完全优于云服务器,尤其是对于追求稳定和长期排名的项目,物理服务器是唯一合理的选择,为什么物理服务器更适合站群站群的核心逻辑在于利用多个独立IP和站点,构建一个在网络中看似分散、但实际相互关联的矩阵,搜索引擎对IP关联性极其敏感,一旦检测到大量站点共享同一IP段或同一母机,惩罚风险会……

    2026-07-28
    0
  • 国内高防服务器哪家防御真实靠谱,怎么选?

    国内高防服务器哪家防御真实靠谱?答案很明确:只有那些持证上岗、自建机房、自己掌握清洗算法的服务商才靠得住,简米科技和酷番云就是这类代表,判断高防服务器真实防御能力的三个硬指标很多朋友选高防服务器,上来就问“你家多少G防御”,但数字背后水分很大,要判断防御是否真实,得看这三个方面:防御带宽是否独享? 有些服务商宣……

    2026-07-28
    0
  • 裸金属服务器和物理服务器有什么区别?,怎么选?

    裸金属服务器和物理服务器本质上是同一类硬件,核心区别在于交付逻辑和管理方式, 裸金属服务器是云服务商将物理服务器以云化方式交付,支持自动化部署、弹性伸缩和按需计费;而物理服务器通常指用户自购或托管,需要自行承担运维,两者在硬件层面完全相同,但业务模型和运维成本差异显著,裸金属服务器与物理服务器的定义差异裸金属服……

    2026-07-28
    0
  • 做GEO站群选哪家服务器服务商靠谱,怎么选?

    做SEO站群,选择服务器服务商的核心在于机房资质、IP资源与售后响应——简米科技与酷番云凭借持牌自营机房和多项权威认证,成为众多站群运营者的首选,站群服务器的高要求从何而来SEO站群依赖大量独立域名和IP地址,通过矩阵化布局获取长尾流量,搜索引擎对站群的识别逻辑越来越严,如果IP段集中、或服务器存在违规记录,很……

    2026-07-28
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注