lvs 轮叫调度(Round-Robin)算法 源码分析

来源:互联网 发布:linux u s 编辑:程序博客网 时间:2024/05/09 14:06

1. Round-Robin 算法

轮叫调度(Round Robin Scheduling)算法就是以轮叫的方式依次将请求调度到不同的服务器,即每次调度执行
     i = (i + 1) mod n,
然后选出第i台服务器。

算法的优点是其简洁性,它无需记录当前所有连接的状态,所以它是一种无状态调度。

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

轮叫调度算法流程
假设有一组服务器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) {        i = j;        return Si;    }} while (j != i);return NULL;

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

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

2.linux源码分析

/* * Round-Robin Scheduling */static struct ip_vs_dest *ip_vs_rr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,          struct ip_vs_iphdr *iph){    struct list_head *p;    struct ip_vs_dest *dest, *last;    int pass = 0;    IP_VS_DBG(6, "%s(): Scheduling...\n", __func__);    spin_lock_bh(&svc->sched_lock);    p = (struct list_head *) svc->sched_data;    last = dest = list_entry(p, struct ip_vs_dest, n_list);    do {        list_for_each_entry_continue_rcu(dest,                         &svc->destinations,                         n_list) {            if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) &&                atomic_read(&dest->weight) > 0)                /* HIT */                goto out;            if (dest == last)                goto stop;        }        pass++;        /* Previous dest could be unlinked, do not loop forever.         * If we stay at head there is no need for 2nd pass.         */    } while (pass < 2 && p != &svc->destinations);stop:    spin_unlock_bh(&svc->sched_lock);    ip_vs_scheduler_err(svc, "no destination available");    return NULL;  out:    svc->sched_data = &dest->n_list;    spin_unlock_bh(&svc->sched_lock);    IP_VS_DBG_BUF(6, "RR: server %s:%u "              "activeconns %d refcnt %d weight %d\n",              IP_VS_DBG_ADDR(dest->af, &dest->addr), ntohs(dest->port),              atomic_read(&dest->activeconns),              atomic_read(&dest->refcnt), atomic_read(&dest->weight));    return dest;}

代码分析:
14行:对svc->sched_lock上锁;
15行:获取list head指针;
16行:获取list head的n_list数据成员;
18-33行: Round-Robin 算法
    严格的说19-28才是真正的Round-Robin算法。
    list_for_each_entry_continue_rcu其实是一个宏,定义位于include/linux/rculist.h文件,具体如下:

/** * list_for_each_entry_continue_rcu - continue iteration over list of given ty    pe * @pos:        the type * to use as a loop cursor. * @head:       the head for your list. * @member:     the name of the list_head within the struct. * * Continue to iterate over list of given type, continuing after * the current position. */#define list_for_each_entry_continue_rcu(pos, head, member)             \        for (pos = list_entry_rcu(pos->member.next, typeof(*pos), member); \             &pos->member != (head);    \             pos = list_entry_rcu(pos->member.next, typeof(*pos), member))

    然后检测dest的flags值,flags值只有两个,具体如下:

// include/uapi/linux/ip_vs.h/* *      Destination Server Flags */#define IP_VS_DEST_F_AVAILABLE  0x0001      /* server is available */#define IP_VS_DEST_F_OVERLOAD   0x0002      /* server is overloaded */

    IP_VS_DEST_F_AVAILABLE表示服务器可用。
    IP_VS_DEST_F_OVERLOAD表示服务器超载,不可用。
    如果服务器可用,并且权值大于0,那么找到可用服务器,直接跳转到out;
    否则检测list是否已经遍历完(当dest == last时,表示遍历一遍),
        如果已经遍历完,跳转到stop;
        否则继续遍历。
33行:如果pass 小于2,且p 不等于svc->destinations,那么遍历第二遍;
41行:当找到可用服务器时,更新svc->sched_data到当前dest->n_list;
36行,42行解锁。

如果找到可用服务器,返回dest;
否则返回NULL。

WAIT
轮叫调度重点在于取模运算,为什么没有呢???
当找到可用服务器时,会更新svc->sched_data(见41行),它的值为dest->n_list。而我们遍历list时,第一元素是svc->sched_data->n_list(15-16行)。
这里就是取模的实现。

PS:有事,博客先到此为止,有空再补。

0 0