lvs实现分析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了lvs实现分析相关的知识,希望对你有一定的参考价值。

1.1   LVS实现分析

1.1.1 LVS流程概述

Netfiler的报文流程概述

数据报从左边进入系统,进行IP校验以后,数据报经过第一个钩子函数NF_IP_PRE_ROUTING进行处理;然后进行路由选择,决定该数据报是需要转发还是发给本机;

若该数据报是发被本机的,则该数据经过钩子函数 NF_IP_LOCAL_IN处理后传递给上层协议;

若该数据报应该被转发,则它被NF_IP_FORWARD处理;经过转发的数据报经过最后一个钩子函数NF_IP_POST_ROUTING处理以后,再传输到网络上。

本地产生的数据经过钩子函数 NF_IP_LOCAL_OUT处理后,进行路由选择处理,然后经过NF_IP_POST_ROUTING处理后发送到网络上。

 

1.1.1.1IPVS初始化注册

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

NF_IP_LOCAL_IN处理过程

1、NF_IP_LOCAL_IN处理过程

用户向虚拟服务器发起请求,数据报经过NF_IP_LOCAL_IN[HOOK2],进入ip_vs_in( )进行处理。

(1)如果传入的是icmp数据报,则调用ip_vs_in_icmp( )。

(2)如果不是tcp/udp数据报,则函数返回NF_ACCEPT(让内核继续处理该数据报)。

(3)如果是tcp/udp数据报,则进行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。

2、ICMP处理细节

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(退出数据报的处理过程)。

3、调度算法及连接建立细节

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( )发送修改后的数据报。

1.1.1.2NF_IP_FORWARD处理过程【NAT方式】

1、NF_IP_FORWARD处理过程

数据报进入NF_IP_FORWARD后,将进入ip_vs_out( )进行处理。这个函数只在NAT方式下被调用。

(1)它首先判断数据报类型,如果为icmp数据报则直接调用ip_vs_out_icmp( )。

(2)其次判断是否为tcp/udp数据报,如果不是这二者则返回NF_ACCEPT。

(3)余下就是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。

2、ICMP处理细节

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

 

1.1.1.3NF_IP_POST_ROUTING处理过程【NAT方式】

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

 

1.1.1.4LVS系统配置与管理

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( )完成。

1.1.1.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;如果子集中所有的服务器均已满负荷,则从集群中选择一个连接数较小的服务器,将它加入到此子集并分配连接;若一定时间内,这个子集未被做任何修改,则将子集中负载最大的节点从子集删除。

 

1.1.2 LVS具体实现

1.1.2.1 内核态几个重要的数据结构

1.        【数据结构】协议

这个结构用来描述IPVS支持的IP协议。IPVS的IP层协议支持TCP, UDP, AH和ESP这4种IP层协议

struct ip_vs_protocol {

 struct ip_vs_protocol *next;//链表中的下一项

 char   *name;//协议名称, "TCP", "UDP"...

 __u16   protocol;//协议值: 6, 17,...

 int   dont_defrag;//不进行分配

 atomic_t  appcnt; /*协议应用计数器,也据是该协议的中多连接协议的数量*/

 int   *timeout_table;/*协议各状态的超时数组*/

 void (*init)(struct ip_vs_protocol *pp);//协议初始化

 void (*exit)(struct ip_vs_protocol *pp);//协议释放

 int (*conn_schedule)(struct sk_buff *skb, struct ip_vs_protocol *pp,

        int *verdict, struct ip_vs_conn **cpp);//协议调度

 struct ip_vs_conn *  (*conn_in_get)(const struct sk_buff *skb,

         struct ip_vs_protocol *pp, const struct iphdr *iph,

         unsigned int proto_off, int inverse);//查找in方向的IPVS连接

 struct ip_vs_conn * (*conn_out_get)(const struct sk_buff *skb,

   struct ip_vs_protocol *pp, const struct iphdr *iph,

   unsigned int proto_off, int inverse);//查找out方向的IPVS连接

 int (*snat_handler)(struct sk_buff **pskb,

       struct ip_vs_protocol *pp, struct ip_vs_conn *cp);//源NAT操作

 int (*dnat_handler)(struct sk_buff **pskb,

        struct ip_vs_protocol *pp, struct ip_vs_conn *cp);//目的NAT操作

 

 int (*csum_check)(struct sk_buff *skb, struct ip_vs_protocol *pp);//协议校验和

 const char *(*state_name)(int state);//当前协议状态: 如"LISTEN","ESTABLISH"...

 int (*state_transition)(struct ip_vs_conn *cp, int direction,

    const struct sk_buff *skb, struct ip_vs_protocol *pp);//协议状态迁移

 int (*register_app)(struct ip_vs_app *inc);//登记应用

 void (*unregister_app)(struct ip_vs_app *inc);//去除应用登记

 int (*app_conn_bind)(struct ip_vs_conn *cp);

 void (*debug_packet)(struct ip_vs_protocol *pp, const struct sk_buff *skb,

        int offset, const char *msg);//数据包打印

 void (*timeout_change)(struct ip_vs_protocol *pp, int flags);//调整超时

 int (*set_state_timeout)(struct ip_vs_protocol *pp, char *sname, int to);//设置各种状态下的协议超时

};

 

