为了保证Web应用程序的高可用性和性能,通常会使用多个应用服务器,然后使用负载均衡器接收用户的请求,将请求导向后端的应用服务器。目前有许多流行的软件可以起到负载均衡器的作用,它们在服务的架构有着非常重要的地位。
应用程序通过网络进行通信,需要不同的软件和硬件合作完成。为了将复杂的问题简化,将通信过程中的相关功能各进行分层。开放系统互连(OSI)将网络通信抽象为七层模型,OSI分层模型如图1所示。
按照OSI模型定义的层级,将负载均衡器分为四层负载均衡和七层负载均衡。
四层负载均衡工作在传输层。传输层负责处理消息的传递而不考虑消息的内容。HTTP协议使用了传输控制协议(TCP),故四层负载均衡器简单地将网络数据包转发到上游服务器和转发上游服务器的数据包,不检查数据包的内容。四层负载均衡器可以通过检查TCP流中的前几个数据包来做出有限的路由决策。
七层负载均衡器工作在应用层。HTTP就是工作在第七层的协议。第七层的负载均衡器的工作方式比第四层的负载均衡器更复杂,它会截取流量,读取其中的信息,并根据消息的内容(如URL、cookie)作出负载均衡的决策。然后,它与选定的上游服务器建立新的TCP连接,并将请求写入服务器。
与七层负载均衡相比,四层负载均衡需要的计算量更小;在IT技术发展的早期,客户端和服务器之间的交互也不如现在复杂,所以当时四层负载均衡是一种更流行的流量处理方法。遵循摩尔定理,硬件在性能提高的同时价格也降低,现在的CPU和内存已经足够便宜,大多数的情况下,四层负载均衡的性能优势可以忽略不计。
七层负载均衡在时间和计算量方面比第四层更加昂贵,不过它可以提供更丰富的功能,从而带来更高的整体效率。比如七层负载均衡器可以确定客户端请求的数据类型,从而不必在所有的服务器上复制相同的数据。
Linux虚拟服务器(下面简称LVS)是基于Linux操作系统内核的负载均衡软件。这个软件采用集群技术,可用来构建高性能和高可用性的服务器,并提供良好的可扩展性、可靠性和可维护性。
LVS是工作在第四层的负载均衡软件,在各个主要用来构建高可用的网络服务,比如Web服务、电子邮件服务、媒体服务、语音服务等。
由于IPv4中定义的IP数量有限,生产环境中难以给所有的服务器分配公网IP地址。常见的做法是给机器分配一个内网IP地址,通过网络地址转换给客户提供服务。
当用户访问集群提供的服务时,发往虚拟IP地址的请求数据包到达负载均衡器。负载均衡器检查目标地址和端口号。负载均衡器根据调度算法从集群中选择真实服务器,然后将请求数据包中的目的IP地址和端口重写为所选服务器的IP地址和端口,并将数据包转发到服务器。
服务器处理完成请求后,回复数据包给负载均衡器。负载均衡器将数据包中的源IP地址和端口重写为虚拟服务的源IP地址和端口。
在这种模式下,真实服务器的必须将网关配置为负载均衡器的IP。不妨设用户请求报文中的源IP和端口分别为202.100.1.2和3456,目的IP和端口分别为202.103.106.5和80。负载均衡器选择了服务器2,修改请求报文的目的IP和端口为172.16.0.3和80。
服务器2处理完成请求后,返回的报文中源IP和端口为172.16.0.3和80,目的IP和端口为202.100.1.2和3456。负载均衡器收到回复报文后,将回复报文中的源IP和端口修改为202.103.106.5和80。
NAT模式有一个问题:负载均衡器成为了集群的瓶颈,因为所有流入和流出的数据包都要经过负载均衡器。直接路由(DR)解决了这个问题。
在LVS-DR模式下,真实服务器和负载均衡共享虚拟IP地址。负载均衡器的虚拟IP接口用于接收请求数据包,并将数据包路由到选定的真实服务器。真实服务器需要单独配置接口用于传输返回报文,并在回环接口配置虚拟IP地址,这是为了让真实服务器能够处理目标地址为虚拟IP的数据包;不能将虚拟IP设置在真实服务器的出口网卡上,否则真实服务器会响应客户端的ARP请求,从而造成内网的混乱。
负载均衡器和真实服务器必须通过集线器/交换机连接。当用户访问集群的虚拟IP时,发送到虚拟IP地址的数据包会到达负载均衡器。负载均衡器检查数据包的目的IP和端口,选择一个真实服务器,然后将报文中目的MAC地址修改为选中的真实服务器的MAC地址,最后将数据包直接发送到局域网中。
真实服务器接收到数据包后,发现包中目的IP为自己配置在回环接口上的IP地址,因此处理这个请求,并将回复请求直接发送给客户端。DR模式的工作方式,如图3所示。
采用DR模式的集群能够处理很大的请求量,是一种应用广泛的模式。不过使用这种模式需要负载均衡器和真实服务器处于同一广播域中,这限制了集群的可扩展性,也不利于集群的异地容灾。
IP隧道模式是比DR模式更利于扩展的模式。IP隧道(IP封装)是一种将IP数据报封装在IP数据报中的技术。它允许将发往一个IP地址的数据报包装并重定向到另一个IP地址,这是网络中非常常见的技术。
在LVS的IP隧道模式中,负载均衡器收到用户的请求后,选择真实服务器,将数据包封装在IP数据报中,数据包中的源IP为负载均衡器的IP,目的IP为真实服务器的IP,最后将数据包转发到所选真实服务器。
真实服务器收到封装的数据报后,它会解封数据包冰处理请求,处理完成后将结果直接返回给请求的用户。
在集群中,真实服务器可以拥有任意真实IP地址,也就是说真实服务器的部署可以分布不同的地理位置,只要支持IP隧道,让服务器能够正确解封所接收的封装数据包即可。同时要注意的是,配置虚拟IP的网卡不能响应ARP请求:可以在不支持ARP的设置上配置虚拟IP,或者将配置虚拟IP的设备发出的数据包重定向到本地套接字中。
在NAT模式下,负载均衡调度器和真实服务器必须在同一个VLAN下,否则负载均衡调度器无法作为真实服务器的网关。LVS的FULLNAT模式解决了这个问题。
FULLNAT模式是NAT模式的升级版。在这种模式下,负载均衡器不仅会将请求数据包中目的IP替换真实服务器的IP,还会请求数据包中的源IP替换为负载均衡器的IP。在返回报文中,负载均衡器将报文目的IP替换为客户端的IP,并将源IP替换为虚拟IP。
FULLNAT模式不要求负载均衡器和真实服务器在同一个网段,因此可以支持真实服务器的跨机房部署。不过也会带来一定的性能损失,同时真实服务器不能直接获取到客户端的请求IP。
LVS的服务是通过名为IPVS的软件实现的,在Linux内核2.4及以上版本中,ip_vs模块已经是内核的一部分了。管理员通过ipvsadm程序来管理服务器集群。LVS的模式在系统
下面演示如何安装和使用ipvsadm。
在命令行终端中输入下面的命令:
# 安装ipvsadm
$ sudo apt-get update && sudo apt-get install ipvsadm
# 查看ipvsadm的版本
$ sudo ipvsadm -l
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
# 添加一台真实服务器,使用Round Robin调度算法
$ sudo ipvsadm -A -t 192.168.1.10:80 -s rr
# 查看真实服务器列表
$ sudo ipvsadm -l -n
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.1.10:80 rr
# 使用加权Round Robin算法
$ sudo ipvsadm -E -t 192.168.1.10:80 -s wrr
Nginx既可以作为四层负载均衡器使用,也可以作为七层负载均衡器使用。这里将主要介绍七层负载均衡的使用。
Nginx我们已经很熟悉了,前面的章节中也演示了如何安装和使用Nginx。下面将重点介绍Nginx的配置。Nginx最简单的负载均衡配置如下:
# http协议块
http {
# 上游配置
upstream myapp1 {
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
# server块配置
server {
# 监听端口
listen 80;
# location配置
location / {
proxy_pass http://myapp1;
}
}
}
在上面的配置中,有三个实例运行相同的服务,这三个实例分别是srv1、srv2和srv3。默认配置下,负载均衡将使用循环调度算法。Nginx的代理支持HTTP、HTTPS、FastCGI、uwsgi、SCGI、memcached和gRPC协议。
Nginx也支持最少连接数算法。在某些请求需要更长时间才能完成的情况下,最少连接数算法允许更公平地控制应用程序实例上的负载。使用最少连接数算法时,Nginx将尝试优先将新请求发送给不太繁忙的服务器,而从避免繁忙的应用程序服务器过载。
在配置中使用least_conn指令将激活最少连接负载均衡,使用示例如下:
upstream myapp1 {
# 启用最小连接负载均衡
least_conn;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
需要注意,使用循环或最少连接数算法,没有后续客户端的请求可能会发送到不同的服务器,也就是说Nginx无法保证同一客户端每次都会请求到同一个服务器。
如果需要将客户端绑定到特定的应用程序服务器,可以使用IP哈希负载均衡算法。使用这个算法,客户端的IP地址将作为哈希密钥,以确定应响应客户端请求服务器。用这个方法可以确保来自同一客户端的请求始终定向到同一服务器。在配置中使用ip_hash指令可以开启IP哈希调度,示例如下:
upstream myapp1 {
ip_hash;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
可以为不同的服务器设置不同的权重,来影响负载调度的结果。上面的循环示例中没有配置服务器权重,所有的服务器都有一样的几率被负载均衡器分发请求。当为服务器指定权重参数时,调度器做负载均衡决策时,会将这个权重考虑进去,比如下面的示例:
upstream myapp1 {
server srv1.example.com weight=3;
server srv2.example.com;
server srv3.example.com;
}
采用了上面的配置后,服务器每接收5个新请求,会将3个请求定向到srv1,一个请求定向到srv2,一个请求定向到srv3。
Nginx的反向代理实现中包含了服务器运行状态检查。如果来自特定服务器的响应失败并显示错误,Nginx会将此服务器标记为宕机状态,在此后的一段时间内避免将请求定向到该服务器。
有两个指令可以用来设置监控检查参数:max_fails和fail_timeout。max_fails指令用于设置与服务器通信连续不成功的尝试次数,默认值是1,当这个值设置为0时,将不会对对应的服务器启用健康检查。fail_timeout参数定义Nginx将服务器标记为宕机的时间。在服务器宕机时间超过fail_timeout设置的值后,Nginx将开始使用正常请求探测服务器,如果探测成功,Nginx会认为服务器已经恢复正常。
本文摘自于《Django项目开发实战》,经出版方授权发布。本次联合清华大学出版社为大家带来
6本书作为福利。在文末留言中写下
对负载均衡的认识与看法,截止7月27日20点前,评论点赞数前6名的读者将获取图书1本。没获奖的同学可以点击下方链接购买。
基于Kubernetes的DevOps实战培训将于2020年8月14日在上海开课,3天时间带你系统掌握Kubernetes,学习效果不好可以继续学习。本次培训包括:容器特性、镜像、网络;Kubernetes架构、核心组件、基本功能;Kubernetes设计理念、架构设计、基本功能、常用对象、设计原则;Kubernetes的数据库、运行时、网络、插件已经落地经验;微服务架构、组件、监控方案等,点击下方图片或者阅读原文链接查看详情。