HAProxy是一种反向代理,能够提供提供高可用性、负载均衡以及基于TCP和HTTP应用的代理,HAProxy特别适用于那些负载特大的web站点。HAProxy实现了一种事件驱动、 单一进程模型,此模型支持非常大的并发连接数。多进程或多线程模型受内存限制 、系统调度器限制以及无处不在的锁限制,很少能处理数千并发连接。事件驱动模型因为在有更好的资源和时间管理的用户空间(User-Space) 实现所有这些任务,所以可轻易突破c10k的限制。
在正式介绍HAProxy前,让我们先了解下什么交反向代理。
代理一般可以分为正向代理和反向代理两类,简单的说,正向代理代理的是客户端,代表一定范围内的客户端到互联网上的各服务器上获取相关资源;反向代理代理的是服务器端,代表一定范围内的服务器接收并处理来自互联网上任何客户端的请求。
目前HAProxy提供了均由1.2版本衍生发展而来的两个主要版本:
1.3― ―内容交换和超强负载;
1.4― ―提供较好的弹性;
本文的所有介绍都是基于1.4版本的。
通过下图,我们可以形象的认识到HAProxy的工作位置及作用:
实验拓扑设计
本文的案例演示将依照上述拓扑实现
HAProxy的所有配置均在如下配置段被完成:
全局配置
global:
配置中的参数为进程级别的参数,且通常与其运行的OS相关;
代理配置
defaults:用于为所有其它配置段提供默认参数,这配置默认配置参数可由下一个“defaults”所重新设定;
frontend:用于定义一系列监听的套接字,这些套接字可接受客户端请求并与之建立连接;
backend:用于定义一系列“后端”服务器,代理将会将对应客户端的请求转发至这些服务器;
listen:通过关联“前端”和“后端”定义了一个完整的代理,通常只对TCP流量有用;
注意:所有代理的名称只能使用大写字母、小写字母、数字、-(中线)、_(下划线)、.(点号)和:(冒号)。此外,ACL名称会区分字母大小写。
global
# to have these messages end up in /var/log/haproxy.log you will
# need to:
#
# 1) configure syslog to accept network log events. This is done
# by adding the '-r' option to the SYSLOGD_OPTIONS in
# /etc/sysconfig/syslog
#
# 2) configure local2 events to go to the /var/log/haproxy.log
# file. A line like the following can be added to
# /etc/sysconfig/syslog
#
# local2.* /var/log/haproxy.log
#
log 127.0.0.1 local2//日志记录位置
chroot /var/lib/haproxy//haproxy工作目录
pidfile /var/run/haproxy.pid//pid文件位置
maxconn 4000//最大并发连接数
user haproxy//运行haproxy进程的用户
group haproxy//运行haproxy进程的组
daemon//以守护进程的模式运行
# turn on stats unix socket
stats socket /var/lib/haproxy/stats
案例1:配置haproxy记录日志
在上述global段定义完日志记录的服务器和位置后,要修改/etc/rsyslog.conf配置文件如下所以,开启日志记录功能:
# Provides UDP syslog reception
$ModLoad imudp
$UDPServerRun 514//启用服务监听端口
local2.* /var/log/haproxy.log//配置相应facility、priority及记录位置
案例2:通过haproxy实现网站的动静分离
要实现客户端访问的动静分离,必须依赖于acl的机制,那么,什么是acl呢?
haproxy的ACL用于实现基于请求报文的首部、响应报文的内容或其它的环境状态信息来做出转发决策,这大大增强了其配置弹性。其配置法则通常分为两步,首先去定义ACL,即定义一个测试条件,而后在条件得到满足时执行某特定的动作,如阻止请求或转发至某特定的后端。
定义acl的语法格式:
acl <aclname> <criterion> [flags] [operator] <value> ...
<aclname>:
ACL名称,区分字符大小写,且其只能包含大小写字母、数字、-(连接线)、_(下划线)、.(点号)和:(冒号);haproxy中,acl可以重名,这可以把多个测试条件定义为一个共同的acl;
<criterion>:
测试标准,即对什么信息发起测试;测试方式可以由[flags]指定的标志进行调整;而有些测试标准也可以需要为其在<value>之前指定一个操作符[operator];
[flags]:
<value>匹配<criterion>时采取的机制,目前haproxy的acl支持的标志位有3个:
-i:不区分<value>中模式字符的大小写;
-f:从指定文件中加载模式;
--:标志符的强制结束标记,在模式中的字符串有相应标识符时使用;
<value>:
acl测试条件支持的值,有以下四类:
整数或整数范围:
如1024:65535表示从1024至65535;仅支持使用正整数(如果出现类似小数的标识,其为通常为版本测试),且支持使用的操作符有5个,分别为eq、ge、gt、le和lt;
字符串:
支持使用“-i”以忽略字符大小写,支持使用“\”进行转义;如果在模式首部出现了-i,可以在其之前使用“--”标志位;
正则表达式:
其机制类同字符串匹配;
IP地址及网络地址
注意:同一个acl中可以指定多个测试条件,这些测试条件需要由逻辑操作符指定其关系。条件间的组合测试关系有三种:“与”(默认即为与操作)、“或”(使用“||”操作符)以及“非”(使用“!”操作符)。
本案例需要的两个测试条件:
path_beg <string>
用于测试请求的URL是否以<string>指定的模式开头。
path_end<string>
用于测试请求的URL是否以<string>指定的模式结尾。
有了上边的基础,我们很容易就可以配置出一个简单的动静分离的web站点,如下图所示:
案例3:安全的状态页面管理配置
状态统计页面会显示一些有用信息,包括HAProxy的版本号、前后端机组的各种负载状态等,配置启用此功能可实现功能的直观查看和管理。显而易见,此项功能应尽量仅向拥有权限的人员开放。
配置实例:
listen stats_page
bind *:9800//基于安全目的,可将stats单独监听于本地的某自定端口上
stats enable//开启stats功能
stats hide-version//隐藏版本信息,防止被针对相应版本的漏洞进行攻击
stats scope . //自定义显示区段
stats uri /haproxyadmin?stats//查看路径
stats realm Haproxy\Statistics//登录提示信息中有空格时需用“\”进行转义
stats auth statsadmin:password//基于帐号密码认证登录
stats auth statsmaster:password
stats admin if TRUE//若帐号密码正确,可提供管理功能
案例4:session sticky
backend appserv
balance roundrobin
option httpchk
cookie SERVERID insert indirect nocache
server web1 172.16.14.2:80 check inter 2000 rise 1 fall 3 cookie web1
server web2 172.16.14.3:80 check inter 2000 rise 1 fall 3 cookie web2
如上图所示,此设定是通过在请求首部中插入第一次调度到的server信息“SERVER=web1”,来实现将以后所有来自同一用户的请求都调度到同一个server上,实现会话绑定;
最后,介绍下haproxy实现负载均衡的几种主要算法
balance <algorithm> [ <arguments> ]
balance url_param <param> [check_post [<max_wait>]]
定义负载均衡算法,可用于“defaults”、“listen”和“backend”段中。<algorithm>用于在负载均衡场景中挑选指定server,其仅应用于持久信息不可用或需要将一个连接重新调度至另一个服务器时。支持的算法有:
roundrobin:
基于权重进行轮叫,在服务器的处理时间保持均匀分布时,这是最平衡、最公平的算法。此算法是动态的,这表示其权重可以在运行时进行调整,不过,在设计上,每个后端服务器仅能最多接受4128个连接;
static-rr:
基于权重进行轮叫,与roundrobin类似,但是为静态方法,在运行时调整其服务器权重不会实时生效;不过,其在后端服务器连接数上没有限制;
leastconn:
新的连接请求被派发至具有最少连接数目的后端服务器;在有着较长时间会话的场景中推荐使用此算法,如LDAP、SQL等,其并不太适用于较短会话的应用层协议,如HTTP;此算法是动态的,可以在运行时调整其权重;
source:
将请求的源地址进行hash运算,并由后端服务器的权重总数相除后派发至某匹配的服务器;这可以使得同一个客户端IP的请求始终被派发至某特定的服务器;不过,当服务器权重总数发生变化时,如某服务器宕机或添加了新的服务器,许多客户端的请求可能会被派发至与此前请求不同的服务器;常用于负载均衡无cookie功能的基于TCP的协议;其默认为静态,不过也可以使用hash-type修改此特性:hash-type为map-based时为静态,hash-type为consistent时为动态;
uri:
对URI的左半部分(“问题”标记之前的部分)或整个URI进行hash运算,并由服务器的总权重相除后派发至某匹配的服务器;这可以使得对同一个URI的请求总是被派发至某特定的服务器,除非服务器的权重总数发生了变化;此算法常用于代理缓存或反病毒代理以提高缓存的命中率;需要注意的是,此算法仅应用于HTTP后端服务器场景;其默认为静态算法,不过也可以使用hash-type修改此特性;
url_param:
通过<argument>为URL指定的参数在每个HTTP GET请求中将会被检索;如果找到了指定的参数且其通过等于号“=”被赋予了一个值,那么此值将被执行hash运算并被服务器的总权重相除后派发至某匹配的服务器;此算法可以通过追踪请求中的用户标识进而确保同一个用户ID的请求将被送往同一个特定的服务器,除非服务器的总权重发生了变化;如果某请求中没有出现指定的参数或其没有有效值,则使用轮叫算法对相应请求进行调度;此算法默认为静态的,不过其也可以使用hash-type修改此特性;
hdr(<name>):
对于每个HTTP请求,通过<name>指定的HTTP首部将会被检索;如果相应的首部没有出现或其没有有效值,则使用轮叫算法对相应请求进行调度;其有一个可选选项“use_domain_only”,可在指定检索类似Host类的首部时仅计算域名部分(比如通过www.magedu.com来说,仅计算magedu字符串的hash值)以降低hash算法的运算量;此算法默认为静态的,不过其也可以使用hash-type修改此特性;
本文提到的两种hash-type简介
(1)map-based:取模算法
用请求url的hash值除以缓存服务器的节点数取余数将请求调度到指定节点上;
缺陷:一旦节点数变化(节点故障或添加新的节点),则整个调度结果几乎完全混乱,导致缓存命中率急遽下降,几乎需要重建整个缓存;
(2)consistent:一致性hash算法
consistent hashing
设计一个闭合的hash环,将32位数字(0~2^32 -1)均匀的分布在环上,将请求url作hash计算的结果除以2^32 ,则所得结果必然是环上的数字之一;而对缓存服务器的节点也作hash计算,其结果同样除以2^32,计算出其在hash环上的位置;调度时作顺时针旋转,将请求调度到第一个到达的节点上
如上图所示,正常情况下A请求将被调度到node1上,当node1因故障拆除后,其将被调度到node2上,由此可知,受影响的只是node4~node1之间的请求,其他区域的请求依然可以命中缓存;
缺陷:调度容易出现偏斜,如上右图所示,造成node1压力过大而其他三个节点却比较空闲;
解决方案:生成虚拟节点,如分别将node1、2、3、4各生成100个虚拟节点分布到hash环上