2.        【数据结构】IPVS连接

这个结构用来描述IPVS的连接。IPVS的连接和netfilter定义的连接类似

 

struct ip_vs_conn {

 struct list_head        c_list;        /* hashed list heads */

 

/* Protocol, addresses and port numbers */

 __u32                   caddr;         /* client address客户机地址*/

 __u32                   vaddr;         /* virtual address服务器对外的虚拟地址*/

 __u32                   daddr;         /* destination address服务器实际地址*/

 __u16                   cport;//客户端的端口

 __u16                   vport;//服务器对外虚拟端口

 __u16                   dport;//服务器实际端口

 __u16                   protocol;      /*协议类型:Which protocol (TCP/UDP) */

 

/* counter and timer */

 atomic_t  refcnt; /* reference count连接引用计数*/

 

 struct timer_list timer; /* Expiration timer定时器*/

 volatile unsigned long timeout;/* timeout 超时时间*/

 

/* Flags and state transition */

 spinlock_t              lock;          /*状态转换锁lock for state transition */

 volatile __u16          flags;         /* status flags */

 volatile __u16          state;         /* state info */

 

/* Control members */

 struct ip_vs_conn       *control;  /*主连接, 如FTP Master control connection */

 atomic_t                n_control;     /* Number of controlled ones子连接数*/

 struct ip_vs_dest       *dest;         /* real server真正服务器*/

 atomic_t                in_pkts; /* incoming packet counter进入的数据统计*/

 

//数据包发送

 int (*packet_xmit)(struct sk_buff *skb, struct ip_vs_conn *cp,

      struct ip_vs_protocol *pp);

 

/* Note: we can group the following members into a structure,

    in order to save more space, and the following members are

    only used in VS/NAT anyway */

    struct ip_vs_app        *app;     /* bound ip_vs_app object IPVS应用 */

   void                    *app_data;/* Application private data应用的私有数据*/

    struct ip_vs_seq        in_seq;   /* incoming seq.struct进入数据的序列号*/

    struct ip_vs_seq        out_seq; /* outgoing seq.struct发出数据的序列号*/

};

 

3.        【数据结构】IPVS虚拟服务器

这个结构用来描述IPVS对外的服务器信息。

struct ip_vs_service {

     struct list_head s_list;  /*按普通协议,地址,端口进行HASH的链表*/

     struct list_head f_list;  /*按nfmark进行HASH的链表(感觉没必要) */

     atomic_t  refcnt;  /* reference counter引用计数*/

     atomic_t  usecnt;  /* use counter使用计数*/

      __u16   protocol;/*协议:which protocol (TCP/UDP) */

      __u32   addr;  /*虚拟服务器地址IP address for virtual service */

      __u16   port;  /*虚拟端口port number for the service */

      __u32       fwmark;/skb中的nfmark:firewall mark of the service */

      unsigned  flags;  /* service status flags标志*/

      unsigned  timeout; /* persistent timeout in ticks超时*/

      __u32   netmask; /* grouping granularity网络掩码*/

      struct list_head destinations;/*真实服务器的地址链表*/

      __u32   num_dests;    /* number of servers真实服务器的数量*/

      struct ip_vs_stats  stats;/* statistics for the service服务统计信息*/

      struct ip_vs_app *inc;  /* bind conns to this app inc应用*/

      struct ip_vs_scheduler *scheduler;   /*调度指针 bound scheduler object */

      rwlock_t  sched_lock;   /* lock sched_data */

      void   *sched_data;  /* scheduler application data */

};

 

4.        【数据结构】IPVS真实服务器

 这个结构用来描述具体的真实服务器的信息

struct ip_vs_dest {

 struct list_head n_list;  /* for the dests in the service */

 struct list_head d_list;  /* for table with all the dests */

__u32   addr; /* IP address of the server服务器地址*/

__u16   port; /* port number of the server服务器端口*/

 volatile unsigned flags; /* dest status flags ,目标标志,易变参数*/

atomic_t  conn_flags;/* flags to copy to conn连接标志*/

atomic_t  weight; /* server weight服务器权重*/

atomic_t  refcnt; /* reference counter引用次数*/

struct ip_vs_stats      stats;         /* statistics 统计数*/

 

/* connection counters and thresholds */

atomic_t  activeconns;/* active connections活动的连接*/

atomic_t  inactconns;/* inactive connections 不活动的连接*/

atomic_t  persistconns;/* persistent connections保持的连接*/

 __u32   u_threshold;/* upper threshold连接上限*/

 __u32   l_threshold;/* lower threshold连接下限*/

 

/* for destination cache */

 spinlock_t  dst_lock;/* lock of dst_cache */

 struct dst_entry *dst_cache;/* destination cache entry */

 u32   dst_rtos;/* RT_TOS(tos) for dst */

 

/* for virtual service */

 struct ip_vs_service *svc; /* service it belongs to */

 __u16   protocol;/* which protocol (TCP/UDP) */

 __u32   vaddr; /* virtual IP address */

 __u16   vport; /* virtual port number */

 __u32   vfwmark;/* firewall mark of service */

};

 

5.        【数据结构】IPVS调度器

IPVS 这个结构用来描述IPVS调度算法,目前调度方法包括rr,wrr,lc, wlc, lblc, lblcr, dh, sh等

