IPVS(LVS)中的调度算法详解(一)
1.IPVS简介
2.IPVS(LVS)中的调度算法详解(一)
一、IPVS中的算法类型
IP Virtual Server version 1.2.1 kernel 6.6.0-28.0.0.34.oe2403.x86_64 --scheduler -s scheduler one of rr|wrr|lc|wlc|lblc|lblcr|dh|sh|sed|nq|fo|ovf|mh,
当前版本的LVS共有rr|wrr|lc|wlc|lblc|lblcr|dh|sh|sed|nq|fo|ovf|mh,共13种算法。下面我们对这集中算法进行详细介绍。
二、IPVS中的算法详解
1.轮询(Round - Robin,RR)算法
-
基本原理
- RR(Round - Robin)即轮询算法,是一种简单且直观的负载均衡算法。其核心原理是按照顺序依次将客户端的请求分配到后端的真实服务器(Real Server)上。
- 假设在一个LVS负载均衡集群中有n台真实服务器,分别标记为RS1、RS2、...、RSn。当第一个客户端请求到达负载均衡器(LVS)时,LVS会将这个请求分配给RS1;当第二个客户端请求到来时,分配给RS2;以此类推,当第n个客户端请求到来时,分配给RSn。当第n + 1个客户端请求到来时,又重新从RS1开始分配,如此循环往复。
- 这种分配方式就像排队按顺序领东西一样,每个服务器都有均等的机会接收请求,使得请求在各个服务器之间均匀分布。
-
实现过程
- 维护服务器列表:
- LVS会维护一个包含所有真实服务器的列表。这个列表记录了服务器的相关信息,如服务器的IP地址、端口号等。在轮询过程中,LVS会根据这个列表的顺序来分配请求。
- 请求分配循环:
- 当有新的客户端请求到达时,LVS会查看当前应该分配到列表中的哪一台服务器。它通过一个内部的计数器来跟踪当前分配到的服务器位置。每次分配请求后,计数器会增加1,当计数器的值超过服务器列表的大小(即n)时,计数器会重新归零,从而开始新的一轮分配。
- 连接处理:
- LVS在将请求分配给真实服务器后,会建立起客户端与该真实服务器之间的连接。对于基于TCP/IP协议的请求,LVS会处理好连接的转发工作,使得客户端和真实服务器之间能够顺利通信。例如,在TCP的三次握手过程中,LVS会协调客户端和真实服务器完成握手,之后将客户端发送的数据转发给真实服务器,并将真实服务器返回的数据转发给客户端。
- 维护服务器列表:
-
特点
- 简单公平:
- RR算法的最大优点是简单易懂且公平。每个真实服务器都有相同的机会处理客户端请求,这在服务器性能相近的情况下能够很好地平衡各服务器的负载。例如,在一个由多台相同配置的Web服务器组成的集群中,使用RR算法可以确保每台服务器大致处理相同数量的HTTP请求。
- 不考虑服务器性能差异:
- 然而,RR算法的缺点也很明显,它没有考虑服务器之间的性能差异。如果集群中的服务器性能不同,比如有的服务器处理能力强,有的服务器处理能力弱,使用RR算法可能会导致性能强的服务器资源得不到充分利用,而性能弱的服务器可能会因为分配到过多的请求而过载。例如,一台服务器的CPU处理速度是另一台的两倍,但RR算法依然会平均分配请求,这可能会导致较慢的服务器响应时间变长,甚至出现故障。
- 缺乏动态适应性:
- 另外,RR算法是一种静态的分配方式,它不会根据服务器的当前负载状态(如连接数、CPU使用率、内存使用率等)来动态调整请求分配。即使某台服务器已经负载过重,只要按照轮询顺序轮到它,还是会继续分配请求给它。这在实际的复杂应用场景中可能会影响系统的整体性能和稳定性。
- 简单公平:
-
适用场景
- 服务器性能相近的集群:
- RR算法适用于后端真实服务器性能基本相同的负载均衡场景。例如,在一个小型的Web服务器集群中,如果所有服务器的硬件配置(CPU、内存、网络带宽等)相同,并且运行相同的软件环境,RR算法可以简单有效地将客户端的HTTP请求均匀分配到各个服务器上,实现基本的负载均衡,从而提高整个集群的处理能力和可用性。
- 另外,在一些对负载均衡精度要求不高,且服务器性能较为均衡的测试环境或者开发环境中,RR算法也可以作为一种简单的负载均衡方案来使用。
- 服务器性能相近的集群:
linux源码中的实现
//源码位置:net/netfilter/ipvs/ip_vs_rr.c /** * @brief 轮询调度算法实现 * * @param svc 指向要调度的 IPVS 服务的指针 * @param skb 指向网络数据包的指针 * @param iph 指向 IP 头部的指针 * @return 指向选中的目标服务器的指针,如果没有可用的目标服务器,则返回 NULL */ 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) { // 如果目标服务器没有过载标志,并且权重大于 0,则选中该服务器 if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) && atomic_read(&dest->weight) > 0) /* HIT */ goto out; // 如果已经遍历到列表的最后一个服务器,则停止循环 if (dest == last) goto stop; } // 增加遍历次数 pass++; // 如果前一个目标服务器已经被移除,并且当前遍历次数小于 2,则继续遍历 /* 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"); // 返回 NULL,表示没有可用的目标服务器 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; } /** * 定义一个静态的 IPVS 调度器结构体,用于实现轮询调度算法 */ static struct ip_vs_scheduler ip_vs_rr_scheduler = { /** * 调度器的名称,这里设置为 "rr",代表轮询(Round-Robin)调度算法 */ .name = "rr", /* name */ /** * 调度器的引用计数,使用 ATOMIC_INIT(0) 初始化,确保初始值为 0 */ .refcnt = ATOMIC_INIT(0), /** * 调度器所属的模块,THIS_MODULE 是一个宏,代表当前模块 */ .module = THIS_MODULE, /** * 初始化一个链表头,用于管理调度器的相关数据 */ .n_list = LIST_HEAD_INIT(ip_vs_rr_scheduler.n_list), /** * 指向初始化服务的函数指针,这里设置为 ip_vs_rr_init_svc 函数 */ .init_service = ip_vs_rr_init_svc, /** * 指向添加目标服务器的函数指针,这里设置为 NULL,表示不支持添加目标服务器的操作 */ .add_dest = NULL, /** * 指向删除目标服务器的函数指针,这里设置为 ip_vs_rr_del_dest 函数 */ .del_dest = ip_vs_rr_del_dest, /** * 指向调度算法的函数指针,这里设置为 ip_vs_rr_schedule 函数 */ .schedule = ip_vs_rr_schedule, }; /** * @brief 模块初始化函数,用于注册 IPVS 轮询调度器 * * @return 0 表示成功,负值表示失败 */ static int __init ip_vs_rr_init(void) { // 调用 register_ip_vs_scheduler 函数注册调度器 return register_ip_vs_scheduler(&ip_vs_rr_scheduler); } /** * @brief 模块清理函数,用于注销 IPVS 轮询调度器 */ static void __exit ip_vs_rr_cleanup(void) { // 调用 unregister_ip_vs_scheduler 函数注销调度器 unregister_ip_vs_scheduler(&ip_vs_rr_scheduler); // 调用 synchronize_rcu 函数等待所有引用计数为 0 的对象被释放 synchronize_rcu(); } // 初始化函数,在模块加载时调用 module_init(ip_vs_rr_init); // 清理函数,在模块卸载时调用 module_exit(ip_vs_rr_cleanup); // 模块描述 MODULE_DESCRIPTION("ipvs round-robin scheduler"); // 模块许可证 MODULE_LICENSE("GPL");
这段代码实现了轮询调度算法。它遍历目标列表,寻找一个没有过载且权重大于 0 的目标。如果找到了这样的目标,它就会跳出循环。如果没有找到,它会继续遍历,最多遍历两次。
本文作者:乐乐0120
本文链接:https://www.cnblogs.com/lele0120/p/18593889
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步