Nginx 入门指南(十)

标签: Nginx

负载均衡模块

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

配置

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

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

        upstream test {
            ip_hash;

            server 192.168.0.1;
            server 192.168.0.2;
        }

从配置我们可以看出负载均衡模块的使用场景:

  1. 核心指令ip_hash只能在 upstream {}中使用。这条指令用于通知 Nginx 使用 ip hash 负载均衡算法。如果没加这条指令,Nginx 会使用默认的 round robin 负载均衡模块。请各位读者对比 handler 模块的配置,是不是有共同点?
  2. upstream {}中的指令可能出现在server指令前,可能出现在server指令后,也可能出现在两条server指令之间。各位读者可能会有疑问,有什么差别么?那么请各位读者尝试下面这个配置:
        upstream test {
            server 192.168.0.1 weight=5;
            ip_hash;
            server 192.168.0.2 weight=7;
        }

神奇的事情出现了:

        nginx: [emerg] invalid parameter "weight=7" in nginx.conf:103
        configuration file nginx.conf test failed

可见 ip_hash 指令的确能影响到配置的解析。

指令

配置决定指令系统,现在就来看 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{}。

钩子

以从前面的章节得到的经验,大家应该知道这里就是模块的切入点了。负载均衡模块的钩子代码都是有规律的,这里通过 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);

        uscf->peer.init_upstream = ngx_http_upstream_init_ip_hash;

        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 回调。

设置 uscf->flags

  1. NGX_HTTP_UPSTREAM_CREATE:创建标志,如果含有创建标志的话,Nginx 会检查重复创建,以及必要参数是否填写;

  2. NGX_HTTP_UPSTREAM_MAX_FAILS:可以在 server 中使用 max_fails 属性;

  3. NGX_HTTP_UPSTREAM_FAIL_TIMEOUT:可以在 server 中使用 fail_timeout 属性;

  4. NGX_HTTP_UPSTREAM_DOWN:可以在 server 中使用 down 属性;

  5. NGX_HTTP_UPSTREAM_WEIGHT:可以在 server 中使用 weight 属性;

  6. NGX_HTTP_UPSTREAM_BACKUP:可以在 server 中使用 backup 属性。

聪明的读者如果联想到刚刚遇到的那个神奇的配置错误,可以得出一个结论:在负载均衡模块的指令处理函数中可以设置并修改 upstream{} 中server指令支持的属性。这是一个很重要的性质,因为不同的负载均衡模块对各种属性的支持情况都是不一样的,那么就需要在解析配置文件的时候检测出是否使用了不支持的负载均衡属性并给出错误提示,这对于提升系统维护性是很有意义的。但是,这种机制也存在缺陷,正如前面的例子所示,没有机制能够追加检查在更新支持属性之前已经配置了不支持属性的server指令。

设置 init_upstream 回调

Nginx 初始化 upstream 时,会在 ngx_http_upstream_init_main_conf 函数中调用设置的回调函数初始化负载均衡模块。这里不太好理解的是 uscf 的具体位置。通过下面的示意图,说明 upstream 负载均衡模块的配置的内存布局。

从图上可以看出,MAIN_CONF 中 ngx_upstream_module 模块的配置项中有一个指针数组 upstreams,数组中的每个元素对应就是配置文件中每一个 upstream{}的信息。更具体的将会在后面的原理篇讨论。

初始化配置

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 模块首先调用另一个负载均衡模块 Round Robin 的初始化函数,然后再设置自己的处理请求阶段初始化钩子。实际上几个负载均衡模块可以组成一条链表,每次都是从链首的模块开始进行处理。如果模块决定不处理,可以将处理权交给链表中的下一个模块。这里,IP hash 模块指定 Round Robin 模块作为自己的后继负载均衡模块,所以在自己的初始化配置函数中也对 Round Robin 模块进行初始化。

初始化请求

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 回调函数,还有另一个r->upstream->peer.free的回调函数。该函数在 upstream 请求完成后调用,负责做一些善后工作。比如我们需要维护一个 upstream 服务器访问计数器,那么可以在 get 函数中对其加 1,在 free 中对其减 1。如果是 SSL 的话,Nginx 还提供两个回调函数 peer.set_session 和 peer.save_session。一般来说,有两个切入点实现负载均衡算法,其一是在这里,其二是在 get 回调函数中。

peer.get 和 peer.free 回调函数

这两个函数是负载均衡模块最底层的函数,负责实际获取一个连接和回收一个连接的预备操作。之所以说是预备操作,是因为在这两个函数中,并不实际进行建立连接或者释放连接的动作,而只是执行获取连接的地址或维护连接状态的操作。需要理解的清楚一点,在 peer.get 函数中获取连接的地址信息,并不代表这时连接一定没有被建立,相反的,通过 get 函数的返回值,Nginx 可以了解是否存在可用连接,连接是否已经建立。这些返回值总结如下:

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

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