struct ip_vs_scheduler {

 struct list_head n_list; /* d-linked list head */

 char   *name; /* scheduler name */

 atomic_t  refcnt; /* reference counter */

 struct module  *module;/* THIS_MODULE/NULL */

 int (*init_service)(struct ip_vs_service *svc);/* initializing */

 int (*done_service)(struct ip_vs_service *svc);/* scheduling service finish */

 int (*update_service)(struct ip_vs_service *svc);/* scheduler updating service*/

 struct ip_vs_dest* (*schedule)(struct ip_vs_service *svc,

      const struct sk_buff *skb);/* selecting a server from the given service */

};

 

6.        【数据结构】IPVS多连接应用

IPVS应用是针对多连接协议的, 目前也就只支持FTP。由于ip_vs_app.c是从2.2过来的,

没有管内核是否本身有NAT的情况,所以相当于自身实现了应用协议的NAT处理,包括内容信息的改变,TCP序列号确认号的调整等,而现在这些都由netfilter实现了,IPVS可以不用管这些,只处理连接调度就行了。

IPVS的应用模块化还不是很好,在处理连接端口时,还要判断是否是FTPPORT,也就是说

不支持其他多连接协议的,应该象netfilter一样为每个多连接协议设置一个helper,自动调用,不用在程序里判断端口。

 

1.1.2.2用户态几个重要的数据结构

用户空间信息是ipvsadm程序接收用户输入后传递给内核ipvs的信息,信息都是很直接的,没有各种控制信息。ipvsadm和ipvs的关系相当于iptables和netfilter的关系。

 

1.        【数据结构】用户态虚拟服务器

struct ip_vs_service_user {

 u_int16_t  protocol;/* virtual service addresses */

 u_int32_t  addr; /* virtual ip address */

 u_int16_t  port;

 u_int32_t  fwmark; /* firwall mark of service */

 char   sched_name[IP_VS_SCHEDNAME_MAXLEN];/* virtual service options */

 unsigned  flags; /* virtual service flags */

 unsigned  timeout;/* persistent timeout in sec */

 u_int32_t  netmask;/* persistent netmask */

};

 

2.        【数据结构】用户态真实服务器

 struct ip_vs_dest_user {

 u_int32_t  addr;/* destination server address */

 u_int16_t  port;/* real server options */

 unsigned  conn_flags;/* connection flags */

 int   weight; /* destination weight */

/* thresholds for active connections */

 u_int32_t  u_threshold;/* upper threshold */

 u_int32_t  l_threshold;/* lower threshold */

};

 

3.        【数据结构】用户态统计信息

struct ip_vs_stats_user

{

 __u32                   conns;         /* connections scheduled */

 __u32                   inpkts;        /* incoming packets */

 __u32                   outpkts;       /* outgoing packets */

 __u64                   inbytes;       /* incoming bytes */

 __u64                   outbytes;      /* outgoing bytes */

 __u32   cps; /* current connection rate */

 __u32   inpps; /* current in packet rate */

 __u32   outpps; /* current out packet rate */

 __u32   inbps; /* current in byte rate */

 __u32   outbps; /* current out byte rate */

};

 

4.        【数据结构】用户态获取信息结构

/* The argument to IP_VS_SO_GET_INFO */

struct ip_vs_getinfo {

 unsigned int  version;/* version number */

 unsigned int  size;/* size of connection hash table */

 unsigned int  num_services;/* number of virtual services */

};

 

5.        【数据结构】用户态服务规则项信息

/* The argument to IP_VS_SO_GET_SERVICE */

struct ip_vs_service_entry {

u_int16_t  protocol;/* which service: user fills in these */

 u_int32_t  addr; /* virtual address */

 u_int16_t  port;

 u_int32_t  fwmark; /* firwall mark of service */

 char   sched_name[IP_VS_SCHEDNAME_MAXLEN]; /* service options */

 unsigned  flags;         /* virtual service flags */

 unsigned  timeout;/* persistent timeout */

u_int32_t  netmask;/* persistent netmask */

unsigned int  num_dests;/* number of real servers */

 struct ip_vs_stats_user stats;/* statistics */

};

 

6.        【数据结构】用户态服务器项信息

struct ip_vs_dest_entry {

 u_int32_t  addr; /* destination address */

 u_int16_t  port;

 unsigned  conn_flags;/* connection flags */

 int   weight; /* destination weight */

 u_int32_t  u_threshold;/* upper threshold */

 u_int32_t  l_threshold;/* lower threshold */

 u_int32_t  activeconns;/* active connections */

 u_int32_t  inactconns;/* inactive connections */

 u_int32_t  persistconns;/* persistent connections */

/* statistics */

 struct ip_vs_stats_user stats;

};

 

7.        【数据结构】用户态获取服务器项信息

struct ip_vs_get_dests {

 u_int16_t  protocol;/* which service: user fills in these */

 u_int32_t  addr; /* virtual address */

 u_int16_t  port;

 u_int32_t  fwmark; /* firwall mark of service */

 unsigned int  num_dests;/* number of real servers */

 struct ip_vs_dest_entry entrytable[0];/* the real servers */

};

 

8.        【数据结构】用户态获取虚拟服务器项信息

