一、Haproxy简介

    HAproxy 是一款高性能的TCP和HTTP负载均衡器。其功能是用来提供基于cookie的持久性,基于内容的交换,过载保护的高级流量管制,自动故障切换,以正则表达式为基础的标题控制运行时间,基于Web的报表,高级日志记录以帮助排除故障的应用或网络,以及其他一些功能。HAProxy运行在当前的硬件上,事件驱动的状态机达到每秒20000点击和超过亿单元以太网,甚至支持数以万计的同步连接。

    HAProxy是法国人Willy Tarreau个人开发的一个开源软件,目标是应对客户端10000以上的同时连接,为后端应用服务器、数据库服务器提供高性能的负载均衡服务。在底层数据结构方面,旧版本HAProxy曾经使用过红黑树,用于任务调度、负载均衡等方面。但是Willy Tarreau认为,在事件响应非常频繁的情况下,任务插入、删除的频率非常高,这时候使用红黑树存在性能瓶颈,尤其不能接受红黑树删除节点的时间复杂度为O(log n)。因此,他发明了一种新的数据结构,叫做弹性二叉树(elastic binary tree),简称ebtree。

    目前新版本的HAProxy已使用ebtree,而除了HAProxy之外,还没有其它著名的开源软件使用ebtree。可以这么说,HAProxy最有特色的地方就是ebtree,ebtree名符其实是HAProxy的独门武器。


二、Haproxy的特性

    HAProxy是免费、极速且可靠的用于为TCP和基于HTTP应用程序提供高可用、负载均衡和代理服务的解决方案,尤其适用于高负载且需要持久连接或7层处理机制的web站点。

   客户端侧的长连接(client-side keep-alive)

   TCP加速(TCP speedups)

   响应池(response buffering)

   基于源的粘性(source-based stickiness)

   更好的统计数据接口(a much better stats interfaces)

   更详细的健康状态检测机制(more verbose health checks)

   基于流量的健康评估机制(traffic-based health)

   基于ACL的持久性(ACL-based persistence)

   ACL:编写内容交换规则;

   负载均衡算法(load-balancing algorithms):更多的算法支持;

   分层设计(layered design):分别实现套接字、TCP、HTTP处理以提供更好的健壮性、更快的处理机制及便捷的演进能力;

   快速、公平调度器(fast and fair scheduler):为某些任务指定优先级可实现理好的QoS;

   会话速率限制(session rate limiting):适用于托管环境;


三、Haproxy的性能

HAProxy借助于OS上几种常见的技术来实现性能的最大化。

  • 单进程、事件驱动模型显著降低了上下文切换的开销及内存占用。

  • O(1)事件检查器(event checker)允许其在高并发连接中对任何连接的任何事件实现即时探测。

  • 在任何可用的情况下,单缓冲(single buffering)机制能以不复制任何数据的方式完成读写操作,这会节约大量的CPU时钟周期及内存带宽;

  • 借助于Linux 2.6 (>= 2.6.27.19)上的splice()系统调用,HAProxy可以实现零复制转发(Zero-copy forwarding),在Linux 3.5及以上的OS中还可以实现零复制启动(zero-starting);

  • MRU内存分配器在固定大小的内存池中可实现即时内存分配,这能够显著减少创建一个会话的时长;

  • 树型存储:侧重于使用作者多年前开发的弹性二叉树,实现了以O(log(N))的低开销来保持计时器命令、保持运行队列命令及管理轮询及最少连接队列;

  • 优化的HTTP首部分析:优化的首部分析功能避免了在HTTP首部分析过程中重读任何内存区域;

  • 精心地降低了昂贵的系统调用,大部分工作都在用户空间完成,如时间读取、缓冲聚合及文件描述符的启用和禁用等;

    所有的这些细微之处的优化实现了在中等规模负载之上依然有着相当低的CPU负载,甚至于在非常高的负载场景中,5%的用户空间占用率和95%的系统空间占用率也是非常普遍的现象,这意味着HAProxy进程消耗比系统空间消耗低20倍以上。因此,对OS进行性能调优是非常重要的。即使用户空间的占用率提高一倍,其CPU占用率也仅为10%,这也解释了为何7层处理对性能影响有限这一现象。由此,在高端系统上HAProxy的7层性能可轻易超过硬件负载均衡设备。

   在生产环境中,在7层处理上使用HAProxy作为昂贵的高端硬件负载均衡设备故障故障时的紧急解决方案也时长可见。硬件负载均衡设备在“报文”级别处理请求,这在支持跨报文请求(request across multiple packets)有着较高的难度,并且它们不缓冲任何数据,因此有着较长的响应时间。对应地,软件负载均衡设备使用TCP缓冲,可建立极长的请求,且有着较大的响应时间


