文章目录
- 调度器
- 注册
- 调度器绑定 svc
- ip_vs_schedule 结构体
- PE
调度器
ipvs 的 调度器(scheduler) 有很多种,这里不详细介绍各个调度器的算法,主要关注于 ipvs 流程中的调度器注册和使用。
ipvs 的调度器有 rr(轮询),wrr(加权轮询),lc(最小链接),sh(源地址散列),sed(最短预期延时) 等等
注册
每个调度器都是通过 module_init() 进行初始化
rr 调度器static struct ip_vs_scheduler ip_vs_rr_scheduler = {.name = "rr", /* name */.refcnt = ATOMIC_INIT(0),.module = THIS_MODULE,.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);
}module_init(ip_vs_rr_init);
register_ip_vs_scheduler 调度函数
将调度器链接在全局链表 ip_vs_schedulers 上
调度器绑定 svc
在第一节 ipvsadm 创建 svc 时 ip_vs_bind_scheduler 会为 svc 绑定调度器
ip_vs_bind_scheduler| -- init_service 执行调度器的 init_service 函数,调度器| -- rcu_assign_pointer(svc->scheduler, scheduler) 将调度器结构赋予虚拟服务结构的 scheduler 成员
看一下 rr 的 init_service
ip_vs_rr_init_svc| -- svc->sched_data = &svc->destinations; 将 rs 链表赋予 svc 的调度器应用 data,svc->sched_data
看一下 wrr 的 init_service
static int ip_vs_wrr_init_svc(struct ip_vs_service *svc)
{struct ip_vs_wrr_mark *mark;mark = kmalloc(sizeof(struct ip_vs_wrr_mark), GFP_KERNEL);if (mark == NULL)return -ENOMEM;mark->cl = list_entry(&svc->destinations, struct ip_vs_dest, n_list); // 后端链表mark->di = ip_vs_wrr_gcd_weight(svc); // 计算最大公约数的方法,即权重步长mark->mw = ip_vs_wrr_max_weight(svc) - (mark->di - 1); // 最大权重计算方法mark->cw = mark->mw; // 当前权重svc->sched_data = mark;return 0;
}
ip_vs_schedule 结构体
接着看 ip_vs_rr_scheduler
其中的 .schedule 即调度方法,如 rr 的 ip_vs_rr_schedule
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),refcount_read(&dest->refcnt), atomic_read(&dest->weight));return dest;
}
可以看到轮询的调度算法,svc->sched_data 是当前链表中后端的指针,当调度时,将之前的后端赋值给 last,然后循环链表给 dst,如果循环到的 dst 可用,即 goto out,设置 sched_data 为当前调度到的后端的指针,然后返回当前后端。
其中的 .add_dest 和 .del_dest 即增加和删除 后端的操作,像 rr 算法添加 后端时不需要调整,但删除时需要 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;
}
在删除的 后端是当前调度的后端时的情况,这个时候将当前调度的后端改为当前节点在链表的前一个,这样后续调度时也能正常调度到当前节点的下一个。
像 wrr 添加 dst,删除 dst,更新 dst,都需要 ip_vs_wrr_dest_changed,这是由于权重变更后,有可能最大权重,最大公约数都会变化,需要更新整个 svc->sched_data。由此看出,后端的变化会即时更新调度算法。
PE
持久化引擎,当前 ip_vs 只有一种 sip 即源 ip 策略
.ct_match = ip_vs_sip_ct_match,
static bool ip_vs_sip_ct_match(const struct ip_vs_conn_param *p,struct ip_vs_conn *ct){bool ret = false;if (ct->af == p->af &&ip_vs_addr_equal(p->af, p->caddr, &ct->caddr) &&/* protocol should only be IPPROTO_IP if* d_addr is a fwmark */ip_vs_addr_equal(p->protocol == IPPROTO_IP ? AF_UNSPEC : p->af,p->vaddr, &ct->vaddr) &&ct->vport == p->vport &&ct->flags & IP_VS_CONN_F_TEMPLATE &&ct->protocol == p->protocol &&ct->pe_data && ct->pe_data_len == p->pe_data_len &&!memcmp(ct->pe_data, p->pe_data, p->pe_data_len))ret = true;IP_VS_DBG_BUF(9, "SIP template match %s %s->%s:%d %s\n",ip_vs_proto_name(p->protocol),IP_VS_DEBUG_CALLID(p->pe_data, p->pe_data_len),IP_VS_DBG_ADDR(p->af, p->vaddr), ntohs(p->vport),ret ? "hit" : "not hit");return ret;
}
在 Kubernetes 中,service 的 sessionAffinity: ClientIP 利用了该特性,在 timeout 时间内,同一个 源 ip 的访问会调度到同一个 后端。