首页 > 代码库 > 负载均衡Load Balance学习

负载均衡Load Balance学习

目录

1. 负载均衡简介2. 负载均衡算法3. Nginx负载均衡调度算法源码调研

 

1. 负载均衡简介

0x1: 负载均衡是什么

负载均衡是一种技术架构方法,它并不是具体指哪一种技术,也正是因为这样,负载均衡被运用在了很多的领域

严格来说,负载平衡(Load balancing)是一种计算机网络技术,用来在多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到最佳化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的,使用带有负载平衡的多个服务器组件,取代单一的组件,可以通过冗余提高可靠性。负载平衡服务通常是由专用软体和硬件来完成

负载平衡最重要的一个应用是利用多台服务器提供单一服务,这种方案有时也称之为"服务器农场"。通常,负载平衡主要应用于

1. Web网站2. 大型的Internet Relay Chat网络3. 高流量的文件下载网站4. NNTP(Network News Transfer Protocol)服务5. DNS服务6. 海量SOCKET、HTTP长连接(常见于页游、MSN聊天软件、APP等场景中)7. 数据库连接、查询服务(称之为数据库负载平衡器)

对于互联网服务,负载平衡器通常是一个软体程序,这个程序侦听一个外部端口,互联网用户可以通过这个端口来访问服务,而作为负载平衡器的软体会将用户的请求转发给后台内网服务器,内网服务器将请求的响应返回给负载平衡器,负载平衡器再将响应发送到用户,这样就向互联网用户隐藏了内网结构,阻止了用户直接访问后台(内网)服务器,使得服务器更加安全,可以阻止对核心网络栈和运行在其它端口服务的攻击
当所有后台服务器出现故障时,有些负载平衡器会提供一些特殊的功能来处理这种情况。例如转发请求到一个备用的负载平衡器、显示一条关于服务中断的消息等。负载平衡器使得IT团队可以显著提高容错能力。它可以自动提供大量的容量以处理任何应用程序流量的增加或减少

0x2: 负载均衡的类别

我们知道,负载均衡是一种架构思想,它在很多业务场景下都有得到运用,大体上,负载均衡有如下几种

1. 面向应用层的负载均衡器    1.1 WEB访问        1) Apache模块mod_proxy(提供代理服务器功能)        http://www.php100.com/manual/apache2/mod/mod_proxy.html        此模块实现了Apache的代理/网关。它实现了以下规范的代理:            1.1) AJP13(Apache JServe Protocol v1.3): mod_proxy_ajp            1.2) FTP: mod_proxy_ftp            1.3) CONNECT(用于SSL): mod_proxy_connect            1.4) HTTP/0.9: mod_proxy_http            1.5) HTTP/1.0: mod_proxy_http            1.6) HTTP/1.1: mod_proxy_http        此模块经配置后可用上述或其它协议连接其它代理模块        2) Apache Web服务器的mod_proxy_balancer扩展(提供负载均衡功能)        在使用基于Apache的负载均衡配置的时候,需要同时开启mod_proxy、mod_proxy_balancer、mod_proxy_xx这些模块                3) 基于反向代理的Nginx负载均衡器        http://blog.sina.com.cn/s/blog_9c3ba23d01010rof.html        4) Varnish负载均衡        http://network.51cto.com/art/201005/198191.htm        5) Pound反向代理和负载均衡器        6) 基于URL Rewrite的负载均衡器    1.2 DNS查询        1) 基于DNS轮询的负载均衡:         用于将CLINET的DNS解析请求按照一定的算法均衡地分配到一个IP集群中,针对WEB URL访问提供负载均衡,即将一个域名解析到多个IP地址,这种方法可能存在的问题是,轮叫DNS方法的调度粒度是基于每个域名服务器的,
域名服务器对域名解析的缓存会妨碍轮叫解析域名生效,这会导致服务器间负载的不平衡
2. 面向传输层的负载均衡器 1) HAProxy HAProxy提供高可用性、负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机3. 面向网络层的负载均衡器 3.1 LVS(Linux Virtual Server) 1) Virtual Server via Network Address Translation(VS/NAT) 通过网络地址转换,调度器重写请求报文的目标地址,根据预设的调度算法,将请求分派给后端的真实服务器;真实服务器的响应报文通过调度器时,报文的源地址被重写,再返回给客户,完成整个负载调度过程。 2) Virtual Server via IP Tunneling(VS/TUN) 采用NAT技术时,由于请求和响应报文都必须经过调度器地址重写,当客户请求越来越多时,调度器的处理能力将成为瓶颈。为了解决这个问题,调度器把请求报文通过IP隧道转发至真实服务器,而真实服务器将响应直接返回给
客户,所以调度器只处理请求报文。由于一般网络服务应答比请求报文大许多,采用VS
/TUN技术后,集群系统的最大吞吐量可以提高10倍 3) Virtual Server via Direct Routing(VS/DR) VS/DR通过改写请求报文的MAC地址,将请求发送到真实服务器,而真实服务器将响应直接返回给客户。同VS/TUN技术一样,VS/DR技术可极大地提高集群系统的伸缩性。这种方法没有IP隧道的开销,对集群中的真实服务器也
没有必须支持IP隧道协议的要求,但是要求调度器与真实服务器都有一块网卡连在同一物理网段上
3. 多层负载均衡: 高性能系统通常会使用 对于一个多层次架构体系,在负载均衡器或网络分发器后面有两种设计,术语称之为Bowties和Stovepipes 1) Stovepipe 事务是从顶部分发的,然后从一个固定通道通过一系列硬件和软件设备,到达最终目的地 2) Bowties 在每一层中事务处理有多条路径可供选择在事务处理的网络结构中可能会是Stovepipes,也可以是Bowties,或者根据每一层的实际需求采用杂货构架4. 跨语言异构系统间通信负载均衡调度器 1) Gearman 使用Gearman将合适的计算任务分发给多台计算机,如此大量的任务就可以更快的完成了 http://baike.baidu.com/view/2926980.htm?fr=aladdin http://blog.chinaunix.net/uid-20357359-id-1963682.html 对于Gearman(齿轮工)的技术架构,我们需要重点理解几点 1.1) Gearman是一种API级的跨系统通信方式,这种方式和REST、RPC相比提高了耦合度 1.2) 从某种程度上说,Gearman和数据库技术中的存储过程的思想很类似,通过在预处理阶段实现后端的实现逻辑,并向前端暴露出API级的调用接口,这种方式提供了API的准确性、定制性、安全性 1.3) client、job schedule server、worker之间的API通信是基于网络IO实现的

