haproxy+keepalived实现高可用负载均衡

Posted on

haproxy+keepalived实现高可用负载均衡

软件负载均衡一般通过两种方式来实现:基于操作系统的软负载实现和基于第三方应用的软负载实现。LVS就是基于Linux操作系统实现的一种软负载,HAProxy就是开源的并且基于第三应用实现的软负载。

HAProxy相比LVS的使用要简单很多,功能方面也很丰富。当 前,HAProxy支持两种主要的代理模式:"tcp"也即4层(大多用于邮件服务器、内部协议通信服务器等),和7层(HTTP)。在4层模式 下,HAProxy仅在客户端和服务器之间转发双向流量。7层模式下,HAProxy会分析协议,并且能通过允许、拒绝、交换、增加、修改或者删除请求 (request)或者回应(response)里指定内容来控制协议,这种操作要基于特定规则。

我现在用HAProxy主要在于它有以下优点,这里我总结下:

一、免费开源,稳定性也是非常好,这个可通过我做的一些小项目可以看出来,单Haproxy也跑得不错,稳定性可以与LVS相媲美;

二、根据官方文档,HAProxy可以跑满10Gbps-New benchmark of HAProxy at 10 Gbps using Myricom's 10GbE NICs (Myri-10G PCI-Express),这个作为软件级负载均衡,也是比较惊人的;

三、HAProxy可以作为MySQL、邮件或其它的非web的负载均衡,我们常用于它作为MySQL(读)负载均衡;

自带强大的监控服务器状态的页面,实际环境中我们结合Nagios进行邮件或短信报警,这个也是我非常喜欢它的原因之一;

HAProxy支持虚拟主机。

===================================================================================

在做反向代理服务器的负载均衡时,我们通常会使用nginx的均衡配置。其实,haproxy的负载均衡也是属于这一类的。那么关于这方面的配置过程我们现在来进行一下讲解。首先,对haproxy进行一个简单的介绍,之后就是安装和配置环节了。

HAProxy介绍

反向代理服务器,支持双机热备支持虚拟主机,但其配置简单,拥有非常不错的服务器健康检查功能,当其代理的后端服务器出现故障, HAProxy会自动将该服务器摘除,故障恢复后再自动将该服务器加入。新的1.3引入了frontend,backend;frontend根据任意 HTTP请求头内容做规则匹配,然后把请求定向到相关的backend.

http://blog.liuts.com/post/223/ (搭建四层负载均衡器)

http://rfyimcool.blog.51cto.com/1030776/413187 (搭建七层负载均衡器)

===================================================================================

keepalived简介  

http://www.keepalived.org

keepalived是一个类似于layer3, 4 & 5交换机制的软件,也就是我们平时说的第3层、第4层和第5层交换。Keepalived的作用是检测web服务器的状态,如果有一台web服务器死机,或工作出现故障,Keepalived将检测到,并将有故障的web服务器从系统中剔除,当web服务器工作正常后Keepalived自动将web服务器加入到服务器群中,这些工作全部自动完成,不需要人工干涉,需要人工做的只是修复故障的web服务器。

类似的HA工具还有heatbeat、drbd等,heatbeat、drbd配置都较为复杂。

keepalived理论工作原理

keepalived可提供vrrp以及health-check功能,可以只用它提供双机浮动的vip(vrrp虚拟路由功能),这样可以简单实现一个双机热备高可用功能。

keepalived是一个类似于layer3, 4 & 5交换机制的软件,也就是我们平时说的第3层、第4层和第5层交换。Keepalived的作用是检测web 服务器的状态。 Layer3,4&5工作在IP/TCP协议栈的IP层,TCP层,及应用层,原理分别如下:

Layer3:Keepalived使用Layer3的方式工作式时,Keepalived会定期向服务器群中的服务器

发送一个ICMP的数据包(既我们平时用的Ping程序),如果发现某台服务的IP地址没有激活,Keepalived便报告这台服务器失效,并将它从服务器群中剔除,这种情况的典型例子是某台服务器被非法关机。Layer3的方式是以服务器的IP地址是否有效作为服务器工作正常与否的标准。在本文中将采用这种方式。

Layer4:如果您理解了Layer3的方式,Layer4就容易了。Layer4主要以TCP端口的状态来决定服务器工作正常与否。如web server的服务端口一般是80,如果Keepalived检测到80端口没有启动,则Keepalived将把这台服务器从服务器群中剔除。

Layer5:Layer5就是工作在具体的应用层了,比Layer3,Layer4要复杂一点,在网络上占用的带宽也要大一些。Keepalived将根据用户的设定检查服务器程序的运行是否正常,如果与用户的设定不相符,则Keepalived将把服务器从服务器群中剔除。

vip即虚拟ip,是附在主机网卡上的,即对主机网卡进行虚拟,此IP仍然是占用了此网段的某个IP。

keepalived作用

随着你的网站业务量的增长你网站的服务器压力越来越大?需要负载均衡方案!商业的硬件如F5又太贵,你们又是创业型互联公司如何有效节约成本,节省不必要的浪费?同时实现商业硬件一样的高性能高可用的功能?有什么好的负载均衡可伸张可扩展的方案吗?答案是肯定的!有!我们利用 LVS+Keepalived基于完整开源软件的架构可以为你提供一个负载均衡及高可用的服务器。

LVS+Keepalived 介绍

LVS

LVS是Linux Virtual Server的简写,意即Linux虚拟服务器,是一个虚拟的服务器集群系统。本项目在1998年5月由章文嵩博士成立,是中国国内最早出现的自由软件项目之一.目前有三种IP负载均衡技术(VS/NAT、VS/TUN和VS/DR)八种调度算法(rr,wrr,lc,wlc,lblc,lblcr,dh,sh)。

Keepalvied

Keepalived在这里主要用作RealServer的健康状态检查以及LoadBalance主机和BackUP主机之间failover的实现。keepalived简介  keepalived是一个类似于layer3, 4 & 5交换机制的软件,也就是我们平时说的第3层、第4层和第5层交换。Keepalived的作用是检测web服务器的状态,如果有一台web服务器死机,或工作出现故障,Keepalived将检测到,并将有故障的web服务器从系统中剔除,当web服务器工作正常后Keepalived自动将web服务器加入到服务器群中,这些工作全部自动完成,不需要人工干涉,需要人工做的只是修复故障的web服务器。

===================================================================================

**Keepalived介绍**

Keepalived是一个基于VRRP协议来实现的WEB 服务高可用方案,可以利用其来避免单点故障。一个WEB服务至少会有2台服务器运行Keepalived,一台为主服务器(MASTER),一台为备份服务器(BACKUP),但是对外表现为一个虚拟IP,主服务器会发送特定的消息给备份服务器,当备份服务器收不到这个消息的时候,即主服务器宕机的时候,备份服务器就会接管虚拟IP,继续提供服务,从而保证了高可用性。 1 +-------------VIP(192.168.0.7)------------------+

2 | | | 3 | | |

4 server(MASTER) <----keepalived----> server(BACKUP) 5 (192.168.0.1) (192.168.0.2)

keepalived是VRRP的完美实现,因此在介绍keepalived之前,先介绍一下VRRP的原理。

VRRP**协议简介**

在现实的网络环境中,两台需要通信的主机大多数情况下并没有直接的物理连接。对于这样的情况,它们之间路由怎样选择?主机如何选定到达目的主机的下一跳路由,这个问题通常的解决方法有二种:

· 在主机上使用动态路由协议(RIP、OSPF等)

· 在主机上配置静态路由

很明显,在主机上配置路态路由是非常不切实际的,因为管理、维护成本以及是否支持等诸多问题。配置静态路由就变得十分流行,但路由器(或者说默认网关default gateway)却经常成为单点。

VRRP的目的就是为了解决静态路由单点故障问题。

