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:有事,博客先到此为止,有空再补。