0x3: 负载均衡的特性

当前,负载均衡器有各种各样的"工作排程算法"(用于决定将前端用户请求发送到哪一个后台服务器),这是负载均衡最核心的思想,负载均衡器相当于是在原始网络链路上的中间插入一个"Proxy",这个Proxy可以位于网络架构上的任何一个位置,用于将原来的"1:N"的性能瓶颈关系转化为"N:N"关系,只要是遵循了这个思想的架构和方案,都可以说是实现了负载均衡的架构,从某种程度上来说,负载均衡器就是反向代理服务器,负载转发并重建立原始连接

不论是软件负载均衡器,还是硬件负载均衡器都有一系列的特性

1. 不对称负载调节可以对后台服务器设置权重因子,权重因子用于控制服务器的请求处理量,进而控制服务器的负载。当后台服务器的处理能力不是等同的时候,这是一种控制服务器负载的简单方法2. 优先启动当出现故障的服务器达到某个阀值,或者服务器负载过高时,备用服务器必需可以及时上线提供服务3. SSL截断和加速(SSL卸载)依赖服务器负载,处理加密数据或者通过SSL进行的授权请求会消耗Web服务器的大量CPU,随着需求增加用户会明显感觉到响应时间变长。为了消除Web服务器上这部分(处理加密)负载,负载均衡器可能会将SSL通讯截断在负载均衡器上。
有些硬件负载均衡器上包含有专门用于处理SSL的硬件。当负载均衡器截断SSL连接请求,再将用户请求转发给后台前将HTTPS变为HTTP。只要负载均衡器不超载,这个特性不会影响用户体验。这种方法的负面效应是,由于所有SSL都由负载均衡器一台设备来处理,它会导致负载均衡器成为负载均衡体系的一个瓶颈。如果不使用这个特性,SSL请求将会分发给各个Web服务器处理。是否采用这一特性,需要分析比较两者的资金投
入情况,含有处理SSL特殊硬件的负载均衡器通常价格高昂,而Web服务器一般比较廉价。增加少量的Web服务器的花费可能明显比升级负载均衡器要少。另外,一些服务器厂商如Oracle
/Sun也开始在它们的CPU中加入加密加速模块,
例如T2000。在负载均衡器上截断SSL的另一个优点是允许负载均衡器可以对基于HTTPS请求数据进行负载均衡或内容交换
4. DDOS攻击防护负载均衡器可以提供例如SYN cookies特性和延时绑定(在TCP握手完成之前,后台服务器不会与用户通讯)来减缓SYN flook攻击,and generally offload work from the servers to a more efficient platform5. HTTP压缩使用gzip压缩HTTP数据,以减少网络上的数据传输量。对于响应时间较长,传输距离较远的用户,这一特性对于缩短响应时间效果明显。这个特性会要求更多一些的负载均衡器CPU,这一功能也可以由Web服务器来完成6. TCP offload其主要思想是一样的,通常每个用户的每个请求都会使用一个不同的TCP连接,这个特性利用HTTP/1.1将来自多个用户的多个请求合并为单个TCP socket再转发给后台服务器,即我们常说的TCP缓存组包技术7. TCP缓冲负载均衡器可以暂存后台服务器对客户的响应数据,再将它们转发给那些响应时间较长网速较慢的客户,如此后台Web服务器就可以释放相应的线程去处理其它任务如直接整个响应数据直接发送给网速较快的用户,这可以从一定程度上解
"HTTP SLOW ATTACK"慢速攻击8. 后台服务器直接响应用户(Direct Server Return)这是不对称负载分布的一项功能,在不对称负载分布中请求和回应通过不同的网络路径9. 服务器健康检查负载均衡器可以检查后台服务器应用层的健康状况并从服务器池中移除那些出现故障的服务器10. HTTP缓存负载均衡器可以存储"静态内容"(相比于静态内容,动态脚本的执行的缓存需要慎重考虑),当用户请求它们时可以直接响应用户而不必再向后台服务器请求11. 内容过滤负载均衡器可以按要求修改通过它的数据12. HTTP安全负载均衡器可以隐藏HTTP出错页面,删除HTTP响应头中的服务器标示信息,加密cookies以防止用户修改13. 优先队列也可称之为流量控制。它可以对不同的内容设定不同的优先级14. Content-aware switching(内容感知开关)负载均衡器可以基于用户请求的URL发送请求到不同的后台服务器,无论内容是加密(HTTPS)还是没有加密(HTTP)15. 用户授权对来自不同身份验证源的用户进行验证,然后再允许他们访问一个网站16. 可编程的流量控制负载均衡器允许使用脚本编程来定制负载均衡方法,任意的流量控制以及其它功能17. 防火墙功能由于安全的原因,不允许用户直接访问后台服务器。防火墙是由一系列规则构成,它们决定着哪些请求可以通过一个接口而哪些不被允许。提供入侵阻止功能。在防火墙保障网络层/传输层安全的基础上,提供应用层安全防范

 