VRRP通过一竞选(election)协议来动态的将路由任务交给LAN中虚拟路由器中的某台VRRP路由器。

工作机制

在一个VRRP虚拟路由器中,有多台物理的VRRP路由器,但是这多台的物理的机器并不能同时工作,而是由一台称为MASTER的负责路由工作,其它的都是BACKUP,MASTER并非一成不变,VRRP让每个VRRP路由器参与竞选,最终获胜的就是MASTER。MASTER拥有一些特权,比如 拥有虚拟路由器的IP地址,我们的主机就是用这个IP地址作为静态路由的。拥有特权的MASTER要负责转发发送给网关地址的包和响应ARP请求。

VRRP通过竞选协议来实现虚拟路由器的功能,所有的协议报文都是通过IP多播(multicast)包(多播地址 224.0.0.18)形式发送的。虚拟路由器由VRID(范围0-255)和一组IP地址组成,对外表现为一个周知的MAC地址。所以,在一个虚拟路由 器中,不管谁是MASTER,对外都是相同的MAC和IP(称之为VIP)。客户端主机并不需要因为MASTER的改变而修改自己的路由配置,对他们来 说,这种主从的切换是透明的。

在一个虚拟路由器中,只有作为MASTER的VRRP路由器会一直发送VRRP广告包(VRRPAdvertisement message),BACKUP不会抢占MASTER,除非它的优先级(priority)更高。当MASTER不可用时(BACKUP收不到广告包), 多台BACKUP中优先级最高的这台会被抢占为MASTER。这种抢占是非常快速的(<1s),以保证服务的连续性。

由于安全性考虑,VRRP包使用了加密协议进行加密。

==========================================

vrrp简介 随着Internet的迅猛发展,基于网络的应用逐渐增多。这就对网络的可靠性提出了越来越高的要求。斥资对所有网络设备进行更新当然是一种很好的可靠性解决方案;但本着保护现有投资的角度考虑,可以采用廉价冗余的思路,在可靠性和经济性方面找到平衡点。 虚拟路由冗余协议就是一种很好的解决方案。在该协议中,对共享多存取访问介质(如以太网)上终端IP设备的默认网关(Default Gateway)进行冗余备份,从而在其中一台路由设备宕机时,备份路由设备及时接管转发工作,向用户提供透明的切换,提高了网络服务质量。 一、协议概述 在基于TCP/IP协议的网络中,为了保证不直接物理连接的设备之间的通信,必须指定路由。目前常用的指定路由的方法有两种:一种是通过路由协议(比如:内部路由协议RIP和OSPF)动态学习;另一种是静态配置。在每一个终端都运行动态路由协议是不现实的,大多客户端操作系统平台都不支持动态路由协议,即使支持也受到管理开销、收敛度、安全性等许多问题的限制。因此普遍采用对终端IP设备静态路由配置,一般是给终端设备指定一个或者多个默认网关(Default Gateway)。静态路由的方法简化了网络管理的复杂度和减轻了终端设备的通信开销,但是它仍然有一个缺点:如果作为默认网关的路由器损坏,所有使用该网关为下一跳主机的通信必然要中断。即便配置了多个默认网关,如不重新启动终端设备,也不能切换到新的网关。采用虚拟路由冗余协议 (Virtual Router Redundancy Protocol,简称VRRP)可以很好的避免静态指定网关的缺陷。 在VRRP协议中,有两组重要的概念:VRRP路由器和虚拟路由器,主控路由器和备份路由器。VRRP路由器是指运行VRRP的路由器,是物理实体,虚拟路由器是指VRRP协议创建的,是逻辑概念。一组VRRP路由器协同工作,共同构成一台虚拟路由器。该虚拟路由器对外表现为一个具有唯一固定IP地址和MAC地址的逻辑路由器。处于同一个VRRP组中的路由器具有两种互斥的角色:主控路由器和备份路由器,一个VRRP组中有且只有一台处于主控角色的路由器,可以有一个或者多个处于备份角色的路由器。VRRP协议使用选择策略从路由器组中选出一台作为主控,负责ARP相应和转发IP数据包,组中的其它路由器作为备份的角色处于待命状态。当由于某种原因主控路由器发生故障时,备份路由器能在几秒钟的时延后升级为主路由器。由于此切换非常迅速而且不用改变IP地址和MAC地址,故对终端使用者系统是透明的。 二、工作原理 一个VRRP路由器有唯一的标识:VRID,范围为0—255。该路由器对外表现为唯一的虚拟MAC地址,地址的格式为00-00-5E-00-01-[VRID]。主控路由器负责对ARP请求用该MAC地址做应答。这样,无论如何切换,保证给终端设备的是唯一一致的IP和MAC地址,减少了切换对终端设备的影响。 VRRP控制报文只有一种:VRRP通告(advertisement)。它使用IP多播数据包进行封装,组地址为224.0.0.18,发布范围只限于同一局域网内。这保证了VRID在不同网络中可以重复使用。为了减少网络带宽消耗只有主控路由器才可以周期性的发送VRRP通告报文。备份路由器在连续三个通告间隔内收不到VRRP或收到优先级为0的通告后启动新的一轮VRRP选举。 在VRRP路由器组中,按优先级选举主控路由器,VRRP协议中优先级范围是0—255。若VRRP路由器的IP地址和虚拟路由器的接口IP地址相同,则称该虚拟路由器作VRRP组中的IP地址所有者;IP地址所有者自动具有最高优先级:255。优先级0一般用在IP地址所有者主动放弃主控者角色时使用。可配置的优先级范围为1—254。优先级的配置原则可以依据链路的速度和成本、路由器性能和可靠性以及其它管理策略设定。主控路由器的选举中,高优先级的虚拟路由器获胜,因此,如果在VRRP组中有IP地址所有者,则它总是作为主控路由的角色出现。对于相同优先级的候选路由器,按照IP地址大小顺序选举。VRRP还提供了优先级抢占策略,如果配置了该策略,高优先级的备份路由器便会剥夺当前低优先级的主控路由器而成为新的主控路由器。 为了保证VRRP协议的安全性,提供了两种安全认证措施:明文认证和IP头认证。明文认证方式要求:在加入一个VRRP路由器组时,必须同时提供相同的VRID和明文密码。适合于避免在局域网内的配置错误,但不能防止通过网络监听方式获得密码。IP头认证的方式提供了更高的安全性,能够防止报文重放和修改等攻击。 三、 应用实例 最典型的VRRP应用:RTA、RTB组成一个VRRP路由器组,假设RTB的处理能力高于RTA,则将RTB配置成IP地址所有者,H1、H2、H3的默认网关设定为RTB。则RTB成为主控路由器,负责ICMP重定向、ARP应答和IP报文的转发;一旦RTB失败,RTA立即启动切换,成为主控,从而保证了对客户透明的安全切换。 在VRRP应用中,RTA在线时RTB只是作为后备,不参与转发工作,闲置了路由器RTA和链路L1。通过合理的网络设计,可以到达备份和负载分担双重效果。让RTA、RTB同时属于互为备份的两个VRRP组:在组1中RTA为IP地址所有者;组2中RTB为IP地址所有者。将H1的默认网关设定为RTA;H2、H3的默认网关设定为RTB。这样,既分担了设备负载和网络流量,又提高了网络可靠性。 VRRP协议的工作机理与CISCO公司的HSRP(Hot Standby Routing Protocol)有许多相似之处。但二者主要的区别是在CISCO的HSRP中,需要单独配置一个IP地址作为虚拟路由器对外体现的地址,这个地址不能是组中任何一个成员的接口地址。

使用VRRP协议,不用改造目前的网络结构,最大限度保护了当前投资,只需最少的管理费用,却大大提升了网络性能,具有重大的应用价值。

keepalive的简单应用——管理VIP的飘动

from:http://www.cnblogs.com/killkill/archive/2010/12/31/1922360.html