四、Haproxy基本实现

haproxy的安装,这里演示的为centos 7,通过yum光盘镜像安装

[root@localhost ~]# yum install haproxy
[root@localhost ~]# rpm -ql haproxy
/etc/haproxy/haproxy.cfg                        #主配置文件
/etc/logrotate.d/haproxy                        #日志回滚
/usr/lib/systemd/system/haproxy.service         #服务管理单元
/usr/sbin/haproxy                               #haproxy主程序


这里先实现一个最基本的基于轮询负载负载均衡,先简单简单配置两个httpd服务器

[root@wlw1 ~]# echo 'My web1 192.168.0.45' > /var/www/html/index.html
[root@wlw2 ~]# echo 'My web2 192.168.0.66' > /var/www/html/index.html

haproxy服务器配置如下,

vim /etc/haproxy/haproxy.cfg 
frontend  main *:80
    default_backend     webserver

backend webserver
    balance     roundrobin
    server      web.ip45 192.168.0.45:80 check
    server      web.ip66 192.168.0.66:80 check

实验结果,可以看到实现了轮询效果

[root@localhost ~]# curl http://192.168.0.56
My web1 192.168.0.45
[root@localhost ~]# curl http://192.168.0.56
My web2 192.168.0.66
[root@localhost ~]# curl http://192.168.0.56
My web1 192.168.0.45
[root@localhost ~]# curl http://192.168.0.56
My web2 192.168.0.66


五、Haproxy常见全局global配置选项及其用法

①、log:通过logserver记录日志,定义全局的syslog服务器,最多可以定义两个

vim /etc/haproxy/haproxy.cfg
global
    log         127.0.0.1 local2
[root@localhost ~]# vim /etc/rsyslog.conf
$ModLoad imudp                            #开启syslog监听服务    
$UDPServerRun 514

local2.*                                                /var/log/haproxy.log

配置好全局日志后通过查看访问日志查询是否成功生效

[root@localhost ~]# tail /var/log/haproxy.log 
Oct 24 06:25:51 localhost haproxy[1626]: 192.168.0.56:39072 [24/Oct/2015:06:25:51.817] main webserver/web.ip45 4/0/4/8/16 200 289 - - ---- 1/1/0/0/0 0/0 "GET / HTTP/1.1"
Oct 24 06:25:52 localhost haproxy[1626]: 192.168.0.56:39075 [24/Oct/2015:06:25:52.687] main webserver/web.ip66 0/0/1/8/9 200 289 - - ---- 1/1/0/1/0 0/0 "GET / HTTP/1.1"
#上面信息,192.168.0.56发送的请求,由main前端响应,这里对应我们配置文件中frontend配置的名字,发送给webserver/web.ip45后端服务器的web.ip45web服务器处理。


②、log-send-hostname <string>:在syslog信息的首部添加当前主机名,可以为“string”指定的名称,也可以缺省使用当前主机名;


③、nbproc <number>:指定启动的haproxy进程的个数,只能用于守护进程模式的haproxy;默认只启动一个进程,鉴于调试困难等多方面的原因,一般只在单进程仅能打开少数文件描述符的场景中才使用多进程模式;


