Nginx upstream模块与负载均衡模块分析

目录

Upstream模块介绍
总体实现流程|
memcached模块分析
负载均衡模块

正文

系列文章:upstream demo_fdsafwagdagadg6576的专栏-CSDN博客
本文是对upstream模块 — Nginx开发从入门到精通 提取干货做的的笔记   

一 Upstream与负载均衡介绍

这里写图片描述
Nginx模块一般被分成三大类:handler、filter和upstream。handler、filter这两类模块,可以使nginx轻松完成任何单机工作。而本章介绍的upstream模块,将使nginx跨越单机的限制,完成网络数据的接收、处理和转发
upstream 一组用来负载均衡的后端服务器。调度机器的方法有轮询,权重轮询,ip_hash,响应时间分配请求,url_hash.(常用的是robin,iphash)。
upstream 模块不能单独使用. upstream模块和负载均衡模块需要联合使用
ngx_http_upsteam_module模块,所支持的代理方式包括proxy_pass、fastcgi_pass、memcached_pass等。

二 配置upstream config

获取 Nginx 配置文件格式化为 json 格式信息

 具体配置参见:【Nginx配置】使用upstream和proxy_pass实现反向代理与负载均衡_章全蛋的博客-CSDN博客_nginx proxy_pass upstream

三 总体实现流程

流程说明:从client发起http请求到http filter 结束.
流程图的详细图在文末,也是源码分析的流程图

​Nginx访问上游服务器的流程大致分以下几个阶段:
启动upstream、连接上游服务器、向上游发送请求、接收上游响应(包头/包体)、结束请求。

  1.  创建upstream 模块对象 ngx_http_upstream_create(r);    
  2.  设置上游服务器
    static struct sockaddr_in backendSockAddr; /*比如:www.baidu.com*/
  3.  设置三个必须实现的回调方法:创建请求、处理头部、请求销毁
    u->create_request = mytest_upstream_create_request;
    u->process_header = mytest_process_status_line;
    u->finalize_request = mytest_upstream_finalize_request;
  4.  启动upstream
     ngx_http_upstream_init(r);

四 memcached模块分析 (一种负载均衡模块) 

memcache是一款高性能的分布式cache系统,得到了非常广泛的应用。memcache定义了一套私有通信协议,使得不能通过HTTP请求来访问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的写法已经很熟悉了。

0  ngx_http_upstream_t

请求结构体是ngx_http_request_t,在该结构体中具有一个ngx_http_upstream_t  结构体类型的成员upstream,它就是upstream模块的对象
若没有实现 upstream 机制,则请求结构体 ngx_http_request_t 中的upstream成员设置为NULL,否则必须设置该成员。
ngx_http_upstream_t结构体包括Nginx与上游的连接对象,读写事件,缓冲区buffer,请求创建/请求处理等回调函数,等等.
notes: ngx_http_upstream_t 是目前见过的最大的结构体

1 Upstream模块!

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

1). 创建upstream数据结构

if (ngx_http_upstream_create(r) != NGX_OK) {
    return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

2). 设置upstream的后端服务器列表数据结构

mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);
u->conf = &mlcf->upstream;

3). 设置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;

4). 创建并设置upstream环境数据结构

ctx = ngx_palloc(r->pool, sizeof(ngx_http_memcached_ctx_t));
ctx->rest = NGX_HTTP_MEMCACHED_END;
ctx->request = r;

ngx_http_set_ctx(r, ctx, ngx_http_memcached_module);
u->input_filter_ctx = ctx;

5). 完成upstream初始化并进行收尾工作

r->main->count++;
ngx_http_upstream_init(r);
return NGX_DONE;

任何upstream模块,简单如memcached,复杂如proxy、fastcgi都是如此。

  upstream整体流程如下: 

http://tengine.taobao.org/book/_images/chapter-5-2.PNG

3  回调函数(主要三个create_request,process_request,finalize_request)

前面剖析了memcached模块的骨架,现在开始逐个解决每个回调函数。
1.) ngx_http_memcached_create_request:创建request
2). ngx_http_memcached_process_header:框架调用。接收处理respond header结果。
Process_header实现业务逻辑,将后端服务器返回的状态翻译成返回给客户端的状态
3 )  ngx_http_memcached_finalize_request:无需额外操作。

4 负载均衡模块

Peer.get,connect,send_request 属于upstream模块与负载均衡模块交互部分.
memcached,fastcgi等属于负载模块.upstream模块和负载模块是两个并列的模块,不是包含关系
详见第二部分:负载均衡