VIP的飘动可以为我们解决很多问题,以前我试过使用ifup/ifdown的方式控制网卡的up/down来实现,这种方式有个小问题,就是每次VIP 飘动之后都要等上几十秒才能生效,感觉时间比较长,而且还要配合一些逻辑脚本才能很好地工作,有没有更好的方法呢?当然有,这就是本文的主角—— keepalived。

安装很简单: 1

tar zxvf keepalived-1.1.20.tar.gz

2

cd keepalived-1.1.20 3

./configure --prefix=/

4

make 5

make install

修改一下 /etc/keepalived/keepalived.conf 这个配置文件就可以用了,以下是我的环境,192.168.10.141和192.168.10.142是两个VIP,可以在两台服务器之间飘动:

主机的配置: 01

global_defs {

02

notification_email { 03

failover@firewall.loc

04

} 05

notification_email_from Alexandre.Cassen@firewall.loc

06

smtp_server 192.168.0.48 07

smtp_connect_timeout 10

08

router_id nginx 09

}

10 11

vrrp_instance VI_141 {

12

state BACKUP 13

interface eth0

14

virtual_router_id 141 15

priority 50

16

advert_int 1 17

authentication {

18

auth_type PASS 19

auth_pass 141

20

} 21

virtual_ipaddress {

22

192.168.10.141/26 dev eth0 23

}

24

} 25

26

vrrp_instance VI_142 { 27

state BACKUP

28

interface eth0 29

virtual_router_id 142

30

priority 100 31

advert_int 1

32

authentication { 33

auth_type PASS

34

auth_pass 142 35

}

36

virtual_ipaddress { 37

192.168.10.142/26 dev eth0

38

} 39

}

备机的配置:

01

global_defs {

02

notification_email { 03

failover@firewall.loc

04

} 05

notification_email_from Alexandre.Cassen@firewall.loc

06

smtp_server 10.168.0.48 07

smtp_connect_timeout 10

08

router_id nginx 09

}

10 11

vrrp_instance VI_141 {

12

state BACKUP 13

interface eth0

14

virtual_router_id 141 15

priority 100

16

advert_int 1 17

authentication {

18

auth_type PASS 19

auth_pass 141

20

} 21

virtual_ipaddress {

22

192.168.10.141/26 dev eth0 23

}

24

} 25

26

vrrp_instance VI_142 { 27

state BACKUP

28

interface eth0 29

virtual_router_id 142

30

priority 50 31

advert_int 1

32

authentication { 33

auth_type PASS

34

auth_pass 142 35

}

36

virtual_ipaddress { 37

192.168.10.142/26 dev eth0

38

} 39

}

乍一看,主机和备机的配置文件是一样的,仔细看一下priority的值,使用以下命令即可将keepalived加入linux的服务中:

1

chkconfig --add keepalived ;

通过启、停keepalived这个服务即可观察到VIP的飘动,至于为什么VIP飘动后可以很快地生效,还有待研究。

===================================================================================

haproxy+keepalived实现高可用负载均衡

我的环境: haproxy keepalived 主:192.168.1.192 haproxy keepalived 备:192.168.1.193 vip:192.168.1.200 web:192.168.1.187:80 192.168.1.187:8000 一:安装过程,在192.168.1.192上: keepalived的安装: /#tar -zxvf keepalived-1.1.17.tar.gz /#ln -s /usr/src/kernels/2.6.18-128.el5-i686/ /usr/src/linux /#cd keepalived-1.1.17 /#./configure --prefix=/ --mandir=/usr/local/share/man/ --with-kernel-dir=/usr/src/kernels/2.6.18-128.el5-i686/ /#make && make install /#cd /etc/keepalived/ /#mv keepalived.conf keepalived.conf.default /#vi keepalived.conf ! Configuration File for keepalived vrrp_script chk_http_port { script "/etc/keepalived/check_haproxy.sh" interval 2 weight 2 global_defs { router_id LVS_DEVEL } vrrp_instance VI_1 { state MASTER /#192.168.1.193上改为BACKUP interface eth0 virtual_router_id 51 priority 150 /#192.168.1.193上改为120 advert_int 1 authentication { auth_type PASS auth_pass 1111 } track_script { chk_http_port } virtual_ipaddress { 192.168.1.200 } } } /#vi /etc/keepalived/check_haproxy.sh /#!/bin/bash A=ps -C haproxy --no-header |wc -l if [ $A -eq 0 ];then /usr/local/haproxy/sbin/haproxy -f /usr/local/haproxy/conf/haproxy.cfg sleep 3 if [ ps -C haproxy --no-header |wc -l -eq 0 ];then /etc/init.d/keepalived stop fi fi /#chmod 755 /etc/keepalived/check_haproxy.sh haproxy的安装(主备都一样): /#tar -zxvf haproxy-1.4.9.tar.gz /#cd haproxy-1.4.9 /#make TARGET=linux26 PREFIX=/usr/local/haproxy install /#cd /usr/local/haproxy/ /#mkdir conf logs /#cd conf /#vi haproxy.cfg global log 127.0.0.1 local3 info maxconn 4096 user nobody group nobody daemon nbproc 1 pidfile /usr/local/haproxy/logs/haproxy.pid defaults maxconn 2000 contimeout 5000 clitimeout 30000 srvtimeout 30000 mode http log global log 127.0.0.1 local3 info stats uri /admin?stats option forwardfor frontend http_server bind :80 log global default_backend info_cache acl test hdr_dom(host) -i test.domain.com use_backend cache_test if test backend info_cache /#balance roundrobin balance source option httpchk HEAD /haproxy.txt HTTP/1.1\r\nHost:192.168.1.187 server inst2 192.168.1.187:80 check inter 5000 fall 3 backend cache_test balance roundrobin /#balance source option httpchk HEAD /haproxy.txt HTTP/1.1\r\nHost:test.domain.com server inst1 192.168.1.187:8000 check inter 5000 fall 3 二:再两台机器上都分别启动: /etc/init.d/keepalived start (这条命令会自动把haproxy启动) 三:测试: 1.再两台机器上分别执行ip add 主: eth0: mtu 1500 qdisc pfifo_fast qlen 1000 link/ether 00:0c:29:98:cd:c0 brd ff:ff:ff:ff:ff:ff inet 192.168.1.192/24 brd 192.168.1.255 scope global eth0 inet 192.168.1.200/32 scope global eth0 inet6 fe80::20c:29ff:fe98:cdc0/64 scope link valid_lft forever preferred_lft forever 备: eth0: mtu 1500 qdisc pfifo_fast qlen 1000 link/ether 00:0c:29:a6:0c:7e brd ff:ff:ff:ff:ff:ff inet 192.168.1.193/24 brd 255.255.255.254 scope global eth0 inet6 fe80::20c:29ff:fea6:c7e/64 scope link valid_lft forever preferred_lft forever 2.停掉主上的haproxy,3秒后keepalived会自动将其再次启动 3.停掉主的keepalived,备机马上接管服务 备: eth0: mtu 1500 qdisc pfifo_fast qlen 1000 link/ether 00:0c:29:a6:0c:7e brd ff:ff:ff:ff:ff:ff inet 192.168.1.193/24 brd 255.255.255.254 scope global eth0 inet 192.168.1.200/32 scope global eth0 inet6 fe80::20c:29ff:fea6:c7e/64 scope link valid_lft forever preferred_lft forever 4.更改hosts 192.168.1.200 test.com 192.168.1.200 test.domain.com 通过IE测试,可以发现 test.com的请求发向了192.168.1.187:80 test.domain.com的请求发向了192.168.1.187:8000

from:http://www.360doc.com/content/11/0425/11/2054285_112154067.shtml

来源: <haproxy+keepalived实现高可用负载均衡 - dkcndk - 博客园>

认识Squid_inJava_百度空间

Posted on

认识SquidinJava百度空间

inJava

