IPVS调度算法Round-Robin
函数register_ip_vs_scheduler将RR调度器链接到全局链表ip_vs_schedulers上。具体内容请参见:https://blog.csdn.net/sinat_20184565/article/details/100126417。
static struct ip_vs_scheduler ip_vs_rr_scheduler = {
.name = "rr", /* name */
.n_list = LIST_HEAD_INIT(ip_vs_rr_scheduler.n_list),
.init_service = ip_vs_rr_init_svc,
.add_dest = NULL,
.del_dest = ip_vs_rr_del_dest,
.schedule = ip_vs_rr_schedule,
};
static int __init ip_vs_rr_init(void)
{
return register_ip_vs_scheduler(&ip_vs_rr_scheduler);
}
虚拟服务接口
在添加IPVS虚拟服务的流程中,绑定调度器函数ip_vs_bind_scheduler时,将首先执行调度器init_service函数指针,对于RR调度器,即函数ip_vs_rr_init_svc。之后执行到虚拟服务的绑定。
static int ip_vs_rr_init_svc(struct ip_vs_service *svc)
{
svc->sched_data = &svc->destinations;
return 0;
}
int ip_vs_bind_scheduler(struct ip_vs_service *svc, struct ip_vs_scheduler *scheduler)
{
if (scheduler->init_service) {
ret = scheduler->init_service(svc);
if (ret) {
pr_err("%s(): init error\n", __func__);
return ret;
}
}
rcu_assign_pointer(svc->scheduler, scheduler);
}
这里是将虚拟服务所对应的真实服务器链表赋予RR调度器结构的成员sched_data。
真实服务器
RR调度器结构没有实现添加真实服务器的操作指针add_dest,因为在上节的初始化服务指针init_service函数中,RR调度器结构的sched_data成员直接指向了虚拟服务的真实服务器链表。所以只要虚拟服务中的真实服务器链表改变,RR调度器结构就可使用更新后的真实服务器链表。
但是,对于真实服务器的删除操作,需要注意的是,如果删除的是真实服务器链表上的第一个元素,需要更新RR调度器结构的sched_data成员。参见以下的RR调度器函数指针del_dest,即函数ip_vs_rr_del_dest:
static int ip_vs_rr_del_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest)
{
struct list_head *p;
spin_lock_bh(&svc->sched_lock);
p = (struct list_head *) svc->sched_data;
/* dest is already unlinked, so p->prev is not valid but p->next is valid, use it to reach previous entry.
*/
if (p == &dest->n_list)
svc->sched_data = p->next->prev;
spin_unlock_bh(&svc->sched_lock);
return 0;
}
调度处理
如下调度函数,虚拟服务svc结构成员sched_data保存有上一次调度的真实服务器结构的链表节点,此次调度将由链表中的下一个真实服务器开始遍历,下一个真实服务器要满足两个条件:
1)不能够是已经过载的服务器;
2)权重值大于零。
另外,遍历pass控制真实服务器链表的遍历次数,整个链表仅完整的遍历一次,因为链表元素由可能会发生改变。
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;
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)
goto out; /* HIT */
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);
out:
svc->sched_data = &dest->n_list;
return dest;
}
如下的添加真实服务器时,分别指定此服务器可接受的最高的连接数量(u-threshold)超过此值将不再接收新的连接;以及低连接阈值(l-threshold),当此真实服务器上的连接数量降低到此值以下后,重新开始接受新的连接。
# ipvsadm -A -t 207.175.44.110:80 -s rr
# ipvsadm -a -t 207.175.44.110:80 -r 192.168.10.1:80 -m --u-threshold 2000 --l-threshold 1000 --weight 200
参数u_threshold的判断逻辑参见以下函数ip_vs_bind_dest:
static inline void ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)
{
if (dest->u_threshold != 0 &&
ip_vs_dest_totalconns(dest) >= dest->u_threshold)
dest->flags |= IP_VS_DEST_F_OVERLOAD;
}
参数l-threshold的判断逻辑参见以下函数ip_vs_unbind_dest。如果设置l-threshold等于0,那么当连接数量降低到u_threshold值的四分之三以下后,清除IP_VS_DEST_F_OVERLOAD标志,开始接受新的连接。
static inline void ip_vs_unbind_dest(struct ip_vs_conn *cp)
{
struct ip_vs_dest *dest = cp->dest;
if (dest->l_threshold != 0) {
if (ip_vs_dest_totalconns(dest) < dest->l_threshold)
dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
} else if (dest->u_threshold != 0) {
if (ip_vs_dest_totalconns(dest) * 4 < dest->u_threshold * 3)
dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
} else {
if (dest->flags & IP_VS_DEST_F_OVERLOAD)
dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
}
}
内核版本 4.15
上一篇: 小议Python中的import