struct ip_vs_get_services {

 unsigned int  num_services;/* number of virtual services */

 struct ip_vs_service_entry entrytable[0];/* service table */

};

 

9.        【数据结构】用户态获取超时信息

struct ip_vs_timeout_user {

 int   tcp_timeout;

 int   tcp_fin_timeout;

 int   udp_timeout;

};

 

10.    【数据结构】用户态获取IPVS内核守护信息

struct ip_vs_daemon_user {

 int   state;/* sync daemon state (master/backup) */

 char   mcast_ifn[IP_VS_IFNAME_MAXLEN];/* multicast interface name */

 int   syncid;/* SyncID we belong to */

};

 

1.1.2.3【函数】模块初始化

初始化函数先初始化ipvs的各种处理机制,然后将ipvs的处理函数挂接到netfilter架构中。

static int __init ip_vs_init(void)/* net/ipv4/ipvs/ip_vs_core.c */

int ip_vs_control_init(void) /* net/ipv4/ipvs/ip_vs_ctl.c */

int ip_vs_protocol_init(void)/* net/ipv4/ipvs/ip_vs_proto.c */

int ip_vs_app_init(void)/* net/ipv4/ipvs/ip_vs_app.c */

int ip_vs_conn_init(void)/* net/ipv4/ipvs/ip_vs_conn.c */

 

1.1.2.4【函数】挂接点netfilter

    nf_hook_ops分别在FORWARD点挂2个, INPUT点和POST_ROUTING点各挂一个

       LOCAL_IN点:ip_vs_in()/* net/ipv4/ipvs/ip_vs_core.c */

   NF_IP_FORWARD:ip_vs_out()/* net/ipv4/ipvs/ip_vs_core.c */

    NF_IP_FORWARD :ip_vs_forward_icmp()/* net/ipv4/ipvs/ip_vs_core.c */

   NF_IP_POST_ROUTING: ip_vs_post_routing()/* net/ipv4/ipvs/ip_vs_core.c */

 

1.1.2.5均衡调度函数

 

1.        【函数】均衡调度算法描述

  在内核中的连接调度算法上,IPVS已实现了以下八种调度算法:

轮叫调度(Round-Robin Scheduling) 

加权轮叫调度(Weighted Round-Robin Scheduling) 

最小连接调度(Least-Connection Scheduling)

加权最小连接调度(Weighted Least-Connection Scheduling) 

基于局部性的最少链接(Locality-Based Least Connections Scheduling) 

带复制的基于局部性最少链接(Locality-Based Least Connections with Replication Scheduling) 

目标地址散列调度(Destination Hashing Scheduling) 

源地址散列调度(Source Hashing Scheduling) 

 

2.1.轮叫调度

轮叫调度(Round Robin Scheduling)算法就是以轮叫的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。算法的优点是其简洁性,它无需记录当前所有连接的状态,所以它是一种无状态调度。

在系统实现时,我们引入了一个额外条件,当服务器的权值为零时,表示该服务器不可用而不被调度。这样做的目的是将服务器切出服务(如屏蔽服务器故障和系统维护),同时与其他加权算法保持一致。所以,算法要作相应的改动,它的算法流程如下: 

假设有一组服务器S = {S0, S1, …, Sn-1},一个指示变量i表示上一次选择的服务器,W(Si)表示服务器Si的权值。变量i被初始化为n-1,其中n > 0。

j = i;

do {

 j = (j + 1) mod n;

 if (W(Sj) > 0) {  /*当权值为0时,表示服务器或者链路不通*/

  i = j;

  return Si;

 }

} while (j != i);

return NULL;

 

轮叫调度算法假设所有服务器处理性能均相同,不管服务器的当前连接数和响应速度。该算法相对简单,不适用于服务器组中处理性能不一的情况,而且当请求服务时间变化比较大时,轮叫调度算法容易导致服务器间的负载不平衡。

虽然Round-Robin DNS方法也是以轮叫调度的方式将一个域名解析到多个IP地址,但轮叫DNS方法的调度粒度是基于每个域名服务器的,域名服务器对域名解析的缓存会妨碍轮叫解析域名生效,这会导致服务器间负载的严重不平衡。这里,IPVS轮叫调度算法的粒度是基于每个连接的,同一用户的不同连接都会被调度到不同的服务器上,所以这种细粒度的轮叫调度要比DNS的轮叫调度优越很多。

 

2.2.加权轮叫调度

加权轮叫调度(Weighted Round-Robin Scheduling)算法可以解决服务器间性能不一的情况,它用相应的权值表示服务器的处理性能,服务器的缺省权值为1。假设服务器A的权值为1,B的权值为2,则表示服务器B的处理性能是A的两倍。加权轮叫调度算法是按权值的高低和轮叫方式分配请求到各服务器。权值高的服务器先收到的连接,权值高的服务器比权值低的服务器处理更多的连接,相同权值的服务器处理相同数目的连接数。加权轮叫调度算法流程如下:

假设有一组服务器S = {S0, S1, …, Sn-1},W(Si)表示服务器Si的权值,一个

指示变量i表示上一次选择的服务器,指示变量cw表示当前调度的权值,max(S) 表示集合S中所有服务器的最大权值,gcd(S)表示集合S中所有服务器权值的最大公约数。变量i初始化为-1,cw初始化为零。