J2EE、架构、搜索引擎、数据挖掘、互联网、创业、媒体、资料库

2008-03-22 13:48

认识Squid

Squid是什么? Squid能做什么? Squid如何工作的? [Squid是什么] Squid是一个高性能的代理缓存服务器,Squid支持FTP、gopher和HTTP协议。和一般的代理缓存软件不同,Squid用一个单独的、非模块化的、I/O驱动的进程来处理所有的客户端请求。 Squid将数据元缓存在内存中,同时也缓存DNS查询的结果,除此之外,它还支持非模块化的DNS查询,对失败的请求进行消极缓存。Squid支持SSL,支持访问控制。由于使用了ICP(轻量Internet缓存协议),Squid能够实现层叠的代理阵列,从而最大限度地节约带宽。 Squid由一个主要的服务程序squid,一个DNS查询程序dnsserver,几个重写请求和执行认证的程序,以及几个管理工具组成。当Squid启动以后,它可以派生出预先指定数目的dnsserver进程,而每一个dnsserver进程都可以执行单独的DNS查询,这样一来就大大减少了服务器等待DNS查询的时间。 Squid是一个缓存internet数据的一个软件,它接收用户的下载申请,并自动处理所下载的数据。也就是说,当一个用户象要下载一个主页时,它向Squid发出一个申请,要Squid替它下载,然后Squid连接所申请网站并请求该主页,接着把该主页传给用户同时保留一个备份,当别的用户申请同样的页面时,Squid把保存的备份立即传给用户,使用户觉得速度相当快。目前,Squid 可以代理HTTP, FTP, GOPHER, SSL 和 WAIS 协议,暂不能代理POP, NNTP等协议。不过,已经有人开始修改Squid,相信不久的将来,Squid能够代理这些协议。

Squid能够缓存任何数据吗?不是的。象缓存信用卡帐号、可以远方执行的scripts、经常变换的主页等是不合适的也是不安全的。Squid可以自动的进行处理,你也可以根据自己的需要设置Squid,使之过滤掉你不想要的东西。

Squid可以工作在很多的操作系统中,如AIX, Digital Unix, FreeBSD, HP-UX, Irix, Linux, NetBSD, Nextstep, SCO, Solaris,OS/2等,也有不少人在其他操作系统中重新编译过Squid。

Squid对硬件的要求是内存一定要大,不应小于128M,硬盘转速越快越好,最好使用服务器专用SCSI硬盘,处理器要求不高,400MH以上既可。 [SQUID能做什么] 1、代理服务器 2、透明代理 3、反向代理 什么是代理服务器 做为代理服务器,这是SQUID的最基本功能;通过在squid.conf文件里添加一系列访问及控制规则,用户在客户端设置服务器地址和端口,即可通过SQUID访问INTERNET。 什么是透明代理 所谓透明代理,是相对于代理服务器而言,客户端不需要做任何和代理服务器相关的设置和操作,对用户而言,更本感觉不到代理服务器的存在,所以称之为透明代理。 透明代理流程说明 用户A发送一个访问请求到防火墙,由防火墙将该用户的访问请求转发到SQUID,SQUID在先检查自身缓存中有无该用户请求的访问内容,如果没有,则请求远端目的服务器,获取该用户的访问内容,在返回给用户的同时,在自身缓存保留一份记录以备下次调用;当用户B发送一个和用户A相同的访问请求时,由防火墙将转发该用户请求到SQUID,SQUID检查自身缓存发现有同样内容后,直接将该内容返回给用户。 注:在实际使用中,通常将SQUID和防火墙放在同一台机器上,为了更清楚的象浏览者描述其工作流程,在以下的流程图中将防火墙和SQUID分开显示。 什么是反向代理 普通代理方式是代理内部网络用户访问internet上服务器的连接请求,客户端必须指定代理服务器,并将本来要直接发送到internet上服务器的连接请求发送给代理服务器处理。反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。

反向代理流程说明 SQUID做为反向代理服务器,通常工作在一个服务器集群的前端,在用户端看来,SQUID服务器就是他所要访问的服务器,而实际意义上SQUID只是接受用户的请求,同时将用户请求转发给内网真正的WEB服务器,如果SQUID本身有用户要访问的内容,则SQUID直接将数据返回给用户。 [SQUID如何工作] /#linux+windows

分享到:

举报浏览(117) 评论转载

你可能也喜欢

评论 帮助中心 | 空间客服 | 投诉中心 | 空间协议

©2013 Baidu

HAProxy的独门武器:ebtree

Posted on

HAProxy的独门武器:ebtree

1. HAProxy和ebtree简介

HAProxy是法国人Willy Tarreau个人开发的一个开源软件,目标是应对客户端10000以上的同时连接,为后端应用服务器、数据库服务器提供高性能的负载均衡服务。 在底层数据结构方面,旧版本HAProxy曾经使用过红黑树,用于任务调度、负载均衡等方面。但是Willy Tarreau认为,在事件响应非常频繁的情况下,任务插入、删除的频率非常高,这时候使用红黑树存在性能瓶颈,尤其不能接受红黑树删除节点的时间复杂度为O(log n)。因此,他发明了一种新的数据结构,叫做弹性二叉树(elastic binary tree),简称ebtree。 目前新版本的HAProxy(本文编写时最新版本为1.4.23)已使用ebtree,而除了HAProxy之外,还没有其它著名的开源软件使用ebtree。可以这么说,HAProxy最有特色的地方就是ebtree,ebtree名符其实是HAProxy的独门武器。 ebtree是不平衡的二叉搜索树(BST),而红黑树、AVL树等都是平衡的BST。传统的BST最怕的就是退化成线性搜索,因此,红黑树等BST插入、删除时都需要对树进行平衡化,而平衡化是一个从叶子节点开始,向根节点方向递归向上的过程,时间复杂度是O(log n)。 有鉴于此,ebtree为了实现删除节点时O(1)的时间复杂度,必然放弃保持树的平衡,为了拒绝由此而来的副作用——退化成线性搜索(或者更准确地说,退化成不受限制的线性搜索),不可避免地引入了一些新的成员和新的思路,且待我慢慢道来。

2. ebtree节点的组成

一个ebtree的节点(以下简称ebnode)分为node部分和leaf部分(Willy Tarreau是这样描述的,但我觉得称为树干部分和叶子部分更合适一些,以下就按我的理解来叙述)。树干负责关联其它ebnode,由父指针(node_p)和分支(Willy Tarreau称之为root,包括左分支L和右分支R),以及一个控制树的高度的特殊成员(bit)组成,叶子负责携带数据(data,一般是数据的键值,所以下文都称为key),另外包含一个指向上层的指针(leaf_p)。 一棵ebtree只有一个根节点(root),包含两个左右分支的指针(L、R)。所有的ebnode总是挂在根节点的左分支下面,根节点的右分支总是为空。在ebtree的遍历过程中,判断当前节点是否根节点就是判断其右指针是否为空。

Ebnode

-

3. 各个指针的附加属性

在32位平台中,一个指针占用4个字节,例如,地址值0xaabbcc00的下一个地址值是0xaabbcc04,再下一个是0xaabbcc08,也就是说,指针的值的最后两个比特不能表示一个合法地址。因此,Willy Tarreau充分利用这一点,来保存上述几个指针的特殊属性。这是一个很重要的优化,每个ebnode可以节省几个成员,整个ebtree就节省大量存储空间。 1)L和R既可以指向其它ebnode的树干,也可以指向其它ebnode的叶子,还可以指向自己的叶子。在ebtree的遍历过程中,对树干和叶子有不同的处理逻辑,L和R有必要知道自己所指向的是树干还是叶子。 2)可以知道node_p和leaf_p究竟挂在其它ebnode的左分支下面,还是挂在其右分支下面。 3)根节点右分支不挂任何树干和叶子,可以把它也利用上,指示该ebtree是否允许重复键值。 熟悉红黑树的读者都知道,红黑树也有同样的优化方法,表示红黑树节点颜色的属性并不占用内存空间。

