路由的负载均衡最好采用网段划分的方式。这样不会破坏网络层的路由行为,因为主机或者路由器是通过路由表查找到的“路由”转发每一个数据包的,除非做到路由表的负载均衡,否则特定的数据包只会选择唯一的一条路由。在linux内核中,并没有实现路由表的负载均衡,我曾经提交过一个补丁,然而不了了之,具体来讲就是将首次命中的路由和与之对应的负载均衡路由一起载入路由cache,然后每次查找路由时简单的通过rr算法轮转在这两条路由之间发送数据包。事后,我觉得这样不妥,并不是技术不优美,而是这种行为完全可以通过“配置”来解决的,在IT领域,能通过配置来解决的问题就不要修改源代码,因为配置的是策略,而修改源代码将影响机制,从而直接影响所有的配置 .具体如何配置呢?那就是网段划分。

     为何不使用修改协议栈的方式实现路由的负载均衡还有另一个重要的原因,那就是路由引导的是网络层的数据报,而网络层是端到端的,并不是一个广播网络,这就和链路层有了本质的区别。 我们知道,以太网是一个广播网络,虽然交换机使能了地址学习机制隔离了广播域,它本质上还是广播网络,因此交换机的多端口聚合(802.3ad或者静态聚合)实现的负载均衡,并不影响将以太帧最终引导到目的地。广播网络意味着走哪条路都能到目的地,只要让地址学习机制支持即可,这完全可以通过链路层的pdu通报来实现(我们完全可以将arp视为一种特殊的链路层通报),但是单播网络就不一样了,如果一个ip数据包走不同的路径,那就需要这条路径上每一个中间站都事先存在到达目的地的路由,然而具体走哪条路是很难确定的,这就是势必导致了整个ip网络最终成了一个全连接的网络,成了一个类似广播的网络,瞬间耗尽了网络资源。这个问题在以太网上并不存在,原因如下:

1.以太网是一个局域网标准,节点数量有限,广播不成问题;
2.广播协议(CSMA/CD)实现起来很简单,成本很低;
3.总线型的以太网和hub连接的以太网都是广播的,能保证以太帧到达目的地;
4.交换机虽然隔离了广播域,然而它也是通过学习机制完成这项功能的,而不存在或者很少存在人为配置因素,在mac地址信息混乱或者没有地址-端口对应信息的时候,还是依靠广播来实现转发的;
5.只要实现地址信息的pdu通报机制或者类似的“大不了广播”的机制,以太帧完全可以到达目的地。
故而端口聚合就很简单,而路由的负载均衡如果想简单实现就只能靠配置来完成了...

     我有一个192.168.0.0/16的网络,有300多台主机需要接入外网,我不希望所有的数据包从一个路由器与外界交流,我需要两台路由器分担流量,很显然我不能修改linux内核的源代码,原因是可能主机们或者路由器运行的根本就不是linux系统,还有就是这个需求可以通过配置来解决。步骤如下:

1.首先我将一个网络划分为两个网络,分别为192.168.1.0/24和192.168.2.0/24(由于只有300多台机器,因此两个24位掩码的网络足够了,虽然不便于以后扩容);
2.192.168.1.0的网络使用路由器1,而192.168.2.0的网路使用路由器2;
3.连上线即可。

这样就很容易的实现了负载均衡。

     然而,很多时候,负载均衡和热备是分不开的,如果能在上述负载均衡的基础上实现热备,那就太OK了。事实上,VRRP完全可以实现这个功能,具体的VRRP的协议本文不赘述,详情可以参见规范,本质上VRRP实现的就是一个虚拟的路由器,在该虚拟路由器中存在一组真实的路由器,该组真实路由器中只有一个路由器为当前工作路由器,负载进行数据包转发,其它路由器都处于standby状态,一组路由器通过组播来传达vrrp的管理信息并且通过回复时间确定是否应该重新选举一个新的工作路由器了...很显然,一个网络将要和所有的虚拟路由器组中的每一个路由器相连接。以下是上述规划加上热备份模式的一个图示:


在该图中,很显然,网络0被划分为了网络1和网络2,R1和R2分别为网路1和网络2的主路由器和对方的备份路由器,如果某时R1失效,此时虚拟组1将选举R2为工作路由器,那么网络1和网络2所有的数据包都将通过R2转发,相反的情况也一样。

     在linux上,有一个keepalived的项目,完全实现了vrrp的规范,而且很好用,对于上述的实例,给出以下的配置文件:

