ip_vs 原理解析 (三)调度器

文章目录

  • 调度器
    • 注册
    • [调度器绑定 svc](#调度器绑定 svc)
  • [ip_vs_schedule 结构体](#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 的访问会调度到同一个 后端。

相关推荐
热爱嵌入式的小许2 小时前
Linux基础项目开发1:量产工具——显示系统
linux·运维·服务器·韦东山量产工具
小堃学编程2 小时前
计算机网络(十) —— IP协议详解,理解运营商和全球网络
网络·tcp/ip·计算机网络
IPFoxy6665 小时前
探索路由器静态IP的获取方式
网络·智能路由器
menge23335 小时前
VLAN:虚拟局域网
网络·智能路由器
ZachOn1y6 小时前
计算机网络:计算机网络概述 —— 初识计算机网络
网络·计算机网络·知识点汇总·考研必备
三金121386 小时前
SpringIoC容器的初识
网络·网络协议·rpc
韩楚风6 小时前
【linux 多进程并发】linux进程状态与生命周期各阶段转换,进程状态查看分析,助力高性能优化
linux·服务器·性能优化·架构·gnu
陈苏同学6 小时前
4. 将pycharm本地项目同步到(Linux)服务器上——深度学习·科研实践·从0到1
linux·服务器·ide·人工智能·python·深度学习·pycharm
Ambition_LAO6 小时前
解决:进入 WSL(Windows Subsystem for Linux)以及将 PyCharm 2024 连接到 WSL
linux·pycharm
Pythonliu76 小时前
茴香豆 + Qwen-7B-Chat-Int8
linux·运维·服务器