LVS中Netfiler的实现

负载调度器

在这一章中,我们走近看一下在Director接收到一个到真实服务器(一个集群节点)的数据包时都发生了什么,这将引导我们深入讨论LVS持续连接 -- LVS用于分配相同客户端计算机到某个特定的真实服务器的技术,我们还将描述如何使用iptables程序选择数据包,通过LVS使用一个叫做数据包标记的技术进行处理。

14.1 LVS和Netfilter

在图14-1中的五个Netfilter钩子在第2章中已经介绍过了,叠加在这些钩子之上的是一系列代表数据包路过内核的小黑盒,内核将它接收到的每个数据包放到一个叫做套接字缓存或sk_buff的内存结构中,因此图14-1中的每个小黑盒子代表一个内核内的sk_buff,但是在这里的讨论中,我们会继续叫它们为数据包,图中的灰色箭头代表所有入站LVS数据包(数据包来自客户端计算机)通过Director抵达真实服务器(集群节点)的路径。






让我们从查看第2章中介绍的五个Netfilter钩子开始,图14-1显示了内核中的这五个钩子。



注意图14-1中入站LVS数据包只命中了三个Netfilter钩子:PRE_ROUTING,LOCAL_IN和POST_ROUTING[1],在本章后面,当它们关系到你在Director上控制数据包的命运的能力时,我们会讨论这些Netfilter钩子的意义,暂时我们想将注意力集中在入站数据包通过Director抵达集群节点的路径,如图14-1中的两个灰色箭头。

图14-1中的第一个灰色箭头表示来自PRE_ROUTING钩子的数据包到达LOCAL_IN钩子,Director接收到的每个到集群服务的数据包,不管你选择哪种转发方法[2],都必须通过PRE_ROUTING钩子抵达LOCAL_IN钩子,即所有Director内核内的数据包路由规则都必须将数据包发送到LOCAL_IN钩子,在你建立Director时这很容易实现,因为所有抵达Director的集群服务数据包都有一个虚拟ip地址(VIP)作为目标地址,回忆我们前面章节对LVS转发方法的讨论,VIP实际上是属于Director的一个ip别名或从属ip地址,因为VIP是Director所有的一个本地ip地址,Director内核中的路由表将总是在内部传递数据包,因此Director接收到的目标地址为VIP的数据包总会命中LOCAL_IN钩子。

图14-1中的第二个灰色箭头表示入站数据包被内核认可为是一个请求虚拟服务的数据包后的路径走向,当数据包命中LOCAL_IN钩子后,运行在内核内的LVS软件知道数据包是一个请求集群服务(在LVS术语中叫做虚拟服务)的数据包,因为数据包的目的是到VIP地址,你在建立你自己的集群时,使用ipvsadm工具添加虚拟服务VIP地址到内核中,以便LVS能够识别发送到VIP LOCAL_IN钩子上的入站数据包。如果你没有添加VIP到LVS虚拟服务表(也叫做IPVS表),到VIP的数据包可能会被传递到Director本地运行的守护进程上。但是因为Director知道VIP地址[3],它可以检查每个命中LOCAL_IN钩子的数据包是否是请求集群服务的数据包,在数据包到达Director本地运行的守护进程前,Director还可以改变数据包的命运,LVS通过告诉内核它不应该将到VIP地址的数据包传递给本地的守护进程,而应该传递给集群节点来实现,这样数据包就会发送到POST_ROUTING钩子,如图14-1中的第二个箭头所指,数据包从连接D/RIP网络的网卡发送出去[4]。

通过Netfilter钩子和你用ipvsadm工具创建的IPVS表使得LVS Director改变数据包命运的能力成为可能,这个能力使得Director可以分发入站集群服务请求给多个真实服务器(集群节点),但是这样做后,Director必须跟踪每个连接,以便客户端计算机每次都能与同一个真实服务器对话[5],Director通过在内存中维护一张连接跟踪表来实现。

注意:正如我们将在本章后面会看到的,在确定所有服务请求(所有新的连接)到相同的集群节点和确定所有连接已经建立的数据包返回给相同的集群节点之间是有区别的,前面的叫做持续连接,后面的是通过连接跟踪控制的。


LVS中Netfiler的实现

  

2、IPVS软件结构与实现

  LVS软件的核心是运行在LB上的IPVS,它使用基于IP层的负载均衡方法。IPVS的总体结构主要由IP包处理负载均衡算法系统配置与管理三个模块及虚拟服务器与真实服务器链表组成。

 


 