④、ulimit-n <number>:设定每进程所能够打开的最大文件描述符数目,默认情况下其会自动进行计算,因此不推荐修改此选项;


⑤、maxconn <number>:设定每个haproxy进程所接受的最大并发连接数,“ulimit -n”自动计算的结果正是参照此参数设定的;


⑥、spread-checks <0..50, in percent>:在haproxy后端有着众多服务器的场景中,在精确的时间间隔后统一对众服务器进行健康状况检查可能会带来意外问题;此选项用于将其检查的时间间隔长度上增加或减小一定的随机时长;


六、Haproxy常见代理配置选项及其用法

①、balance:定义负载均衡算法,可用于“defaults”、“listen”和“backend”。

支持以下算法(动态:权重可动态调整,修改权重后无需重启服务。静态:调整权重不会实时生效,需要重启服务)

  • roundrobin: 基于权重轮询,动态算法,每个后端主机最多支持4128个连接;

  • static-rr:基于权重轮询,静态算法,每个后端主机支持的数量无上限;

  • leastconn:新的连接请求被派发至具有最少连接数目的后端服务器;在有着较长时间会话的场景中推荐使用此算法,如LDAP、SQL等,其并不太适用于较短会话的应用层协议,如HTTP;此算法是动态的,可以在运行时调整其权重;

  • source:将请求的源地址进行hash运算,并由后端服务器的权重总数相除后派发至某匹配的服务器;这可以使得同一个客户端IP的请求始终被派发至某特定的服务器;不过,当服务器权重总数发生变化时,如某服务器宕机或添加了新的服务器,许多客户端的请求可能会被派发至与此前请求不同的服务器;常用于负载均衡无cookie功能的基于TCP的协议;其默认为静态,不过也可以使用hash-type修改此特性;

  • uri:对URI的左半部分(“问题”标记之前的部分)或整个URI进行hash运算,并由服务器的总权重相除后派发至某匹配的服务器;这可以使得对同一个URI的请求总是被派发至某特定的服务器,除非服务器的权重总数发生了变化;此算法常用于代理缓存或反病毒代理以提高缓存的命中率;需要注意的是,此算法仅应用于HTTP后端服务器场景;其默认为静态算法,不过也可以使用hash-type修改此特性;

    URL Syntax: <scheme>://<host>:<port>/<path>;<params>?<query>#<frag>

             http://www.wlw.com/test/go;type=s

vim /etc/haproxy/haproxy.cfg 
frontend  main *:80
    default_backend     webserver
    
backend webserver
    balance     uri
    hash-type   consistent
    server      web.ip45 192.168.0.45:80 check
    server      web.ip66 192.168.0.66:80 check

为了测试出结果。我们为web1和web2各提供五个页面供测试

[root@wlw1 ~]# for i in {1..5};do echo "Page $i from  web.ip45" > /var/www/html/page$i.html;done
[root@wlw2 ~]# for i in {1..5};do echo "Page $i from  web.ip66" > /var/www/html/page$i.html;done

这里我们使用三个客户端来测试结果,可以看到只要是访问同一个uri都是调度到同一台服务器上

[root@localhost ~]# curl http://192.168.0.56/page1.html
Page 1 from  web.ip66
[root@wlw1 ~]# curl http://192.168.0.56/page1.html
Page 1 from  web.ip66
[root@wlw2 ~]# curl http://192.168.0.56/page1.html
Page 1 from  web.ip66

[root@localhost ~]# curl http://192.168.0.56/page4.html
Page 4 from  web.ip45
[root@wlw1 ~]# curl http://192.168.0.56/page4.html
Page 4 from  web.ip45
[root@wlw2 ~]# curl http://192.168.0.56/page4.html
Page 4 from  web.ip45


  • 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修改此特性;

[root@localhost ~]# vim /etc/haproxy/haproxy.cfg 
frontend  main *:80
    default_backend     webserver