4. bit的定义

引入bit就是为了限制树的高度,避免极端不平衡。在一棵不允许重复键值的ebtree中,key是32位的情况下,bit的取值范围是从0到31,此时,它的定义是:子树所有的键中,第一个不同的二进制位的位置。允许重复键值的ebtree稍后再详细介绍。 例如,下图的右下角子树中只有两个键,左边叶子节点的键值为300,右边叶子节点的键值为400,300的二进制是100101100,400的二进制是110010000,从右边数起第7位起(注意,程序员都是从0开始数数的),300和400左边的位都相同,所以,bit等于7。

bit

这时候,读者可能会问,这样定义bit为什么能够限制树的高度呢?不用着急,马上隆重介绍bit的两个重要意义!

5. bit的第一个重要意义

这里只讨论键值大于等于零的情况,事实上,ebtree可以支持键值为负数,不过,我还没有仔细研究过这种情况,应该是对符号位进行某些转换处理。 bit的第一个重要意义:同一个ebnode中的bit和key,联合决定该ebnode属下的子树内,所有key的取值范围。 先看下图挂在根节点下面,key = 300的那个ebnode,bit = 8,300的二进制为100101100,从右边数起第8位是最高位那个1,参考bit的定义,也就是说,该子树所有的键,第8位左边都是0,所以,它们的取值范围是从0到511(二进制111111111)。 再看最下面那个ebnode,bit = 5,250的二进制为11111010,从右边数起第5位是第三个1,再对照bit的定义,该子树的键,第5位左边都是11,所以,它们的取值范围是从192(二进制11000000)到255(二进制11111111)。 同理,最右边那个ebnode,bit = 7,key = 400,取值范围是256-511。

bit1

6. bit的第二个重要意义和查找过程

bit的第二个重要意义:如果要查找的数据x在该子树的取值范围内,bit可以指示其可能会在左分支下面还是右分支下面。 ebtree的具体查找过程是,遍历到某个ebnode时,如果key = x,返回查找结果;如果x已经超出bit规定的取值范围,返回查找失败;否则,取x的第bit位,如果bit = 0,那么从该ebnode的左分支查找,反之,从右分支查找;如果已到达叶子还是没有匹配,返回查找失败。 还是上一节那个图,假如要找的键x = 249,二进制为11111001,从根节点左分支开始查找,bit = 8,右边数起第8位为0,于是从它的左分支继续查找,bit = 5,249右边数起第5位为1,于是从它的右分支继续查找,此时已到达叶子,且250 != 249,本次查找失败。 假如要找的键x = 300,因为就在查找路径的节点上,直接返回结果。 假如要找的键x = 600,已经超出该子树中bit规定的取值范围,返回查找失败。

7. 插入不可重复的键值

首先,要介绍的是空树的情况。由前面的叙述可以得知,一棵ebtree为空树当且仅当它的根节点的左分支为空。所以,此时插入的ebnode就直接挂在根节点的左分支下面,由于新插入的ebnode不存在左右分支,也没有父节点(上层ebnode),显然也不需要bit来控制树的高度,因此,该ebnode的树干都没有使用。

insert0

其次,介绍ebtree只有一个ebnode时,再插入一个ebnode的情况。此时,新的ebnode必定插入在根节点与旧的ebnode之间,如果新的键值大于原来的键值,旧的ebnode挂在新的ebnode的左分支下面,新的ebnode的叶子挂在自己的右分支下面,再计算bit;反之,则左右相反,再计算bit。 下图的例子,是已有key = 200,再插入key = 300的情形。读者可以根据上面的描述画出已有key = 200,再插入key = 100的情形。

insert1

然后,就可以介绍在ebtree中插入新的ebnode的五种基本情形。在这里,都以上图为初始状态。任何具有更多ebnode的情形,都可以通过对ebtree的遍历,递推到其中一种情形。

1) 新的键值可以插入子树中,而且小于子树中的最小键值。

假如新插入ebnode的key为100,根据bit的第二个重要意义,100应该在该子树的左分支下面,而且,100小于200,于是,该ebnode插入在原图的左分支上,自己的左分支指向自己的叶子,自己的右分支指向原来子树的左分支。如下图所示。

insert2_1

键值范围[0, 200)都属于这种情形。

2) 新的键值可以插入子树中,该键值在确定要插入的两个ebnode的键值之间,且应该在该子树的左分支下面。

假如新插入ebnode的key为225,根据bit的第二个重要意义,225应该在该子树的左分支下面,而且,225大于200,于是,该ebnode插入在原图的左分支上,自己的左分支指向原来子树的左分支,自己的右分支指向自己的叶子。如下图所示。

insert2_2

键值范围(200, 255]都属于这种情形。

3) 新的键值可以插入子树中,该键值在确定要插入的两个ebnode的键值之间,且应该在该子树的右分支下面。

假如新插入ebnode的key为275,根据bit的第二个重要意义,275应该在该子树的右分支下面,而且,275小于300,于是,该ebnode插入在原图的右分支上,自己的左分支指向自己的叶子,自己的右分支指向原来子树的右分支。如下图所示。

insert2_3

键值范围(255, 300)都属于这种情形。

4) 新的键值可以插入子树中,而且大于子树中的最大键值。

假如新插入ebnode的key为400,根据bit的第二个重要意义,400应该在该子树的右分支下面,而且,400大于300,于是,该ebnode插入在原图的右分支上,自己的左分支指向原来子树的右分支,自己的右分支指向自己的叶子。如下图所示。

insert2_4

键值范围(300, 511]都属于这种情形。

5) 新的键值不可以插入子树中。

假如新插入ebnode的key为600,根据bit的第一个重要意义,600不可插入到子树中,于是,该ebnode插入在原图的子树之上,自己的左分支指向原来的子树,自己的右分支指向自己的叶子。如下图所示。

insert2_5

键值范围(511, +∞)都属于这种情形。

8. bit的第三个重要意义和插入重复的键值

ebtree是专门为任务调度而生的,同样的优先级,必须保证能够按照任务触发的次序来进行访问。所以,ebtree支持存储重复的键值,这一点并不是所有的BST都支持,可以说是ebtree的优点。而且,解决键值冲突不会退化成链表。 bit的第三个重要意义:bit为负值表示该子树下所有的键都是重复的,而且,该值表示重复子树的层次。当然,必须要在根节点右指针允许的情况下。 插入第一个重复键值,例如300(ebnode底纹为点点),可以参考上一节的第二种和第四种基本情形,不同的是,bit为-1。

insert_dup1

如果再插入一个重复键值300(ebnode底纹为方格),应该在重复键值子树上插入,而且是向上生长。

insert_dup2

上图已经有四个ebnode,信息量较大,为了后续叙述方便,把它简化,去掉几个指针域,保留bit和key,得到下图。

insert_dup2s

再插入一个300(ebnode底纹为斜方格),得到下面的ebtree。

insert_dup3s

再插入两个300(ebnode底纹分别为左斜线和右斜线),得到下面的ebtree。

insert_dup5s

读者可以思考一下,如果再来一个、两个、三个300,应该在哪里插入?如果插入不同于300的其它键值,应该在哪里插入? 从上面几张图,大家可以看到,一个ebnode的树干和叶子会随着树的增长而拉长到不同的层次上,好像很有弹性的样子,这就是弹性二叉树名字的由来。

9. 删除节点

删除一个ebnode,概括起来比较简单,就是把要删除的叶子和该叶子的父亲(树干部分)删除,然后把兄弟挂到祖父下面。因为不需要对树进行平衡化,不需要访问其它ebnode,效率很高。 具体操作,分为两种情况: 1)被删除的叶子直连自己的树干,可直接删除该ebnode,然后对它的兄弟重新指派原来的祖父为父亲。 2)被删除的叶子不是直连自己的树干,以该叶子的父亲(其它ebnode的树干)替换该ebnode的树干,然后删除该ebnode,再把它的兄弟重新指派原来的祖父为父亲。