5.Filter

Http处理完respond ,都要经过filter做些修饰,然后发给客户端

5.1 ngx_http_memcached_filter_init:
修正从后端服务器收到的内容长度。因为在处理header时没有加上这部分长度。
5.2  ngx_http_memcached_filter:
memcached模块是少有的带有处理正文的回调函数的模块。
因为memcached模块需要过滤正文末尾CRLF"END"CRLF,所以实现了自己的filter回调函数。
处理正文的实际意义是从后端服务器收到的正文有效内容,封装成ngx_chain_t,并加在
u->out_bufs末尾
.nginx并不进行数据拷贝,而是建立ngx_buf_t数据结构指向这些数据内存区,然后由ngx_chain_t组织这些buf

本节回顾

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

五  负载均衡模块

负载均衡模块用于从”upstream”指令定义的后端主机列表中选取一台主机。
nginx先使用负载均衡模块找到一台主机,再使用upstream模块实现与这台主机的交互。
为了介绍负载均衡模块,做到言之有物,以下选取nginx内置的ip hash模块作为实际例子进行分析.

1 配置

要了解负载均衡模块的开发方法,首先需要了解负载均衡模块的使用方法。因为负载均衡模块与之前书中提到的模块差别比较大,所以我们从配置入手比较容易理解。

在配置文件中,我们如果需要使用ip hash的负载均衡算法。我们需要写一个类似下面的配置:

upstream test {
    ip_hash;
    server 192.168.0.1;
    server 192.168.0.2;
}

从配置我们可以看出负载均衡模块的使用场景:
核心指令”ip_hash”只能在upstream {}中使用
这条指令用于通知nginx使用ip hash负载均衡算法。如果没加这条指令,nginx会使用默认的round robin负载均衡模块。

2 指令

配置决定指令系统,现在就来看ip_hash的指令定义:

static ngx_command_t  ngx_http_upstream_ip_hash_commands[] = {

    { ngx_string("ip_hash"),
      NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,
      ngx_http_upstream_ip_hash,
      0,
      0,
      NULL },

    ngx_null_command
};

没有特别的东西,除了指令属性是NGX_HTTP_UPS_CONF.这个属性表示该指令的适用范围是upstream{}

3 模块回调函数

这里就是模块的切入点.负载均衡模块的钩子都是有规律的,这里通过ip_hash模块来分析这个规律

static char *
ngx_http_upstream_ip_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_upstream_srv_conf_t  *uscf;
    uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
        //set callback
    uscf->peer.init_upstream = ngx_http_upstream_init_ip_hash;
        //set flag
    uscf->flags = NGX_HTTP_UPSTREAM_CREATE
                |NGX_HTTP_UPSTREAM_MAX_FAILS
                |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
                |NGX_HTTP_UPSTREAM_DOWN;

    return NGX_CONF_OK;
}

这段代码中有两点值得我们注意。一个是uscf->flags的设置,另一个是设置init_upstream回调。

1) 设置uscf->flags

不同的负载均衡模块对各种属性的支持情况是不一样的

2) 设置init_upstream回调

nginx初始化upstream时,会在ngx_http_upstream_init_main_conf函数中调用设置的回调函数初始化负载均衡模块。

http://tengine.taobao.org/book/_images/chapter-5-1.PNG

从图上可以看出,MAIN_CONF中ngx_upstream_module模块配置项中有一个指针数组upstreams,
数组中的每个元素对应就是配置文件中每一个upstream{}的信息。

4 初始化配置

init_upstream回调函数执行时需要初始化负载均衡模块的配置,还要设置一个新钩子,这个钩子函数会在nginx处理每个请求时作为初始化函数调用.这里,我们先分析IP hash模块初始化配置的代码:

ngx_http_upstream_init_round_robin(cf, us);
us->peer.init = ngx_http_upstream_init_ip_hash_peer;

IP hash模块: (代码截取自:ngx_http_upstream_ip_hash_module)
1) 调用另一个负载均衡模块Round Robin的初始化函数.
2) 再设置自己的处理请求阶段初始化钩子。
实际上几个负载均衡模块可以组成一条链表,每次都是从链首的模块开始进行处理。如果模块决定不处理,可以将处理权交给链表中的下一个模块。
这里,IP hash模块指定Round Robin模块作为自己的后继负载均衡模块,所以在自己的初始化配置函数中也对Round Robin模块进行初始化。

