介绍基于HAProxy的高性能缓存服务器nuster

摘要: Nuster是一个基于HAProxy的高性能缓存服务器 介绍 Nuster是一个基于HAProxy的高性能缓存服务器。Nuster完全兼容HAProxy,并且利用HAProxy的ACL功能来提供非常细致的缓存规则,比如 请求地址为某某时缓存 请求参数中的X为Y时缓存 响应头中的X为Y时缓存 请求速率超过多少时缓存 等等 性能 非常快, 单进程模式下是nginx的3倍,多进程下nginx的2倍,varnish的3倍。

Nuster是一个基于HAProxy的高性能缓存服务器

介绍

Nuster是一个基于HAProxy的高性能缓存服务器。Nuster完全兼容HAProxy,并且利用
HAProxy的ACL功能来提供非常细致的缓存规则,比如

  • 请求地址为某某时缓存
  • 请求参数中的X为Y时缓存
  • 响应头中的X为Y时缓存
  • 请求速率超过多少时缓存
  • 等等

性能

非常快, 单进程模式下是nginx的3倍,多进程下nginx的2倍,varnish的3倍。

详见 https://github.com/jiangwenyuan/nuster/wiki/Performance-benchmark:-nuster-vs-nginx-vs-varnish?spm=5176.100239.blogcont307591.18.c776f1eZVmaLX

安装

make TARGET=linux2628
make install

具体参照 https://github.com/jiangwenyuan/nuster/wiki/Performance-benchmark:-nuster-vs-nginx-vs-varnish?spm=5176.100239.blogcont307591.18.c776f1eZVmaLX

使用方法

在global中添加cache on, 然后在backend或listen中添加cache filter和cache rule

指令

cache

syntax: cache on|off [share on|off] [data-size size] [dict-size size]

default: none

context: global

控制是否开启缓存。
可以设置data-size来控制缓存数据的内存使用量。可以使用m, M, g 和 G.
默认是1MB,同时也是最小使用量。只有http内容计算在内,并不包括使用缓存带来的内存开销。

filter cache

syntax: filter cache [on|off]

default: on

context: backend, listen

定义一个cache filter, 另外cache-rule也需要添加。
可以为多个代理添加,并单独设置某个代理的缓存是否开启。
如果定义了多个filter,需要把cache filter放在最后。

cache-rule

syntax: cache-rule name [key KEY] [ttl TTL] [code CODE] [if|unless condition]

default: none

context: backend, listen

定义缓存规则。可以同时定义多个,但是需要注意顺序,匹配则会停止测试。

acl pathA path /a.html
filter cache
cache-rule all ttl 3600
cache-rule path01 ttl 60 if pathA

path01这条规则永远不会执行,因为all会匹配所有的规则。

name

定义一个名字。

key KEY

定义key,由以下关键字组成:

  • method: http method, GET/POST…
  • scheme: http or https
  • host: the host in the request
  • path: the URL path of the request
  • query: the whole query string of the request
  • header_NAME: the value of header NAME
  • cookie_NAME: the value of cookie NAME
  • param_NAME: the value of query NAME
  • body: the body of the request

默认key是method.scheme.host.path.query.body

Example

GET http://www.example.com/q?name=X&type=Y

http header:
GET /q?name=X&type=Y HTTP/1.1
Host: www.example.com
ASDF: Z
Cookie: logged_in=yes; user=nuster;

会得到:

  • method: GET
  • scheme: http
  • host: www.example.com
  • path: /q
  • query: name=X&type=Y
  • header_ASDF: Z
  • cookie_user: nuster
  • param_type: Y
  • body: (empty)

所以默认的key就会得到GEThttpwww.example.com/qname=X&type=Y, 而
key method.scheme.host.path.header_ASDF.cookie_user.param_type则会生成
GEThttpwww.example.com/qZnusterY

一个请求的key能在缓存中找到则返回缓存内容。

ttl TTL

定义key的失效时间,可以使用 d, h, m and s。默认3600秒.
如果不希望失效则设为0

code CODE1,CODE2…

默认只缓存200的响应,如果需要缓存其他的则可以添加,all会缓存任何状态码。

cache-rule only200
cache-rule 200and404 code 200,404
cache-rule all code all

if|unless condition

定义ACL条件
详见 HAProxy configuration 的 7. Using ACLs and fetching samples

FAQ

如何调试?

在global添加debug, 或者带-d启动haproxy

缓存相关的调试信息以[CACHE]开头

如何缓存POST请求?

添加option http-buffer-request

如果自定义了key的话需要使用body关键字

请求body可能不完整,详见HAProxy configuration 的
option http-buffer-request小节

另外可以为post请求单独设置一个后端

Example

global
    cache on data-size 100m
    #daemon
    ## to debug cache
    #debug
defaults
    retries 3
    option redispatch
    timeout client  30s
    timeout connect 30s
    timeout server  30s
frontend web1
    bind *:8080
    mode http
    acl pathPost path /search
    use_backend app1a if pathPost
    default_backend app1b
backend app1a
    balance roundrobin
    # mode must be http
    mode http

    # http-buffer-request must be enabled to cache post request
    option http-buffer-request

    acl pathPost path /search

    # enable cache for this proxy
    filter cache

    # cache /search for 120 seconds. Only works when POST/PUT
    cache-rule rpost ttl 120 if pathPost

    server s1 10.0.0.10:8080
backend app1b
    balance     roundrobin
    mode http

    filter cache on

    # cache /a.jpg, not expire
    acl pathA path /a.jpg
    cache-rule r1 ttl 0 if pathA

    # cache /mypage, key contains cookie[userId], so it will be cached per user
    acl pathB path /mypage
    cache-rule r2 key method.scheme.host.path.query.cookie_userId ttl 60 if pathB

    # cache /a.html if response's header[cache] is yes
    http-request set-var(txn.pathC) path
    acl pathC var(txn.pathC) -m str /a.html
    acl resHdrCache1 res.hdr(cache) yes
    cache-rule r3 if pathC resHdrCache1

    # cache /heavy for 100 seconds if be_conn greater than 10
    acl heavypage path /heavy
    acl tooFast be_conn ge 100
    cache-rule heavy ttl 100 if heavypage tooFast 

    # cache all if response's header[asdf] is fdsa
    acl resHdrCache2 res.hdr(asdf)  fdsa
    cache-rule resCache ttl 0 if resHdrCache1

    server s1 10.0.0.10:8080

frontend web2
    bind *:8081
    mode http
    default_backend app2
backend app2
    balance     roundrobin
    mode http

    # disable cache on this proxy
    filter cache off
    cache-rule all

    server s2 10.0.0.11:8080

listen web3
    bind *:8082
    mode http

    filter cache
    cache-rule everything

    server s3 10.0.0.12:8080

Haproxy 负载均衡配置集群配置

首先安装HAProxy

注:负载均衡建议使用多台服务器,达到更高的性能。

Centos使用

yum install haproxy -y

Debian使用

vi /etc/apt/sources.list

添加如下内容

deb http://ftp.us.debian.org/debian/ wheezy-backports main

然后

apt-get update apt-get install haproxy

接下来设置配置文件

vi /etc/haproxy/haproxy.cfg

清空配置文件后,输入如下内容

  global
    log 127.0.0.1   local0
    log 127.0.0.1   local1 notice
    #log loghost    local0 info
    maxconn 4096
    chroot /var/haproxy
    uid 99
    gid 99
    daemon
    nbproc  1
    #debug
    #quiet

defaults
    log global
    mode    http
    option  httplog
    option  dontlognull
    retries 3
    option redispatch # 自动重定向到健康机器
    maxconn 200000
    timeout check 2000ms # 检查超时
    timeout connect 5000ms # 连接超时
    timeout client 50000ms # 客户端连接超时
    timeout server 50000ms # 服务端连接超时

#配置客户端
listen webinfo :80
       mode http
       balance roundrobin
       stats uri /ha_status
       option httpclose
       option forwardfor
       server web1 127.0.0.1:810 check weight 1 minconn 1 maxconn 200 check inter 40000  #其中一台web服务器
       server web2 127.0.0.1:811 check weight 1 minconn 1 maxconn 200 check inter 40000  #其中一台web服务器
       server web4 127.0.0.1:812 check weight 1 minconn 1 maxconn 200 check inter 40000  #其中一台web服务器

#配置控制台
listen stats :6553
       mode http
       transparent
       stats uri / haproxy-stats
       stats realm Haproxy  statistic
       stats auth admin:admin

haproxy监控页面 和页面详细参数

stats enable是haproxy通过WEB界面进行统计数据展示的功能,通过WEB界面可以查看到haproxy当前运行状态的一些数据,合理配置的情况下,还可以通过WEB界面调整某个后端服务器的状态,权重,启用或暂停一个后端服务器等。

stats enable可以出现在default/listen/backend这几个区块中,stats enable这一条命令就可以开启统计功能,开启后下面参数拥有的默认值:

stats uri : /haproxy?stats
stats realm : “HAProxy Statistics”
stats auth : no authentication
stats scope : no restriction

尽量不要用默认的值,根据自己的情况,明确指定要使用的值。
常用配置:

编辑haproxy.cfg 加上下面参数

