文章目录
What and Why
负载均衡和反向代理其实是非常融合的概念。在后端的设计,为了提高系统的可用性,我们往往会使用多个服务器实例给 client 提供服务,但是这些对于客户端是不应该有感知的。也就是说客户端在发送请求的时候,是通过一个网址,网址在进行DNS的解析以后会被发送到一个固定的IP地址上(先不考虑DNS的路由选择)。
反向代理的目的就是接受 client发送过来的请求,并将请求转发到配置到的多个路由实例上。转发的规则就是负载均衡算法。
七层负载均衡Nginx
初入江湖:加一层Nginx
没有什么是加一层解决不了的。在我们的网络层之前,我们可以加一层Nginx进行代理和转发。Nginx的性能本身还是非常好的,因为使用了master-worker设计,epoll的IO,以及协程机制。当然这些不是本文的讨论重点。当然一般为了安全,实际在流量打到 server 前再做一层鉴权操作,鉴权通过了我们才让它打到 server 上,我们把这一层叫做网关。
小试牛刀:动静分离CDN
在实际的后端业务种,可以将请求分为动态请求(需要去数据库中查询或者执行逻辑)和静态请求(如 js,css文件)。不管是动态请求,还是静态资源请求都打到 tomcat 了,这样在流量大时会造成 tomcat 承受极大的压力,其实对于静态资源的处理 tomcat 不如 Nginx,tomcat 每次都要从磁盘加载文件比较影响性能,而 Nginx 有 proxy cache 等功能可以极大提升对静态资源的处理能力。
当然对于静态资源最好的方案就是存放在CDN上,还可以考虑地理上的就近接入。
同时也需要避免一些单点问题,比如我们前面的Nginx是一个典型的单点。我们可以部署两台Nginx,以主备的形式存在,备 Nginx 会通过 keepalived 机制(发送心跳包) 来及时感知到主 Nginx 的存活,发现宕机自己就顶上充当主 Nginx 的角色。
瓶颈分析:socket连接池
看起来这样的架构确实不错,但要注意的是 Nginx 是七层(即应用层)负载均衡器 ,这意味着如果它要转发流量首先得和 client 建立一个 TCP 连接,并且转发的时候也要与转发到的上游 server 建立一个 TCP 连接,而我们知道建立 TCP 连接其实是需要耗费内存(TCP Socket,接收/发送缓存区等需要占用内存)的,客户端和上游服务器要发送数据都需要先发送暂存到到 Nginx 再经由另一端的 TCP 连接传给对方。
因此在 Nginx 的负载能力受限于机器I/O,CPU内存等一系列配置,一旦连接很多(比如达到百万)的话,Nginx 抗负载能力就会急剧下降。
通过对问题的分析我们可以看出,Nginx的强大之处在于非常丰富的整合,自己做到的了反向代理、负载均衡、提供了缓存静态资源的shared dict、lua脚本等。但是在高流量的压力下,这种丰富的整合反而成为了软肋,我们希望有一个功能更为单一,性能更强大的选择。
四层负载均衡LVS
我们希望有一个类似Nginx的路由器,但是只负责转发包,不需要真正的建立连接,这样就不需要维护额外的TCP连接。
LVS在接收到第一个来自客户端的SYN 请求时,即通过负载均衡算法选择一个最佳的服务器,并对报文中目标IP地址进行修改(改为后端服务器 IP ),直接转发给该服务器。TCP 的连接建立,即三次握手是客户端和服务器直接建立的,负载均衡设备只是起到一个类似路由器的转发动作。在某些部署情况下,为保证服务器回包可以正确返回给负载均衡设备,在转发报文的同时可能还会对报文原来的源地址进行修改。
我们在 Nginx 上再加了一层 LVS,以让它来承接我们的所有流量,当然为了保证 LVS 的可用性,我们也采用主备的方式部署 LVS,另外采用这种架构如果 Nginx 容量不够我们可以很方便地进行水平扩容,于是我们的架构改进如下:
LVS的工作原理 之 NAT,网络地址转换
我们可以想象一个网络数据包,由源IP,目的IP,源端口,目的端口组成了TCP四元组,可以唯一的确认一条链接。
首先调度器(LB)接收到客户的请求数据包时(请求的目的IP为VIP、对外公网IP),根据调度算法决定将请求发送给哪个后端的真实服务器(RS)。然后调度就把客户端发送的请求数据包的目标IP地址及端口改成后端真实服务器的IP地址(RIP),这样真实服务器(RS)就能够接收到客户的请求数据包了。
真实服务器响应完请求后,查看默认路由(NAT模式下我们需要把RS的默认路由设置为LB服务器。)把响应后的数据包发送给LB,LB再接收到响应包后,把包的**源地址改成对外地址(VIP)**然后发送回给客户端。
LVS 只是起到了修改 IP 地址并且转发数据包的功能而已,由于它在数据包的进出过程中都修改了 IP 地址,我们称这模式为 NAT(Network Address Translation,网络地址转换) 模式,可以看到这种工作模式下,网络请求包和网络响应包都要经过 LVS。
- 缺点:性能问题。因为所有数据包的进出都要经过它,这让它成为了很大的瓶颈,随着 RS 水平扩展数量越来越多, LVS 迟早要挂掉。
LVS的工作原理 之 DR(Direct Router,直接路由)、
这个方案的核心思路在于:
首先 LVS 还是要承载所有的请求流量(接收所有数据包),然后再根据负载均衡算法转发给 RS
RS 处理完后是不经过 LVS,直接将数据包转发给路由器再发给客户端的,意味着 RS 必须要有与 LVS 同样的 VIP(四元组不能变),另外由以上拓扑图可知,它们也必须在同一个子网里(严格地说,应该是同一个 vlan,因为是通过交换机通信的),这就意味着 LVS 和 RS 都必须要有两个 IP,一个 VIP,一个子网 IP
为了实现这两种网卡,需要使用到物理网卡eth和虚拟网卡lo。标记红色的IP地址为VIP。
arp_ignore = 1
首先我们知道 LVS 和 RS 都位于同一个子网,子网一般称为以太网,主要用 mac 地址来通信,位于 ISO 模型的二层,一开始内网的机器互相不知道彼此的 mac 地址,需要通过 arp 机制来根据 IP 获取其对应的 mac,获取之后首先会在本地的 arp 表记录此 IP 对应的 mac(下次就直接在本地缓存查找 mac),然后会在包头上附上 IP 对应的 mac,再将包传输出去,交换机就会找到对应的机器了。
所以当客户端请求 VIP 后,请求到达了上图中的路由器,路由器要转发给此 IP 对应的机器,于是它首先发起了一个 arp 请求希望拿到 VIP 对应的 mac 地址。
由于请求都要经过 LVS,所以只让 LVS 响应 arp,抑制住另外两台 RS 对 VIP 的 arp 响应即可,不过请求到达 LVS 后,LVS 还要将包转发给 RS(假设为 RS2 吧),此时也要用到 arp 来获取 RS 的 mac 地址,但是注意从 LVS 发起的 arp 请求目的 IP 变成了 RS2 的内网 IP:115.205.4.217(绑定在物理网卡 eth0 上)。
综上所述, RS 不能响应目的 IP 为虚拟网卡绑定的 VIP 的 arp 请求,但能响应目的 IP 为物理网卡绑定的 IP 的 arp 请求,这就是为什么 RS 需要把 VIP 绑定在虚拟网卡上,而把内网 IP 绑定在物理网卡上的真实原因,就是为了 arp 响应的需要。
通过对RS配置arp_ignore = 1
,实现只响应目的 IP 为接收网卡(即物理网卡)上的 IP 的 arp 请求(会忽略目的 IP 为虚拟网卡 上 VIP 的 arp 请求)。并且需要让数据包使用 lo 接口发送
在使用了以上设置以后,所有的请求通过APR解析都会达到LVS上,然后LVS将数据包发送给RS2;RS2在处理完以后将数据包通过lo网口发出。
arp_announce=2
在RS将数据包发送给网关的过程中,同样需要一次ARP寻找到对应的网关的mac地址。而在ARP协议中需要添加上自己的IP和mac地址的,这里对于RS并不能正常的填写上自己发送请的lo地址(这个RS的lo地址和LVS的eth地址是一眼给的),否则网卡会更新ARP缓存导致之后的请求无法被正确的路由到LVS上。所以 RS2 要发 arp 获取网关的 mac 时使用的源 IP 应该为其物理网卡(eth0)对应的 IP(即 115.205.4.217)。
在DR模式下,需要配置arp_announce=2
表示的是忽略 IP 数据包的源 IP 地址,选择该发送网卡上最合适的本地地址作为 arp 请求的源 IP 地址
解决NAT模式下的单点问题FullNAT
NAT 的基础上又衍生出了 FullNAT,FullNAT 其实就是为了公有云而生的。
NAT 模式下,LVS 只将数据包的目标 IP 改成了 RS 的 IP,而在 FullNAT 模式下,LVS 还会将源 IP 地址也改为 LVS 的内网 IP(修改 IP 主要由 LVS 的内核模块 ip_vs 来操作),注意上图 LVS 内网 IP 和 RS 的 IP 是可以在不同网段下的,通常在公有云平台上,它们是部署在 intranet 即企业内网中的,这样的话 LVS 就可以跨网段和 RS 通信了,也避免了 LVS 的单点瓶颈,多台 LVS 都可以将请求转发给 RS