3、LVS中Netfiler的实现

  利用Netfilter,LVS处理数据报从左边进入系统,进行IP校验以后,数据报经过第一个钩子函数NF_IP_PRE_ROUTING [HOOK1]进行处理;然后进行路由选择,决定该数据报是需要转发还是发给本机;若该数据报是发被本机的,则该数据经过钩子函数 NF_IP_LOCAL_IN[HOOK2]处理后传递给上层协议;若该数据报应该被转发,则它被NF_IP_FORWARD[HOOK3]处理;经过转发的数据报经过最后一个钩子函数NF_IP_POST_ROUTING[HOOK4]处理以后,再传输到网络上。本地产生的数据经过钩子函数NF_IP_LOCAL_OUT[HOOK5]处理后,进行路由选择处理,然后经过NF_IP_POST_ROUTING[HOOK4]处理后发送到网络上。

  当启动IPVS加载ip_vs模块时,模块的初始化函数ip_vs_init( )注册了NF_IP_LOCAL_IN[HOOK2]、NF_IP_FORWARD[HOOK3]、NF_IP_POST_ROUTING[HOOK4] 钩子函数用于处理进出的数据报。

 

 

 3.1 NF_IP_LOCAL_IN处理过程

  用户向虚拟服务器发起请求,数据报经过NF_IP_LOCAL_IN[HOOK2],进入ip_vs_in( )进行处理。如果传入的是icmp数据报,则调用ip_vs_in_icmp( );否则继续判断是否为tcp/udp数据报,如果不是tcp/udp数据报,则函数返回NF_ACCEPT(让内核继续处理该数据报);余下情况便是处理tcp/udp数据报。首先,调用ip_vs_header_check( )检查报头,如果异常,则函数返回NF_DROP(丢弃该数据报)。接着,调用ip_vs_conn_in_get( )去ip_vs_conn_tab表中查找是否存在这样的连接:它的客户机和虚拟服务器的ip地址和端口号以及协议类型均与数据报中的相应信息一致。如果不存在相应连接,则意味着连接尚未建立,此时如果数据报为tcp的sync报文或udp数据报则查找相应的虚拟服务器;如果相应虚拟服务器存在但是已经满负荷,则返回NF_DROP;如果相应虚拟服务器存在并且未满负荷,那么调用ip_vs_schedule( )调度一个RS并创建一个新的连接,如果调度失败则调用ip_vs_leave( )继续传递或者丢弃数据报。如果存在相应连接,首先判断连接上的RS是否可用,如果不可用则处理相关信息后返回NF_DROP。找到已存在的连接或建立新的连接后,修改系统记录的相关信息如传入的数据报的个数等。如果这个连接在创建时绑定了特定的数据报传输函数,调用这个函数传输数据报,否则返回 NF_ACCEPT。

 

  ip_vs_in()调用的ip_vs_in_icmp( )处理icmp报文。函数开始时检查数据报的长度,如果异常则返回NF_DROP。函数只处理由tcp/udp报文传送错误引起的目的不可达、源端被关闭或超时的icmp报文,其他情况则让内核处理。针对上述三类报文,首先检查检验和。如果检验和错误,直接返回NF_DROP;否则,分析返回的icmp差错信息,查找相应的连接是否存在。如果连接不存在,返回NF_ACCEPT;如果连接存在,根据连接信息,依次修改差错信息包头的ip地址与端口号及 ICMP数据报包头的ip地址,并重新计算和修改各个包头中的检验和,之后查找路由调用ip_send( )发送修改过的数据报,并返回NF_STOLEN(退出数据报的处理过程)。

 

  ip_vs_in()调用的函数ip_vs_schedule( )为虚拟服务器调度可用的RS并建立相应连接。它将根据虚拟服务器绑定的调度算法分配一个RS,如果成功,则调用ip_vs_conn_new( )建立连接。ip_vs_conn_new( )将进行一系列初始化操作:设置连接的协议、ip地址、端口号、协议超时信息,绑定application helper、RS和数据报传输函数,最后调用ip_vs_conn_hash( )将这个连接插入哈希表ip_vs_conn_tab中。一个连接绑定的数据报传输函数,依据IPVS工作方式可分为ip_vs_nat_xmit( )、ip_vs_tunnel_xmit( )、ip_vs_dr_xmit( )。例如ip_vs_nat_xmit( )的主要操作是:修改报文的目的地址和目的端口为RS信息,重新计算并设置检验和,调用ip_send( )发送修改后的数据报。

 

 

 3.2 NF_IP_FORWARD处理过程

  数据报进入NF_IP_FORWARD后,将进入ip_vs_out( )进行处理。这个函数只在NAT方式下被调用。它首先判断数据报类型,如果为icmp数据报则直接调用ip_vs_out_icmp( );其次判断是否为tcp/udp数据报,如果不是这二者则返回NF_ACCEPT。余下就是tcp/udp数据报的处理。首先,调用 ip_vs_header_check( )检查报头,如果异常则返回NF_DROP。其次,调用ip_vs_conn_out_get( )判断是否存在相应的连接。若不存在相应连接:调用ip_vs_lookup_real_service( )去哈希表中查找发送数据报的RS是否仍然存在,如果RS存在且报文是tcp非复位报文或udp 报文,则调用icmp_send( )给RS发送目的不可达icmp报文并返回NF_STOLEN;其余情况下均返回NF_ACCEPT。若存在相应连接:检查数据报的检验和,如果错误则返回NF_DROP,如果正确,修改数据报,将源地址修改为虚拟服务器ip地址,源端口修改为虚拟服务器端口号,重新计算并设置检验和,并返回 NF_ACCEPT。

  ip_vs_out_icmp( )的流程与ip_vs_in_icmp( )类似,只是修改数据报时有所区别:ip报头的源地址和差错信息中udp或tcp报头的目的地址均修改为虚拟服务器地址,差错信息中udp或tcp报头的目的端口号修改为虚拟服务器的端口号。

  3.3 NF_IP_POST_ROUTING处理过程

  NF_IP_POST_ROUTING钩子函数只在NAT方式下使用。数据报进入NF_IP_POST_ROUTING后,由ip_vs_post_routing( )进行处理。它首先判断数据报是否经过IPVS,如果未经过则返回NF_ACCEPT;否则立刻传输数据报,函数返回NF_STOLEN,防止数据报被 iptable的规则修改。

 

 

 4、LVS系统配置与管理

  IPVS模块初始化时注册了setsockopt/getsockopt( ),ipvsadm命令调用这两个函数向IPVS内核模块传递ip_vs_rule_user结构的系统配置数据,完成系统的配置,实现虚拟服务器和RS 地址的添加、修改、删除操作。系统通过这些操作完成对虚拟服务器和RS链表的管理。

  虚拟服务器的添加操作由ip_vs_add_service( )完成,该函数根据哈希算法向虚拟服务器哈希表添加一个新的节点,查找用户设定的调度算法并将此算法绑定到该节点;虚拟服务器的修改由 ip_vs_edit_service( )完成,此函数修改指定服务器的调度算法;虚拟服务器的删除由ip_vs_del_service( )完成,在删除一个虚拟服务器之前,必须先删除此虚拟服务器所带的所有RS,并解除虚拟服务器所绑定的调度算法。

  与之类似,RS的添加、修改、删除操作分别由ip_vs_add_dest( )、ip_vs_edit_dest( )和ip_vs_edit_server( )完成。

 

 

