###############################################################################
#
Name : Mahavairocana
#
Author : Mahavairocana
#
QQ : 10353512
#
WeChat : shenlan-qianlan
#
Blog : http://www.cnblogs.com/Mahavairocana/
#
Description : You are welcome to reprint, or hyperlinks to indicate the
#
source of the article, as well as author
information.
###############################################################################
一、简介
Haproxy:高可用性、负载均衡以及基于TCP(第四层)和HTTP(第七层)应用的代理软件
HAProxy 支持两种主要的代理模式:"tcp"也即4 层(大多用于邮件服务器、内部协议通信服务器等),和7 层(HTTP)。在4 层模式下,HAproxy仅在客户端和服务器之间转发双向流量。7 层模式下,HAProxy 会分析协议,并且能通过允许、拒绝、交换、增加、修改或者删除请求(request)或者回应(response)里指定内容来控制协议,这种操作要基于特定规则。
二、术语
1、基于cookie 插入的简单HTTP 负载均衡
配置 haproxy (LB1) : ------------------------- listen webfarm 192.168.1.1:80 mode http balance roundrobin cookie SERVERID insert indirect option httpchk HEAD /index.html HTTP/1.0 server webA 192.168.1.11:80 cookie A check server webB 192.168.1.12:80 cookie B check server webC 192.168.1.13:80 cookie C check server webD 192.168.1.14:80 cookie D check 描述: ------------- - LB1 接收clients的requests - 如果1个request不包含cookie,则把这个request前传到分配的一个有效的server - 作为回报, 1个拥有server名称的cookie "SERVERID"会被插入到response中 - 当client带有cookie "SERVERID=A"再此访问时,LB1就会知道这个request必须被前传到server A. 同时删除这个cookie是的server不会看到它 - 当server"webA"宕机时,request会被前传至另外一个有效的server,并且重新分配cookie
2、带cookie前缀的HTTP负载均衡和高可用性
现在你可以不用添加更多的cookie,而是使用已有的cookie。如果应用已经生成J足够跟踪session的SESSIONID cookie,我们会在看到该cookie时,在它前面加上server name前缀。由于load-balancer变得关键,因此,可以通过利用keepalived使得运行在VRRP模式下的第二台热备它。 从网站上下载最新版本的keepalived,并且安装在load-balancer LB1和LB2上。 在两个load-balancer(我们仍然使用原始IP)之间,我们使用一个共享IP。在任何时刻只有1个load-balancers处于活跃状态。为了允许proxy在Linux2.4下绑定一个共享IP,需要在/proc中启用如下配置: # echo 1 >/proc/sys/net/ipv4/ip_nonlocal_bind shared IP=192.168.1.1 192.168.1.3 192.168.1.4 192.168.1.11-192.168.1.14 192.168.1.2 -------+------------+-----------+-----+-----+-----+--------+---- | | | | | | _|_db +--+--+ +--+--+ +-+-+ +-+-+ +-+-+ +-+-+ (___) | LB1 | | LB2 | | A | | B | | C | | D | (___) +-----+ +-----+ +---+ +---+ +---+ +---+ (___) haproxy haproxy 4 cheap web servers keepalived keepalived proxies上的配置(LB1 and LB2): listen webfarm 192.168.1.1:80 mode http balance roundrobin cookie JSESSIONID prefix option httpclose option forwardfor option httpchk HEAD /index.html HTTP/1.0 server webA 192.168.1.11:80 cookie A check server webB 192.168.1.12:80 cookie B check server webC 192.168.1.13:80 cookie C check server webD 192.168.1.14:80 cookie D check 提示:proxy会修改client和server发出的每个cookie,因此proxy能够访问每个session中的所有request的所有cookie是非常重要的。这意味着不是keepalive(HTTP/1.1),需要使用'httpclose'选项。只有你能确认clients不会使用keepalive,才能删除这个选项。 LB1和LB2上keepalived的配置: vrrp_script chk_haproxy { # Requires keepalived-1.1.13 script "killall -0 haproxy" # cheaper than pidof interval 2 # check every 2 seconds weight 2 # add 2 points of prio if OK } vrrp_instance VI_1 { interface eth0 state MASTER virtual_router_id 51 priority 101 # 101 on master, 100 on backup virtual_ipaddress { 192.168.1.1 } track_script { chk_haproxy } } 描述: —LB1是VRRP的主(keepalived),LB2是备。他们都监控haproxy进程,并且如果haproxy failed则降低他们的权重,迁移至另外的节点; —LB1将在IP:192.168.1.1上接收client request —两个LB用他们的内部IP发送健康检测 —如果request不包含cookie,request会被前传至1个有效的server —在回复时,如果看到JESSIONID cookie,proxy会在cookie加上以('~')为分隔符的server name前缀; —如果client再次访问时带着"JSESSIONID=A~xxx" cookie,LB1会知道这个request必须前传至 server A。在把cookie发送给server之前,cookie中的server name会先被剔除出来。 —如果server "webA"宕机,requests会被前传到另外的有效服务器,并且会重新分配cookie; 数据流: (client) (haproxy) (server A) >-- GET /URI1 HTTP/1.0 ------------> | ( no cookie, haproxy forwards in load-balancing mode. ) | >-- GET /URI1 HTTP/1.0 ----------> | X-Forwarded-For: 10.1.2.3 | <-- HTTP/1.0 200 OK -------------< ( no cookie, nothing changed ) <-- HTTP/1.0 200 OK ---------------< | >-- GET /URI2 HTTP/1.0 ------------> | ( no cookie, haproxy forwards in lb mode, possibly to another server. ) | >-- GET /URI2 HTTP/1.0 ----------> | X-Forwarded-For: 10.1.2.3 | <-- HTTP/1.0 200 OK -------------< | Set-Cookie: JSESSIONID=123 ( the cookie is identified, it will be prefixed with the server name ) <-- HTTP/1.0 200 OK ---------------< | Set-Cookie: JSESSIONID=A~123 | >-- GET /URI3 HTTP/1.0 ------------> | Cookie: JSESSIONID=A~123 | ( the proxy sees the cookie, removes the server name and forwards to server A which sees the same cookie as it previously sent ) | >-- GET /URI3 HTTP/1.0 ----------> | Cookie: JSESSIONID=123 | X-Forwarded-For: 10.1.2.3 | <-- HTTP/1.0 200 OK -------------< ( no cookie, nothing changed ) <-- HTTP/1.0 200 OK ---------------< | ( ... ) 提示: 有时,在一个集群中有一些性能强劲的server,也有一些性能差一些的server。在这种情况下,很有必要告诉haproxy这些server在性能上的差异。假设WebA和WebB是两台老的P3-1.2 GHz,WebC和WebD是最新的Opteron-2.6 GHz。如果你的application关注CPU,则你可以假设这两台服务器之间的性能比为2.6/1.2。你可以通过使用1-256之间数值标记的"weight"关键字,告诉haproxy这些性能差异。它可以用这些比例来平滑load: server webA 192.168.1.11:80 cookie A weight 12 check server webB 192.168.1.12:80 cookie B weight 12 check server webC 192.168.1.13:80 cookie C weight 26 check server webD 192.168.1.14:80 cookie D weight 26 check 2.1 包含外部L4 load-balancers的变化 作为基于VRRP主备方案的替代方案,他们也可以使用L4 load-balancer(如:Alteon)进行负载,这些L4 load-balancer也可以检查这些运行在proxy上的服务: | VIP=192.168.1.1 +----+----+ | Alteon | +----+----+ | 192.168.1.3 | 192.168.1.4 192.168.1.11-192.168.1.14 192.168.1.2 -------+-----+------+-----------+-----+-----+-----+--------+---- | | | | | | _|_db +--+--+ +--+--+ +-+-+ +-+-+ +-+-+ +-+-+ (___) | LB1 | | LB2 | | A | | B | | C | | D | (___) +-----+ +-----+ +---+ +---+ +---+ +---+ (___) haproxy haproxy 4 cheap web servers proxy(LB1和LB2)上的配置: listen webfarm 0.0.0.0:80 mode http balance roundrobin cookie JSESSIONID prefix option httpclose option forwardfor option httplog option dontlognull option httpchk HEAD /index.html HTTP/1.0 server webA 192.168.1.11:80 cookie A check server webB 192.168.1.12:80 cookie B check server webC 192.168.1.13:80 cookie C check server webD 192.168.1.14:80 cookie D check "dontlognull"选项用来防止记录Alteon发出的健康检测。如果一个session交互没有数据,这个session就不会被记录。 Alteon上的配置: /c/slb/real 11 ena name "LB1" rip 192.168.1.3 /c/slb/real 12 ena name "LB2" rip 192.168.1.4 /c/slb/group 10 name "LB1-2" metric roundrobin health tcp add 11 add 12 /c/slb/virt 10 ena vip 192.168.1.1 /c/slb/virt 10/service http group 10 提示:Alteon上的健康检测被设置为'tcp',用来防止proxy把连接前传。Alteon也可以设置为'http',但是这样proxy必须把配置Alteon的地址为"monitor-net",那样Alteon可以真实的以http会话的方式检测proxy而不会把连接前传到后端的servers。检查下一节可以看到怎样使用monitor-net的例子。 2.2 通用的TCP中继和外部L4 load-balancers 有时,能够中继通用的TCP协议(如:SMTP, TSE, VNC等)是非常有用的,如访问内部稀有网络。当你在使用外部 load-balancers需要发送周期行的健康检测至proxy时问题就会出现,这是因为健康检测会被前传至后端servers。对这个问题的1个解决方案是通过使用"monitor-net"用一个专用的网络来监控系统,并且必须不会前传连接和记录。 提示:这个特性只有在1.1.32或者1.2.6版本以上才支持。 | VIP=172.16.1.1 | +----+----+ +----+----+ | Alteon1 | | Alteon2 | +----+----+ +----+----+ 192.168.1.252 | GW=192.168.1.254 | 192.168.1.253 | | ------+---+------------+--+-----------------> TSE farm : 192.168.1.10 192.168.1.1 | | 192.168.1.2 +--+--+ +--+--+ | LB1 | | LB2 | +-----+ +-----+ haproxy haproxy 在LB1和LB2上的配置: listen tse-proxy bind :3389,:1494,:5900 # TSE, ICA and VNC at once. mode tcp balance roundrobin server tse-farm 192.168.1.10 monitor-net 192.168.1.252/31 "monitor-net"选项指示haproxy对来自192.168.1.252和192.168.1.253的请求不记录、不前传并且马上关闭连接。Alteon load-balancers将可以检测到proxy是活跃的而不会扰动正常服务。 Alteon上的配置: ---------------------- /c/l3/if 1 ena addr 192.168.1.252 mask 255.255.255.0 /c/slb/real 11 ena name "LB1" rip 192.168.1.1 /c/slb/real 12 ena name "LB2" rip 192.168.1.2 /c/slb/group 10 name "LB1-2" metric roundrobin health tcp add 11 add 12 /c/slb/virt 10 ena vip 172.16.1.1 /c/slb/virt 10/service 1494 group 10 /c/slb/virt 10/service 3389 group 10 /c/slb/virt 10/service 5900 group 10 SSL的特殊处理: 有时,即使在TCP模式下,你需要给远程系统发送健康检测以便能够在server发生故障时能够切换到备份服务器。当然,你可以简单地启用TCP健康检查,但是在proxy和远程server之间的firewalls会自己确认TCP连接,从而显示为1个始终活跃的server。由于这是在使用SSL的长途通信中普遍遇到的问题,一个SSL健康检查已实现来要解决这个问题。 它发出的SSL messages 消息到远程server,server回复Hello messages。可以很容易的进行配置: listen tcp-syslog-proxy bind :1514 # listen to TCP syslog traffic on this port (SSL) mode tcp balance roundrobin option ssl-hello-chk server syslog-prod-site 192.168.1.10 check server syslog-back-site 192.168.2.10 check backup
3、带cookie插入的简单HTTP/HTTPS负载均衡
这是跟示例1相同的情况下,但是Web server使用的是https。 +-------+ |clients| clients +---+---+ | -+-----+--------+---- | _|_db +--+--+ (___) | SSL | (___) | web | (___) +-----+ 192.168.1.1 192.168.1.2 由于haproxy不能处理SSL,SSL这一部分将不得不从server中提取出来(腾出更多的资源),而安装在load-balancer上。在一个box上安装haproxy和apache+mod_ssl,把该box的负载分散至新的box上。Apache运行在SSL的reverse-proxy-cache模式下。如果应用程序设计的合理,甚至可能会降低其负载。不过,由于现在是一个与客户端和缓存的haproxy,一些安全必须采取措施,以确保插入的cookies不被缓存。 192.168.1.1 192.168.1.11-192.168.1.14 192.168.1.2 -------+-----------+-----+-----+-----+--------+---- | | | | | _|_db +--+--+ +-+-+ +-+-+ +-+-+ +-+-+ (___) | LB1 | | A | | B | | C | | D | (___) +-----+ +---+ +---+ +---+ +---+ (___) apache 4 cheap web servers mod_ssl haproxy LB1上的配置: ------------------------- listen 127.0.0.1:8000 mode http balance roundrobin cookie SERVERID insert indirect nocache option httpchk HEAD /index.html HTTP/1.0 server webA 192.168.1.11:80 cookie A check server webB 192.168.1.12:80 cookie B check server webC 192.168.1.13:80 cookie C check server webD 192.168.1.14:80 cookie D check 描述: ------------------------- - LB1上的apache在443端口上接收clients的请求 - apache前传请求至绑定在127.0.0.1:8000上的haproxy - 如果request不带cookie,则被前传至一台有效的server - 在回复时,haproxy会在回复中插入一个包含server名称(如:A)的"SERVERID" cookie,和一个"Cache-control: private" header。那样apache就不会cache带这样cookie的page; - 如果client再次访问时带了"SERVERID=A" cookie,则LB1会知道必须把该request前传至server A。haproxy会删除该cookie,而server不会看到它; - 如果server "webA"宕机,request会被前传至另外一个有效的server,而cookie会被重置; 提示: ------------------------- - 如果cookie工作在"prefix"模式下,haproxy就不需要配置"nocache"选项。因为,这个application cookie会被修改,并且application flags会被保留; - 如果haproxy的前段使用了apache1.3,则它会一直禁用后端HTTP keep-alive,因此可以不必在haproxy上配置 "httpclose"; - 如果application需要知道client's IP,则在apache上配置X-Forwarded-For header,而不要在haproxy上配置; 数据流: ------------------------- (apache) (haproxy) (server A) >-- GET /URI1 HTTP/1.0 ------------> | ( no cookie, haproxy forwards in load-balancing mode. ) | >-- GET /URI1 HTTP/1.0 ----------> | <-- HTTP/1.0 200 OK -------------< ( the proxy now adds the server cookie in return ) <-- HTTP/1.0 200 OK ---------------< | Set-Cookie: SERVERID=A | Cache-Control: private | >-- GET /URI2 HTTP/1.0 ------------> | Cookie: SERVERID=A | ( the proxy sees the cookie. it forwards to server A and deletes it ) | >-- GET /URI2 HTTP/1.0 ----------> | <-- HTTP/1.0 200 OK -------------< ( the proxy does not add the cookie in return because the client knows it ) <-- HTTP/1.0 200 OK ---------------< | >-- GET /URI3 HTTP/1.0 ------------> | Cookie: SERVERID=A | ( ... ) 3.1 使用Stunnel的替代方案 如果只需要SSL而不需要cache,则stunnel是一个比Apache+mod_ssl更廉价的解决方案。stunnel默认不出HTTP并且不能增加X-Forwarded-For header,但是在haproxy的官方网站上有针对最新stunnel versions版本支持该特性的patch。这时,stunnel只是处理HTTPS而不处理HTTP。这也意味着haproxy会接收所有的HTTP访问。因此,haproxy需要在HTTP访问中增加X-Forwarded-For header,而不用对HTTPS访问做处理。因为,stunnel已经做过处理了。我们可以使用"except"关键字告诉haproxy,来自本地的连接已经有有效的header了。 192.168.1.1 192.168.1.11-192.168.1.14 192.168.1.2 -------+-----------+-----+-----+-----+--------+---- | | | | | _|_db +--+--+ +-+-+ +-+-+ +-+-+ +-+-+ (___) | LB1 | | A | | B | | C | | D | (___) +-----+ +---+ +---+ +---+ +---+ (___) stunnel 4 cheap web servers haproxy stunnel(LB1)上的配置: cert=/etc/stunnel/stunnel.pem setuid=stunnel setgid=proxy socket=l:TCP_NODELAY=1 socket=r:TCP_NODELAY=1 [https] accept=192.168.1.1:443 connect=192.168.1.1:80 xforwardedfor=yes haproxy(LB1)上的配置: listen 192.168.1.1:80 mode http balance roundrobin option forwardfor except 192.168.1.1 cookie SERVERID insert indirect nocache option httpchk HEAD /index.html HTTP/1.0 server webA 192.168.1.11:80 cookie A check server webB 192.168.1.12:80 cookie B check server webC 192.168.1.13:80 cookie C check server webD 192.168.1.14:80 cookie D check 描述: ------------- - LB1上的stunnel会在443上接收client的requests; - stunnel前传request至绑定在80的haproxy; - haproxy会在80端口接收HTTP client request,在同一个端口(80)上解析来自stunnel的SSL requests; - stunnel增加X-Forwarded-For header(SSL 请求) - haproxy在除了来自本地的地址(stunnel)的每个request中增加X-Forwarded-For header;
4、内存管理机制
haproxy的内存管理采用了pool机制,即通过pool提高内存的重用,减少内存频繁的申请和释放。 Pool结构及逻辑 Pool数据结构 struct list { struct list *n; /* next */ struct list *p; /* prev */ }; struct pool_head { void **free_list; struct list list; /* list of all known pools */ unsigned int used; /* how many chunks are currently in use */ unsigned int allocated; /* how many chunks have been allocated */ unsigned int limit; /* hard limit on the number of chunks */ unsigned int minavail; /* how many chunks are expected to be used */ unsigned int size; /* chunk size */ unsigned int flags; /* MEM_F_* */ unsigned int users; /* number of pools sharing this zone */ char name[12]; /* name of the pool */ }; 其中未说明的分量含义解释如下: free_list:保存空闲的内存链表 Pool的逻辑结构 static struct list pools:全局变量,所有不同尺寸pool的链表head; pool2_session:全局变量,session大小尺寸的pool; pool2_buffer:全局变量,buffer大小尺寸的pool; Pool的初始化 函数原型: struct pool_head *create_pool(char *name, unsigned int size, unsigned int flags) 函数流程: list_for_each_entry(entry, &pools, list) { if (entry->size == size && entry->flags & MEM_F_SHARED) return entry; else calloc a pool; insert pool to poos; } 内存的申请 函数原型: static inline void *pool_alloc2(struct pool_head *pool) 函数流程: if (pool->free_list) get first node from free_list; else malloc a buf; 内存的释放 函数原型: static inline void pool_free2(struct pool_head *pool, void *ptr) 函数流程: 在相应的pool中,把ptr指向的buf插入至free_list头。 Pool的销毁 函数原型: void *pool_destroy2(struct pool_head *pool); 函数流程: free对应pool free_list中的buf,并且从pools中删除。 Haproxy内存管理机制的缺陷 分析Haproxy内存管理机制,可以看出该机制存在如下缺陷: 缺少pool收缩机制,即pool中分配的buf数目只会增长不会减少;也真是这个缺陷导致haproxy占用内存不会随着请求量的下降而下降,而只会把不用的buf存放至free_list中。
5、健康检查机制
option httpchk 启用七层健康检测 http-check disable-on-404 如果backend返回404,则除了长连接之外的后续请求将不被分配至该backend http-check send-state 增加一个header,同步HAProxy中看到的backend状态。该header为server可见。 X-Haproxy-Server-State: UP 2/3; name=bck/srv2; node=lb1; weight=1/2; scur=13/22; qcur=0 server option check:启用健康检测 inter:健康检测间隔 rise:检测服务可用的连续次数 fall:检测服务不可用的连续次数 error-limit:往server写数据连续失败的次数上限,执行on-error的设定 observe :把正常服务过程作为健康检测请求,即实时检测 on-error :满足error-limit后执行的操作(fastinter、fail-check、sudden-death、mark-down) 。其中fastinter表示立即按照fastinter的检测延时进行。fail-check表示改次error作为一次检测;sudden-death表示模仿一次fatal,如果紧接着一次fail则置server为down;mark-down表示直接把server置为down状态。 其它 retries:连接失败重试的次数,如果重试该次数后还不能正常服务,则断开连接。 3 检测机制 3.1 相关数据结构 struct server { ...... int health; /* 0->rise-1 = bad; rise->rise+fall-1 = good */ int consecutive_errors; /* current number of consecutive errors */ int rise, fall; /* time in iterations */ int consecutive_errors_limit; /* number of consecutive errors that triggers an event */ short observe, onerror; /* observing mode: one of HANA_OBS_*; what to do on error: on of ANA_ONERR_* */ int inter, fastinter, downinter; /* checks: time in milliseconds */ ...... } 3.2 check流程 3.3 server状态切换条件 UP-->DOWN 初始为s->health=s->rise; if (s->health < s->rise + s->fall – 1) then s->health = s->rise + s->fall – 1; check失败:s->health-- if (s->health <= s->rise) then set_server_down(), s->health = 0; DOWN-->UP 初始为s->health=0; check成功:s->health++ if (s->health == s->rise) then set_server_up(), s->health = s->rise + s->fall – 1; 3.4 observe机制 observe机制是分析请求服务过程中发生错误的时候调用heath_adjust函数来实时更新check机制中的相关计数。其跟check机制的区别在于,check机制只通过定时检测。observe机制基于check机制。在不同的on-error(mode)情况下对s->health的影响如下: 备注:执行on-error(mode)的前提是 s->consecutive_errors < s->consecutive_errors_limit(连接失败的次数超过了上限) fastinter 不修改s->health值,但是会调整check出发的时间,时间为间隔fastinter后的数字。 fail-check 把本次连接的失败作为1次check,s->health-- sudden-death 把本次连接作为1次致命的失败,s->health = s->rise + 1,如下次还失败则置为DOWN mark-down 本次连接失败后,直接把后端server置为DOWN
6、HAProxy负载均衡器算法与使用技巧
HAProxy支持的负载均衡算法 (1)、roundrobin,表示简单的轮询,负载均衡基础算法 (2)、static-rr,表示根据权重 (3)、leastconn,表示最少连接者先处理 (4)、source,表示根据请求源IP (5)、uri,表示根据请求的URI; (6)、url_param,表示根据请求的URl参数来进行调度 (7)、hdr(name),表示根据HTTP请求头来锁定每一次HTTP请求; (8)、rdp-cookie(name),表示根据据cookie(name)来锁定并哈希每一次TCP请求。 常用的负载均衡算法 (1)轮询算法:roundrobin (2)根据请求源IP算法:source (3)最少连接者先处理算法:lestconn
三、配置
HAproxy 配置中分成五部分内容,当然这些组件不是必选的,可以根据需要选择部分作为配置
global:参数是进程级别的,通常和操作系统(OS)有关。这些参数一般只设置一次,如果配置无误,就不需要再次配置进行修改。
defaults:配置默认参数的,这些参数可以被利用配置frontend,backend。Listen 组件
frontend:接收请求的前虚拟节点,frontend 可以根据规则直接指定具体使用后端的
backend :后端服务集群的配置,是真实的服务器,一个backend 对应一个或者多个实体服务器
listen:frontend 和backend 的组合体1、主配置文件
#--------------------------------------------------------------------- # Global settings #--------------------------------------------------------------------- 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 #修改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 #日志配置,所有的日志都记录本地,通过local2输出 chroot /var/lib/haproxy #改变haproxy的工作目录 pidfile /var/run/haproxy.pid #指定pid文件的路径 maxconn 4000 #最大连接数的设定 user haproxy #指定运行服务的用户 group haproxy #指定运行服务的用户组 daemon # turn on stats unix socket stats socket /var/lib/haproxy/stats #--------------------------------------------------------------------- # common defaults that all the 'listen' and 'backend' sections will # use if not designated in their block #--------------------------------------------------------------------- defaults mode http #默认使用协议,可以为{http|tcp|health} http:是七层协议 tcp:是四层 health:只返回OK log global #全局日志记录 option httplog #详细记录http日志 option dontlognull #不记录空日志 option http-server-close #启用http-server-close option forwardfor except 127.0.0.0/8 #来自这些信息的都不forwardfor option redispatch #重新分发,ServerID对应的服务器宕机后,强制定向到其他运行正常的服务器 retries 3 #3次连接失败则认为服务不可用 timeout http-request 10s #默认http请求超时时间 timeout queue 1m #默认队列超时时间 timeout connect 10s #默认连接超时时间 timeout client 1m #默认客户端超时时间 timeout server 1m #默认服务器超时时间 timeout http-keep-alive 10s #默认持久连接超时时间 timeout check 10s #默认检查时间间隔 maxconn 3000 #最大连接数 #--------------------------------------------------------------------- # main frontend which proxys to the backends #--------------------------------------------------------------------- frontend main *:5000 #定义ACL规则以如".html"结尾的文件;-i:忽略大小写 acl url_static path_beg -i /static /images /javascript /stylesheets acl url_static path_end -i .jpg .gif .png .css .js use_backend static if url_static #调用后端服务器并检查ACL规则是否被匹配 default_backend app #客户端访问时默认调用后端服务器地址池 #--------------------------------------------------------------------- # static backend for serving up images, stylesheets and such #--------------------------------------------------------------------- backend static #定义后端服务器 balance roundrobin #定义算法;基于权重进行轮询 server static 127.0.0.1:4331 check check:启动对后端server的健康状态检测 #--------------------------------------------------------------------- # round robin balancing between the various backends #--------------------------------------------------------------------- backend app balance roundrobin server app1 127.0.0.1:5001 check server app2 127.0.0.1:5002 check server app3 127.0.0.1:5003 check server app4 127.0.0.1:5004 check 状态监控 listen stats #关联前端和后端定义一个完整的代理 mode http #设置代理协议 bind 0.0.0.0:1080 #绑定相应的端口 stats enable #开启Haproxy统计状态 stats refresh 3s #统计页面自动刷新时间间隔 stats hide-version #隐藏代理服务器版本 stats uri /haproxyadmin?stats #访问的url stats realm Haproxy Statistics #统计页面认证时提示内容信息 stats auth admin:123456 #设置登录用户和密码 stats admin if TRUE #如果认证通过,则就可以打开stats 网站检测listen 定义 listen site_status bind 0.0.0.0:1081 mode http log 127.0.0.1 local0 err #[err warning info debug] monitor-uri /site_status 网站健康检测url,用来检测haproxy 管理的网站是否可以用,正常返回200,不正常返回500 acl site_dead nbsrv(denali_server) lt 1 当挂载在负载均衡上的指定backend 的中有效机器数小于1 台时返回true acl site_dead nbsrv(tm_server) lt 1 acl site_dead nbsrv(mms_server) lt 1 monitor fail if site_dead 当满足策略的时候返回500 monitor-net 192.168.0.252/31 如果192.168.0.252 或者192.168.0.31 这两台机器挂了,就认为网站挂了,这时候返回500,判断标准是如果mode 是http 返回200 认为是正常的,如果 mode 是tcp 认为端口畅通是好的 动静分离 frontend http-in bind *:80 mode http log global option httpclose option logasap option dontlognull capture request header Host len 20 capture request header Referer len 60 acl url_static path_end -i .html .jpg .gif acl url_dynamic path_end -i .php default_backend servers use_backend lnmmp if url_dynamic backend servers balance roundrobin server websrv1 192.168.0.102:80 check rise 2 fall 1 weight 2 maxconn 2000 server websrv2 192.168.0.106:80 check rise 2 fall 1 weight 2 maxconn 2000 backend lnmmp balance source server websrv3 192.168.0.107:80 check rise 2 fall 1 maxconn 2000 https 的配置方法 listen loging_https_server bind 0.0.0.0:443 绑定https 的443 端口 mode tcp https 必须使用tcp 模式 log global balance roundrobin option httpchk GET /member/login.html HTTP/1.1 Host:login.daily.taobao.net server vm94f.sqa 192.168.212.94:443 check port 80 inter 6000 rise 3 fall 3 server v21520.sqa 192.168.215.120:443 check port 80 inter 6000 rise 3 fall 3 Frontedn 配置 frontend http_80_in bind 0.0.0.0:80 监听端口 mode http http 的7 层模式 log global 应用全局的日志配置 option httplog 启用http 的log option httpclose 每次请求完毕后主动关闭http 通道,http-proxy 不支持keep-alive 模式 option forwardfor 如果后端服务器需要获得客户端的真实ip 需要配置此参数,可以从http Header 中获得客户端ip HAProxy 错误页面设置 errorfile 400 /home/admin/haproxy/errorfiles/400.http errorfile 403 /home/admin/haproxy/errorfiles/403.http errorfile 408 /home/admin/haproxy/errorfiles/408.http errorfile 500 /home/admin/haproxy/errorfiles/500.http errorfile 502 /home/admin/haproxy/errorfiles/502.http errorfile 503 /home/admin/haproxy/errorfiles/503.http errorfile 504 /home/admin/haproxy/errorfiles/504.http Backend 的设置 Backend mmm_server Mode http http 的7 层模式 Balance roudrobin 负载均衡的方式,roundrobin 为平均方式 Cookies SERVERID 允许插入serverid 到cookie 中,serverid 后面可以定义 Server mms1 10.1.5.134:80 cookie 1 check inter 1500 rise 3 fall 3 weight 1 Server mms2 10..1.6.134:80 cookie 2 check inter 1500 rise 3 fall 3 weight 2 服务器定义,cookie1 标示serverid 为1,check inter 1500 是检测心跳频率,rise 3 是3 次正确认为服务器可用。Fall 3 是3 次失败认为服务器不可用,weight 代表权重 Backend denali_server Mode http Balance source 负载均衡的方式,source 根据客户端ip 进行哈希的方式 Option allbackups 但设置了backup 的时候,默认第一个backup 会优先,设置option allbackups后,所有备份服务器权重都一样 Option httpchk GET /myfile/home/check.html HTTP/1.1 Host:my.gemini.taobao.net Server denlai1 10.1.5.114:80 minconn 4 maxconn 12 check inter 1500 rise 3 fall 3 Server denlai2 10.1.6.104:80 minconn 10 maxconn 20 check inter 1500 rise 3 fall 3 可以根据机器的性能不同,不使用默认的连接数配置而使用自己的特殊的连接数配置 Server dnali-back1 10.1.7.114:80 check backup inter 1500 rise 3 fall 3 Server dnali-back2 10.1.7.114:80 check backup inter 1500 rise 3 fall 3备份机器配置,正常情况备用机不会使用,当主机的全部服务器都down 的时候备份机会启 用 backend tm_server mode http#负载均衡的方式,leastconn 根据服务器当前的请求数,取当前请求数最少的服务器 balance leastconn option httpchk GET /trade/itemlist/prepayCard.htm HTTP/ server tm1 10.1.5.115:80 check inter 1500 rise 3 fall 3 server tm2 10.1.6.105:80 check inter 1500 rise 3 fall 3 ######reqisetbe 自定义关键字匹配backend 部分####################### backend dynamic mode http balance source option httpchk GET /welcome.html HTTP/1.1 Host:www.taobao.net server denlai1 10.3.5.114:80 check inter 1500 rise 3 fall 3 server denlai2 10.4.6.104:80 check inter 1500 rise 3 fall 3 backend stats mode http balance source option httpchk GET /welcome.html HTTP/1.1 Host:www.taobao.net server denlai1 10.5.5.114:80 check inter 1500 rise 3 fall 3 server denlai2 10.6.6.104:80 check inter 1500 rise 3 fall 3 listen www-balancer bind 192.168.16.21:80 mode http balance roundrobin maxconn 32768 timeout connect 5000ms #连接超时 timeout client 50000ms #客户端超时 timeout server 50000ms #服务端超时 retries 3 server www1 192.168.16.2:80 cookie A check inter 1500 rise 3 fall 3 weight 1 server www2 192.168.16.3:80 cookie B check inter 1500 rise 3 fall 3 weight 1 server www3 192.168.16.4:80 cookie C check inter 1500 rise 3 fall 3 weight 1 option httpchk GET /index.html option httplog option forwardfor 获取clietn ip 地址的“X-Forwarder-For" header,需注 意,此选项要在apache 或者nginx 里面配置日志格式,见附录 option httpclose option redispatch option originalto 获取原始目的地ip 的"X-Original-To" header
2、日志配置文件
配置Haproxy的日志 默认情况下,Haproxy没有启用日志文件,但是我们可以根据haproxy的配置文件做修改。 (1)修改系统日志的配置文件 # vim /etc/sysconfig/rsyslog SYSLOGD_OPTIONS="-c 2 -r" (2)增加日志设备 # vim /etc/rsyslog.conf local2.* /var/log/haproxy.log (3)重启一下日志服务 # /etc/init.d/rsyslog restart 关闭系统日志记录器: [确定] 启动系统日志记录器: [确定] (4)查看日志记录信息 # tail -f /var/log/haproxy.log
四、连接处理
1. 关键数据结构 session haproxy 负责处理请求的核心数据结构是 struct session, 从业务的处理的角度,简单介绍一下对 session 的理解: haproxy 每接收到 client 的一个连接,便会创建一个 session 结构,该结构一直伴随着连接的处理,直至连接被关闭,session 才会被释放 haproxy 其他的数据结构,大多会通过引用的方式和 session 进行关联 一个业务 session 上会存在两个 TCP 连接,一个是 client 到 haproxy,一个是 haproxy 到后端 server。 此外,一个 session,通常还要对应一个 task,haproxy 最终用来做调度的是通过 task。 2. 相关初始化 在 haproxy 正式处理请求之前,会有一系列初始化动作。这里介绍和请求处理相关的一些初始化。 2.1. 初始化处理 TCP 连接的方法 初始化处理 TCP 协议的相关数据结构,主要是和 socket 相关的方法的声明。详细见下面 proto_tcpv4 (proto_tcp.c)的初始化: static struct protocol proto_tcpv4 = { .name = "tcpv4", .sock_domain = AF_INET, .sock_type = SOCK_STREAM, .sock_prot = IPPROTO_TCP, .sock_family = AF_INET, .sock_addrlen = sizeof(struct sockaddr_in), .l3_addrlen = 32/8, .accept = &stream_sock_accept, .read = &stream_sock_read, .write = &stream_sock_write, .bind = tcp_bind_listener, .bind_all = tcp_bind_listeners, .unbind_all = unbind_all_listeners, .enable_all = enable_all_listeners, .listeners = LIST_HEAD_INIT(proto_tcpv4.listeners), .nb_listeners = 0, }; 2.2. 初始化 listener listener,顾名思义,就是用于负责处理监听相关的逻辑。 在 haproxy 解析 bind 配置的时候赋值给 listener 的 proto 成员。函数调用流程如下: cfgparse.c -> cfg_parse_listen -> str2listener -> tcpv4_add_listener -> listener->proto = &proto_tcpv4; 由于这里初始化的是 listener 处理 socket 的一些方法。可以推断, haproxy 接收 client 新建连接的入口函数应该是 protocol 结构体中的 accpet 方法。对于tcpv4 来说,就是 stream_sock_accept() 函数。该函数到 1.5-dev19 中改名为 listener_accept()。这是后话,暂且不表。 listener 的其他初始化 cfgparse.c -> check_config_validity -> listener->accept = session_accept; listener->frontend = curproxy; (解析 frontend 时,会执行赋值: curproxy->accept = frontend_accept) listener->handler = process_session; 整个 haproxy 配置文件解析完毕,listener 也已初始化完毕。可以简单梳理一下几个 accept 方法的设计逻辑: stream_sock_accept(): 负责接收新建 TCP 连接,并触发 listener 自己的 accept 方法 session_accept() session_accept(): 负责创建 session,并作 session 成员的初步初始化,并调用 frontend 的 accept 方法 front_accetp() frontend_accept(): 该函数主要负责 session 前端的 TCP 连接的初始化,包括 socket 设置,log 设置,以及 session 部分成员的初始化 下文分析 TCP 新建连接处理过程,基本上就是这三个函数的分析。 2.3. 绑定所有已注册协议上的 listeners haproxy.c -> protocol_bind_all -> all registered protocol bind_all -> tcp_bind_listeners (TCP) -> tcp_bind_listener -> [ fdtab[fd].cb[DIR_RD].f = listener->proto->accept ] 该函数指针指向 proto_tcpv4 结构体的 accept 成员,即函数 stream_sock_accept 2.4. 启用所有已注册协议上的 listeners 把所有 listeners 的 fd 加到 polling lists 中 haproxy.c -> protocol_enable_all -> all registered protocol enable_all -> enable_all_listeners (TCP) -> enable_listener 函数会将处于 LI_LISTEN 的 listener 的状态修改为 LI_READY,并调用 cur poller 的 set 方法, 比如使用 sepoll,就会调用 __fd_set 3. TCP 连接的处理流程 3.1. 接受新建连接 前面几个方面的分析,主要是为了搞清楚当请求到来时,处理过程中实际的函数调用关系。以下分析 TCP 建连过程。 haproxy.c -> run_poll_loop -> cur_poller.poll -> __do_poll (如果配置使用的是 sepoll,则调用 ev_sepoll.c 中的 poll 方法) -> fdtab[fd].cb[DIR_RD].f(fd) (TCP 协议的该函数指针指向 stream_sock_accept ) -> stream_sock_accept -> 按照 global.tune.maxaccept 的设置尽量可能多执行系统调用 accept,然后再调用 l->accept(),即 listener 的 accept 方法 session_accept -> session_accept session_accept 主要完成以下功能 调用 pool_alloc2 分配一个 session 结构 调用 task_new 分配一个新任务 将新分配的 session 加入全局 sessions 链表中 session 和 task 的初始化,若干重要成员的初始化如下 t->process = l->handler: 即 t->process 指向 process_session t->context = s: 任务的上下文指向 session s->listener = l: session 的 listener 成员指向当前的 listener s->si[] 的初始化,记录 accept 系统调用返回的 cfd 等 初始化 s->txn 为 s->req 和 s->rep 分别分配内存,并作对应的初始化 s->req = pool_alloc2(pool2_buffer) s->rep = pool_alloc2(pool2_buffer) 从代码上来看,应该是各自独立分配 tune.bufsize + sizeof struct buffer 大小的内存 新建连接 cfd 的一些初始化 cfd 设置为非阻塞 将 cfd 加入 fdtab[] 中,并注册新建连接 cfg 的 read 和 write 的方法 fdtab[cfd].cb[DIR_RD].f = l->proto->read,设置 cfd 的 read 的函数 l->proto->read,对应 TCP 为 stream_sock_read,读缓存指向 s->req, fdtab[cfd].cb[DIR_WR].f = l->proto->write,设置 cfd 的 write 函数 l->proto->write,对应 TCP 为 stream_sock_write,写缓冲指向 s->rep p->accept 执行 proxy 的 accept 方法即 frontend_accept 设置 session 结构体的 log 成员 根据配置的情况,分别设置新建连接套接字的选项,包括 TCP_NODELAY/KEEPALIVE/LINGER/SNDBUF/RCVBUF 等等 如果 mode 是 http 的话,将 session 的 txn 成员做相关的设置和初始化 3.2. TCP 连接上的接收事件 haproxy.c -> run_poll_loop -> cur_poller.poll -> __do_poll (如果配置使用的是 sepoll,则调用 ev_sepoll.c 中的 poll 方法) -> fdtab[fd].cb[DIR_RD].f(fd) (该函数在建连阶段被初始化为四层协议的 read 方法,对于 TCP 协议,为 stream_sock_read ) -> stream_sock_read stream_sock_read 主要完成以下功能 找到当前连接的读缓冲,即当前 session 的 req buffer: struct buffer *b = si->ib 根据配置,调用 splice 或者 recv 读取套接字上的数据,并填充到读缓冲中,即填充到从 b->r(初始位置应该就是 b->data)开始的内存中 如果读取到 0 字节,则意味着接收到对端的关闭请求,调用 stream_sock_shutr 进行处理 读缓冲标记 si->ib->flags 的 BF_SHUTR 置位,清除当前 fd 的 epoll 读事件,不再从该 fd 读取 如果写缓冲 si->ob->flags 的 BF_SHUTW 已经置位,说明应该是由本地首先发起的关闭连接动作 将 fd 从 fdset[] 中清除,从 epoll 中移除 fd,执行系统调用 close(fd), fd.state 置位 FD_STCLOSE stream interface 的状态修改 si->state = SI_ST_DIS 唤醒任务 task_wakeup,把当前任务加入到 run queue 中。随后检测 runnable tasks 时,就会处理该任务 3.3. TCP 连接上的发送事件 haproxy.c -> run_poll_loop -> cur_poller.poll -> __do_poll (如果配置使用的是 sepoll,则调用 ev_sepoll.c 中的 poll 方法) -> fdtab[fd].cb[DIR_WR].f(fd) (该函数在建连阶段被初始化为四层协议的 write 方法,对于 TCP 协议,为 stream_sock_write ) -> stream_sock_write stream_sock_write主要完成以下功能 找到当前连接的写缓冲,即当前 session 的 rep buffer: struct buffer *b = si->ob 将待发送的数据调用 send 系统调用发送出去 或者数据已经发送完毕,需要发送关闭连接的动作 stream_sock_shutw-> 系统调用 shutdown 唤醒任务 task_wakeup,把当前任务加入到 run queue 中。随后检测 runnable tasks 时,就会处理该任务 3.4. http 请求的处理 haproxy.c -> run_poll_loop -> process_runnable_tasks,查找当前待处理的任务所有 tasks, 然后调用 task->process(大多时候就是 process_session) 进行处理 -> process_session process_session 主要完成以下功能 处理连接需要关闭的情形,分支 resync_stream_interface 处理请求,分支 resync_request (read event) 根据 s->req->analysers 的标记位,调用不同的 analyser 进行处理请求 ana_list & AN_REQ_WAIT_HTTP: http_wait_for_request ana_list & AN_REQ_HTTP_PROCESS_FE: http_process_req_common ana_list & AN_REQ_SWITCHING_RULES:process_switching_rules 处理应答,分支 resync_response (write event) 根据 s->rep->analysers 的标记位,调用不同的 analyser 进行处理请求 ana_list & AN_RES_WAIT_HTTP: http_wait_for_response ana_list & AN_RES_HTTP_PROCESS_BE:http_process_res_common 处理 forward buffer 的相关动作 关闭 req 和 rep 的 buffer,调用 pool2_free 释放 session 及其申请的相关内存,包括读写缓冲 (read 0 bytes) pool_free2(pool2_buffer, s->req); pool_free2(pool2_buffer, s->rep); pool_free2(pool2_session, s); task 从运行任务队列中清除,调用 pool2_free 释放 task 申请的内存: task_delete(); task_free();