backend webserver
    balance     hdr(User-Agent)
    hash-type   consistent
    server      web.ip45 192.168.0.45:80 check
    server      web.ip66 192.168.0.66:80 check

这里我们使用curl的选项-A来模拟不同浏览器,测试我们根据用户浏览器类型来调度到不同的服务器

[root@localhost ~]# curl -A 'Google' http://192.168.0.56/index.html
My web2 192.168.0.66
[root@localhost ~]# curl -A 'IE 6' http://192.168.0.56/index.html
My web1 192.168.0.45


②、hash-type <method>

    定义用于将hash码映射至后端服务器的方法;其不能用于frontend区段;可用方法有map-based和consistent,在大多数场景下推荐使用默认的map-based方法。

    map-based:hash表是一个包含了所有在线服务器的静态数组。其hash值将会非常平滑,会将权重考虑在列,但其为静态方法,对在线服务器的权重进行调整将不会生效,这意味着其不支持慢速启动。此外,挑选服务器是根据其在数组中的位置进行的,因此,当一台服务器宕机或添加了一台新的服务器时,大多数连接将会被重新派发至一个与此前不同的服务器上,对于缓存服务器的工作场景来说,此方法不甚适用。

    consistent:hash表是一个由各服务器填充而成的树状结构;基于hash键在hash树中查找相应的服务器时,最近的服务器将被选中。此方法是动态的,支持在运行时修改服务器权重,因此兼容慢速启动的特性。添加一个新的服务器时,仅会对一小部分请求产生影响,因此,尤其适用于后端服务器为cache的场景。不过,此算法不甚平滑,派发至各服务器的请求未必能达到理想的均衡效果,因此,可能需要不时的调整服务器的权重以获得更好的均衡性。


③、bind:此指令仅能用于frontend和listen区段,用于定义一个或几个监听的套接字。

[root@localhost ~]# vim /etc/haproxy/haproxy.cfg 
frontend  main 
    bind *:80
    bind *:8989

[root@localhost ~]# ss -tnlp
[root@localhost ~]# ss -tnlp
State      Recv-Q Send-Q             Local Address:Port               Peer Address:Port 
LISTEN     0      128                            *:80                            *:*      users:(("haproxy",2146,5))
LISTEN     0      128                            *:8989                          *:*      users:(("haproxy",2146,6))


④、mode: HAProxy的工作模式;默认为tcp;还有基于web应用的http


⑤、为每个实例启用事件和流量日志,因此可用于所有区段。每个实例最多可以指定两个log参数,不过,如果使用了“log global”且"global"段已经定了两个log参数时,多余了log参数将被忽略。


⑥、maxconn:设定一个前端的最大并发连接数,因此,其不能用于backend区段。对于大型站点来说,可以尽可能提高此值以便让haproxy管理连接队列,从而避免无法应答用户请求。当然,此最大值不能超出“global”段中的定义。


⑦、default_backend:在没有匹配的"use_backend"规则时为实例指定使用的默认后端,因此,其不可应用于backend区段。在"frontend"和"backend"之间进行内容交换时,通常使用"use-backend"定义其匹配规则;而没有被规则匹配到的请求将由此参数指定的后端接收。


⑧、server:为后端声明一个server,因此,不能用于defaults和frontend区段。

server <name> <addr>[:port] [param*]

  • backup: 设定当前server为backup server;

  • check: 健康状态检测;

    • inter <delay>:检测时间间隔;单位为ms, 默认为2000; 

    • fall: up --> down, soft state, soft state, hard state; 

    • rise:down --> up, 

后端http服务时的健康状态的检测方法:

option httpchk:请求首页,对方响应200即为成功

option httpchk <uri>:指定的uri,对方响应200即为成功

option httpchk <method> <uri>:指定方法和uri

option httpchk <method> <uri> <version>:不能用于frontend段