5、负载均衡调度算法

  前面已经提到,用户在添加一个虚拟服务时要绑定调度算法,这由ip_vs_bind_scheduler( )完成,调度算法的查找则由ip_vs_scheduler_get( )完成。ip_vs_scheduler_get( )根据调度算法的名字,调用ip_vs_sched_getbyname( )从调度算法队列中查找此调度算法,如果没找到则加载相应调度算法模块再查找,最后返回查找结果。

 

目前系统有八种负载均衡调度算法,具体如下:

rr:轮循调度(Round-Robin) 它将请求依次分配不同的RS,也就是在RS中均摊请求。这种算法简单,但是只适合于RS处理性能相差不大的情况。
wrr:加权轮循调度(Weighted Round-Robin) 它将依据不同RS的权值分配任务。权值较高的RS将优先获得任务,并且分配到的连接数将比权值较低的RS更多。相同权值的RS得到相同数目的连接数。
dh:目的地址哈希调度 (Destination Hashing) 以目的地址为关键字查找一个静态hash表来获得需要的RS。
sh:源地址哈希调度(Source Hashing) 以源地址为关键字查找一个静态hash表来获得需要的RS。
Lc:最小连接数调度(Least-Connection) IPVS表存储了所有的活动的连接。把新的连接请求发送到当前连接数最小的RS。
Wlc:加权最小连接数调度(Weighted Least-Connection) 假设各台RS的权值依次为Wi(I = 1..n),当前的TCP连接数依次为Ti(I=1..n),依次选取Ti/Wi为最小的RS作为下一个分配的RS。
Lblc:基于地址的最小连接数调度(Locality-Based Least-Connection) 将来自同一目的地址的请求分配给同一台RS如果这台服务器尚未满负荷,否则分配给连接数最小的RS,并以它为下一次分配的首先考虑。
Lblcr:基于地址的带重复最小连接数调度(Locality-Based Least-Connection with Replication) 对于某一目的地址,对应有一个RS子集。对此地址的请求,为它分配子集中连接数最小的RS;如果子集中所有的服务器均已满负荷,则从集群中选择一个连接数较小的服务器,将它加入到此子集并分配连接;若一定时间内,这个子集未被做任何修改,则将子集中负载最大的节点从子集删除。

你可能感兴趣的:(内核模块开发(笔记))