vrrp_instance XXXXXX {
     interface eth0
     virtual_router_id 1
     priority 100
     ...
     notify_backup "vrrp机制使之称为backup时需要执行的程序"
     notify_master "vrrp机制使之称为master时需要执行的程序"
     notify_fault  "..."
     
     authentication {
         auth_type PASS
         auth_pass 12345
     }
     
     virtual_ipaddress {
         192.168.1.1/32
     }
 }

将上述配置文件部署在R1和R2上即可(两台路由器均运行linux系统,或者起码要支持keepalived),然后启用keepalived即可。在我们的master节点R1上,我们用ip addr ls看一下地址信息,我们发现R1的eth0上的地址多了一个192.168.1.1/32,而R2的eth0上并没有这个地址,如果我们把R1电源拔掉,会发现R2的eth0上有了192.168.1.1/32这个地址。

     理解了网络层的负载均衡和热备模式之后,其实我们遗漏了另外一种也非常常用的负载均衡模式,那就是集群,然而这个和上述的基于配置负载均衡有所不同,基于配置的负载均衡大多数为了解决路由器瓶颈的问题,而集群负载均衡则大多数是为了解决服务器瓶颈的问题,对于linux,lvs是一种常见的方式,通过netfilter完美的实现了负载均衡,lvs的实现需要一台机器作为负载均衡器运行,然后显式将请求根据不同算法负载到后面不同的服务器上。常见的模式有DR模式和NAT模式,DR模式依赖好几个在真实服务器的lo网口上的不响应arp的虚拟ip地址实现的,负载均衡器拥有一个可以响应arp的相同的地址,然后拿到数据包后,仅仅是修改一下目的mac地址和源mac地址来将数据包转向真实的服务器,实际上,DR模式就是负载均衡器替真实服务器响应它们本应该响应的arp请求,然后截获数据包,最终可以通过可配置的算法将数据包转发,等到真实服务器回复客户端时,由于并没有修改ip层的任何信息,就可以直接回复了,不再经过负载均衡器。而NAT模式与此不同,它修改了ip层的信息,然而此处不赘述(需要注意网络层和链路层的冲突,也就是同一个口进入和同一个口出来的forward数据包被发现后要发送icmp重定向报文的...)。不管怎样,这种lvs的负载均衡以及热备模式和上述的vrrp的方式本质不同,为了解决不同的问题,可是keepalived集成了这一切,它又支持了vrrp,同时又可以为lvs提供支持。

附:说点别的

时到今日,我们的网络协议已经再也不像N年前的那种分层模型啦!VLAN的引入使得以太网不再广播,VLAN引入了第三层的一些机制,甚至在支持VLan的交换机上也不得不重新完善,为了不影响原有的IP协议,不得不引入一个802.1q的vlan协议,看似vlan是一个lan协议,实际上从层次上看,它已经是三层协议了。802.1q在以太帧头中引入了一个tag字段,支持vlan的交换机收到带有tag字段的以太帧可以通过这个tag字段选择该往哪个端口转发,并且决定是否要去掉tag,或者收到没有tag的以太帧根据端口的配置信息决定是否转发该帧以及是否打上tag...

     我们说以太网是一个广播网络,这个以太网是指物理层而言的,对于链路层来讲,它只是部分的广播网络,因为有了交换机的存在可以识别mac地址了。因此不要拘泥于教科书上所学的理论知识,实际应用中很少有完全根据理论来的。比如有些人认为主板上的总线设计和以太网不相干,实际上现在已经有将主板上的pci设备延伸到以太网上的了,这是怎么实现的呢?很难吗?其实这一点也不奇怪,PCIe总线是串行的,以太网也是串行的,线缆数量也基本兼容,剩余的就是在芯片内部实现一些协议逻辑就可以了。最简单的例子,两台机器的两块PCIe的网卡是通过一根双绞线连接在一起的,我们把线缩短,然后塞进一个机箱并且焊接到一个主板上,那么它们就是一个主板上的芯片了,两块不同机器的网卡和同一个主板上的北桥和南桥其实没有任何区别。