listen admin_stats
        stats   enable
        bind    *:8080    //监听的ip端口号
        mode    http    //开关
        option  httplog
        log     global
        maxconn 10
        stats   refresh 30s   //统计页面自动刷新时间
        stats   uri /admin?stats    //访问的uri   ip:8080/admin
        stats   realm haproxy
        stats   auth admin:admin  //认证用户名和密码
        stats   hide-version   //隐藏HAProxy的版本号
        stats   admin if TRUE   //管理界面,如果认证成功了,可通过webui管理节点

保存退出后

重起service haproxy restart

然后访问 http://ip:8080/admin?stats 用户名:admin 密码:admin

注意:访问监控页面是ip+端口号,之前忽略了端口号无法访问

Haproxy的安装、配置

未分类

Haproxy的介绍、安装、配置

1、Haproxy简介

Haproxy是一个开源的,高性能的基于HTTP和TCP应用代理的高可用,负载均衡服务软件。它支持双机热备,高可用,负载均衡。有图形界面。
而且拥有很好的对服务器节点的健康检查功能。

单台的haproxy可以满足每天1-3千万的PV。

haproxy为代理,用户的请求和后端的服务器的回应都需要经过haproxy服务器。

2、Haproxy的常用算法

balance leastconn   最少连接数
balance roundrobin   轮循
balance static-rr  权重
balance source   相当于nginx的ip_hash

3、Haproxy的安装

centos6.6默认的源yum安装proxy的版本为1.5.4

yum 安装haproxy

yum install -y haproxy

打开内核转发

vim /etc/sysctl.conf
net.ipv4.ip_forward = 1

让上面配置的/etc/sysctl.conf文件生效

sysctl -p

4、Haproxy配置文件详解

Haproxy配置文件可以分为五部分:

global : 全局配置参数段,主要用来控制haproxy启动前的进程及系统相关设置。

defaults : 配置一些默认参数,如frontend,backend,listen等段末没有设置则使用default段来设置。

listen : 监听

frontend : 用来匹配接收客户所请求的域名,URL等,并针对不同的匹配,做不同的请求处理。

backend : 定义后端服务器集群,以及对后端服务器的一些权重,队列,连接数等的设置

下面为正式环境下的haproxy.cfg配置文件

cat >> /etc/haproxy/haproxy.cfg <<EOF
global
log 127.0.0.1:514 local0 error #一定不能用info,这样会造成很大的磁盘IO
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 20000 #最大连接数
user haproxy
group haproxy
spread-checks 3
nbproc 2
daemon

defaults
mode http
log global
retries 3
option redispatch
#contimeout 5000
#clitimeout 50000
#srvtimeout 50000

listen alex
bind 192.168.0.29:80 #绑定的IP地址。相当于后面real server的VIP
mode http #HTTP模式,也可以作为TCP的反向代理
stats hide-version
stats enable #开启WEB查看proxy的状态
stats uri /admin?stats #查看PROXY状态的URI
stats auth proxy:kobe24 #查看PROXY状态时登录录要用到的用户名和密码
option httpclose
option forwardfor #让后端的real server 的日志可以记录到CLIENT的IP
cookie SERVERID insert indirect
timeout server 15s
timeout connect 15s
timeout client 20s
#option httpchk HEAD /check.html HTTP/1.0
server www01 192.168.0.100:80 cookie A check port 80 inter 5000 fall 5
server www02 192.168.0.101:80 cookie B check port 80 inter 5000 fall 5
EOF

HAProxy系列—Linux下的安装

HAProxy是一个使用C语言编写的自由及开放源代码软件,其提供高可用性、负载均衡,以及基于TCP和HTTP的应用程序代理。
  
HAProxy特别适用于那些负载特大的web站点,这些站点通常又需要会话保持或七层处理。HAProxy运行在当前的硬件上,完全可以支持数以万计的并发连接。并且它的运行模式使得它可以很简单安全的整合进您当前的架构中, 同时可以保护你的web服务器不被暴露到网络上。
  
HAProxy实现了一种事件驱动, 单一进程模型,此模型支持非常大的并发连接数。多进程或多线程模型受内存限制 、系统调度器限制以及无处不在的锁限制,很少能处理数千并发连接。事件驱动模型因为在有更好的资源和时间管理的用户空间(User-Space) 实现所有这些任务,所以没有这些问题。此模型的弊端是,在多核系统上,这些程序通常扩展性较差。这就是为什么他们必须进行优化以 使每个CPU时间片(Cycle)做更多的工作。

一、下载并解压

下载地址:http://pkgs.fedoraproject.org/repo/pkgs/haproxy/

[root@haproxy-server-master ~]# cd /usr/local/src/
[root@haproxy-server-master src]# wget http://pkgs.fedoraproject.org/repo/pkgs/haproxy/haproxy-1.7.9.tar.gz/sha512/d1ed791bc9607dbeabcfc6a1853cf258e28b3a079923b63d3bf97504dd59e64a5f5f44f9da968c23c12b4279e8d45ff3bd39418942ca6f00d9d548c9a0ccfd73/haproxy-1.7.9.tar.gz
[root@haproxy-server-master src]# tar zxvf haproxy-1.7.9.tar.gz

二、安装

[root@haproxy-server-master src]# cd haproxy-1.7.9
[root@haproxy-server-master haproxy-1.7.9]# uname -r
3.10.0-514.el7.x86_64
[root@haproxy-server-master haproxy-1.7.9]# make TARGET=linux310 ARCH=x86_64 PREFIX=/usr/local/haproxy
[root@haproxy-server-master haproxy-1.7.9]# make install PREFIX=/usr/local/haproxy

参数说明:

  • TARGET=linux310,内核版本,使用uname -r查看内核,如:3.10.0-514.el7,此时该参数就为linux310;kernel 大于2.6.28的可以用:TARGET=linux2628;
  • ARCH=x86_64,系统位数;
  • PREFIX=/usr/local/haprpxy #/usr/local/haprpxy,为haprpxy安装路径。

三、添加配置文件

1.79及以后的版本解压后文件内就没有haproxy.cfg文件,所以需要我们自己找个模板写一下。

由于没有配置其他的服务器,这里就简单的添加一个可以让Haproxy启动的配置。

[root@haproxy-server-master haproxy]# mkdir conf
[root@haproxy-server-master haproxy]# ls
conf  doc  sbin  share
[root@haproxy-server-master haproxy]# cd conf/
[root@haproxy-server-master conf]# vim haproxy.cfg

global
        log 127.0.0.1   local0
        maxconn 1000
        daemon

defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        retries 3
        timeout connect 5000
        timeout client  50000
        timeout server 50000

listen admin_stats
        bind 0.0.0.0:1080
        mode http
        option httplog
        maxconn 10
        stats refresh 30s
        stats uri /stats
        stats realm XingCloud Haproxy
        stats auth admin:admin
        stats auth  Frank:Frank
        stats hide-version
        stats  admin if TRUE

四、启动haproxy

[root@haproxy-server-master conf]# /usr/local/haproxy/sbin/haproxy -f /usr/local/haproxy/conf/haproxy.cfg

验证一下是否启动成功:

[root@haproxy-server-master conf]# lsof -i :1080
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
haproxy 2221 root    3u  IPv4  20285      0t0  TCP *:socks (LISTEN)

五、访问统计页面

地址:http://10.73.150.4:1080/stats

未分类

附:其他问题

六、haproxy记录日志

在配置前,我们先来了解一下日志的level:local0~local7 16~23保留为本地使用:

  • emerg 0 系统不可用;
  • alert 1 必须马上采取行动的事件;
  • crit 2 关键的事件;
  • err 3 错误事件;
  • warning 4 警告事件;
  • notice 5 普通但重要的事件;
  • info 6 有用的信息;
  • debug 7 调试信息。

默认 haproxy 是不记录日志的,为了记录日志还需要配置 syslog 模块,在 linux 下是 rsyslogd 服务。

1、先安装 rsyslog(系统应该都安装了,先查看一下)

[root@haproxy-server-master /]# yum -y install rsyslog

2、添加haproxy的日志配置

[root@haproxy-server-master /]# vim /etc/rsyslog.d/haproxy.conf

$ModLoad imudp
$UDPServerRun 514
local0.* /var/log/haproxy.log

[root@haproxy-server-master /]# ls /etc/rsyslog.d/
haproxy.conf  listen.conf

3、修改 /etc/rsyslog.conf 文件

在#### RULES ####上面一行的地方加入以下内容(文件里应该默认有这个配置,可以看一下):

[root@haproxy-server-master /]# vim /etc/rsyslog.conf