while (true) {

  i = (i + 1) mod n;

  if (i == 0) {

     cw = cw - gcd(S); 

     if (cw <= 0) {

       cw = max(S);

       if (cw == 0)

         return NULL;

     }

  } 

  if (W(Si) >= cw) 

    return Si;

}  

例如,有三个服务器A、B和C分别有权值4、3和2,则在一个调度周期内(mod sum(W(Si)))调度序列为AABABCABC。加权轮叫调度算法还是比较简单和高效。当请求的服务时间变化很大,单独的加权轮叫调度算法依然会导致服务器间的负载不平衡。

从上面的算法流程中,我们可以看出当服务器的权值为零时,该服务器不被被调度;当所有服务器的权值为零,即对于任意i有W(Si)=0,则没有任何服务器可用,算法返回NULL,所有的新连接都会被丢掉。加权轮叫调度也无需记录当前所有连接的状态,所以它也是一种无状态调度。

 

2.3.最小连接调度

最小连接调度(Least-Connection Scheduling)算法是把新的连接请求分配到当前连接数最小的服务器。最小连接调度是一种动态调度算法,它通过服务器当前所活跃的连接数来估计服务器的负载情况。调度器需要记录各个服务器已建立连接的数目,当一个请求被调度到某台服务器,其连接数加1;当连接中止或超时,其连接数减一。

在系统实现时,我们也引入当服务器的权值为零时,表示该服务器不可用而不被调度,它的

算法流程如下:

 

假设有一组服务器S = {S0, S1,..., Sn-1},W(Si)表示服务器Si的权值,C(Si)表示服务器Si的当前连接数。

for (m = 0; m < n; m++) {

 if (W(Sm) > 0) {

  for (i = m+1; i < n; i++) {

   if (W(Si) <= 0)

    continue;

   if (C(Si) < C(Sm))

    m = i;

  }

  return Sm;

 }

}

return NULL;

当各个服务器有相同的处理性能时,最小连接调度算法能把负载变化大的请求分布平滑到各个服务器上,所有处理时间比较长的请求不可能被发送到同一台服务器上。但是,当各个服务器的处理能力不同时,该算法并不理想,因为TCP连接处理请求后会进入TIME_WAIT状态,TCP的TIME_WAIT一般为2分钟,此时连接还占用服务器的资源,所以会出现这样情形,性能高的服务器已处理所收到的连接,连接处于TIME_WAIT状态,而性能低的服务器已经忙于处理所收到的连接,还不断地收到新的连接请求。

 

2.4.加权最小连接调度

加权最小连接调度(Weighted Least-Connection Scheduling)算法是最小连接调度的超集,各个服务器用相应的权值表示其处理性能。服务器的缺省权值为1,系统管理员可以动态地设置服务器的权值。加权最小连接调度在调度新连接时尽可能使服务器的已建立连接数和其权值成比例。加权最小连接调度的算法流程如下:

假设有一组服务器S = {S0, S1,..., Sn-1},W(Si)表示服务器Si的权值,C(Si)表示服务器Si的当前连接数。所有服务器当前连接数的总和为 CSUM = ΣC(Si)  (i=0, 1,.., n-1)。当前的新连接请求会被发送服务器Sm,当且仅当服务器Sm满足以下条件

  (C(Sm)/CSUM)/W(Sm) = min { (C(Si)/CSUM)/W(Si)}  (i=0, 1,., n-1)

  其中W(Si)不为零 因为CSUM在这一轮查找中是个常数,所以判断条件可以简化为  C(Sm)/W(Sm) = min { C(Si)/W(Si)}  (i=0, 1,., n-1) 。其中W(Si)不为零 因为除法所需的CPU周期比乘法多,且在Linux内核中不允许浮点除法,服务器的 权值都大于零,所以判断条件C(Sm)/W(Sm) > C(Si)/W(Si) 可以进一步优化 为C(Sm)*W(Si) > C(Si)* W(Sm)。同时保证服务器的权值为零时,服务器不被调 度。所以,算法只要执行以下流程。

for (m = 0; m < n; m++) {

 if (W(Sm) > 0) {

  for (i = m+1; i < n; i++) {

   if (C(Sm)*W(Si) > C(Si)*W(Sm))

    m = i;

  }

  return Sm;

 }

}

return NULL;

 

2.5.基于局部性的最少链接调度

基于局部性的最少链接调度(Locality-Based Least Connections Scheduling,简称为LBLC)算法是针对请求报文的目标IP地址的负载均衡调度,目前主要用于Cache集群系统,因为在Cache集群中客户请求报文的目标IP地址是变化的。这里假设任何后端服务器都可以处理任一请求,算法的设计目标是在服务器的负载基本平衡情况下,将相同目标IP地址的请求调度到同一台服务器,来提高各台服务器的访问局部性和主存Cache命中率,从而整个集群系统的处理能力。

LBLC调度算法先根据请求的目标IP地址找出该目标IP地址最近使用的服务器,若该服务器是可用的且没有超载,将请求发送到该服务器;若服务器不存在,或者该服务器超载且有服务器处于其一半的工作负载,则用“最少链接”的原则选出一个可用的服务器,将请求发送到该服务器。该算法的详细流程如下:

假设有一组服务器S = {S0, S1,..., Sn-1},W(Si)表示服务器Si的权值,C(Si)表示服务器Si的当前连接数。ServerNode[dest_ip]是一个关联变量,表示 目标IP地址所对应的服务器结点,一般来说它是通过Hash表实现的。WLC(S)表示在集合S中的加权最小连接服务器,即前面的加权最小连接调度。Now为当前系统 时间。

if (ServerNode[dest_ip] is NULL) then {

 n = WLC(S);

 if (n is NULL) then return NULL;

 ServerNode[dest_ip].server = n;

} else {

 n = ServerNode[dest_ip].server;

 if ((n is dead) OR

     (C(n) > W(n) AND

      there is a node m wit

h C(m) < W(m)/2))) then {

  n = WLC(S);

  if (n is NULL) then return NULL;

  ServerNode[dest_ip].server = n;

 }

}

ServerNode[dest_ip].lastuse = Now;

return n;

 

此外,对关联变量ServerNode[dest_ip]要进行周期性的垃圾回收(Garbage Collection),将过期的目标IP地址到服务器关联项进行回收。过期的关联项是指哪些当前时间(实现时采用系统时钟节拍数jiffies)减去最近使用时间超过设定过期时间的关联项,系统缺省的设定过期时间为24小时。

 

2.6.带复制的基于局部性最少链接调度

带复制的基于局部性最少链接调度(Locality-Based Least Connections with Replication Scheduling,以下简称为LBLCR)算法也是针对目标IP地址的负载均衡,目前主要用于Cache集群系统。它与LBLC算法的不同之处是它要维护从一个目标IP地址到一组服务器的映射,而LBLC算法维护从一个目标IP地址到一台服务器的映射。对于一个“热门”站点的服务请求,一台Cache 服务器可能会忙不过来处理这些请求。这时,LBLC调度算法会从所有的Cache服务器中按“最小连接”原则选出一台Cache服务器,映射该“热门”站点到这台

Cache服务器,很快这台Cache服务器也会超载,就会重复上述过程选出新的Cache服务器。这样,可能会导致该“热门”站点的映像会出现在所有的Cache服务器上,降低了Cache服务器的使用效率。LBLCR调度算法将“热门”站点映射到一组Cache服务器(服务器集合),当该“热门”站点的请求负载增加时,会增加集合里的Cache服务器,来处理不断增长的负载;当该“热门”站点的请求负载降低时,会减少集合里的Cache服务器数目。这样,该“热门”站点的映像不太可能出现在所有的Cache服务器上,从而提供Cache集群系统的使用效率。

LBLCR算法先根据请求的目标IP地址找出该目标IP地址对应的服务器组;按“最小连接”原则从该服务器组中选出一台服务器,若服务器没有超载,将请求发送到该服务器;若服务器超载;则按“最小连接”原则从整个集群中选出一台服务器,将该服务器加入到服务器组中,将请求发送到该服务器。同时,当该服务器组有一段时间没有被修改,将最忙的服务器从服务器组中删除,以降低复制的程度。LBLCR调度算法的流程如下:

LBLCR调度算法流程 

假设有一组服务器S = {S0, S1,..., Sn-1},W(Si)表示服务器Si的权值,C(Si)表示服务器Si的当前连接数。ServerSet[dest_ip]是一个关联变量,表示 目标IP地址所对应的服务器集合,一般来说它是通过Hash表实现的。WLC(S)表示 在集合S中的加权最小连接服务器,即前面的加权最小连接调度;WGC(S)表示在 集合S中的加权最大连接服务器。Now为当前系统时间,lastmod表示集合的最近 修改时间,T为对集合进行调整的设定时间。

if (ServerSet[dest_ip] is NULL) then {

 n = WLC(S);

 if (n is NULL) then return NULL;

 add n into ServerSet[dest_ip];

} else {

 n = WLC(ServerSet[dest_ip]);

 if ((n is NULL) OR

     (n is dead) OR

     (C(n) > W(n) AND

      there is a node m with C(m) < W(m)/2))) then {

  n = WLC(S);

  if (n is NULL) then return NULL;

  add n into ServerSet[dest_ip];

 } else

 if (|ServerSet[dest_ip]| > 1 AND

     Now - ServerSet[dest_ip].lastmod > T) then {

  m = WGC(ServerSet[dest_ip]);

  remove m from ServerSet[dest_ip];

 }

}

 

ServerSet[dest_ip].lastuse = Now;

if (ServerSet[dest_ip] changed) then

 ServerSet[dest_ip].lastmod = Now;

return n;

 

此外,对关联变量ServerSet[dest_ip]也要进行周期性的垃圾回收(Garbage Collection),将过期的目标IP地址到服务器关联项进行回收。过期的关联项是指哪些当前时间(实现时采用系统时钟节拍数jiffies)减去最近使用时间(lastuse)超过设定过期时间的关联项,系统缺省的设定过期时间为24小时。

 

2.7.目标地址散列调度

目标地址散列调度(Destination Hashing Scheduling)算法也是针对目标IP地址的负载均衡,但它是一种静态映射算法,通过一个散列(Hash)函数将一个目标IP地址映射到一台服务器。 目标地址散列调度算法先根据请求的目标IP地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。该算法的流程如下:

假设有一组服务器S = {S0, S1,..., Sn-1},W(Si)表示服务器Si的权值,C(Si)表示服务器Si的当前连接数。ServerNode[]是一个有256个桶(Bucket)的 Hash表,一般来说服务器的数目会运小于256,当然表的大小也是可以调整的。 算法的初始化是将所有服务器顺序、循环地放置到ServerNode表中。若服务器的 连接数目大于2倍的权值,则表示服务器已超载。

n = ServerNode[hashkey(dest_ip)];

if ((n is dead) OR

 (W(n) == 0) OR

    (C(n) > 2*W(n))) then

 return NULL;

return n;

 

在实现时,我们采用素数乘法Hash函数,通过乘以素数使得散列键值尽可能地达到较均匀的分布。所采用的素数乘法Hash函数如下: 素数乘法Hash函数 

static inline unsigned hashkey(unsigned int dest_ip)

{

    return (dest_ip* 2654435761UL) & HASH_TAB_MASK;

}

 

其中,2654435761UL是2到2

^32 (4294967296)间接近于黄金分割的素数,

  (sqrt(5) - 1)/2 =  0.618033989

  2654435761/4294967296 = 0.618033987

 

2.8.源地址散列调度

源地址散列调度(Source Hashing Scheduling)算法正好与目标地址散列调度算法相反,它根据请求的源IP地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。它采用的散列函数与目标地址散列调度算法的相同。它的算法流程与目标地址散列调度算法的基本相似,除了将请求的目标IP地址换成请求的源IP地址,所以这里不一一叙述。

在实际应用中,源地址散列调度和目标地址散列调度可以结合使用在防火墙集群中,它们可以保证整个系统的唯一出入口。

 

2.        【函数】均衡调度算法实现

    每个调度算法的实现就是填写一个ip_vs_scheduler结构,在IPVS服务ip_vs_service结构中指向它即可,这样在连接到达该服务时,通过调度算法选择具体的目的主机。每个算法作为一个单独的内核模块,可由内核配置是否包括该模块。

以下以最简单的rr算法来说明,该算法在net/ipv4/ipvs/ip_vs_rr.c中定义。

static struct ip_vs_scheduler ip_vs_rr_scheduler = {.name =   "rr",/* name */

.refcnt =  ATOMIC_INIT(0),

.module =  THIS_MODULE,

.init_service =  ip_vs_rr_init_svc,

.done_service =  ip_vs_rr_done_svc,

.update_service = ip_vs_rr_update_svc,

.schedule =  ip_vs_rr_schedule,

};

 

init_service()函数进行算法初始化,在虚拟服务ip_vs_service和调度器绑定时调用

(ip_vs_bind_scheduler()函数);done_service()函数进行算法的清除,在虚拟服务ip_vs_service和调度器解除绑定时调用(ip_vs_unbind_scheduler()函数);update_service()函数在目的服务器变化时调用(如ip_vs_add_dest(), ip_vs_edit_dest()等函数);

算法核心函数schedule()则是在ip_vs_schedule()函数中在新建IPVS连接前调用,找到真

正的服务器提供服务,建立IPVS连接。

系统基本调度函数为ip_vs_schedule(),在TCP、UDP的conn_shedule中调用,而AH、ESP协议不管: ip_vs_schedule() /* net/ipv4/ipv4/ip_vs_core.c */

    固定调度函数,用在多连接协议处理中将子连接与主连接挂钩:

ip_vs_sched_persist() /* net/ipv4/ipv4/ip_vs_core.c */

 

1.1.2.6IPVS的连接管理

和netfilter的连接类似,IPVS的连接管理是IPVS的一个重要组成部分,但相对来说IPVS的连接比netfilter的连接要简单一些。

1.        IPVS五元组

和netfilter一样是五元组,为IP协议、源地址、源端口、目的地址和目的端口,不过没定义方向的概念,所以在IPVS中请求方向和回应方向要用不同的查找函数处理,由于IPVS是在INPUT点处理请求,在FORWARD点处理回应包,不会在同一个点同时处理请求包和回应包,因此可以没有方向的概念。

进入方向:

static inline struct ip_vs_conn *__ip_vs_conn_in_get()

struct ip_vs_conn *ip_vs_conn_in_get ()

另外还有个获取连接模板的函数,没有s_port为0的特殊处理,在查找固定连接和模板连接时使用: struct ip_vs_conn *ip_vs_ct_in_get()

 

发出方向查找:

struct ip_vs_conn *ip_vs_conn_out_get()

 

2.        连接HASH表

和netfilter一样,IPVS的连接表是通过HASH表来实现的,不过和netfilter不同的是该HASH表大小是固定的,可在内核内核参数时设置,而不是象netfitler那样是根据系统内存动态计算出来的:

 

3.        连接的建立

struct ip_vs_conn * ip_vs_conn_new(int proto, __u32 caddr, __u16 cport, __u32 vaddr, __u16 vport,  __u32 daddr, __u16 dport, unsigned flags, struct ip_vs_dest *dest);

 

绑定连接目的服务器:

ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)

 

绑定协议应用:

int ip_vs_bind_app(struct ip_vs_conn *cp, struct ip_vs_protocol *pp)

 