10. 总结

没有最好的数据结构,只有最合适的数据结构。ebtree有它的优点: 1)支持存储重复的键值,而且,在此情况下,也不会退化成线性操作。 2)删除节点时,不需要对树进行平衡化。 3)插入键值时,很可能不需要深入到树的叶子,当然,很多BST都这样。 4)查询键值时,可以预知子树的取值范围,从而可以选择访问还是不访问该子树。 缺点也很明显: 1)逻辑比较复杂,熟悉的人不多(希望读者看完本文之后都有茅塞顿开的感觉)。 2)ebnode占用空间比较多,如果把bit也算一个指针,相当于花了5个指针才携带1个数据。 3)键值严重依赖于可以进行位运算的数据类型。 总而言之,ebtree适合有高频率插入、删除操作(例如50万次/秒)的使用场合,不适合查询较多、插入、删除较少的场合,非常不适合用于缓存。

11. 参考资料

来源: [http://tech.uc.cn/?p=1031](http://tech.uc.cn/?p=1031)

HAProxy几个重要的结构体

Posted on

HAProxy几个重要的结构体

HAProxy几个重要的结构体

上一篇文章《HAProxy的event_accept函数源码分析》(以下简称上文)理顺了HAProxy接收客户端连接的主要流程,遗留下相关的基础设施和数据结构没有深入分析,这一篇先介绍几个重要的结构体。

另外要说明的是,本系列文章对HAProxy的分析都基于目前的稳定版本HAProxy 1.4,而目前的开发版本HAProxy 1.5和1.4相比,重构力度比较大,不少函数名称和结构体都发生变化,但是关键流程还是基本一致的,请各位读者留意。

1. session

上文讲的其实就是HAProxy怎样在客户端和服务端之间建立一个完整的链路,相关信息都保存到一个session结构体中。原本的session结构体有80行,可以说非常冗长。和上文思路一样,本着不追求旁枝末节,以较小的代价描述HAProxy原理的原则,我把它进行简化,去掉了HTTP处理相关的成员和一些实现额外功能的成员,只把必不可少的成员列出来:

1

2 3

4 5

6 7

8 9

10 11

12 13 struct session {

struct list list;         //* position in global sessions list /*/
struct task /*task;        //* the task associated with this session /*/

int conn_retries;         //* number of connect retries left /*/
int flags;                //* some flags describing the session /*/

struct buffer /*req;       //* request buffer /*/
struct buffer /*rep;       //* response buffer /*/

struct stream_interface si[2];     //* client and server stream interfaces /*/
struct sockaddr_storage cli_addr;  //* the client address /*/

struct sockaddr_in srv_addr;       //* the address to connect to /*/
struct server /*srv;       //* the server the session will be running or has been running on /*/

struct server /*prev_srv;  //* the server the was running on, after a redispatch, otherwise NULL /*/

};

其中,task、req、rep和si在上文已经有所提及,下面也会有进一步解释;conn_retries、flags、cli_addr和srv_addr甚至不用看注释就明白其意义;srv和prev_srv也很明显,一个指向将要执行或正在执行的后端服务器结构体,一个指向上一次曾经执行的后端服务器结构体。

list也是一个结构体,包含n和p两个指向struct list类型的指针,详见mini-clist.h。它在这里的作用是把各个session结构体串起来,形成 一个双向链表,如下图所示。这是包括HAProxy和Linux内核广泛使用的一种数据结构。我认为用在这里倒不是很必要,不过仍然是一种值得学习的技巧。

haproxy_sessions

值得注意的是,这种链表是嵌入到各个结构体中使用,p和n两个指针成员各自指向前、后节点节点的list成员,而非前、后节点本身。

2. task

task结构体的主要成员如下:

1

2 3

4 5

6 7

8 struct task {

struct eb32_node wq;  //* ebtree node used to hold the task in the wait queue /*/
struct eb32_node rq;  //* ebtree node used to hold the task in the run queue /*/

int state;            //* task state : bit field of TASK_/* /*/
int expire;           //* next expiration date for this task, in ticks /*/

struct task /* (/*process)(struct task /*t);  //* the function which processes the task /*/
void /*context;        //* the task's context /*/

};

其中,state和expire都是顾名思义;process是函数指针,根据task的类型会执行不同的处理函数,以后的文章再叙述;context指向与task关联的结构体,在event_accept函数中,context指向的就是session。

重点讲一下wq和rq,它们是两个使用32位键的ebtree节点(以下简称ebnode),分别代表该task与等待队列和运行队列的关系。在第一篇文章《HAProxy的独门武器:ebtree》中,因为没有具体例子印证,有一个关于ebnode的特点没有交代:一个ebnode在ebtree中,当且仅当该ebnode的leaf_p不为空。这一点可以从ebnode的插入、删除操作中推导得出。

因此,判断一个task是否在运行队列中就是:

1

2 3

4 static inline int task_in_rq(struct task /*t)

{ return t->rq.node.leaf_p != NULL;

}

同样,判断一个task是否在等待队列中就是:

1

2 3

4 static inline int task_in_wq(struct task /*t)

{ return t->wq.node.leaf_p != NULL;

}

从运行队列中删除一个task:

1

2 3

4 5 static inline struct task /__task_unlink_rq(struct task /t)

{ eb32_delete(&t->rq);

return t;

}

从等待队列中删除一个task,这里有个优化,last_timer指向最后一个入队列的task(稍后再介绍):

1

2 3

4 5

6 7 static inline struct task /__task_unlink_wq(struct task /t)

{ eb32_delete(&t->wq);

if (last_timer == &t->wq)
    last_timer = NULL;

return t;

}

添加task到运行队列,也就是唤醒该task,上文的event_accept函数,最终也是会执行到这里:

1

2 3

4 5

6 7

8 extern struct task /__task_wakeup(struct task /t)

{ t->rq.key = ++rqueue_ticks;

// clear state flags at the same time
t->state &= ~TASK_WOKEN_ANY;

eb32_insert(&rqueue, &t->rq);
return t;

}

添加task到等待队列,也就是让该task排队,乍看上去有点复杂,其实核心部分就只有两三句,关于last_timer的都是一些优化工作,使得HAProxy更快地找到插入的地方:

1

2 3

4 5

6 7

8 9

10 11

12 13

14 15

16 17

18 19

20 21

22 23

24 extern void __task_queue(struct task /*task)

{ if (likely(task_in_wq(task)))

    __task_unlink_wq(task);


//* the task is not in the queue now /*/
task->wq.key = task->expire;


if (likely(last_timer &&

       last_timer->node.bit < 0 &&
       last_timer->key == task->wq.key &&

       last_timer->node.node_p)) {
    eb_insert_dup(&last_timer->node, &task->wq.node);

    if (task->wq.node.bit < last_timer->node.bit)
        last_timer = &task->wq;

    return;
}

eb32_insert(&timers, &task->wq);


//* Make sure we don't assign the last_timer to a node-less entry /*/
if (task->wq.node.node_p && (!last_timer || (task->wq.node.bit < last_timer->node.bit)))

    last_timer = &task->wq;
return;

}

由此可以看出,ebtree函数库在使用上,只负责ebnode的组织,并不负责对ebnode的分配和释放,否则wq和rq就是两个ebnode类型的指针,而不是两个ebnode。这样做是有道理的,利用以上ebnode的特点,一个task可以先后多次挂载到相应的以ebtree为具体设施的队列中,减少了内存分配和释放次数。

3. stream interface

stream interface结构体的主要成员如下:

1

2 3

4 5

6 7

8 9

10 11

12 13

14 15

16 17

18 19

20 21 struct stream_interface {

unsigned int state;       //* SI_ST/* /*/
unsigned int prev_state;  //* SI_ST/*, copy of previous state /*/

void /*owner;              //* generally a (struct task/*) /*/
int fd;                   //* file descriptor for a stream driver when known /*/

unsigned int flags;
unsigned int exp;         //* wake up time for connect, queue, turn-around, ... /*/

void (/*update)(struct stream_interface /*);     //* I/O update function /*/
void (/*shutr)(struct stream_interface /*);      //* shutr function /*/

void (/*shutw)(struct stream_interface /*);      //* shutw function /*/
void (/*chk_rcv)(struct stream_interface /*);    //* chk_rcv function /*/

void (/*chk_snd)(struct stream_interface /*);    //* chk_snd function /*/
int (/*connect)(struct stream_interface /*, struct proxy /*, struct server /*,

   struct sockaddr /*, struct sockaddr /*);      //* connect function if any /*/
void (/*iohandler)(struct stream_interface /*);  //* internal I/O handler when embedded /*/

struct buffer /*ib, /*ob;   //* input and output buffers /*/
unsigned int err_type;    //* first error detected, one of SI_ET_/* /*/

void /*err_loc;            //* commonly the server, NULL when SI_ET_NONE /*/
void /*private;            //* may be used by any function above /*/

unsigned int st0, st1;    //* may be used by any function above /*/

};

大部分成员可以从注释中了解其用途,值得注意的是几个函数指针。大致上,它们可以指向两组函数:默认一组是以streamsock开头,用于在socket与缓冲区之间传输数据,转发处理业务流;另一组是以streamint开头,负责拦截、统计和响应客户端的监控请求,因为HAProxy的业务请求与监控请求是发送到同一个端口,通过URI来区分的,而监控请求显然是不能转发到后端服务器的,所以必须在获得各计数器数据后返回客户端。

以下简述一下streamsock开头的那一组函数的作用。这一组函数在上文的event_accept函数中初始化。 update指向的函数(stream_sock_data_finish)用于更新stream interface的fd的读写状态和相关标志位。 shutr和shutw指向的函数(stream_sock_shutr和stream_sock_shutw)分别用于关闭stream interface上的读和写。 chk_rcv指向的函数(stream_sock_chk_rcv)由消费者调用,用于通知生产者:缓冲区可能有空间了。 chk_snd指向的函数(stream_sock_chk_snd)由生产者调用,用于通知消费者:缓冲区可能有数据了。 当si是客户端stream interface时,connect为空,因为客户端连接显然已经建立。 当si是服务端stream interface时,connect指向HAProxy与服务端建立连接的那个函数(tcpv4_connect_server),而它会在backend.c的connect_server执行时被调用。 iohandler总是为空。

streamint开头的那一组函数在stream_interface.c的stream_int_register_handler函数中初始化,而该函数只有在监控分支才会执行。最明显区别是这一组会设置iohandler,而且调用时会重新唤醒所属的task,调用后马上返回,不再向下处理,如下所示:

1

2 3

4 5

6 7

8 9

10 11

12 13

14 15

16 17

18 19 struct task /process_session(struct task /t)

{ ...

//* Call the second stream interface's I/O handler if it's embedded.
 /* Note that this one may wake the task up again.

 /*/
if (s->req->cons->iohandler) {

    s->req->cons->iohandler(s->req->cons);
    if (task_in_rq(t)) {

        //* If we woke up, we don't want to requeue the
         /* task to the wait queue, but rather requeue

         /* it into the runqueue ASAP.
         /*/

        t->expire = TICK_ETERNITY;
        return t;

    }
}

...

}

4. buffer

完整的buffer结构体如下:

1

2 3

4 5

6 7

8 9

10 11

12 13

14 15

16 17

18 19

20 21

22 23 struct buffer {

unsigned int flags;             //* BF_/* /*/
int rex;                        //* expiration date for a read, in ticks /*/

int wex;                        //* expiration date for a write or connect, in ticks /*/
int rto;                        //* read timeout, in ticks /*/

int wto;                        //* write timeout, in ticks /*/
int cto;                        //* connect timeout, in ticks /*/

unsigned int l;                 //* data length /*/
char /*r, /*w, /*lr;               //* read ptr, write ptr, last read /*/

unsigned int size;              //* buffer size in bytes /*/
unsigned int send_max;          //* number of bytes the sender can consume om this buffer, <= l /*/

unsigned int to_forward;        //* number of bytes to forward after send_max without a wake-up /*/
unsigned int analysers;         //* bit field indicating what to do on the buffer /*/

int analyse_exp;                //* expiration date for current analysers (if set) /*/
void (/*hijacker)(struct session /*, struct buffer /*); //* alternative content producer /*/

unsigned char xfer_large;       //* number of consecutive large xfers /*/
unsigned char xfer_small;       //* number of consecutive small xfers /*/

unsigned long long total;       //* total data read /*/
struct stream_interface /*prod;  //* producer attached to this buffer /*/

struct stream_interface /*cons;  //* consumer attached to this buffer /*/
struct pipe /*pipe;              //* non-NULL only when data present /*/

char data[0];                   //* <size> bytes /*/

};

可以看到,HAProxy的buffer结构体也由许多成员组成,常规buffer该有的一个都不少,例如size、data、l、r、w、lr等等,除此之外,还有具有HAProxy特有的生产者、消费者stream interface指针(prod和cons),还有众多表示读写过期时间、超时时间的成员,还有指示具体请求响应解析过程的标志位(analysers),还有回调函数hijacker(不过看起来没有使用),还有表示该buffer最多能向其消费者发送多少字节的send_max,还有在HTTP chunked格式转发时才有意义的to_forward,还有连续满负荷传输数据和连续低负荷传输数据的计数器(xfer_large和xfer_small),以触发判断是否使用splice系统调用。面对把这么多功能揽于一身的buffer,看你晕不晕!

下面只挑一些具有通用借鉴意义的成员提及一下。

最后的那个成员data[0]是一个“零长度数组”,不懂的google一下就知道了,很简单,不多说。

5. pipe

buffer的倒数第二个成员pipe指针,指向的是这么一个链表结构:

1

2 3

4 5

6 struct pipe {

int data;   //* number of bytes present in the pipe  /*/
int prod;   //* FD the producer must write to ; -1 if none /*/

int cons;   //* FD the consumer must read from ; -1 if none /*/
struct pipe /*next;

};

因为splice系统调用要求输入和输出至少必须有一个描述符是管道符,所以,HAProxy准备了一对管道符(prod和cons)。当使用splice读数据时,HAProxy从源socket描述符(对于请求,就是连接客户端与HAProxy的socket描述符,对于响应,则是连接服务端与HAProxy的socket 描述符;下面的目的socket描述符刚好相反)splice到管道符prod;当使用splice写数据时,HAProxy从管道符cons splice到目的socket描述符。详见stream_socket.c的stream_sock_splice_in函数和stream_sock_write_loop函数:

1

2 3

4 5

6 7

8 9

10 11

12 13

14 15 static int stream_sock_splice_in(struct buffer /b, struct stream_interface /si)

{ ...

ret = splice(fd, NULL, b->pipe->prod, NULL, max,
        SPLICE_F_MOVE|SPLICE_F_NONBLOCK);

...

}

static int stream_sock_write_loop(struct stream_interface /si, struct buffer /b)

{ ...

ret = splice(b->pipe->cons, NULL, si->fd, NULL, b->pipe->data,
        SPLICE_F_MOVE|SPLICE_F_NONBLOCK);

...

}

这样一来,每个buffer就要创建一对管道符,从上文和上面的分析可知,每个session就有两个buffer,而每个session的生命周期是从客户端连接建立到客户端连接释放(这里之前没有提到,以后可能会讲一下),所以,如果并发量很大,这是一个巨大的消耗。为此,HAProxy用管道池来解决这一问题,就像内存池的使用那样,需要有一个next指针把pipe结构体串起来。具体流程是,读取数据之前,调用get_pipe函数获取一对管道符,全部数据写完之后(由data指示数据是否写完),调用put_pipe函数进行释放,这样管道只会在异步等待写的时候被占用,大大减少使用量,从而减少系统开销。详见上面两个函数的代码。

6. 小结

五个我认为比较重要的结构体就介绍到这里。当然,HAProxy还有许多结构体,例如proxy、server、listener等等,不过,这些结构体,要么比较容易看懂,要么网上已经有比较齐全的资料,要么可以陆续在后面的文章中单独说明。而session、task、stream interface、buffer和pipe这五个结构体,连同第一篇介绍的ebtree,向我们展现了HAProxy作为一个高性能代理服务器的底层数据组织和一些重要的处理细节。

您可能感兴趣的文章

来源: [http://tech.uc.cn/?p=1738](http://tech.uc.cn/?p=1738)

新型的大型bbs架构(squid+nginx)

Posted on

新型的大型bbs架构(squid+nginx) - 开源中国 OSChina.NET

当前访客身份:游客 [ 登录 | 加入开源中国 ]

开源中国

讨论区

当前位置: 讨论区 » 技术分享 » LVS 搜 索 鉴客

新型的大型bbs架构(squid+nginx)

鉴客 发表于 2010-9-13 07:46 2年前, 3回/4622阅, 最后回答: 4个月前

Java、PHP、Ruby、iOS、Python 等 JetBrains 开发工具低至 99 元(3折),详情»

这个架构基于squid、nginx和lvs等技术,从架构上对bbs进行全面优化和保护,有如下特点: 1、高性能:所有的点击基本上全部由前端缓存负责,提供最快速的处理。 2、高保障度:不需考虑应用程序稳定与否、程序语言是何种、数据库是何种,都能从架构上保证稳定。 3、高可用性:对应用程序的修改达到最简化:在程序的某些地方加入清缓存的语句即可,当然还需要做页面静态化的工作和统计工作。 首先看图,这个图比较大: 这个架构的特点和一些流程的说明: 1、主域名和图片域名分离 域名分离可以使流量分离,缓存策略分离等等,好处诸多。bbs初期一定要做好规划,将图片用另外的域名独立服务,即使没有足够机器,域名也要先分开。另 外,图片服务器可以使用有别于主域名的另一个域名,一个好处是可以减少读取cookie对图片服务器的压力,另一个是提高安全性,避免cookie泄露。 2、使用LVS作为前端、二级代理和数据库的访问入口 使用LVS作为入口,比其他任何一种方式都来得更优质。首先LVS的负载能力很强,因为它工作在网络协议的第4层,使用虚拟ip技术,所以它本身并不担负 任何流量的处理,仅仅是一个封包转发的功能;第二,LVS的配置相对简单而且稳定,一般去调整的几率比较低,也减少了因人为等因素而出现故障;第 三,LVS可以处理任何端口的负载均衡,所以它基本可以做所有服务的负载均衡和容错。在这个架构中,除了处理http的80端口之外,LVS也处理了数据 库mysql的3306端口,在数据库这个应用中是采用的双机热备策略。 3、使用nginx+squid作为最前端的缓存组合 在这个架构中,是最能体现app_nginx_squid_nginx架构的优势的。在这个架构中的bbs运行在缓存上,用户每发布一张帖子,都需要使用 purge指令清除该帖子的缓存,如果是squid在最前端,那么每次发布一张帖子,都需要在所有的squid中调用purge指令,这样在机器比较多的 时候,purge将成为一个巨大的压力。 所以在这里将nginx放在最前端并使用手工url_hash的方式分流,将经常需要purge的帖子页面和列表页面按一个url对应一台squid的策 略,分布到各台squid上,并提供了一台或一组backup的squid,个别squid出现异常时将自动使用backup的机器继续提供一段时间的服 务直到其正常。在这样的架构下,purge就不再是关键问题,因为一个url只会对应到一台机器上,所以purge的时候,后端app_server找到 对应的机器就可以了。 可以看到在前端中还有一台nginx(purge)的机器,这台机器是专用于purge的,只要发送purge指令和需要清除的url到这台机器,就可以 找到相应的服务器并清除缓存了。另外,purge时还需要清理backup机器上的缓存,所以无论前端机器增加到多少,purge指令只会在2台机器上执 行,如果backup机器使用到2-3台,purge指令就会在3-4台机器上执行,仍然在可接受范围之内。 nginx作为前端,另有的好处: 1/使用nginx的日志统计点击量非常方便 2/nginx也可作为缓存,一般可以直接负责favicon.ico和logo等固定的小图片 4、基于nginx的中层代理 nginx中层代理的优势,在: nginx和squid配合搭建的web服务器前端系统 这篇文章中有解释。 在这个架构中,假如后端的app_server上把帖子页和列表页直接生成了静态页面,那么使用中层代理再做一次url_hash,将可以解决后端 app_server的硬盘容量的压力,但是如果使用到url_hash的话,那做容错就相对麻烦了。所以建议不要采用生成静态页的方式,后端的压力一般 不会非常的大,所以没有必要生成静态页。假如前端squid的命中率实在太低下,造成大量穿透,可以考虑使用二级代理暂顶。 5、基于LVS的数据库双机热备 在这个架构中,因为大量的并发和访问量都由前端的缓存处理掉了,所以后端的mysql主要压力来自于数据的写入,所以压力并不是非常大,并且负载比较稳 定,一般不会随着访问量上升而提高过快,估计目前一台64位的机器,加满内存并使用高速的硬盘,前端负载数亿访问量时数据库都不会出现性能问题。在数据库 这方面应主要考虑故障恢复,因为数据库崩溃的话,按照一般使用备份恢复的做法,耗时很长而且难免丢失数据,是很棘手的问题。使用双机热备的方案,出现故障 时首先可由一台时刻同步着的备用数据库即刻充当主数据库,然后卸下的数据库可以有充分的时间对其进行维修,所以是个很安全有效的办法。 当然,数据库的优化还是要细心做的,参考: mysql性能的检查和调优方法 细心地调一遍,性能会好很多。 6、图片服务器 图片服务器我在这个架构中没有特别详细的介绍,在大型的bbs系统下,图片常常会出现容灾现象——图片数量严重超过了单台前端服务器容纳能力,导致前端服务器命中率低下。处理容灾问题也是非常棘手的,往后会有更详细的介绍。 7、简单的点击量统计办法 1/使用js的script标签访问另一(台)组服务器的空文件,然后定期向数据库更新 2/在前端的nginx上直接开启日志功能,按需要统计点击量的链接规则进行记录,然后定期更新数据库

标签: LVS Squid Nginx 补充话题说明»

分享到 **

收藏 **

38 **

举报 **

0 | 1 **

按默认排序 | 显示最新评论 | 回页面顶部 共有3个评论 发表评论»

  • pizigou

pizigou 回答于 2012-02-14 22:26

举报 非常收益,学习了。 有帮助(0) | 没帮助(0) | 评论(0) | 引用此评论

  • 江边望海

江边望海 回答于 2012-02-17 08:18

举报 很有干货的一篇文章,收藏啦 有帮助(0) | 没帮助(0) | 评论(0) | 引用此评论

  • 隋济远

隋济远 回答于 2012-11-29 10:09

举报 谢谢,受益! 有帮助(0) | 没帮助(0) | 评论(0) | 引用此评论

非会员用户 回评论顶部 | 回页面顶部

有什么技术问题吗? 我要提问 全部(1710)...鉴客的其他问题

类似的话题

© 开源中国(OsChina.NET) | 关于我们 | 广告联系 | @新浪微博 | 开源中国手机版 | 粤ICP备12009483号-3 开源中国手机客户端: Android iPhone WP7