# Include all config files in /etc/rsyslog.d/
$IncludeConfig /etc/rsyslog.d/*.conf

4、修改 /etc/sysconfig/rsyslog 文件

把SYSLOGD_OPTIONS=”-m 0”改成 SYSLOGD_OPTIONS=”-r -m 0 -c 2”

[root@haproxy-server-master /]# vim /etc/sysconfig/rsyslog

SYSLOGD_OPTIONS="-r -m 0 -c 2"

相关解释说明:

  • -r:打开接受外来日志消息的功能,其监控514 UDP端口;
  • -x:关闭自动解析对方日志服务器的FQDN信息,这能避免DNS不完整所带来的麻烦;
  • -m:修改syslog的内部mark消息写入间隔时间(0为关闭),例如240为每隔240分钟写入一次”–MARK–”信息;
  • -h:默认情况下,syslog不会发送从远端接受过来的消息到其他主机,而使用该选项,则把该开关打开,所有 接受到的信息都可根据syslog.conf中定义的@主机转发过去。

3、保存,重启 rsyslog 服务

[root@haproxy-server-master /]# systemctl restart rsyslog.service
[root@haproxy-server-master /]# systemctl status rsyslog.service
● rsyslog.service - System Logging Service
   Loaded: loaded (/usr/lib/systemd/system/rsyslog.service; enabled; vendor preset: enabled)
   Active: active (running) since 一 2017-11-27 10:51:26 CST; 11s ago
     Docs: man:rsyslogd(8)
           www.rsyslog.com/doc/
 Main PID: 3855 (rsyslogd)
   CGroup: /system.slice/rsyslog.service
           └─3855 /usr/sbin/rsyslogd -n

11月 27 10:51:26 haproxy-server-master systemd[1]: Starting System Logging Service...
11月 27 10:51:26 haproxy-server-master rsyslogd[3855]:  [origin software="rsyslogd" swVersion="8.24.0" x-pid="38...tart
11月 27 10:51:26 haproxy-server-master systemd[1]: Started System Logging Service.
Hint: Some lines were ellipsized, use -l to show in full.

现在你就可以看到日志(/var/log/haproxy.log)了(如果没有,重启一下Haproxy)。

使用haproxy进行会话保持

现象说明

http出口的负载均衡策略是roundrobin,部分业务系统将会话信息保存在backend server,backend server之间未做同步。

用户在访问、操作的时候,会出现会话不连贯的现象 。

方案说明

haproxy可以使用多种方式做到会话保持,可以在balance中指定均衡算法:

balance <algorithm> [ <arguments> ]
balance url_param <param> [check_post]

source的方式将同一个源IP的请求转发给同一个backend server,可以作用于tcp和http。但是当某个源IP的请求量较大,或者用户请求经过NAT后到达,会导致backend server的负载严重不均衡。不采用。

url_param的方式,需要业务在url中带有sessionid,适用于http。不采用。

也可以使用stick-tables的方式。stick-table设置复杂,且需要维护记录表。不采用。

还可以使用cookie的方式。cookie本身也有多种策略,例如insert,prefix,rewrite等,适用于http。

经过对比,决定采用以下方式:

cookie cookie.XXXXX.cn insert indirect postonly

即:

只有遇到post请求的时候,haproxy在响应中设置一个名为
`cookie.XXXXX.cn`的cookie,后续带有该cookie的请求到达
haproxy时,haproxy将该cookie去除后,转发给cookie指定的backen server。

过程说明

未发送post请求之前,依然采用roundrobin的方式

第一次get请求:

未分类

第二次get请求:

未分类

发送了post请求后,被设置cookie

cookie的值为处理post请求的backend server的ID。

未分类

后续请求被转发到同一个backend server

用户后续所有http请求,都会带上cookie,被转发到同一个backend server。

未分类

未分类

绑定的backend server宕机后,cookie失效

绑定的backend server宕机后,虽然后续发送的请求中依然会带有cookie,但是这时候会重新回到roundrobin的状态,直到用户再次发送POST请求,重新绑定backend server。

宕机后,再次发送POST的情形:

未分类

HAProxy常用配置介绍,ACL详解

一、HAProxy简介

HAProxy 是一款高性能TCP/HTTP 反向代理负载均衡服务器,具有如下功能:

  • 根据静态分配的cookies完成HTTP请求转发
  • 在多个服务器间实现负载均衡,并且根据HTTP cookies 实现会话粘性
  • 主备服务器切换
  • 接受访问特定端口实现服务监控
  • 实现平滑关闭服务,不中断已建立连接的请求响应,拒绝新的请求
  • 在请求或响应HTTP报文中添加,修改,或删除首部信息
  • 根据正则规则阻断请求
  • 提供带有用户认证机制的服务状态报告页面

HAProxy特别适用于那些负载特大的web站点,这些站点通常又需要会话保持或七层处理。HAProxy运行在时下的硬件上,完全可以支持数以万计的 并发连接。并且它的运行模式使得它可以很简单安全的整合进您当前的架构中, 同时可以保护你的web服务器不被暴露到网络上。

HAProxy 实现了一种事件驱动、单一进程模型,此模型支持非常大的并发连接数。多进程或多线程模型受内存限制 、系统调度器限制以及无处不在的锁限制,很少能处理数千并发连接。事件驱动模型因为在有更好的资源和时间管理的用户端(User-Space) 实现所有这些任务,所以没有这些问题。此模型的弊端是,在多核系统上,这些程序通常扩展性较差。这就是为什么他们必须进行优化以 使每个CPU时间片(Cycle)做更多的工作。

HAProxy实际工作中,它占用用户空间时间要比内核运行时间少20倍,所以对系统参数调优是十分必要的一项工作。

另外衡量一个负载均衡服务器主要考量三个指标

  • session rate
    此项指标非常重要,它决定了一个load balancer 能不能分发所有接受的请求。这项指标通常是由CPU性能决定。测量指标的大小跟传输的每个对象的大小有关,通常用空对象来测试,Session rates 在 100,000 sessions/s 左右,使用 Xeon E5 在 2014测试。

  • session concurrency
    该指标与前一指标相关联。这一指标与服务器内存和系统可以处理的文件描述符数量有关。 通常每个session占用34KB,即大概3W个session占用1GB内存空间,实际上,socket buffer也会占用内存空间,2W个session socket占用1GB内存。

  • data forwarding rate
    这一指标与 session rate 相对立,它的衡量单位通常是 Megabytes/s (MB/s), 或者 Gigabits/s (Gbps)。传输较大的对象有利于该指标的提升,因为较大的对象传输可以减少session建立和关闭浪费的时间。而测量session rate 则在传输小对象时有利于指标提升。haproxy 在2014年使用 Xeon E5 测试成绩为40 Gbps。

二、HAProxy程序环境

本文环境:CentOS7.2 haproxy 1.5 通过yum 安装

程序环境:
    配置文件:/etc/haproxy/haproxy.cfg
        Unit File: haproxy.service
        主程序:/usr/sbin/haproxy

配置文件:
    global:全局配置段
        进程及安全配置相关的参数
        性能调整相关的参数
        Debug相关的参数
    proxies:代理配置段
        defaults:为frontend, backend以及listen提供默认配置;
        frontend:前端,相当于Nginx中的server{ ... };
        backend:后端,相当于nginx中的upstream { ...  };
        listen:前后端的直接组合;
    **关于前端与后端的关系:一个前端可以指向多个后端;同时一个后端可以被多个调用。

三、HAProxy配置详解

3.1 global配置段

3.1.1 进程相关配置

  • 定义日志系统相关属性
log <address> [len <length>] <facility> [max level [min level]]

harpoxy 将日志发送到指定的rsyslog服务器,在本地记录也要开启rsyslog服务;
全局端最多可配置两个log 服务器;
< address> :日志服务器地址
[ len ] 指定记录的日志最大长度

  • 定义运行用户,所属组

username
group groupname

  • 运行方式

意味着后台守护进程

3.1.2 参数调优

    maxconn <number>:设定单haproxy进程的最大并发连接数;
    maxconnrate <number>:设定单haproxy进程每秒接受的连接数;
    maxsslconn <number>:设定单haproxy进程的ssl连接最大并发连接数;
    maxsslrate <number>:单haproxy进程的ssl连接的创建速率上限;
    spread-checks <0..50, in percent>:避免对于后端检测同时并发造成
    的问题,设置错开时间比,范围0到50,一般设置2-5较好。

3.1.3 用户列表

用于对haproxy 状态监控页面的用户认证。至少要定义一个用户列表并且添加一个用户
密码可以加密或明文。

Example:

userlist L1
  group G1 users tiger,scott
  group G2 users xdb,scott

  user tiger password $6$k6y3o.eP$JlKqe4(...)xHSwRv6J.C0/D7cV91
  user scott insecure-password elgato
  user xdb insecure-password hello

userlist L2
  group G1
  group G2

  user tiger password $6$k6y3o.eP$JlKBx(...)xHSwRv6J.C0/D7cV91 groups G1
  user scott insecure-password elgato groups G1,G2
  user xdb insecure-password hello groups G2

3.2 proxy配置段

这部分配置在下列定义区域下使用

        - defaults  < name >
        - frontend < name >
        - backend  < name >
        - listen   < name >
  • “defaults” 区域定义了frontend,backend,listen 的默认参数
  • “frontend“ 区域描述了接收客户端请求的监听配置
  • ”backend“ 区域描述接受请求处理的后端服务器配置
  • ”listen“ 区域描述一组前端和后端直接一对一绑定的组配置

HAProxy 配置的关键字与区域限制特性,即有些关键字在某个区域不可以使用
下面开始讲解关键字的用法

3.2.1 常用配置指令

bind [<address>]:<port_range> [, ...] [param*]

仅在frontend和listen区域使用。定义服务监听端口地址等参数
[ param* ] 参数根据系统而定,一般不需要指定
example:

bind :80     #监听本机所有IP的80端口
bind *:80    #监听本机所有IP的80端口
bind 192.168.12.1:8080,10.1.0.12:8090
mode {tcp|http|health}

tcp:基于layer4实现代理,可代理大多数基于tcp的应用层协议,例如ssh/mysql/pgsql等;
http:客户端的http请求会被深度解析;
health:工作为健康状态检查响应模式,当请求到达时仅回应“OK”即断开连接;

balance <algorithm> [ <arguments> ]
balance url_param <param> [check_post]

在backend区域定义调度算法
< algorithm > 如下:

  • roundrobin
带有权重的轮询调度算法;
server后面使用weight来定义权重;
动态算法:支持权重的运行时调整,支持慢启动(缓慢接收大量请求在刚启动时);仅支持最大4095个后端活动主机
  • static-rr
    静态的roundrobin算法;

不支持权重的运行时调整及慢启动;但后端主机数量无限制;

  • leastconn
    带权重的最少连接分配动态算法;

适用长连接应用协议,如ssh等

  • first
    第一优先算法;

如果第一个服务端可接受请求则总是把连接分配给它,直到第一个服务端处于繁忙,分配给下一个,顺序按服务端的数字ID从小到大排列

  • source
    源IP hash 算法;

该算法保证在后端服务器组没有减少或增加的情况下,能将来自同一客户端IP的请求分配至同一个服务端;
该算法适合在无法使用cookie插入的TCP模式下使用
动态算法或静态算法取决于hash-type;

  • uri
    uri hash 算法;
    该算法hash uri 的查询标记的左侧部分,或者指定whole 参数时hash全部uri;
    该算法保证访问同一uri的请求分配至同一服务端,适用于后端为缓存服务器的情况,以提高缓存命中率;
    动态算法或静态算法取决于hash-type;
    另外:该算法支持追加参数[ < arguments > ]:
    (1) whole :hash完整uri
    (2) len number:hash指定uri的长度
    (3) depth nubmer:hash指定目录深度,每个”/”代表一个深度

  • uri_param

param hash 算法;

对用户请求的url中的< param >部分中的指定的参数的值(uri中”=”部分)作hash计算;
该算法适用于有用户识别参数的uri ,它保证同一user id 的请求分配至同一服务端;
若果check_post 标识启用,则在uri中没有找到”?”参数时,对HTTP Post 请求实体查找参数声明;
动态算法或静态算法取决于hash-type;

Example:

balance url_param userid
balance url_param session_id check_post 64
  • hdr(< name >)
    HTTP 首部字段hash算法;

指定的http首部将会被取出做hash计算。如果没有值,则降至轮询调度;
动态算法或静态算法取决于hash-type;

hash_type < method >

在balance 指令中选定与hash 有关的算法,都会受此影响。
默认采取的方法为map-based
< method > 如下:

  • map-based:取模法,hash数据结构是静态数组;
    该hash是静态的,不支持在线调整权重,不支持慢启动;

该算法调度平滑,后端服务器能够均匀承受负载;
缺点也是明显的:当服务器的总权重发生变化时,即有服务器上线或下线,都会导致调度结果整体改变。如果想避免此种情况应采用consistent 方法;

  • consistent:一致性哈希,哈希的数据结构是“树”;
    该hash是动态的,支持在线调整权重,支持慢启动

每一个server 会在”树”中出现多次, 在树中查找hash key,并选择最近的server;
该方法的优点在于,当服务器的总权重发生变化时,对调度结果影响是局部的,不会引起大的变动。所以十分适合缓存服务器;
缺点:该算法不够平滑,很容易导致后端服务器负载不均衡。所以很有必要对服务器的权重以或者服务器ID进行调整;
为保持均匀负载,应该保证所有服务器ID保持一致;

server <name> <address>[:[port]] [param*]
default-server [param*]

server用于在backend和listen中定义一个主机;
default-server 用于设定server的默认参数;

[param*] 如下:

  • weight < weight >:当前server的权重;
  • id < number > :设定server ID
  • cookie < value >:为当前server指定其cookie值,此值会在收到请求报文时进行检测,其功能在于实现基于cookie会话保持;
  • check:对当前server进行健康状态检测;
    inter < delay >:时间间隔;
    rise < count >:判定为“健康”状态需要检测的次数,默认2;
    fall < count >:判定为“不健康”状态需要检测的次数,默认3;
    addr <ipv4|ipv6>:健康状态检测时使用的地址;
    port < port >:健康状态检测时使用的端口;
    注意:默认为传输层检测,即探测端口是否能响应;需要执行应用层检测,则需要httpchk, smtpchk, mysql-check, pgsql-check, ssl-hello-chk;
  • maxconn :当前server的最大并发连接数;
  • maxqueue :当前server的等待队列的最大长度;
  • disabled:将主机标记为不可用;
  • redir
    :将发往当前server的所有请求GET和HEAD类的请求均重定向至指定的URL;
Examples :
server first  10.1.1.1:1080 id 3 cookie first  check inter 1000 maxconn 10000 maxqueue 2000
server second 10.1.1.2:1080 id 4 cookie second check inter 1000
option httpchk
option httpchk <uri>
option httpchk <method> <uri>
option httpchk <method> <uri> <version>    

基于http协议作7层健康状态检测机制,默认是基于tcp层进行检测;
TCP 模式也可以使用该检测机制
< method > < uri > < version >:请求报文的超始行;
method 默认方法为 OPTIONS;返回状态码2XX,3XX意味成功;

Examples :
# Relay HTTPS traffic to Apache instance and check service availability
# using HTTP request "OPTIONS * HTTP/1.1" on port 80.
backend https_relay
    mode tcp
    option httpchk OPTIONS /index.html HTTP/1.1rnHost: www
    server apache1 192.168.1.1:443 check port 80
http-check expect [!] <match> <pattern>

定义检测有效期望值;
! 表示认定的错误值;< match > 可取值为:

  • status < string >
  • rstatus < regex > 正则方式
  • string < string >
  • rstring < regex >
Examples :
# only accept status 200 as valid
http-check expect status 200

# consider SQL errors as errors
http-check expect ! string SQL Error

# consider status 5xx only as errors
http-check expect ! rstatus ^5

# check that we have a correct hexadecimal tag before /html
http-check expect rstring <!--tag:[0-9a-f]*</html>
cookie <name> [ rewrite | insert | prefix ] [ indirect ] [ nocache ] [ postonly ] [ preserve ] [ httponly ] [ secure ] [ domain <domain> ]* [ maxidle <idle> ] [ maxlife <life> ]

启用基于cookie的会话黏性,要结合server指定的cookie参数一起实现;
常用形式:cookie WEBSRV insert nocache indirect

Example:
backend websrvs
balance     roundrobin
cookie WEBSRV insert nocache indirect
server      web1 10.1.0.68:80 check weight 2 maxconn 5000 cookie web1
server      web2 10.1.0.69:80 check weight 1 maxconn 3000 cookie web2
default_backend <backend>

当use_backend 的使用规则没有被匹配时,由default_backend 指定默认服务器组;
关于use_backend 使用后续会在acl 章节中讲解;

3.2.2 log 相关

为frontend或backend定义日志记录机制;

log global  :使用全局定义的日志记录方式
log <address> [len <length>] <facility> [<level> [<minlevel>]]:自定义
no log :不记录
capture request header <name> len <length>
-->记录请求报文中的指定的首部的值于日志中;len用于指定要记录的信息的长度;
capture response header <name> len <length>
-->记录响应报文中的指定的首部的值于日志中;len用于指定要记录的信息的长度;
示例:
    capture request header Referer len 30

3.2.3 自定义错误页面

- errorfile <code> <file>

< code > 指定HTTP返回的状态码。200, 400, 403, 408, 500, 502, 503, and 504 可使用;
< file > 指定一个文件代替HTTP响应;
Example:errorfile 503 /etc/haproxy/errorfiles/503sorry.http

- errorloc <code> <url>
- errorloc302 <code> <url>

发生错误时由haproxy重定向至指定url,以上两个命令等同,响应状态码为302
Example:errorloc 503 http://www.mydomain.com/index…

- errorloc303 <code> <url>

响应状态码为303,表示以GET方法重新请求页面

3.2.4 修改请求或响应报文首部

option forwardfor [ except <network> ] [ header <name> ] [ if-none ]

HAProxy把请求报文发往后端主机之前在请求报文添加“X-Forwared-For”首部
目的为使后端服务器可记录发出请求客户端的IP地址
[ except < network> ] :选择排除的网络地址
[ header < name> ] :不使用X-Forwared-For,自定义名称
[ if-none ]:有时请求原来带有该字段,此时不再更改
Example:option forwardfor if-none

reqadd  <string> [{if | unless} <cond>]
rspadd <string> [{if | unless} <cond>]

在HTTP请求或响应首部内容尾部添加值
Example:rspadd X-Via: HAProxy/1.5

reqdel  <search> [{if | unless} <cond>]
reqidel <search> [{if | unless} <cond>]  (不区分大小写)        

删除HTTP请求中正则匹配的所有首部

rspdel  <search> [{if | unless} <cond>]
rspidel <search> [{if | unless} <cond>]  (不区分大小写)

删除HTTP响应中正则匹配的所有首部。属于安全加强策略,删除一些服务器版本信息,防止针对攻击
Example:rspidel Server.*

3.2.5 超时时长设定

timeout client <timeout>

设定客户端最大非活动时长, 默认单位是ms;最好与timeout server一致

timeout server <timeout>

设定服务端最大非活动时长, 默认单位是ms;

timeout connect <timeout>

设定最大与服务端建立连接的时长

timeout http-keep-alive <timeout>

设定最大等待新请求的空闲时长,默认单位为ms;

timeout client-fin <timeout>

在客户端侧设定半关闭连接非活动超时

timeout server-fin <timeout>

在服务端侧设定半关闭连接非活动超时
Example:

defaults http
timeout connect 5s
timeout client 30s
timeout server 30s
timeout client-fin 10s
timeout http-keep-alive 500

四、使用ACLs和获取样本

Haproxy 能够从请求报文,响应报文,从客户端或者服务端信息,从表,环境信息等等中提取数据。提取这样的数据的动作我们称之为获取样本。进行检索时,这些样本可以用来实现各种目的,比如作为粘滞表的键,最常用的用途是,根据预定义的模式来进行匹配。
访问控制列表(ACL)提供一个灵活方案进行内容切换,或者在从请求,响应,任何环境状态中提取的数据基础之上做出决策。控制列表的原则很简单:

  • 从数据流,表,环境中提取数据样本
  • 对提取的样本可选地应用格式转换
  • 对一个样本应用一个或多个模式匹配
  • 当模式匹配样本时才执行动作

执行的动作通常是阻断请求,选择一个后端服务器或者添加一个HTTP首部
需要提醒的是,获取的样本数据不光可以使用在acl中,也可以使用别处,例如记录log中
定义ACL的语法为:

acl <aclname> <criterion> [flags] [operator] [<value>] ...

这样一条语句建立了一个acl 测试;
这些测试应用在请求或响应中被”标准”< criterion > 部分所指定的内容,而且可以指定[ flags] 进行特性调整,有些< criterion > 支持操作符[operator] 进行运算,同时一些转换格式的关键字可以跟在< criterion >后面,使用” , “隔开。而值[< value >] 要求被
< criterion > 所支持的数据形式,多个值使用空格分隔。
< criterion > 通常是指获取样本方法的名称。使用一个获取样本方法,暗含着其输出样本的类型,类型是以下列出的一种:

  • boolean
  • integer (signed or unsigned)
  • IPv4 or IPv6 address
  • string
  • data block

ACL引擎匹配数据使用的模式类型如下:

  • boolean
  • integer or integer range
  • IP address / network
  • string (exact, substring, suffix, prefix, subdir, domain)
  • regular expression
  • hex block

ACL flags 可用列表如下:

  • -i : 忽略大小写
  • -f filename : 从文件中载入模式
  • -m method : 指定模式匹配方法
  • -n : 禁止DNS解析
  • -M : -f 载入的文件作为映射文件使用
  • -u : 强制ACL的名称唯一
  • — : 强制结束flag结束,避免了字符串中含有的- 引起混淆

其中flag中的 -m 选项可使用的模式匹配方法如下,需要说明的是有些方法已被默认指定无需声明,例如int,ip

  • “found” : 只是用来探测数据流中是否存在指定数据,不进行任何比较
  • “bool” : 检查结果返回布尔值。匹配没有模式,可以匹配布尔值或整数,不匹配0和false,其他值可以匹配
  • “int” : 匹配整数类型数据;可以处理整数和布尔值类型样本,0代表false,1代表true
  • “ip” : 匹配IPv4,IPv6地址类型数据。该模式仅被IP地址兼容,不需要特别指定
  • “bin” : 匹配二进制数据
  • “len” : 匹配样本的长度的整数值
  • “str” : 精确匹配,根据字符串匹配文本
  • “sub” : 子串匹配,匹配文本是否包含子串
  • “reg” : 正则匹配,根据正则表达式列表匹配文本
  • “beg” : 前缀匹配,检查文本是否以指定字符串开头
  • “end” : 后缀匹配,检查文本是否以指定字符串结尾
  • “dir” : 子目录匹配,检查部分文本中以” / “作为分隔符的内容是否含有指定字符串
  • “dom” : 域匹配。检查部分文本中以” . “作为分隔符的内容是否含有指定字符串

如果获取样本值为整数,数值比较符可使用,:

eq : true if the tested value equals at least one value
ge : true if the tested value is greater than or equal to at least one value
gt : true if the tested value is greater than at least one value
le : true if the tested value is less than or equal to at least one value
lt : true if the tested value is less than at least one value

想必前面一堆理论性的论述已经把大家搞的晕头转向,下面结合获取样本方法和访问控制动作指令具体阐述ACL使用方法

先介绍控制动作指令

  • layer 4 传输层控制指令
tcp-request connection <action> [{if | unless} <condition>]

对tcp请求控制指令
< condition > 即为ACL定义的访问控制列表
< action > 常用值有 “accept”, “reject”

  • layer 7 应用层控制指令
#阻断符合ACL的访问请求
block { if | unless } <condition> 
#http请求的控制指令
http-request { allow | deny}  [ { if | unless } <condition> ]
  • 后端主机调用
#根据条件来调用指定后端
use_backend <backend> [{if | unless} <condition>]
  • 由ACL定义的多个< condition > 组成联合条件,逻辑符为
    • and (默认操作符,可省略)
    • or (或者使用 “||”)
    • ! (取反)

4.1 获取内部状态样本

# 与后端建立会话速率,每秒钟建立的新会话
be_sess_rate([<backend>]) : integer

Example :

# 某后端被请求过于繁忙,则重定向至错误页
    mode http
    acl being_scanned be_sess_rate gt 100
    redirect location /denied.html if being_scanned

4.2 获取layer 4 样本

在传输层获取样本,通常是TCP/IP 协议的IP和端口,以及建立连接速率等等。而且此部分样本通常用于”tcp-request connection”指令中的规则之中。

        dst : ip             #目标地址
        dst_port : integer
        src : ip             #源地址
        src_port : integer

Example:

#阻断来自非指定IP的访问8080端口的请求
acl myhost src 10.1.0.200
acl myport dst_port 8080
tcp-request connection reject if !myhost myport        

4.3 获取layer 7 样本

/1

path : string

提取请求url的地址信息,从第一个”/”开始,不包含host,不包含参数
ACL 衍生,即包含了-m 选项中匹配模式方法 :

path : exact string match
path_beg : prefix match
path_dir : subdir match
path_dom : domain match
path_end : suffix match
path_len : length match
path_reg : regex match
path_sub : substring match

Example:

#请求资源为图片,则调用图片服务器后端
 acl picture path_end -i .jpg .png .gif
 use_backend server_pic if picture

/2

url : string

提取URL的全部内容,包含host和参数
ACL 衍生类似,不再列举

/3

req.hdr([<name>[,<occ>]]) : string

提取http请求的指定首部字段值,< occ >可指定出现的位置
ACL 衍生 :

  hdr([<name>[,<occ>]])     : exact string match
  hdr_beg([<name>[,<occ>]]) : prefix match
  hdr_dir([<name>[,<occ>]]) : subdir match
  hdr_dom([<name>[,<occ>]]) : domain match
  hdr_end([<name>[,<occ>]]) : suffix match
  hdr_len([<name>[,<occ>]]) : length match
  hdr_reg([<name>[,<occ>]]) : regex match
  hdr_sub([<name>[,<occ>]]) : substring match

Example:

#阻断火狐浏览器发送的请求
acl firefox hdr_reg(User-Agent)     -i      .*firefox.*
block if firefox

/4

method : integer + string

提取请求报文中的请求方法

Example:

#拒绝GET HEAD 方式之外的HTTP请求
acl valid_method method GET HEAD
http-request deny if ! valid_method

4.4 内建ACL

HAProxy有众多内建的ACLs,这些ACLs可直接调用,例如

  • LOCALHOST 匹配来自本地IP的连接,127.0.0.1/8
  • HTTP_1.1 匹配http版本1.1
  • METH_GET 匹配http请求GET或HEAD方法
  • TRUE
  • FALSE

Example:

#拒绝GET HEAD 方式之外的HTTP请求
http-request deny if ! METH_GET

传送门:官方1.5使用手册 http://cbonte.github.io/haproxy-dconv/1.5/configuration.html

— EOF —

HAProxy系列—配置文件详解

haproxy 配置中分成五部分内容,分别如下:

  • global:参数是进程级的,通常是和操作系统相关。这些参数一般只设置一次,如果配置无误,就不需要再次进行修改;

  • defaults:配置默认参数,这些参数可以被用到frontend,backend,Listen组件;

  • frontend:接收请求的前端虚拟节点,Frontend可以更加规则直接指定具体使用后端的backend;

  • backend:后端服务集群的配置,是真实服务器,一个Backend对应一个或者多个实体服务器;

  • Listen Fronted和backend的组合体。

global   # 全局参数的设置
    log 127.0.0.1 local0 info
    # log语法:log <address_1>[max_level_1] # 全局的日志配置,使用log关键字,指定使用127.0.0.1上的syslog服务中的local0日志设备,记录日志等级为info的日志
    user haproxy
    group haproxy
    # 设置运行haproxy的用户和组,也可使用uid,gid关键字替代之
    daemon
    # 以守护进程的方式运行
    nbproc 16
    # 设置haproxy启动时的进程数,根据官方文档的解释,我将其理解为:该值的设置应该和服务器的CPU核心数一致,即常见的2颗8核心CPU的服务器,即共有16核心,则可以将其值设置为:<=16 ,创建多个进程数,可以减少每个进程的任务队列,但是过多的进程数也可能会导致进程的崩溃。这里我设置为16
    maxconn 4096
    # 定义每个haproxy进程的最大连接数 ,由于每个连接包括一个客户端和一个服务器端,所以单个进程的TCP会话最大数目将是该值的两倍。
    #ulimit -n 65536
    # 设置最大打开的文件描述符数,在1.4的官方文档中提示,该值会自动计算,所以不建议进行设置
    pidfile /var/run/haproxy.pid
    # 定义haproxy的pid 
defaults # 默认部分的定义
    mode http
    # mode语法:mode {http|tcp|health} 。http是七层模式,tcp是四层模式,health是健康检测,返回OK
    log 127.0.0.1 local3 err
    # 使用127.0.0.1上的syslog服务的local3设备记录错误信息
    retries 3
    # 定义连接后端服务器的失败重连次数,连接失败次数超过此值后将会将对应后端服务器标记为不可用
    option httplog
    # 启用日志记录HTTP请求,默认haproxy日志记录是不记录HTTP请求的,只记录“时间[Jan 5 13:23:46] 日志服务器[127.0.0.1] 实例名已经pid[haproxy[25218]] 信息[Proxy http_80_in stopped.]”,日志格式很简单。
    option redispatch
    # 当使用了cookie时,haproxy将会将其请求的后端服务器的serverID插入到cookie中,以保证会话的SESSION持久性;而此时,如果后端的服务器宕掉了,但是客户端的cookie是不会刷新的,如果设置此参数,将会将客户的请求强制定向到另外一个后端server上,以保证服务的正常。
    option abortonclose
    # 当服务器负载很高的时候,自动结束掉当前队列处理比较久的链接
    option dontlognull
    # 启用该项,日志中将不会记录空连接。所谓空连接就是在上游的负载均衡器或者监控系统为了探测该服务是否存活可用时,需要定期的连接或者获取某一固定的组件或页面,或者探测扫描端口是否在监听或开放等动作被称为空连接;官方文档中标注,如果该服务上游没有其他的负载均衡器的话,建议不要使用该参数,因为互联网上的恶意扫描或其他动作就不会被记录下来
    option httpclose
    # 这个参数我是这样理解的:使用该参数,每处理完一个request时,haproxy都会去检查http头中的Connection的值,如果该值不是close,haproxy将会将其删除,如果该值为空将会添加为:Connection: close。使每个客户端和服务器端在完成一次传输后都会主动关闭TCP连接。与该参数类似的另外一个参数是“option forceclose”,该参数的作用是强制关闭对外的服务通道,因为有的服务器端收到Connection: close时,也不会自动关闭TCP连接,如果客户端也不关闭,连接就会一直处于打开,直到超时。
    contimeout 5000
    # 设置成功连接到一台服务器的最长等待时间,默认单位是毫秒,新版本的haproxy使用timeout connect替代,该参数向后兼容
    clitimeout 3000
    # 设置连接客户端发送数据时的成功连接最长等待时间,默认单位是毫秒,新版本haproxy使用timeout client替代。该参数向后兼容
    srvtimeout 3000
    # 设置服务器端回应客户度数据发送的最长等待时间,默认单位是毫秒,新版本haproxy使用timeout server替代。该参数向后兼容

listen status # 定义一个名为status的部分
    bind 0.0.0.0:1080
    # 定义监听的套接字
    mode http
    # 定义为HTTP模式
    log global
    # 继承global中log的定义
    stats refresh 30s
    # stats是haproxy的一个统计页面的套接字,该参数设置统计页面的刷新间隔为30s
    stats uri /admin?stats
    # 设置统计页面的uri为/admin?stats
    stats realm Private lands
    # 设置统计页面认证时的提示内容
    stats auth admin:password
    # 设置统计页面认证的用户和密码,如果要设置多个,另起一行写入即可
    stats hide-version
    # 隐藏统计页面上的haproxy版本信息

frontend http_80_in # 定义一个名为http_80_in的前端部分
    bind 0.0.0.0:80
    # http_80_in定义前端部分监听的套接字
    mode http
    # 定义为HTTP模式
    log global
    # 继承global中log的定义
    option forwardfor
    # 启用X-Forwarded-For,在requests头部插入客户端IP发送给后端的server,使后端server获取到客户端的真实IP
    acl static_down nbsrv(static_server) lt 1
    # 定义一个名叫static_down的acl,当backend static_sever中存活机器数小于1时会被匹配到
    acl php_web url_reg /*.php$
    #acl php_web path_end .php
    # 定义一个名叫php_web的acl,当请求的url末尾是以.php结尾的,将会被匹配到,上面两种写法任选其一
    acl static_web url_reg /*.(css|jpg|png|jpeg|js|gif)$
    #acl static_web path_end .gif .png .jpg .css .js .jpeg
    # 定义一个名叫static_web的acl,当请求的url末尾是以.css、.jpg、.png、.jpeg、.js、.gif结尾的,将会被匹配到,上面两种写法任选其一
    use_backend php_server if static_down
    # 如果满足策略static_down时,就将请求交予backend php_server
    use_backend php_server if php_web
    # 如果满足策略php_web时,就将请求交予backend php_server
    use_backend static_server if static_web
    # 如果满足策略static_web时,就将请求交予backend static_server

backend php_server #定义一个名为php_server的后端部分
    mode http
    # 设置为http模式
    balance source
    # 设置haproxy的调度算法为源地址hash
    cookie SERVERID
    # 允许向cookie插入SERVERID,每台服务器的SERVERID可在下面使用cookie关键字定义
    option httpchk GET /test/index.php
    # 开启对后端服务器的健康检测,通过GET /test/index.php来判断后端服务器的健康情况
    server php_server_1 10.12.25.68:80 cookie 1 check inter 2000 rise 3 fall 3 weight 2
    server php_server_2 10.12.25.72:80 cookie 2 check inter 2000 rise 3 fall 3 weight 1
    server php_server_bak 10.12.25.79:80 cookie 3 check inter 1500 rise 3 fall 3 backup
    # server语法:server [:port] [param*] # 使用server关键字来设置后端服务器;为后端服务器所设置的内部名称[php_server_1],该名称将会呈现在日志或警报中、后端服务器的IP地址,支持端口映射[10.12.25.68:80]、指定该服务器的SERVERID为1[cookie 1]、接受健康监测[check]、监测的间隔时长,单位毫秒[inter 2000]、监测正常多少次后被认为后端服务器是可用的[rise 3]、监测失败多少次后被认为后端服务器是不可用的[fall 3]、分发的权重[weight 2]、最后为备份用的后端服务器,当正常的服务器全部都宕机后,才会启用备份服务器[backup]

backend static_server
    mode http
    option httpchk GET /test/index.html
    server static_server_1 10.12.25.83:80 cookie 3 check inter 2000 rise 3 fall 3

你真的掌握 LVS、Nginx 及 HAProxy 的工作原理吗

当前大多数的互联网系统都使用了服务器集群技术,集群是将相同服务部署在多台服务器上构成一个集群整体对外提供服务,这些集群可以是 Web 应用服务器集群,也可以是数据库服务器集群,还可以是分布式缓存服务器集群等等。

未分类

在实际应用中,在 Web 服务器集群之前总会有一台负载均衡服务器,负载均衡设备的任务就是作为 Web 服务器流量的入口,挑选最合适的一台 Web 服务器,将客户端的请求转发给它处理,实现客户端到真实服务端的透明转发。

最近几年很火的「云计算」以及分布式架构,本质上也是将后端服务器作为计算资源、存储资源,由某台管理服务器封装成一个服务对外提供,客户端不需要关心真正提供服务的是哪台机器,在它看来,就好像它面对的是一台拥有近乎无限能力的服务器,而本质上,真正提供服务的,是后端的集群。

LVS、Nginx、HAProxy 是目前使用最广泛的三种软件负载均衡软件。

一般对负载均衡的使用是随着网站规模的提升根据不同的阶段来使用不同的技术。具体的应用需求还得具体分析,如果是中小型的 Web 应用,比如日 PV 小于1000万,用 Nginx 就完全可以了;如果机器不少,可以用 DNS 轮询,LVS 所耗费的机器还是比较多的;大型网站或重要的服务,且服务器比较多时,可以考虑用 LVS。

目前关于网站架构一般比较合理流行的架构方案:Web 前端采用 Nginx/HAProxy+Keepalived 作负载均衡器;后端采用 MySQ L数据库一主多从和读写分离,采用 LVS+Keepalived 的架构。

LVS

LVS 是 Linux Virtual Server 的简称,也就是 Linux 虚拟服务器。现在 LVS 已经是 Linux 标准内核的一部分,从 Linux2.4 内核以后,已经完全内置了 LVS 的各个功能模块,无需给内核打任何补丁,可以直接使用 LVS 提供的各种功能。

LVS 自从1998年开始,发展到现在已经是一个比较成熟的技术项目了。

LVS 的体系结构

未分类

LVS 架设的服务器集群系统有三个部分组成:

  • 最前端的负载均衡层,用 Load Balancer 表示
  • 中间的服务器集群层,用 Server Array 表示
  • 最底端的数据共享存储层,用 Shared Storage 表示

LVS 负载均衡机制

LVS 不像 HAProxy 等七层软负载面向的是 HTTP 包,所以七层负载可以做的 URL 解析等工作,LVS 无法完成。

LVS 是四层负载均衡,也就是说建立在 OSI 模型的第四层——传输层之上,传输层上有我们熟悉的 TCP/UDP,LVS 支持 TCP/UDP 的负载均衡。因为 LVS 是四层负载均衡,因此它相对于其它高层负载均衡的解决办法,比如 DNS 域名轮流解析、应用层负载的调度、客户端的调度等,它的效率是非常高的。

所谓四层负载均衡 ,也就是主要通过报文中的目标地址和端口。七层负载均衡 ,也称为“内容交换”,也就是主要通过报文中的真正有意义的应用层内容。

未分类

LVS 的转发主要通过修改 IP 地址(NAT 模式,分为源地址修改 SNAT 和目标地址修改 DNAT)、修改目标 MAC(DR 模式)来实现。

NAT 模式:网络地址转换

NAT(Network Address Translation)是一种外网和内网地址映射的技术。

NAT 模式下,网络数据报的进出都要经过 LVS 的处理。LVS 需要作为 RS(真实服务器)的网关。

当包到达 LVS 时,LVS 做目标地址转换(DNAT),将目标 IP 改为 RS 的 IP。RS 接收到包以后,仿佛是客户端直接发给它的一样。RS 处理完,返回响应时,源 IP 是 RS IP,目标 IP 是客户端的 IP。这时 RS 的包通过网关(LVS)中转,LVS 会做源地址转换(SNAT),将包的源地址改为 VIP,这样,这个包对客户端看起来就仿佛是 LVS 直接返回给它的。

未分类

DR 模式:直接路由

DR 模式下需要 LVS 和 RS 集群绑定同一个 VIP(RS 通过将 VIP 绑定在 loopback 实现),但与 NAT 的不同点在于:请求由 LVS 接受,由真实提供服务的服务器(RealServer,RS)直接返回给用户,返回的时候不经过 LVS。

详细来看,一个请求过来时,LVS 只需要将网络帧的 MAC 地址修改为某一台 RS 的 MAC,该包就会被转发到相应的 RS 处理,注意此时的源 IP 和目标 IP 都没变,LVS 只是做了一下移花接木。RS 收到 LVS 转发来的包时,链路层发现 MAC 是自己的,到上面的网络层,发现 IP 也是自己的,于是这个包被合法地接受,RS 感知不到前面有 LVS 的存在。而当 RS 返回响应时,只要直接向源 IP(即用户的 IP)返回即可,不再经过 LVS。

未分类

DR 负载均衡模式数据分发过程中不修改 IP 地址,只修改 mac 地址,由于实际处理请求的真实物理 IP 地址和数据请求目的 IP 地址一致,所以不需要通过负载均衡服务器进行地址转换,可将响应数据包直接返回给用户浏览器,避免负载均衡服务器网卡带宽成为瓶颈。因此,DR 模式具有较好的性能,也是目前大型网站使用最广泛的一种负载均衡手段。

LVS 的优点

  • 抗负载能力强、是工作在传输层上仅作分发之用,没有流量的产生,这个特点也决定了它在负载均衡软件里的性能最强的,对内存和 cpu 资源消耗比较低。

  • 配置性比较低,这是一个缺点也是一个优点,因为没有可太多配置的东西,所以并不需要太多接触,大大减少了人为出错的几率。
    工作稳定,因为其本身抗负载能力很强,自身有完整的双机热备方案,如 LVS+Keepalived。

  • 无流量,LVS 只分发请求,而流量并不从它本身出去,这点保证了均衡器 IO 的性能不会受到大流量的影响。

  • 应用范围比较广,因为 LVS 工作在传输层,所以它几乎可以对所有应用做负载均衡,包括 http、数据库、在线聊天室等等。

LVS 的缺点

  • 软件本身不支持正则表达式处理,不能做动静分离;而现在许多网站在这方面都有较强的需求,这个是 Nginx、HAProxy+Keepalived 的优势所在。

  • 如果是网站应用比较庞大的话,LVS/DR+Keepalived 实施起来就比较复杂了,相对而言,Nginx/HAProxy+Keepalived就简单多了。

Nginx

Nginx 是一个强大的 Web 服务器软件,用于处理高并发的 HTTP 请求和作为反向代理服务器做负载均衡。具有高性能、轻量级、内存消耗少,强大的负载均衡能力等优势。

未分类

Nignx 的架构设计

相对于传统基于进程或线程的模型(Apache就采用这种模型)在处理并发连接时会为每一个连接建立一个单独的进程或线程,且在网络或者输入/输出操作时阻塞。这将导致内存和 CPU 的大量消耗,因为新起一个单独的进程或线程需要准备新的运行时环境,包括堆和栈内存的分配,以及新的执行上下文,当然,这些也会导致多余的 CPU 开销。最终,会由于过多的上下文切换而导致服务器性能变差。

反过来,Nginx 的架构设计是采用模块化的、基于事件驱动、异步、单线程且非阻塞。

Nginx 大量使用多路复用和事件通知,Nginx 启动以后,会在系统中以 daemon 的方式在后台运行,其中包括一个 master 进程,n(n>=1) 个 worker 进程。所有的进程都是单线程(即只有一个主线程)的,且进程间通信主要使用共享内存的方式。

其中,master 进程用于接收来自外界的信号,并给 worker 进程发送信号,同时监控 worker 进程的工作状态。worker 进程则是外部请求真正的处理者,每个 worker 请求相互独立且平等的竞争来自客户端的请求。请求只能在一个 worker 进程中被处理,且一个 worker 进程只有一个主线程,所以同时只能处理一个请求。(原理同 Netty 很像)

未分类

Nginx 负载均衡

Nginx 负载均衡主要是对七层网络通信模型中的第七层应用层上的 http、https 进行支持。

Nginx 是以反向代理的方式进行负载均衡的。反向代理(Reverse Proxy)方式是指以代理服务器来接受 Internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 Internet 上请求连接的客户端,此时代理服务器对外就表现为一个服务器。

Nginx 实现负载均衡的分配策略有很多,Nginx 的 upstream 目前支持以下几种方式:

  • 轮询(默认):每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器 down 掉,能自动剔除。
  • weight:指定轮询几率,weight 和访问比率成正比,用于后端服务器性能不均的情况。
  • ip_hash:每个请求按访问 ip 的 hash 结果分配,这样每个访客固定访问一个后端服务器,可以解决 session 的问题。
    fair(第三方):按后端服务器的响应时间来分配请求,响应时间短的优先分配。
  • url_hash(第三方):按访问 url 的 hash 结果来分配请求,使每个 url 定向到同一个后端服务器,后端服务器为缓存时比较有效。

Nginx 的优点

  • 跨平台:Nginx 可以在大多数 Unix like OS编译运行,而且也有 Windows 的移植版本
  • 配置异常简单:非常容易上手。配置风格跟程序开发一样,神一般的配置
  • 非阻塞、高并发连接:官方测试能够支撑5万并发连接,在实际生产环境中跑到2~3万并发连接数
  • 事件驱动:通信机制采用 epoll 模型,支持更大的并发连接
  • Master/Worker 结构:一个 master 进程,生成一个或多个 worker 进程
  • 内存消耗小:处理大并发的请求内存消耗非常小。在3万并发连接下,开启的10个 Nginx 进程才消耗150M 内存(15M*10=150M)
  • 内置的健康检查功能:如果 Nginx 代理的后端的某台 Web 服务器宕机了,不会影响前端访问
  • 节省带宽:支持 GZIP 压缩,可以添加浏览器本地缓存的 Header 头
  • 稳定性高:用于反向代理,宕机的概率微乎其微

Nginx 的缺点

  • Nginx 仅能支 持http、https 和 Email 协议,这样就在适用范围上面小些,这个是它的缺点
  • 对后端服务器的健康检查,只支持通过端口来检测,不支持通过 ur l来检测。不支持 Session 的直接保持,但能通过 ip_hash 来解决

HAProxy

HAProxy 支持两种代理模式 TCP(四层)和HTTP(七层),也是支持虚拟主机的。

HAProxy 的优点能够补充 Nginx 的一些缺点,比如支持 Session 的保持,Cookie 的引导;同时支持通过获取指定的 url 来检测后端服务器的状态。

HAProxy 跟 LVS 类似,本身就只是一款负载均衡软件;单纯从效率上来讲 HAProxy 会比 Nginx 有更出色的负载均衡速度,在并发处理上也是优于 Nginx 的。

HAProxy 支持 TCP 协议的负载均衡转发,可以对 MySQL 读进行负载均衡,对后端的 MySQL 节点进行检测和负载均衡,大家可以用 LVS+Keepalived 对 MySQL 主从做负载均衡。

HAProxy 负载均衡策略非常多:Round-robin(轮循)、Weight-round-robin(带权轮循)、source(原地址保持)、RI(请求URL)、rdp-cookie(根据cookie)。

使用 HAProxy + Keepalived 构建基于 Docker 的高可用负载均衡服务(二)

本文主要介绍把 HAProxy + Keepalived 服务组合形成单独的镜像,并能够自定义配置项。适合具有 Docker 和 Bash 相关基础的开发、运维等同学。

使用 Docker 组合构建 haproxy-keepalived 服务

未分类

上图为我们要构建的 haproxy-keepalived 服务的工作原理图,原理表述如下:

START:

  • 根据 Bash 脚本和 Docker 容器的环境变量生成 HAProxy 和 Keepalived 的配置文件
  • 在容器内部启动 HAProxy 和 Keepalived 服务

STOP:

  • ENTRYPOINT 使用 trap 接收到传递来的 SIGTERM 信号
  • 使用 kill -ITEM $pid 的方式,结束掉 HAProxy & Keepalived 两个服务。

下边会按照上述步骤介绍如何工作。

生成 HAProxy & Keepalived 配置文件

haproxy_cfg_init.sh
#!/bin/bash

HAPROXY_DIR="/usr/local/etc/haproxy"
TMP_HAPROXY="/tmp/haproxy"

TMP_FILE="${TMP_HAPROXY}/tmp.cfg"
TMP_TEMPLATE="${TMP_HAPROXY}/template.cfg"

TEMPLATE_FILE="/haproxy/template.cfg"
HAPROXY_CFG="${HAPROXY_DIR}/haproxy.cfg"

# mkdir to prevent not exists
mkdir -p ${HAPROXY_DIR}
mkdir -p ${TMP_HAPROXY}
rm -rf ${HAPROXY_DIR}/haproxy.cfg

# delete old haproxy.cfg and touch tmp.cfg
rm -rf ${HAPROXY_CFG}
rm -rf ${TMP_FILE}
touch ${TMP_FILE}

# copy template.cfg to tmp dir
cp ${TEMPLATE_FILE} ${TMP_TEMPLATE}

COUNTER=1
while [ ${COUNTER} -gt 0 ]
do
    haproxy_item=$(printenv haproxy_item${COUNTER})

    if [ -z "${haproxy_item}" ]
    then
        break;
    fi

    # echo haproxy_item to /tmp/haproxy/tmp.cfg
    echo "${haproxy_item${COUNTER}}" >> ${TMP_FILE}

    let COUNTER+=1
done
echo "Success generate tmp.cfg!"

sed -i "/#CUSTOM/r ${TMP_FILE}" ${TMP_TEMPLATE}
echo "Success generate template.cfg"

touch ${HAPROXY_CFG}
envsubst < ${TMP_TEMPLATE} > ${HAPROXY_CFG}

echo "Success generate haproxy.cfg, file content is below:"
echo "----------------haproxy.cfg------------------"
cat ${HAPROXY_CFG}
echo "------------------ End ----------------------"

容器在启动的时候,传入 haproxy_item_${CURSOR} 环境变量,上述脚本根据传入的环境变量与 template.cfg 文件结合,生成最终的 /usr/local/etc/haproxy/haproxy.cfg 配置文件。

大致相似的原理,根据 init_keepalived_conf.sh 脚本,与传入的环境变量结合,使用环境变量替换 keepalived_template.conf 文件中的环境变量,来生成 最终的 /etc/keepalived/keepalived.conf 配置文件。

#!/bin/bash
set -e

TEMPLATE="/keepalived/keepalived_template.conf"
TMP_TEMPLATE="/tmp/keepalived/tmp_keepalived_template.conf"
KEEPALIVED_FILE="/etc/keepalived/keepalived.conf"

# mkdir to prevent not exist
mkdir -p /tmp/keepalived/
mkdir -p /etc/keepalived/

# copy template to /tmp/keepalived/tmp_keepalived_template.conf
cp ${TEMPLATE} ${TMP_TEMPLATE}

envsubst < ${TMP_TEMPLATE} > ${KEEPALIVED_FILE}
echo "Success generate ${KEEPALIVED_FILE}"
echo "----------------------keepalived.conf----------------------"
cat ${KEEPALIVED_FILE}
echo "--------------------------- End ---------------------------"

容器内部启动 HAProxy & Keepalived 服务

查看 Dockerfile 文件,这个 haproxy-keepalived 镜像是基于 haproxy:1.7.9 基础镜像来做的修改。为了符合一个容器启动 HAProxy & Keepalived 两个服务的预期,我们增加了一个 docker-entrypoint-override.sh 的脚本来作为 haproxy-keepalived 镜像的 ENTRYPOINT。

在 docker-entrypoint-override.sh 中,我们分别用如下两条命令启动来启动两个服务:

  • HAProxy: exec /docker-entrypoint.sh “$@” &

    直接后台运行 haproxy:1.7.9 官方镜像的原 docker-entrypoint.sh 来启动 haproxy

  • Keepalived: exec /start_keepalived.sh “$@” &

    后台运行 start_keepalived.sh 脚本来启动 Keepalived 服务

接下来会稍微详细说一下 start_keepalived.sh 脚本:

#!/bin/bash

LANUCH_SCRIPT="keepalived --dont-fork --dump-conf --log-console --log-detail --log-facility 7 --vrrp -f /etc/keepalived/keepalived.conf"

eval $LANUCH_SCRIPT
while true; do
  k_pid=$(pidof keepalived)
  if [ -n "$k_id" ]; then
    break;
  fi

  kill -TERM $(cat /var/run/vrrp.pid)
  kill -TERM $(cat /var/run/keepalived.pid)
  echo "ERROR: Keepalived start failed, attempting restart ."
  eval $LANUCH_SCRIPT
done

echo "Keepalived started successfuly!"

在上述脚本中,我们在运行 LANUCH_SCRIPT 后,又做了一个循环判断。原因是在我们使用这个自定义的 haproxy-keepalived 服务的时候,在重启容器后 Keepalived 并不能正常启动,会有个报错:

Keepalived: Daemon is already running

但实际上,这个 Daemon 我们已经给它 kill 掉了。我们这里再执行一次判断,如果 $k_id 值不为空,则 Keepalived 启动成功;如果 $k_id 值为空,我们会再 kill 一次 Keepalived 的进程,然后重启 Keepalived。

容器停止,Graceful Shutdown

在容器停止的时候,需要做 Graceful Shutdown 的操作,在这里的目标就是:容器关闭前干掉 HAProxy & Keepalived 的进程。

仍然回到 docker-entrypoint-override.sh 脚本,在脚本中我们定义了接收 SIGITERM 信号的处理事件:

trap "stop; exit 0;" SIGTERM SIGINT

# handler for SIGINT & SIGITEM
stop() {
  echo "SIGTERM caught, terminating <haproxy & keepalived> process..."

  # terminate haproxy
  h_pid=$(pidof haproxy)

  kill -TERM $h_pid > /dev/null 2>&1
  echo "HAProxy had terminated."

  # terminate keepalived
  kill -TERM $(cat /var/run/vrrp.pid)
  kill -TERM $(cat /var/run/keepalived.pid)
  echo "Keepalived had terminated."

  echo "haproxy-keepalived service instance is successfuly terminated!"
}

当容器被 stop 或者 kill 的时候,会触发这个 handler 事件。我们在事件中对两个服务做了下简单的处理。

如何使用

首先,准备两台 Linux 虚拟机,假定 IP 分别为:192.168.0.41、192.168.0.42,并找到一个 VIP,假定为 192.168.0.40。

在 192.168.0.41 机器上新建 /data/haproxy-keepalived/docker-compose.yml 文件:

root@haproxy-master:/data/haproxy-keepalived# cat docker-compose.yml
version: '3'
services:

  haproxy-keepalived:
    image: "pelin/haproxy-keepalived"
    privileged: true
    network_mode: host
    restart: always
    environment:
      KEEPALIVED_STATE: "MASTER"
      KEEPALIVED_INTERFACE: "ens18"
      KEEPALIVED_PRIORITY: "105"
      KEEPALIVED_V_ROUTER_ID: "40"
      KEEPALIVED_VIP: "192.168.0.40"
      haproxy_item1: |-
        listen app-1
            bind *:4000
            mode http
            maxconn 300
            balance roundrobin
            server server1 192.168.0.21:4001 maxconn 300 check
            server server2 192.168.0.22:4002 maxconn 300 check
         listen app-2
            bind *:5000
            mode http
            maxconn 300
            balance roundrobin
            server server1 192.168.0.21:5001 maxconn 300 check
            server server2 192.168.0.22:5002 maxconn 300 check

注意上述配置:
KEEPALIVED_STATE             # Keepalived 节点初始状态
KEEPALIVED_INTERFACE         # Keepalived 绑定的网卡
KEEPALIVED_PRIORITY          # 权重
KEEPALIVED_V_ROUTER_ID       # router_id
KEEPALIVED_VIP               # 虚拟 IP

其中,KEEPALIVED_INTERFACE 需要根据实际情况而定。比如笔者用的 Ubuntu 16.04 的主网卡是 ens18,Ubuntu 14.04 的网卡是 eth0。

然后使用如下命令开启服务:

root@haproxy-keepalived:/data/haproxy-keepalived# docker-compose up

在启动后,访问浏览器:192.168.0.41:1080/stats 即可看到有界面出现

同理,在 42 机器上做上述操作,其中 KEEPALIVED_STATE 需要修改为 BACKUP

如何扩展

目前实现的功能:

  • 根据环境变量动态调整 HAProxy & Keepalived 配置
  • 容器 Graceful Shutdown
  • 容器能够正常 Restart,Crashed 后能正常自动重启

TODO:

  • 直接 Bind 配置文件进容器

如果需要自定义一个 haproxy-keepalived 镜像的话,可以参考上边的思路来做。当然,如果场景可以使用 Bind 的方式,那自然是更简单了。

如果可以直接 Bind 配置文件进容器,上述很多脚本都可以省略掉。但是,我们更寄期望于在容器编排工具中,使用环境变量的方式来初始化配置文件。为什么有这样的考虑?

  • 当有多台机器的时候,我们需要一台一台去修改 Bind 的配置文件。虽然可以使用 Ansible 之类的自动化机制来做,但是在这种场景下把配置和服务分离并不太合适;
  • 对以后做服务的自动伸缩机制友好。服务在伸缩之后,通过服务发现我们需要更新我们的 LB 的配置。如何更新?自然是通过我们的中心容器编排工具来自动更新。去触发脚本更新与我们的期望不符。

LICENSE

Source code: https://coding.net/u/Pelin_li/p/haproxy-keepalived/git

Based on MIT LICENSE: https://coding.net/u/Pelin_li/p/haproxy-keepalived/git/blob/master/LICENSE