图4、DR模式的转发示意图,响应流量不会经过负载均衡器
图5、Tunnel模式转发示意图,和DR一样,响应流量不会经过负载均衡器
既然都不是很理想,那还有没有其他方案?
我们期望的是后端服务器不感知前端有负载均衡存在,服务可以放在任何地方,且不做任何过多配置。既然做不到原封不动传送客户端的数据包,那就代理客户端的请求。也就是对客户端发出来的数据包的源IP和目的IP分别做一次IP地址转换。
详细来说,负载均衡器收到客户端A的请求后,自己以客户端的角色向后端服务器发起相同的请求,请求所带的payload来自于客户端A的请求payload,这样保证了请求数据一致。
此时,负载均衡器相当于发起了一个新连接(不同于客户端A发起的连接),新建的连接将会使用负载均衡器的IP地址(称之为LocalIP)作为源地址直接和后端服务器IP通信。
当后端返回数据给负载均衡器时,再用客户端A的连接将数据返回给客户端A。整个过程涉及了两个连接,对应两次IP地址转换,
- 请求时刻:CIP→VIP, 转换成了LocalIP→后端服务器IP,
- 数据返回时刻:后端服务器IP→ LocalIP, 转换成了VIP→ CIP。
客户端该过程完全基于IP地址转发数据,而不是MAC地址,只要网络可达,数据就可以顺畅在客户端和后端之间传输。
图6、FULLNAT转发模式
上述方案称之为FULLNAT转发模式,也就是实现了两次地址转换。显然,这种方式足够简单,不需要后端服务器做任何调整,也不限制后端部署在何处。但也有个明显问题,那就是后端看到的请求全部来自于负载均衡器,真实的客户端IP信息完全看不到了,这就相当于屏蔽了真实客户端的情况。幸运的是数据中心中绝大多数应用并不需要明确知道真实客户端的IP地址信息,即便是需要真实客户端IP信息,负载均衡器也可以将这部分信息加载在TCP/UDP协议数据中,通过按需安装一定的插件获取。
综合上述几种方案来说,FULLNAT模式对我们的业务场景(非虚拟化环境)适合度是最佳的。
既然我们打算使用FULLNAT模式, 则④的解决就没有任何困难了,因为负载均衡器间接充当了客户端角色,后端的数据必然要全部转发给负载均衡器,由负载均衡器再发给真正的客户端即可。
表1:各种模式之间的优劣势分析
3.2 如何剔除异常后端服务器
负载均衡一般提供对后端的健康检查探测机制,以便能快速剔除异常的后端IP地址。理想情况下,基于语义的探测是最好的,能更有效的检测到后端是不是异常。但这种方式一来会带来大量的资源消耗,特别是后端庞大的情况下;这还不是特别严重的,最严重的是后端可能运行了HTTP、DNS、Mysql、Redis等等很多种协议,管理配置非常多样化,太复杂。两个问题点加起来导致健康检查太过于笨重,会大量占用本来用于转发数据的资源,管理成本过高。所以一个简单有效的方式是必要的,既然作为4层负载,我们都不去识别上游业务是什么,只关注TCP或者UDP端口是不是可达即可。识别上层业务是什么的工作交给Nginx这类型的7层负载去做。
所以只要定期检查所有后端的TCP/UDP端口是否开放,开放就认为后端服务是正常的,没有开放就认为是服务异常,后端列表中剔除,后续将不再往该异常后端转发任何数据。
那么如何探测?既然只是判断TCP端口是不是正常开放,我们只要尝试建立一个TCP连接即可,如果能建立成功,则表明端口是正常开放的。但是对于UDP来说,因为UDP是无连接的,没有新建连接的这种说法,但同样能够靠直接发送数据来达到探测的目的。即,直接发送一份数据,假设UDP端口是正常开放的,所以后端通常不会做响应。如果端口是没有开放的,操作系统会返回一个icmp port unreachable状态,可以借此判断端口不可达。但有个问题,如果UDP探测数据包里带了payload,可能会导致后端认为是业务数据导致收取到无关数据。
3.3 如何实现负载均衡器的故障隔离
前面我们说过,4层负载均衡依赖于网络层衡来确保4层负载均衡器之间的负载是平衡的,那么负载均衡器的故障隔离就是依赖于网络层来做。
具体如何做?
实际上,我们是依赖于路由的方式来做网络层的负载均衡,负载均衡集群中每台服务器都把相同的VIP地址通过路由协议BGP通告给网络设备(交换机或者路由器),网络设备收到相同的一个VIP来自不同的服务器,就会形成一个等价路由(ECMP),言外之意就是形成负载均衡。所以如果我们想要隔离掉某台负载均衡器的话,只要在该服务器上把通过BGP路由协议发布的VIP撤销即可,这样,上游交换机就认为该服务器已经被隔离,从而不再转发数据到对应设备上。