backend https_relay
    mode tcp
    option httpchk OPTIONS * HTTP/1.1\r\nHost:\ 
    #使用OPTINOS方法请求,请求uriHTTP/1.1\r\nHost:\ www.wlw.com
    server web1 192.168.0.88:443 check port 80
    #转发服务器为192.168.0.88:443,请求443端口。然而可以对80端口做检测

  • cookie <value>:为指定server设定cookie值,此处指定的值将在请求入站时被检查,第一次为此值挑选的server将在后续的请求中被选中,其目的在于实现持久连接的功能;基于cookie会话的源绑定。

  • maxconn: 此服务接受的并发连接的最大数量;

  • maxqueue: 请求队列的最大长度;

  • observe: 根据流量判断后端server的健康状态;

  • weight: 指定权重,默认为1,最大为256;0表示不被调度,即为下线。

  • redir <prefix>: 启用重定向功能,将发往此服务器的GET和HEAD请求均以302状态码响应;需要注意的是,在prefix后面不能使用/,且不能使用相对地址,以免造成循环

server web1 192.168.0.88:80 redir http://img.wlw.com check


⑨、stats:启用基于程序编译时默认设置的统计报告,不能用于“frontend”区段。

listen stats
    bind *:1515                        #让stats统计报告监听在1515端口上
    stats enable                       #开启stats功能
    stats hide-version                 #隐藏版本信息
    stats uri /hpadmin?stats           #设置uri
    stats realm HAporxy\ admin         #开启认证时登录框显示的信息
    stats auth wlw:wlw                 #认证的账号密码
    stats auth test:test
    stats admin if TRUE                #如果上面条件认证通过,开启管理接口
    stats scope .                      #仅能为127.0.0.1本地访问

wKioL1YsS2GTCH9hAAinKTOAJ6w049.jpg


⑩、option forwardfor:允许在发往服务器的请求首部中插入“X-Forwarded-For”首部。

[root@localhost ~]# vim /etc/haproxy/haproxy.cfg 
defaults
    option forwardfor       except 127.0.0.0/8
    
[root@wlw ~]# vim /etc/httpd/conf/httpd.conf 
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
#为http访问日志中的主机替换为发送客户端首部的真实IP地址

[root@wlw ~]# tail /var/log/httpd/access_log    
192.168.0.102 - - [25/Oct/2015:05:48:43 +0800] "GET / HTTP/1.1" 304 - "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"


十一、errorfile:在用户请求不存在的页面时,返回一个页面文件给客户端,这里可用的状态码有200、400、403、408、500、502、503和504;

errorfile 503 /etc/haproxy/errorpages/503sorry.http


十二、添加请求或响应报文首部:reqadd,请求报文发完server的时候添加。rspadd,响应报文发给客户端的时候添加。

[root@localhost ~]# vim /etc/haproxy/haproxy.cfg
frontend  main
    bind *:8989
    bind *:80
    default_backend     webserver
    rspadd Via:\ 192.168.0.56

[root@localhost ~]# curl -I http://192.168.0.56
HTTP/1.1 200 OK
Via: 192.168.0.56


六、Haproxy实现LAMP动静分离

[root@localhost system]# vim /etc/haproxy/haproxy.cfg
frontend http
    bind *:80
    mode http
    acl url_static path_beg -i /static /p_w_picpaths /javascript /stylesheets
    acl url_static path_end -i .htm .jpg .jpeg .gif .png .css .js

    use_backend static_servers          if url_static
    default_backend dynamic_servers

backend static_servers
    balance roundrobin
    server web_static 192.168.0.45:80 check

backend dynamic_servers
    cookie srv insert nocache
    balance roundrobin
    server web_dynamic 192.168.0.66:80 check cookie dynamic

在浏览器中通过开发工具查看

wKiom1YsQr-xXn6yAARIDD5JnL8558.jpg

测试把192.168.0.66的httpd关闭。因为192.168.0.66是响应动态页面的服务器,所以关闭后php页面无法响应,但是访问图片正常

wKiom1YsQ7aD29pYAADjlKbE6zw092.jpg

wKioL1YsQ-WTdc4bAAFRRNsAf6I646.jpg