绑定发送方法:

static inline void ip_vs_bind_xmit(struct ip_vs_conn *cp);

 

将连接结构添加到连接HASH表

static inline int ip_vs_conn_hash(struct ip_vs_conn *cp) ;

 

4.        连接的释放

接超时函数: static void ip_vs_conn_expire(unsigned long data);

从连接HASH表中断开: static inline int ip_vs_conn_unhash(struct ip_vs_conn *cp);

从主连接中断开: static inline void ip_vs_control_del(struct ip_vs_conn *cp)

解除与应用的绑定: void ip_vs_unbind_app(struct ip_vs_conn *cp)

连接和目的服务器解除绑定: static inline void ip_vs_unbind_dest()

 

5.        其他的释放连接的函数

 释放所有的连接:static void ip_vs_conn_flush(void)

在删除IPVS模块时调用,方法是让所有连接定时器到期而自动调用定时到期函数。

连接定时器立即到期: void ip_vs_conn_expire_now(struct ip_vs_conn *cp)

 

 6.5.2  定时器随机删除连接

定时函数defense_work_handler()定期调用。

随机删除连接:vid ip_vs_random_dropentry(void) 。

 

6.        其他的连接相关函数

//该函数对未设置客户端端口的连接提供一个端口值,在ip_vs_nat_xmit()函数中调用

void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __u16 cport)

 

检查连接:ip_vs_check_template()

模板的目的服务器是否可用,ip_vs_sched_persist()函数中调用

 

1.1.2.7IPVS的协议(TCP)管理

1.        基本管理

IPVS协议的一些共用处理函数在net/ipv4/ipvs/ip_vs_proto.c中定义:

IPVS服务登记:static int register_ip_vs_protocol(struct ip_vs_protocol *pp)

        登记IPVS服务,就是把服务结构挂接到IPVS服务链表中

IPVS服务拆除:static int unregister_ip_vs_protocol(struct ip_vs_protocol *pp)

        拆除IPVS服务,就是把服务结构从IPVS服务链表中拆除

查找服务,返回服务结构指针: struct ip_vs_protocol * ip_vs_proto_get()

创建状态超时表:int * ip_vs_create_timeout_table(int *table, int size)

修改状态超时值: int ip_vs_set_state_timeout()

返回当前协议状态名称字符串:const char * ip_vs_state_name(__u16 proto, int state)

 

时初始化了TCP、UDP、AH和ESP四个协议,分别用一个struct ip_vs_protocol结构描述,这个结构定义了协议的各种操作。

下面以TCP协议的实现来详细说明,相关代码文件为net/ipv4/ipvs/ip_vs_proto_tcp.c。

struct ip_vs_protocol ip_vs_protocol_tcp = {

.name =   "TCP",

.protocol =  IPPROTO_TCP,

.dont_defrag =  0,

.appcnt =  ATOMIC_INIT(0),

.init =   ip_vs_tcp_init,

.exit =   ip_vs_tcp_exit,

.register_app =  tcp_register_app,

.unregister_app = tcp_unregister_app,

.conn_schedule = tcp_conn_schedule,

.conn_in_get =  tcp_conn_in_get, .conn_out_get =  tcp_conn_out_get,

.snat_handler =  tcp_snat_handler,

.dnat_handler =  tcp_dnat_handler,

.csum_check =  tcp_csum_check,

.state_name =  tcp_state_name,

.state_transition = tcp_state_transition,

.app_conn_bind = tcp_app_conn_bind,

.debug_packet =  ip_vs_tcpudp_debug_packet,

.timeout_change = tcp_timeout_change,

.set_state_timeout = tcp_set_state_timeout,

};

 

2.         TCP初始化函数

 static void ip_vs_tcp_init(struct ip_vs_protocol *pp);

IPVS定义的超时,和netfilter类似,不过比netfilter的超时少得多,而且这些值不是通过/proc调整,而是通过ipvsadm命令来调整.

static int tcp_timeouts[IP_VS_TCP_S_LAST+1] = {

 [IP_VS_TCP_S_NONE]  = 2*HZ,

 [IP_VS_TCP_S_ESTABLISHED] = 15*60*HZ,

 [IP_VS_TCP_S_SYN_SENT]  = 2*60*HZ,

 [IP_VS_TCP_S_SYN_RECV]  = 1*60*HZ,

 [IP_VS_TCP_S_FIN_WAIT]  = 2*60*HZ,

 [IP_VS_TCP_S_TIME_WAIT]  = 2*60*HZ,

 [IP_VS_TCP_S_CLOSE]  = 10*HZ,

 [IP_VS_TCP_S_CLOSE_WAIT] = 60*HZ,

 [IP_VS_TCP_S_LAST_AC

以上是关于lvs实现分析的主要内容,如果未能解决你的问题,请参考以下文章

lvs+keepalived 高可用负载均衡模式分析

lvs源代码分析

群集 之 LVS负载均衡(DR模式)

负载均衡——LVS,HAProxy和Nginx对比分析

分布式技术专题「LVS负载均衡」全面透析Web基础架构负载均衡LVS机制的原理分析指南

分布式技术专题「LVS负载均衡」全面透析Web基础架构负载均衡LVS机制的原理分析指南