Q: 什么时候连接是已经建立的?

A: 使用后端 keepalive 连接的时候,连接在使用完以后并不关闭,而是存放在一个队列中,新的请求只需要从队列中取出连接,这些连接都是已经准备好的。

Q: 什么叫所有连接均不可用?

A: 初始化请求的过程中,建立了一张表,get 函数负责每次从这张表中不重复的取出一个连接,当无法从表中取得一个新的连接时,即所有连接均不可用。

Q: 对于一个请求,peer.get 函数可能被调用多次么?

A: 正式如此。当某次 peer.get 函数得到的连接地址连接不上,或者请求对应的服务器得到异常响应,Nginx 会执行 ngx_http_upstream_next,然后可能再次调用 peer.get 函数尝试别的连接。upstream 整体流程如下:

本节回顾

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

版权声明:本文为liyongzhi1992原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/liyongzhi1992/article/details/81451064

智能推荐

单链表+单链表代码(链表最基础)

链表 链表是有顺序的表,在内存中存储: 链表是以节点的方式存储的 每个节点包括data域,next域:指向下一个节点 如图:发现链表的各个节点不一定是连续存放的,有跳跃的,不是连续存储 链表分为带头节点的链表和没有头结点的链表 添加: 1.先创建一个head头结点,作用就是单链表的头 2.后面每添加一个节点,就直接加入到链表最后 遍历: 代码 添加节点到链表里: 这里借助于temp节点,通过循环找...

Rtthread学习笔记(十三)RT-Thread Studio开启硬件看门狗Watchdog

一、开启硬件看门狗Watchdog 1、配置RT-Thread Settings 2、开启stm32f1xx_hal_conf.h中的宏定义 3.使用RT接口函数初始化硬件看门狗...

TYVJ 4864 天天去哪吃 || 清北学堂金秋杯大奖赛

题目描述: 记录一下i这个值上次出现的位置在哪里,就是pre...

java反编译

jvm 把Boolean类型的值flag当做int类型处理。​​​ Foo.java: 由 class 文件生成 jasm 文件:java -jar asmtools.jar jdis Foo.class > Foo.jasm  修改jasm文件: 执行反编译: java -jar jd-gui-1.6.6.jar File 打开Foo.class文件:b修改为2 重新执行java...

【学习笔记】03-v-html的学习和示例

v-html的认识和使用 示例: 显示结果: 注意:v-html是有复制的...

猜你喜欢

Java实现在线考试系统(系统介绍)

1.和现在有的考试系统有以下几种优势: a.和现在有的系统比较起来,本系统有科目、章节、老师、学生、班级等信息的管理,还有批阅试卷查看已批阅试卷等。传统的考试系统划分并不细,业务功能简单。 b.和学校的考试系统还有外面的考试系统比较起来,本系统是B/S结构,学校的考试系统一般为C/S结构,性能方面不如B/S结构,并且C/S接口需要安装客户端,客户端压力很大,我的系统只需要电脑具有浏览器,在同一局域...

计算机视觉--多视几何初步尝试

基础矩阵的原理 K和K’分别是两个相机的参数矩阵。p和p’是X在平面π的坐标表示。所以可以得出 具体计算过程 代码: #!/usr/bin/env python coding: utf-8 from PIL import Image from numpy import * from pylab import * import numpy as np from imp ...

java初学者怎么学习才可以快速入门

java初学者怎么学习才可以快速入门 一、了解JAVA 我们要知道:Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计语言。 Java之父:詹姆斯·高斯林 1.1 java的三个体系 Java SE(Java Platform Standard Edition)。Java SE 以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境...

字段属性之主键&增删改查&自增长&唯一键约束

字段属性之主键&自增长&唯一键约束 主键 主键:primary key 主要的键 一张表中只有一个字段可以使用对应的键,用来唯一的约束该字段里面的数据,不能重复,这种称之为主键 一张表只能最多一个主键 增加主键 SQL操作中有多种方式增加主键大体分为三种 1.在创建表的时候直接在字段之后跟primary key关键字(主键本身不允许为空) 优点:非常直接:缺点:只能使用一个字段作为...

linux下 基于libmad的socket多用户mp3音频在线播放服务器

在众多大神的帮助下,这个在线播放流媒体服务器终于完成啦。。。。 这个mp3流媒体服务器设计的思路是,服务器程序server用多线程实现和多个客户端的通信(这是必然的),然后发送给客户端当前的音频列表公客户端选择,之后根据k客户端的选择给多个客户端传输相应mp3文件的数据,同时,客户端进行实时地音频解码并播放。 关于libmad开源mp3音频解码库的使用,见上一篇博客吧。。。。 在服务器程序这一端,...