• Nginx的负载均衡


    Nginx版本:1.9.1

    我的博客:http://blog.csdn.net/zhangskd

    什么是负载均衡

    我们知道单台服务器的性能是有上限的,当流量很大时,就需要使用多台服务器来共同提供服务,这就是所谓的集群。

    负载均衡服务器,就是用来把经过它的流量,按照某种方法,分配到集群中的各台服务器上。这样一来不仅可以承担

    更大的流量、降低服务的延迟,还可以避免单点故障造成服务不可用。一般的反向代理服务器,都具备负载均衡的功能。

    负载均衡功能可以由硬件来提供,比如以前的F5设备。也可以由软件来提供,LVS可以提供四层的负载均衡(利用IP和端口),

    Haproxy和Nginx可以提供七层的负载均衡(利用应用层信息)。

    来看一个最简单的Nginx负载均衡配置。

    http {
        upstream cluster {
            server srv1;
            server srv2;
            server srv3;
        }
     
        server {
            listen 80;
    
            location / {
                proxy_pass http://cluster;
            }
        }
    }

    通过上述配置,Nginx会作为HTTP反向代理,把访问本机的HTTP请求,均分到后端集群的3台服务器上。

    此时使用的HTTP反向代理模块是ngx_http_proxy_module。

    一般在upstream配置块中要指明使用的负载均衡算法,比如hash、ip_hash、least_conn。

    这里没有指定,所以使用了默认的HTTP负载均衡算法 - 加权轮询。

    负载均衡流程图

    在描述负载均衡模块的具体实现前,先来看下它的大致流程:

    负载均衡模块 

    Nginx目前提供的负载均衡模块:

    ngx_http_upstream_round_robin,加权轮询,可均分请求,是默认的HTTP负载均衡算法,集成在框架中。

    ngx_http_upstream_ip_hash_module,IP哈希,可保持会话。

    ngx_http_upstream_least_conn_module,最少连接数,可均分连接。

    ngx_http_upstream_hash_module,一致性哈希,可减少缓存数据的失效。

    以上负载均衡模块的实现,基本上都遵循一套相似的流程。

    1. 指令的解析函数

    比如least_conn、ip_hash、hash指令的解析函数。

    这些函数在解析配置文件时调用,主要用于:

    检查指令参数的合法性

    指定peer.init_upstream函数指针的值,此函数用于初始化upstream块。

    2. 初始化upstream块

    在执行完指令的解析函数后,紧接着会调用所有HTTP模块的init main conf函数。

    在执行ngx_http_upstream_module的init main conf函数时,会调用所有upstream块的初始化函数,

    即在第一步中指定的peer.init_upstream,主要用于:

    创建和初始化后端集群,保存该upstream块的数据

    指定peer.init,此函数用于初始化请求的负载均衡数据

    来看下ngx_http_upstream_module。

    ngx_http_module_t ngx_http_upstream_module_ctx = {
        ...
        ngx_http_upstream_init_main_conf, /* init main configuration */
        ...
    };
    static char *ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf)
    {
        ...
        /* 数组的元素类型是ngx_http_upstream_srv_conf_t */
        for (i = 0; i < umcf->upstreams.nelts; i++) {
            /* 如果没有指定upstream块的初始化函数,默认使用round robin的 */
            init = uscfp[i]->peer.init_upstream ? uscfp[i]->peer.init_upstream :
                            ngx_http_upstream_init_round_robin;
    
            if (init(cf, uscfp[i] != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
        ...
    }

     

    3. 初始化请求的负载均衡数据块

    当收到一个请求后,一般使用的反向代理模块(upstream模块)为ngx_http_proxy_module,

    其NGX_HTTP_CONTENT_PHASE阶段的处理函数为ngx_http_proxy_handler,在初始化upstream机制的

    函数ngx_http_upstream_init_request中,调用在第二步中指定的peer.init,主要用于:

    创建和初始化该请求的负载均衡数据块

    指定r->upstream->peer.get,用于从集群中选取一台后端服务器(这是我们最为关心的)

    指定r->upstream->peer.free,当不用该后端时,进行数据的更新(不管成功或失败都调用)

    请求的负载均衡数据块中,一般会有一个成员指向对应upstream块的数据,除此之外还会有自己独有的成员。

    "The peer initialization function is called once per request.
    It sets up a data structure that the module will use as it tries to find an appropriate
    backend server to service that request; this structure is persistent across backend re-tries,
    so it's a convenient place to keep track of the number of connection failures, or a computed
    hash value. By convention, this struct is called ngx_http_upstream_<module_name>_peer_data_t."

     

    4. 选取一台后端服务器

    一般upstream块中会有多台后端,那么对于本次请求,要选定哪一台后端呢?

    这时候第三步中r->upstream->peer.get指向的函数就派上用场了:

    采用特定的算法,比如加权轮询或一致性哈希,从集群中选出一台后端,处理本次请求。 

    选定后端的地址保存在pc->sockaddr,pc为主动连接。

    函数的返回值:

    NGX_DONE:选定一个后端,和该后端的连接已经建立。之后会直接发送请求。

    NGX_OK:选定一个后端,和该后端的连接尚未建立。之后会和后端建立连接。

    NGX_BUSY:所有的后端(包括备份集群)都不可用。之后会给客户端发送502(Bad Gateway)。

    5. 释放一台后端服务器

    当不再使用一台后端时,需要进行收尾处理,比如统计失败的次数。

    这时候会调用第三步中r->upstream->peer.free指向的函数。

    函数参数state的取值:

    0,请求被成功处理

    NGX_PEER_FAILED,连接失败

    NGX_PEER_NEXT,连接失败,或者连接成功但后端未能成功处理请求

    一个请求允许尝试的后端数为pc->tries,在第三步中指定。当state为后两个值时:

    如果pc->tries不为0,需要重新选取一个后端,继续尝试,此后会重复调用r->upstream->peer.get。

    如果pc->tries为0,便不再尝试,给客户端返回502错误码(Bad Gateway)。

    在upstream模块的回调

    负载均衡模块的功能是从后端集群中选取一台后端服务器,而具体的反向代理功能是由upstream模块实现的,

    比如和后端服务器建立连接、向后端服务器发送请求、处理后端服务器的响应等。

    我们来看下负载均衡模块提供的几个钩子函数,是在upstream模块的什么地方回调的。

    Nginx的HTTP反向代理模块为ngx_http_proxy_module,其NGX_HTTP_CONTENT_PHASE阶段的处理函数为

    ngx_http_proxy_handler,每个请求的upstream机制是从这里开始的。

    ngx_http_proxy_handler
        ngx_http_upstream_create /* 创建请求的upstream实例 */
        ngx_http_upstream_init /* 启动upstream机制 */
            ngx_htp_upstream_init_request /* 负载均衡模块的入口 */
    
                uscf->peer.init(r, uscf) /* 第三步,初始化请求的负载均衡数据块 */
                ...
    
                ngx_http_upstream_connect /* 可能会被ngx_http_upstream_next重复调用 */
    
                    ngx_event_connect_peer(&u->peer); /* 连接后端 */
                        pc->get(pc, pc->data); /* 第四步,从集群中选取一台后端 */        
                    ...
    
                    /* 和后端建连成功后 */
                    c = u->peer.connection;
                    c->data = r;
                    c->write->handler = ngx_http_upstream_handler; /* 注册的连接的读事件处理函数 */
                    c->read->handler = ngx_http_upstream_handler; /* 注册的连接的写事件处理函数 */                
                    u->write_event_handler = ngx_http_upstream_send_request_handler; /* 写事件的真正处理函数 */
                    u->read_event_handler = ngx_http_upstream_process_header; /* 读事件的真正处理函数 */

    选定后端之后,在和后端通信的过程中如果发生了错误,会调用ngx_http_upstream_next来继续尝试其它的后端。

    ngx_http_upstream_next
        if (u->peer.sockaddr) {
              if (ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_403 ||
                   ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_404)
                   state = NGX_PEER_NEXT;
              else
                   state = NGX_PEER_FAILED;
    
              /* 第五步,释放后端服务器 */
              u->peer.free(&u->peer, u->peer.data, state);
              u->peer.sockaddr = NULL;
         }
    

    Reference

    [1]. http://www.evanmiller.org/nginx-modules-guide.html#proxying

    [2]. http://tengine.taobao.org/book/chapter_05.html#id5

  • 相关阅读:
    蓝桥杯算法训练 区间k大数查询
    【模板】快读
    [ACM] hdu 2544 最短路(dijkstra算法)
    [ACM] hdu 3791 二叉搜索树
    [ACM] hdu 2141 Can you find it? (二分查找)
    [ACM] hdu 2025查找最大元素(水题)
    [ACM] hdu 1232 畅通工程(并查集)
    [ACM] hdu 1022 Train Problem I(栈的使用)
    [ACM] hdu 2857 Mirror and Light (对称点+两条直线的交点)
    [ACM] hdu 爆头(点到直线距离)
  • 原文地址:https://www.cnblogs.com/aiwz/p/6333227.html
Copyright © 2020-2023  润新知