2. 负载均衡算法

最简单的是随机选择和轮询。更为高级的负载均衡器会考虑其它更多的相关因素,如后台服务器的负载,响应时间,运行状态,活动连接数,地理位置,处理能力,或最近分配的流量

1. 针对WEB访问请求的负载均衡调度策略(反向代理的网络连接方式)    1) RR(Round Robin)    每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除,在实现RR算法的时候,还需要根据其他的因素: max_fails、fail_timeout、weight等因素进行综合评估        2) WEIGHT(权重)     指定轮询几率,WEIGHT和访问比率成正比,用于后端服务器性能不均的情况    3) IP_HASH     每个请求按访问IP的Hash结果分配,这样每个访客固定访问一个后端服务器,可以解决"负载集群SESSION同步"的问题(因为Hash具有结果一致性)    4) Fair(第三方)     按后端服务器的响应时间来分配请求,响应时间短的优先分配     5) URL_HASH(第三方)    按访问URL的Hash结果来分配请求,使每个URL定向到同一个后端服务器,后端服务器为缓存时比较有效2. 针对网络IP层访问请求的负载均衡调度策略(面向SOCKET的IP负载均衡)    1) 轮叫(Round Robin)    调度器通过"轮叫"调度算法将外部请求按顺序轮流分配到集群中的真实服务器上,它均等地对待每一台服务器,而不管服务器上实际的连接数和系统负载    2) 加权轮叫(Weighted Round Robin)    调度器通过"加权轮叫"调度算法根据真实服务器的不同处理能力来调度访问请求。这样可以保证处理能力强的服务器处理更多的访问流量。调度器可以自动问询真实服务器的负载情况,并动态地调整其权值。    3) 最少链接(Least Connections)    调度器通过"最少连接"调度算法动态地将网络请求调度到已建立的链接数最少的服务器上。如果集群系统的真实服务器具有相近的系统性能,采用"最小连接"调度算法可以较好地均衡负载。    4) 加权最少链接(Weighted Least Connections)    在集群系统中的服务器性能差异较大的情况下,调度器采用"加权最少链接"调度算法优化负载均衡性能,具有较高权值的服务器将承受较大比例的活动连接负载。调度器可以自动问询真实服务器的负载情况,并动态地调整其权值。    5) 基于局部性的最少链接(Locality-Based Least Connections)    "基于局部性的最少链接" 调度算法是针对目标IP地址的负载均衡,目前主要用于Cache集群系统。该算法根据请求的目标IP地址找出该目标IP地址最近使用的服务器,若该服务器 是可用的且没有超载,将请求发送到该服务器;若服
务器不存在,或者该服务器超载且有服务器处于一半的工作负载,则用"最少链接"的原则选出一个可用的服务器,将请求发送到该服务器。 6) 带复制的基于局部性最少链接(Locality-Based Least Connections with Replication) "带复制的基于局部性最少链接"调度算法也是针对目标IP地址的负载均衡,目前主要用于Cache集群系统。它与LBLC算法的不同之处是它要维护从一个目标IP地址到一组服务器的映射,而LBLC算法维护从一个目标IP地址到一台服务
器的映射。该算法根据请求的目标IP地址找出该目标IP地址对应的服务 器组,按"最小连接"原则从服务器组中选出一台服务器,若服务器没有超载,将请求发送到该服务器,若服务器超载;则按"最小连接"原则从这个集群中选出一台服
务器,将该服务器加入到服务器组中,将请求发送到该服务器。同时,当该服务器组有一段时间没有被修改,将最忙的服务器从服务器组中删除,以降低复制的 程度。
7) 目标地址散列(Destination Hashing) "目标地址散列"调度算法根据请求的目标IP地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。 8) 源地址散列(Source Hashing) "源地址散列"调度算法根据请求的源IP地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空

