如来神掌第一式第十四招----HAPROXY详解

###############################################################################
# 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\r\nHost: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\r\nHost: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\r\nHost: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\r\nHost: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();

 

posted on 2018-01-04 23:23  Mahavairocana  阅读(533)  评论(0编辑  收藏  举报

导航