5 初始化请求

nginx收到一个请求以后,如果发现需要访问upstream,就会执行对应的peer.init函数,这是在初始化配置时设置的回调函数.这个函数最重要的作用是构造一张表,当前请求可以使用的upstream服务器被依次添加到这张表中。
之所以需要这张表,最重要的原因是如果upstream服务器出现异常,不能提供服务时,可以从这张表中取得其他服务器进行重试操作。此外,这张表也可以用于负载均衡的计算。
之所以构造这张表的行为放在这里而不是在前面初始化配置的阶段,是因为upstream需要为每一个请求提供独立隔离的环境。

为了讨论peer.init的核心,我们还是看IP hash模块的实现:

r->upstream->peer.data = &iphp->rrp;
ngx_http_upstream_init_round_robin_peer(r, us);
r->upstream->peer.get = ngx_http_upstream_get_ip_hash_peer;

第一行是设置数据指针,这个指针就是指向前面提到的那张表;
第二行是调用Round Robin模块的回调函数对该模块进行请求初始化。面前已经提到,一个负载均衡模块可以调用其他负载均衡模块以提供功能的补充。
第三行是设置一个新的回调函数get。该函数负责从表中取出某个服务器.
一般来说,有两个切入点实现负载均衡算法,其一是在这里,其二是在get回调函数中

6 peer.get和peer.free回调函数

这两个函数是负载均衡模块最底层的函数,负责实际获取一个连接和回收一个连接.
通过返回值,nginx可以了解是否存在可用连接,连接是否已经建立。这些返回值总结如下:

返回值说明nginx后续动作g
NGX_DONE得到了连接地址信息,并且连接已经建立。直接使用连接,发送数据。
NGX_OK得到了连接地址信息,但连接并未建立。建立连接,如连接不能立即建立,设置事件, 暂停执行本请求,执行别的请求。
NGX_BUSY所有连接均不可用。返回502错误至客户端。

各位读者看到上面这张表,可能会有几个问题浮现出来:

Q:什么时候连接是已经建立的?
A:使用后端keepalive连接的时候,连接在使用完以后并不关闭,而是存放在一个队列中,新的请求只需要从队列中取出连接,这些连接都是已经准备好的。
(nginx与后面server需要keepalive 保活)
Q:什么叫所有连接均不可用?
A:初始化请求的过程中,建立了一张表,get函数负责每次从这张表中不重复的取出一个连接,当无法从表中取得一个新的连接时,即所有连接均不可用。
Q:对于一个请求,peer.get函数可能被调用多次么?
A:正式如此。当某次peer.get函数得到的连接地址连接不上,或者请求对应的服务器得到异常响应,nginx会执行ngx_http_upstream_next,然后可能再次调用peer.get函数尝试别的连接。

本节回顾

这一节介绍了负载均衡模块的基本组成。负载均衡模块的配置区集中在upstream{}块中。
负载均衡模块的回调函数体系是以init_upstream为起点,经历init_peer,最终到达peer.get和peer.free。其中init_peer负责建立每个请求使用的server列表,peer.get负责从server列表中选择某个server(一般是不重复选择),而peer.free负责server释放前的资源释放工作。
最后,这一节通过一张图将upstream模块和负载均衡模块在请求处理过程中的相互关系展现出来

QA: Upstream 和 subrequest 区别?

      upstream 被定义为访问上游服务器, 它把Nginx 定义为反代理服务器,首要功能是透传,其次才是以TCP 获取第三方服务器的内容.Nginx 的HTTP 反向代理模块是基于 upstream 方式实现的.
      subrequest 是子请求,也就是说subrequest 将会为用户创建子请求,即将一个复杂的请求分解为多个子请求,每个子请求负责一种功能项,而最初的原始请求负责构成并发送响应给用户。
当subrequest 访问第三服务时,首先派生出子请求访问上游服务器,父请求在完全取得上游服务器的响应后再决定如何处理来自客户端的请求。
       因此,若希望把是第三方服务的内容原封不动地返回给用户时,则使用 upstream 方式。若访问第三方服务是为了获取某些信息,再根据这些信息来构造响应并发给用户,则应使用 subrequest 方式
        subrequest 和upstream:create request,process request,finalize request是一样的.
不同的是subrequest由主request调用,并把subrequest放入request postponed。
upstream需要有tcp处理,与后端server通信.

附录: 源码分析流程图

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值