说完了Nginx, 接下来就来看看另外一个性能优越的load balancer – HAProxy吧.
HAProxy
首先要说一下啊, 这个玩意虽然叫做HA, 但是他不是任何高可用服务器, 归结到底 HAProxy只是一个代理服务器罢了. 同时, 也是http层的一个负载均衡调度器. 既然这样, HAProxy为什么要叫做HA呢? 原因很简单, HAProxy能够在http层实现健康状态检测, 从而认为这也是能够提供高可用的一个能力, 从而将自己命名为HAProxy ( 我猜的hhh )
说到代理, 我们说过有正向和反向两种. 由于之前解释过了, 所以这里就不再展开了. 来着重介绍今天的主角-HAProxy. 这个玩意是个非常纯正的http反向代理服务器, 不能进行缓存. 但是也额外支持对TCP通信的负载均衡.
继续说说HAProxy的特性, 由于Haproxy是基于事件驱动和单一进程模式的, 所以省去了进程间切换, 使得支持的并发连接数非常大 ( 但是用户也可以调整成为多进程的 ). 不过, Haproxy对于应用层的内容处理和动态资源和静态资源分离是做不到了.
但是由于是单一进程, 所以所有的连接会话都由一个进程维护, 如果会话非常多, 那么存储的数据结构就是一个很重要的考虑部分. 不同的数据结构会很大程度上影响到检索存储在内存中的会话节点, 而且还会有增加, 删除, 修改的操作. HAProxy选择的数据结构是弹性二叉树. 由HAProxy作者自己研发的数据结构.
还是直接装上看看吧:
1 | [root@VM-node1 ~]# rpm -ql haproxy |
从生成的文件也可以看出, haproxy是一个十分轻量化的程序, 除了配置文件和主程序其他就没有什么了.
先来从配置文件入手,
1 | [root@VM-node1 haproxy]# grep "^[^#][^[:space:]].*$" haproxy.cfg |
这就是配置文件的组成部分.
其实很好理解层次结构, 例如前端(也就是自己了), 后端静态服务器组, 后端动态服务器组啥啥的.
我们在Nginx中 是用了什么proxy_pass配合upstream来进行的调度, 而haproxy更好理解了, 直接使用frontend, backend来定义. 通过在前端中定义使用那些后端服务器组来建立联系, 另外这种联系还支持条件式建立, 还可以配置默认连接. default就是为frontend和backend设置的各个选项了. 总之, 熟悉了Nginx的我们, 现在肯定能炒鸡快速的上手Haproxy的.
现在就直接来试试吧, 还是想之前那样, 我们开启两台Web服务器.
1 | frontend main *:80 |
这就是最简单的一个配置了, 我们启动haproxy服务, 访问试试.
1 | C:\Users\lenovo |
由于我们使用了roundrobin算法, 所以是轮询访问.
HAProxy的配置
现在就来好好看看HAProxy的配置项吧, 首先第一行就是日志记录的配置. HAProxy和其他的不太一样, 他只能基于网络进行日志记录 :
1 | global |
所以, 为了使得日志能够记录下去. 我们还要配置一下rsyslog才行:
1 | # Provides UDP syslog reception |
首先开启监听, 接着要把我们的haproxy配置日志路径.
接着重启服务, 访问一下.
1 | [root@VM-node1 haproxy]# tail /var/log/haproxy.log |
这样就有日志了.
接着往后看一下:
1 | global |
但是, 可以使用的参数还有很多. 比如quiet
, debug
, 还可以调整连接参数, 运行参数等等. 但是很多参数都不建议修改, 可能会用到的参数很少, 例如: 上面列出来的这些, 还有spread-check
: 这个是用来修改健康状态检查的延时的 ( 因为大量同时的检查可能会拥塞网络, 这个选项可以进行随机的后退. )
balance
首先来看balance关键字. 这个是用来指定调度算法的, HAProxy支持的调度算法也很多, 并且也分成动态和静态两类. HAProxy理解的动态静态是根据是否可进行权重的改变. 能够则被认为是动态的.
roundrobin
: 轮询, 动态算法, 会追踪后端主机的连接, 所以每个主机最大支持4192条连接.static-rr
: 静态的轮询算法.least-conn
: 最小连接, 适合长连接场景.source
: 源地址哈希, 默认是静态算法. 但是可以根据哈希算法来改变map-based
除模取余consistent
: 一致性哈希
url
: 这是一个对HAProxy而言, 很有竞争力的算法. 这个算法就是将请求的URL的前半段或者全部进行Hash运算, 接着和服务器的总权重进行运算最终得到rs. 这样的一个应用就是缓存服务器. 不管来源是什么, 只要你请求那个缓存的内容, 我就把你调度到缓存服务器那里去. 这个算法也一样可以进行两种hash_type.
这里我做了个测试, 我们在后端的两个主机都新建了10个页面, 接着访问几个, 会发现一旦第一次被定向到了哪一个主机, 就会被绑定到那一台. 这个时候我们关闭Web2的httpd服务, 于是再次访问, 一开始堵塞了一会, 接着就访问到了Web1的页面, 另外其他原本是Web2的页面也立即就被转向到了Web1的页面. 然而, 当服务恢复了之后, 立即被定向到了原本的页面, 恢复绑定了.
1 | [root@VM-node3 ~]# for n in [1..10]; do echo "<h1>Page $n on Web2</h1>" > /var/www/html/test$n.html ; done |
url_param
: 也是一个从URL中计算哈希的方法, 通过取得URL中被赋值的键的值进行运算接着除以总权重得到的结果. 同样也是支持两种哈希类型.hdr(<name>)
: 可以根据请求报文中的指定首部进行调度, 强大吧. 比如host, referer, user-agent等等.
做个实验吧, 配置成
1
2
3
4
5 backend webservers
balance hdr(User-Agent)
hash-type consistent
server static 192.168.206.21:80 check
server static 192.168.206.22:80 check接着访问:
1
2
3
4
5
6
7
8
9
10
11
12 [root@VM-node1 haproxy]# curl http://192.168.206.9
<h1>This is node2(NEW)</h1>
[root@VM-node1 haproxy]# curl http://192.168.206.9 -A "Hello"
<h1>This is node2(NEW)</h1>
[root@VM-node1 haproxy]# curl http://192.168.206.9 -A "Hello Hi"
<h1>It works! (From node3)</h1>
[root@VM-node1 haproxy]# curl http://192.168.206.9 -A "Hello Hi1"
<h1>It works! (From node3)</h1>
[root@VM-node1 haproxy]# curl http://192.168.206.9 -A "Hello Hi~~"
<h1>This is node2(NEW)</h1>
[root@VM-node1 haproxy]# curl http://192.168.206.9 -A "Hello hhh"
<h1>It works! (From node3)</h1>
bind
bind就是监听的套接字位置, 其实和我们在frontend后面加上的那个玩意是一个东西, bind可以出现多次.
1 | frontend main |
mode
指明haproxy运行的模式, 一共支持三种: { http | tcp | health }
1 | defaults |
log
定义日志, 单个实例最多定义两个. 而且如果在global中定义了两个, 后面的都会被忽略.
1 | log <address><facility>[<level>[<minlevel>]] |
也可以在后面导向global的定义:
1 | log global |
maxconn
设定前端的最大连接数, 如果在global中写, 那就说明整个HAProxy的前端最大并发, 如果在单个实例中写, 最终就会加起来算作整个服务器的前端最大并发. 每个连接大致是17Kb的大小
default_backend
默认的后端服务器. 一个使用案例:
1 | use_backend dynamic if url_dyn |
这里的use_backend是另外一个指令, 和后面的连用. 只要匹配到请求的URL符合后面定义的ACL中, 就会使用那个后端服务器组. 如果都没有匹配的到, 就会使用最后的那个后端服务器组.
server
这个server参数非常重要, 支持超多的选项, 格式如下:
1 | server <name> <address>[:port] [param*...] |
前面都很好理解, 主要是后面的参数, 我们提取几个重要的来看一下:
backup
:设定为备用服务器,仅在负载均衡场景中的其它server均不可用于启用此server;check
:启动对此server执行健康状态检查,其可以借助于额外的其它参数完成更精细的设定,如:
inter <delay>
:设定健康状态检查的时间间隔,单位为毫秒,默认为2000;也可以使用fastinter和downinter来根据服务器端状态优化此时间延迟;rise <count>
:设定健康状态检查中,某离线的server从离线状态转换至正常状态需要成功检查的次数;fall <count>
:确认server从正常状态转换为不可用状态需要检查的次数;
cookie <value>
:为指定server设定cookie值,此处指定的值将在请求入站时被检查,第一次为此值挑选的server将在后续的请求中被选中,其目的在于实现持久连接的功能;maxconn <maxconn>
:指定此服务器接受的最大并发连接数;如果发往此服务器的连接数目高于此处指定的值,其将被放置于请求队列,以等待其它连接被释放;maxqueue <maxqueue>
:设定请求队列的最大长度;observe <mode>
:通过观察服务器的通信状况来判定其健康状态,默认为禁用,其支持的类型有“layer4”和“layer7”,“layer7”仅能用于http代理场景;redir <prefix>
:启用重定向功能,将发往此服务器的GET和HEAD请求均以302状态码响应;需要注意的是,在prefix后面不能使用/,且不能使用相对地址,以免造成循环;例如:
1 | server srv1 172.16.100.6:80 redir http://imageserver.justin.com check |
weight <weight>
:权重,默认为1,最大值为256,0表示不参与负载均衡;
另外 在进行健康检查的时候, 我们还可以更加细致的定义:
1 | backend https_relay |
option
httplog
, 如果使用的模式是httpd, 就可以开启这个选项. 该选项会将日志的记录格式变得十分详细.
logasap
, 用于如果传输的数据较大, 日志记录会有延时, 这个选项可以提前记录日志,
forwardfor [except <network>][header <name>][if-none]
, 在首部中添加X-Forwarded-For
http-server-close
可以允许服务器端(HAProxy)主动关闭连接, 会在头部加入Connection: close的标识, 有些服务器见到这个标识 会返回一个不可用的数据. 这里是指HAProxy -> 后端服务器.
http-pretend-keepalive
字面意思了, 在头部加入Connection: Keep-Alive的标识. 经常和上面的选项连用.
redisatch
如果一台服务器宕机, 会进行重新分发
接下来我们说说访问控制:
http-request
格式如下:
1 | http-request { allow | deny | auth [realm <realm>]} [ {if | unless} <condition> ] |
不过这里的定义会用到acl指令, 上面也略微提过, 其实很好理解的: [ src就是说源地址嘛 ]
1 | acl nagios src 192.168.129.3 |
redirect & acl
除了指明来源, acl还可以这么写:
1 | acl login dst_port 8080 |
最后一个的意思是说, 取头部的cookie字串.
结合这些我们就可以做到灵活的重定向请求:
1 | redirect prefix https://yaoxuannn.com set-cookie SEEN=1 if !cookie_set |
不止这些, acl还可以更加强大. 我们可以使用下面的acl进行会话速率的检测:
1 | acl begin_scanned be_sess_rate gt 50 |
be_sess_rate
就是说每秒创建的会话数.
还可以使用method
匹配方法, 还可以仅匹配
reqadd rspadd
这两个选项可以在头部添加内容, 同样支持if条件判断.
另外, 还有很多超时选项:
timeout
http-request
connect
http-keep-alive
…(omitted)
基于Cookie实现单客户端绑定
现在我们来使用cookie+HAProxy实现一下单客户端绑定.
首先我们做如下配置:
1 | backend webservers |
然后我们重启服务, 第一次访问:
可以看到服务器返回了一个设置Cookie的字段, 接着第二次访问:
这个时候, 我们无论访问什么URL都会被带到这个服务器上, 因为我们是带着Cookie访问的.
很简单吧.
AProxy_mind.png)