Relevant Link:

http://blog.sina.com.cn/s/blog_9c3ba23d01010rof.htmlhttp://zh.wikipedia.org/wiki/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1_(%E8%AE%A1%E7%AE%97%E6%9C%BA)http://www.linuxvirtualserver.org/zh/lvs1.html#7

 

3. Nginx负载均衡调度算法源码调研

nginx 的负载均衡策略可以划分为两大类:内置策略和扩展策略。内置策略包含加权轮询和ip hash,在默认情况下这两种策略会编译进nginx内核,只需在nginx配置中指明参数即可。扩展策略有很多,如fair、通用hash、 consistent hash等,默认不编译进nginx内核,是第三方模块

在开始学习调度算法之前,我们需要先了解一下Nginx调度算法所涉及到的数据结构,Nginx的负载均衡是根据配置文件进行的,所以Nginx在初始化的时候需要从配置文件中读取配置并保存到相应的结构体中

1. struct ngx_conf_t

\nginx-1.0.14_comment-master\src\core\ngx_core.h

typedef struct ngx_conf_s        ngx_conf_t;

\nginx-1.0.14_comment-master\src\core\ngx_conf_file.h

struct ngx_conf_s {    char                 *name;  //没有使用    ngx_array_t          *args;  //指令的参数    ngx_cycle_t          *cycle; //指向系统参数,在系统整个运行过程中,                                 //需要使用的一些参数、资源需要统一的管理    ngx_pool_t           *pool;  //内存池    ngx_pool_t           *temp_pool; //分配临时数据空间的内存池    ngx_conf_file_t      *conf_file; //配置文件的信息    ngx_log_t            *log; //日志    void                 *ctx;  //模块的配置信息    ngx_uint_t            module_type; //当前指令的类型    ngx_uint_t            cmd_type; //命令的类型    ngx_conf_handler_pt   handler; //指令处理函数,有自己行为的在这里实现    char                 *handler_conf; //指令处理函数的配置信息};

2. struct ngx_http_upstream_srv_conf_t

\nginx-1.0.14_comment-master\src\http\ngx_http_upstream.h

typedef struct ngx_http_upstream_srv_conf_s  ngx_http_upstream_srv_conf_t;

\nginx-1.0.14_comment-master\src\http\ngx_http_upstream.h

struct ngx_http_upstream_srv_conf_s {    ngx_http_upstream_peer_t         peer;    void                           **srv_conf;  // 在ngx_http_upstream()函数中被设置,指向的是本层的srv_conf      ngx_array_t                     *servers;   // ngx_http_upstream_server_t    ngx_uint_t                       flags;     // 调用函数时ngx_http_upstream_add() 指定的标记      ngx_str_t                        host;      // 在函数 ngx_http_upstream_add()中设置(e.g. upstream backend中的backend)    u_char                          *file_name; // "/usr/local/nginx/conf/nginx.conf"      ngx_uint_t                       line;      // proxy在配置文件中的行号      in_port_t                        port;      // 使用的端口号,ngx_http_upstream_add()函数中添加, 指向ngx_url_t-->port,通常在函数ngx_parse_inet_url()中解析    in_port_t                        default_port;  //默认使用的端口号(ngx_http_upstream_add()函数中添加, 指向ngx_url_t-->default_port)};

3. struct ngx_http_upstream_peer_t

\nginx-1.0.14_comment-master\src\http\ngx_http_upstream.h

typedef struct {    //使用负载均衡的类型,默认采用ngx_http_upstream_init_round_robin()    ngx_http_upstream_init_pt        init_upstream;    //使用的负载均衡类型的初始化函数     ngx_http_upstream_init_peer_pt   init;    //us->peer.data = http://www.mamicode.com/peers; 指向的是 ngx_http_upstream_rr_peers_t(函数 ngx_http_upstream_init_round_robin()中设置)    void                            *data;} ngx_http_upstream_peer_t;

ngx_http_upstream_init_peer_pt、ngx_http_upstream_init_pt 是函数指针类型

\nginx-1.0.14_comment-master\src\http\ngx_http_upstream.h

typedef ngx_int_t (*ngx_http_upstream_init_pt)(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us);typedef ngx_int_t (*ngx_http_upstream_init_peer_pt)(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us);

4. struct ngx_array_t

\nginx-1.0.14_comment-master\src\core\ngx_core.h

typedef struct ngx_array_s       ngx_array_t;

\nginx-1.0.14_comment-master\src\core\ngx_array.h

// 动态数组struct ngx_array_s {    // elts指向数组的首地址    void        *elts;     // nelts是数组中已经使用的元素个数    ngx_uint_t   nelts;     // 每个数组元素占用的内存大小    size_t       size;      // 当前数组中能够容纳元素个数的总大小    ngx_uint_t   nalloc;     // 内存池对象    ngx_pool_t  *pool;  };

5. struct ngx_http_upstream_server_t

\nginx-1.0.14_comment-master\src\http\ngx_http_upstream.h
需要注意的是,配置文件中出现的参数只能和某些策略配合使用,所以如果发现某参数没有生效,则应该检查这一点。在配置解析的过程中,这些选项设置都被转换为Nginx内对于的变量值,对应的结构体ngx_http_upstream_server_t如下

typedef struct {    ngx_addr_t                      *addrs;         //指向存储IP地址的数组的指针,host信息(对应的是 ngx_url_t->addrs )      ngx_uint_t                       naddrs;        //与第一个参数配合使用,数组元素个数(对应的是 ngx_url_t->naddrs )      ngx_uint_t                       weight;    ngx_uint_t                       max_fails;    time_t                           fail_timeout;    unsigned                         down:1;    unsigned                         backup:1;} ngx_http_upstream_server_t;

6. struct ngx_http_upstream_rr_peer_t

\nginx-1.0.14_comment-master\src\http\ngx_http_upstream_round_robin.h

typedef struct {    struct sockaddr                *sockaddr;        //后端服务器地址      socklen_t                       socklen;        //后端服务器地址长度    ngx_str_t                       name;        //后端名称      ngx_str_t                       server;    ngx_int_t                       current_weight;    //当前权重,nginx会在运行过程中调整此权重      ngx_int_t                       effective_weight;    //配置的权重    ngx_int_t                       weight;        //current_weight是运行时的动态权值    ngx_uint_t                      fails;        //已尝试失败次数      time_t                          accessed;        //检测失败时间,用于计算超时      time_t                          checked;    ngx_uint_t                      max_fails;        //最大失败次数      time_t                          fail_timeout;    //多长时间内出现max_fails次失败便认为后端down掉了      ngx_uint_t                      down;          /* unsigned  down:1; */#if (NGX_HTTP_SSL)    ngx_ssl_session_t              *ssl_session;   /* local to a process */#endif} ngx_http_upstream_rr_peer_t;

列表最前面需要带有一些head信息,用结构体ngx_http_upstream_rr_peers_t与之对应

7. struct ngx_http_upstream_rr_peers_t

\nginx-1.0.14_comment-master\src\http\ngx_http_upstream_round_robin.h

typedef struct ngx_http_upstream_rr_peers_s  ngx_http_upstream_rr_peers_t;struct ngx_http_upstream_rr_peers_s {    ngx_uint_t                      single;         // unsigned  single:1;      ngx_uint_t                      number;         // 队列中服务器数量     ngx_uint_t                      last_cached; /* ngx_mutex_t                    *mutex; */    ngx_connection_t              **cached;    ngx_str_t                      *name;    ngx_http_upstream_rr_peers_t   *next;           // 后备服务器列表挂载在这个字段下      ngx_http_upstream_rr_peer_t     peer[1];};

0x1: 带权重的RR调度策略