Linux-2.6.21的负载均衡
负载均衡是指多CPU系统(如SMP-Symmetric Multi-Processor,NUMA-Non Uniform Memory Architecture) 中的所有逻辑CPU发挥的功效相当,最大限度地利用所有的硬件资源,从而提高系统的性能。
本文介绍Linux2.6.21内核负载均衡的设计思想。
为了充分发挥硬件性能,在多CPU系统上做负载均衡时需要考虑系统的硬件拓朴,调度域正是内核反映CPU硬件结构的一种抽象描述,根据不同的硬件结构自上而下有NUMA、物理、核和超线程四种类型调度域。
系统中的每一个逻辑CPU都会关联一组调度域,相同类型的逻辑CPU处于同一个调度域中。例如,同一个物理CPU的所有核(包括其超线程)处于核域,同一个核的所有超线程处于超线程域。
调度域中又包含有一组调度组,这些调度组反映了此调度域中CPU的拓朴信息,调度组记录的拓朴结构有点类似下一层调度域。例如,核域的每个调度组记录有每个核的超线程信息。另外,调度组中还记录有组内CPU的处理能力,可以区分对待比如超线程CPU等特殊硬件的处理能力。
内核针对不同类型调度域的硬件特性差异配置有不同的负载均衡算法参数称为调度域参数,应用可查看或根据需要调整各CPU的域参数,详见4.1节。
举例:某X86_64服务器,存在有NUMA、核和超线程3个域(注:物理域会被内核优化掉),也就是说每个CPU都关联有三个调度域,见图1。
图1 CPU0的域结构
系统中存在的负载有:中断、异常、软中断、系统调用、任务;系统调用是在任务的上下文中执行的,异常不可控制且一般也是由于任务触发可以忽略,只剩下中断、软中断和任务;有些中断可以通过CPU的中断亲和掩码在多个CPU上做均衡(X86架构中已有此实现,CONFIG_IRQBALANCE),软中断过高的系统可以考虑软中断线程化后算成任务或RPS补丁(CONFIG_NETDEVICES_RPS)将软中断负载均衡至多个CPU间;标准内核的负载均衡算法是针对任务的。
内核为每一个调度实体(本文中即任务)关联一个任务权重load_task,其值是根据优先级计算出来的。在设计时,优先级120的非实时任务权重定为1024,而优先级每差1两者相差10%的CPU负载,例如优先级为121的任务权重为820,因为1024/(1024+820)约为55%,820/(1024+820)约为45%,这样两者相差为10%;这样就形成了一个任务权重表(实时任务的权值固定为88761*2=177522):
表1 全局权重表
根据上述可以得出优先级与权重的关系见图2:
图2 任务权重与优先级的关系
算法第一步:计算静态权重
内核在每一个运行队列结构中为当前cpu维护一个静态权重load_rq,其值为此运行队列中的每一个任务权重load_task的总和,load_rq = ∑(load_task)。
内核为每个运行队列维护了一个cpu_load[5]数组记录一些历史权重信息,此数组在时钟中断的scheduler_tick()中进行更新(初始值为0),算法如下:
M=load_rq, m=cpu_load[i],每次的计算遵循公式:(M-m)/2^i + m,即
i=0: M-m + m
i=1: (M-m)/2 + m
i=2: (M-m)/4 + m
i=3: (M-m)/8 + m
i=4: (M-m)/16 + m
历史权重目的是用来平滑静态权重,以缓解负载均衡时系统性能发生抖动。
系统运行过程中,内核会不停地进入负载均衡流程对当前cpu遍历其所关联的所有调度域(自下向上);对每一个调度域遍历所属的所有调度组,找出一个负载最大的调度组(不能是当前cpu所属的调度组);遍历此调度组中所属的cpu,找出静态权重符合条件的目标cpu,最后将此cpu上的可运行任务迁移至当前cpu(称为拉任务),以期达到负载均衡状态。整个过程可抽象为如下步骤:
算法第二步:计算动态权重
负载均衡算法会将静态权重load_rq、调度域参数、历史权重cpu_load[i]等信息,按照一定的规则计算出一个动态权重load,计算规则大致如下:
根据不同的均衡时机(见1.6)和调度域类型(见1.1)选择i,根据不同的均衡时机在cpu_load[i]和load_rq二者中选取出load_rq值或最小值或最大值作为当前动态权重。
例如,针对物理域和核域进行定时均衡操作时:i为2,动态权重取cpu_load[2]和load_rq二者中的最大值。
算法第三步:均衡判别规则
有了动态权重后,就可以用来判别系统负载是否均衡了:记当前cpu动态权重值为load_current,目标cpu动态权重值为load_target,判别规则类似如下:
imbalance_pct *load_current < 100*load_target
其中,imbalance_pct为当前cpu的某个调度域参数,例如物理和核域为125;
判别结果为真表示目标cpu负载过重,就会从目标cpu迁移任务至当前cpu。
算法均衡的时机共有定时均衡、闲时均衡、唤醒均衡和创建时均衡四种。
时钟中断时,会根据条件触发均衡软中断(内核为其设置了专门的软中断),
相应均衡函数为load_balance(),此函数只在调度组中的第一个cpu或第一个idle状态cpu上作均衡;此称为定时均衡(又可分为定时忙均衡和定时闲均衡)。
任务调度时,会检查当前cpu是否空闲,如果空闲则进行负载均衡,相应均衡函数为idle_balance(),与定时均衡相比,只要拉到任务就会返回属于轻量级均衡算法(但实时系统中,此为主要均衡时机,因为发生频率较高);此称为闲时均衡(又称NEW_IDLE均衡)。
唤醒任务时,会在try_to_wake_up()中根据当前cpu的调度域参数决定是否进行负载均衡;此称为唤醒均衡(唤醒均衡倾向于将被唤醒任务迁移至当前cpu所在的调度域中的某个cpu上,如果系统中存在过于频繁的唤醒,此操作在某些NUMA系统中反而会造成负载不均衡)。
创建任务时,会在sched_fork()/sched_exec()中,将此任务迁移至一个相对空闲的CPU上运行。称此为创建时均衡(包括fork和execve)。
满足1.5节负载均衡判别条件后,内核就会从目标cpu的运行队列中选择任务迁移至当前cpu;任务迁移包括拉任务(一次操作多个任务)和推任务(一次操作一个任务)。
拉任务指的是从目的CPU迁移任务至当前CPU,对应实现为pull_task();定时均衡、闲时均衡和唤醒均衡对应的是拉任务。
推任务指的是从当前CPU迁移任务至目的CPU,内核在每个运行队列中存放一个内核线程负责处理推任务请求;定时均衡多次失败时,置推任务标志,唤醒相应内核线程;创建时均衡(sched_exec),发推任务请求,唤醒相应内核线程;设置任务的cpu亲和掩码时,发推任务请求,唤醒相应内核线程。
实际负载均衡过程中,主要为拉任务操作。
算法第四步:任务迁移规则
任务迁移也要符合一定的规则,不同优先级按照由高到低、同种优先级按照LIFO的顺序进行选择任务。但对如下几种类型的任务不能被迁移:
1) 任务的cpu亲和(见1.9)不允许被迁移到当前cpu;
2) 为目标cpu上的当前运行任务;
3) 任务具有cache亲和性(参考try_to_wake_up);
4) 任务被迁移到当前cpu上会产生新的失衡。但如果任务迁移到当前cpu后优先级为最高,这时就考虑让优先级高的任务先运行;但如果要迁移的是目标cpu上的最高优先级任务,且最高优先级任务只有一个,也不允许迁移。
每一个任务都会关联一个cpu亲和属性,该属性值的每一位表示此任务运行及迁移所允许的cpu集合(置位表示允许),cpu亲和影响均衡操作中的任务迁移(见1.8)。
cpu亲和的设置可通过线程绑定和排它绑定来实现(见2.2和2.3)。
由上可知,与均衡算法相关的因素主要有:任务(均指处于运行状态的任务)优先级,任务个数,调度域参数,任务的cpu亲和。
另外,Linux负载均衡算法没有考虑中断、软中断以及CPU利用率等因素,也不能很好地满足对时延有要求的场景。
实例:
a. 双核系统中存在两个逻辑cpu,共四个FIFO任务t1, t2, t3和t4;
b. t1和t2在cpu0上运行,t3和t4在cpu1上运行;
c. 优先级高低关系:t1=t2,t3=t4;t1为死循环,t2等待中断唤醒执行完业务后睡眠,t3,t4互相唤醒且始终保持交替运行(两个cpu不会idle);
d. 任务都刚创建好准备运行,此时t1和t3分别为当前运行任务;
e. 任务均没有设置cpu亲和,即默认允许在所有cpu上运行或迁移。
分析:
此系统存在两个调度域:物理域和核域;由于cpu0和cpu1都不会idle,只
会发生定时均衡(无cpu间的唤醒均衡);对于物理域上的定时均衡,因为
只会在调度组中的第一个cpu或第一个idle状态cpu上作均衡,即cpu0上
作均衡(见1.6)。
1) 第一步:计算静态权重
load_rq0=177522*2(2个任务),load_rq1=177522*1(1个任务);
2) 第二步:计算动态权重
在物理或核域上进行定时均衡时,用的cpu_load[2],且取cpu_load[2]和
load_rq的最大值(见1.4),此时系统刚启动创建好任务,cpu_load[2]的
值(M-m)/4+m=(M+3m)/4<(M+3M)/4=M,即其值始终小于M,因此,
对于cpu0,load_current=load_rq0,load_target=load_rq1;
对于cpu1,load_current=load_rq1,load_target=load_rq0;
3) 第三步:均衡判别规则
计算公式[100 + (imbalance_pct - 100) / 2]*load_current < 100*load_target,调
度域只有物理和核两个域,imbalance_pct都为125,可简化为:
当112*load_current < 100*load_target时,表示需要做负载均衡进行任务迁移:
对于cpu0, 177522*2*112 < 177522*1*100不成立,不需要进行任务迁移。
对于cpu1, 177522*1*112 < 177522*2*100成立,需要进行任务迁移。
4) 第四步:任务迁移规则
对于cpu1的任务迁移,t1不满足迁移条件2,对于t2:
a.如果t2优先级低于t3,如果t2迁移到cpu1会造成新的失衡即不符合1.8
节中的迁移条件4,t2不允许迁移,均衡失败。
b.如果t2优先级高于t3,而任务迁移到当前cpu后优先级为最高,符合1.8
节中的迁移条件4,t2允许迁移,均衡成功。
结论:
如果t2优先级低于t3,t2将长期得不到调度,此种情况下要想t2得到及时
运行应用必须合理布置这些线程的cpu亲和。
可以从绑定和调度域两方面入手,对调度均衡相关问题进行调优。
不同类型的调度域参数的详细介绍请参考4.1节(阅读此节前请先了解)。
对于NUMA硬件架构,如果配置有NUMA功能,内核会为每个CPU初始化一个顶层NUMA域,此域许多参数与其它域不同,例如存在着唤醒拉操作,无NEW_IDLE均衡等等。
内核try_to_wake_up()中会检查本cpu所在的调度域的SD_WAKE_BALANCE标记进入唤醒均衡流程;实时系统中通常会存在太多的唤醒均衡,往往会导致负载不均,可以去掉此标志来禁用NUMA域的唤醒均衡(见1.6)。
附内核中默认的NUMA调度域(X86_64)参数值:
#define SD_NODE_INIT (struct sched_domain) { \
.span = CPU_MASK_NONE, \
.parent = NULL, \
.child = NULL, \
.groups = NULL, \
.min_interval = 8, \
.max_interval = 32, \
.busy_factor = 32, \
.imbalance_pct = 125, \
.cache_nice_tries = 2, \
.busy_idx = 3, \
.idle_idx = 2, \
.newidle_idx = 0, \
.wake_idx = 1, \
.forkexec_idx = 1, \
.flags = SD_LOAD_BALANCE \
| SD_BALANCE_FORK \
| SD_BALANCE_EXEC \
| SD_SERIALIZE \
| SD_WAKE_BALANCE, \
.last_balance = jiffies, \
.balance_interval = 1, \
.nr_balance_failed = 0, \
}
线程绑定是针对任务的,可将某个任务绑定到一个cpu集合。
对于特殊业务关联性比较强的任务,不允许在同一个cpu上运行,由于内核看不到此关联性不能保证这些任务不调度到同一个cpu上,就需要对这些任务执行线程绑定,这样内核在做负载均衡时就不会将这些任务迁移至不允许的cpu上面运行了(见1.8节的任务迁移条件1)。
负载均衡不单单是均衡任务,还要考虑在均衡任务的同时兼顾硬件资源的最高效的利用,因此负载均衡算法与硬件的架构密切相关,不同的硬件架构在算法中抽象出不同的调度域。硬件架构主要有:UP架构,SMP 架构和NUMA 架构,这些架构从硬件层次可分为:NUMA节点、物理、核和超线程四个层次,分别对应内核负载均衡算法中的四种调度域类型。
可通过“/proc/sys/kernel/sched_domain/cpu/domain/”查看或根据需要修改不同CPU对应的域参数值,此功能的开启需要在配置内核时选用宏CONFIG_SMP,CONFIG_SCHED_DEBUG和CONFIG_SYSCTL。
实际应用中,需要针对单板的硬件架构选用合适的内核宏进行调度域的初始化。例如,针对多核以及超线程的架构,需要配置宏CONFIG_SMP,CONFIG_SCHED_MC和CONFIG_SCHED_SMT来通知内核来为此单板建立核域和超线程域。
内核的调度域结构:
1) 结构中前三个字段的作用是将内核启动后初始化好的所有的调度域和调度组联系起来,span字段表示调度域包含的CPU集合,这四个字段的含义参考下1.1节中的图1就明白了。
2) last_balance记录了上次做定时均衡的时间点,单位为tick,只在定时均衡中更新,和balance_interval一起用于计算下次做定时均衡的时间;
5) imbalance_pct用作均衡判别,见1.5节;此值增大可放宽负载失衡的判别条件,从而降低负载均衡的频率;
7) busy_idx, idle_idx, newidle_idx, wake_idx, forkexec_idx用作下标选择运行队列中的历史权重cpu_load[]数组中的元素,见1.4节;
8) flags用于配置不同的均衡算法以及一些特殊的标志,其值含义如下:
a) SD_LOAD_BALANCE表示做负载均衡;做负载均衡就会做定时均衡,定
c) SD_BALANCE_EXEC表示做创建时均衡中的execve均衡;
d) SD_BLANCE_FORK表示做创建时均衡中的fork均衡;
e) SD_WAKE_IDLE表示开启超线程后在try_to_wake_up()中将被唤醒任务
f) SD_WAKE_AFFINE表示在try_to_wake_up()中考虑任务的cache亲和,如
g) SD_WAKE_BALANCE功能类似于SD_WAKE_AFFINE,只是算法判别
h) SD_SHARE_CPUPOWER表示共享CPU的处理能力,主要用于同一个核的超线程CPU间;从而让操作系统感知到超线程硬件特性予以特殊处理;
i) SD_SHARE_PKG_RESOURCES用于初始化调度组的CPU能力;
j) SD_SERIALIZE主要用于不同CPU间的负载均衡算法的串行化(增加了一把自旋锁),一般NUMA域可设置此选项;
下面详细描述当前内核对任务亲和性的计算过程,以下使用本地CPU代表执行唤醒操作的CPU,而目的CPU代表任务所属的CPU:
4) 以如下条件判断目的CPU与任务之间不具亲和性,如果条件成立则选择本地CPU;否则认为目的CPU与任务之间具有亲和性,进行步骤5)的判断。
(tl<=load && tl+target_load(cpu,idx)<=tl_per_task) ||
100*(tl+p->load_weight)<=imbalance*load
假设cond1=(tl<=load && tl+target_load(cpu,idx)<=tl_per_task)
cond2=100*(tl+p->load_weight)<=imbalance*load
上式可以简化为表达式1: (cond1 || cond2);
那么具有亲和性(任务仍选择目的CPU)的条件可以改写为表达式2: (!cond1 && !cond2);
如果条件cond1和cond2其中一个为真则认为目的CPU与任务之间不具亲和性。下面对这两个条件进行进一步分析:
等价于(tl+p->load_weight) > (imbalance/100)*load
i. 本地CPU负载小于目的CPU,且处于失衡状态。但任务权重很大,在本地CPU加上任务以后打破了失衡,使本地CPU负载反大于目的CPU而产生新的失衡;但如果将任务加载目的CPU上反而会扩大失衡。
ii. 本地CPU负载接近目的CPU,处于均衡状态。本地CPU权重加上任务权重以后造成负载的失衡。
iii. 本地CPU负载大于目的CPU,且处于失衡状态。这时本地CPU加上任务以后使失衡更加扩大。
对于情况a)鼓励将任务加在本地CPU上;对于情况b)在考虑cache有效性的因素下鼓励任务加在目的CPU上;对于情况c)鼓励任务加在目的CPU上。
因此在调度域设置允许的情况下需要对本地CPU和目的CPU的负载进行判断,判断的条件如下:
imbalance*this_load <= 100*load
等价于(imbalance/100)*this_load <= load
其中this_load是本地CPU的权重值;imbalance是负载平衡系数;load是目的CPU的权重值。
(imbalance/100)恒大于1,因此可以看出,如果条件成立那么目的CPU与本地CPU的权重值之比超过负载平衡系数,而产生负载失衡。
如果以上的条件成立则出现了情况a),这时应选择本地CPU;否则应选择目的CPU。
然而实时性系统多为实时任务,实时任务的权重值固定为177522(见1.2节),再加上某些业务过高的网络软中断的影响,导致内核原有的亲和算法不能很好地发挥作用,反而会起到副作用。
1. 在电信级业务的场景中,NUMA域默认参数存在两个问题。
a)产品会伴随着大量的网络收发包,进而产生大量的网口中断和软中断,频繁的网络软中断就会产生大量的唤醒任务操作,导致唤醒均衡算法不停地将任务拉至中断所在的NUMA节点的CPU上;
b)同时很多业务对系统中每一个CPU的利用率比较敏感,要求各个CPU利用率严格均衡。
第1个需要去掉NUMA域的唤醒均衡;第2个需要配上NUMA域的闲时均衡。
2. 超线程域默认参数配有SD_WAKE_AFFINE算法。
此参数在某些业务场景会导致系统性能发生抖动,进而影响系统性能;但与SD_WAKE_BALANCE算法相比影响相对较小,在出现开启硬件超线程特性后如果出现了性能抖动,就可以考虑去除此参数。
内核选项的配置请务必与实际的硬件架构配置,这样才能达到最好的性能和均衡效果;例如,对非NUMA架构的硬件单板配上了CONFIG_NUMA就有可能存在NUMA调度域。
CONFIG_SMP, CONFIG_SCHED_MC, CONFIG_SCHED_SMT
4. 单节点,多物理CPU,每个物理CPU有多个核,无超线程
5. 单节点,多物理CPU,每个物理CPU有多个核,有超线程
CONFIG_SMP, CONFIG_SCHED_MC, CONFIG_SCHED_SMT
6. 多节点,多物理CPU,每个物理CPU有多个核,无超线程
CONFIG_SMP, CONFIG_NUMA, CONFIG_SCHED_MC
7. 多节点,多物理CPU,每个物理CPU有多个核,有超线程
CONFIG_SMP, CONFIG_NUMA, CONFIG_SCHED_MC, CONFIG_SCHED_SMT
2) 调度域中只有一个调度组,且无SD_WAKE_IDLE, SD_WAKE_AFFINE和SD_WAKE_BALANCE标志,则将此调度域优化掉;
3) 两个相邻的父子调度域中所包含的CPU集合相同,并且flags值也相同,则将父调度域优化掉。
详细实现细节请参考cpu_attach_domain()内核代码。
会严重影响系统负载均衡效果,以及可能会对系统造成较大性能或性能抖动问题几个调度域参数总结如下:
1. flags选择不同的负载均衡算法,其值的不同会直接影响系统的负载均衡效果以及性能;其中最重要的是唤醒均衡和闲时均衡算法。
Linux-2.6.21的负载均衡相关推荐
- 基于redhat linux虚拟服务器的web负载均衡集群(piranha+LVS)
基于redhat linux虚拟服务器的web负载均衡集群 硬件环境 分发机 LB1:192.168.0.129 LB2:192.168.0.130 真实节点主机 NODE1:192.168.0.13 ...
- Linux的Nginx九:负载均衡
简介 当一台服务器的性能达到极限时,我们可以使用服务器集群来提高网站的整体性能.那么,在服务器集群中,需要有一台服务器充当调度者的角色,用户的所有请求都会首先由它接收,调度者再根据每台服务器的负载情况 ...
- 查看linux cpu负载均衡,关于linux内核cpu进程的负载均衡
2.6内核中进程调度模块的负载均衡行为分为"拉"和"推",推这里不考虑,关于拉均衡有一篇文章特别好,具体出处就不记得了,我当时用的百度快照,那篇文章我认为最精彩 ...
- [Linux]使用宝塔面板做负载均衡时遇到的问题和解决办法
[Linux]使用宝塔面板做负载均衡时遇到的问题和解决办法 参考文章: (1)[Linux]使用宝塔面板做负载均衡时遇到的问题和解决办法 (2)https://www.cnblogs.com/guan ...
- 深入理解Linux 进程管理之CFS负载均衡
什么是负载均衡? 为了CPU之间减少"干扰",每个CPU上都有一个任务队列.运行的过程种可能会出现有的CPU"忙的一笔",有的CPU"闲的蛋疼&quo ...
- 双网卡聚合 linux,linux双网卡聚合 做负载均衡
<linux双网卡聚合 做负载均衡>由会员分享,可在线阅读,更多相关<linux双网卡聚合 做负载均衡(5页珍藏版)>请在人人文库网上搜索. 1.linux双网卡聚合 做负载均 ...
- Linux企业运维——LVS负载均衡
Linux企业运维--LVS负载均衡 文章目录 Linux企业运维--LVS负载均衡 一.LVS简介 二.DR模式 三.使用DR模式实现负载均衡 四.问题解决 五.LVS的10个调度算法简介 一.LV ...
- linux window nginx性能,Nginx负载均衡搭建(Window与Linux)
windows上搭建nginx负载均衡 1.准备几台http服务器软件,这里选用一台apache一台tomcat apache(windows)下载链接:https://www.apachehaus. ...
- linux下一个apache+tomcat负载均衡和集群
先说一下我的环境 一个ubuntu虚拟机, 一个apache2.2示例 两tomcat1.7示例 1.安装apacheserver sudo apt-get install apache2 假设要重新 ...
- Linux系统(五)负载均衡LVS集群之DR模式
序言 DR模式是lvs集群中三种负载均衡模式的其中一种,那么上一篇中我写啦关于NAT模式的搭建与原理,为什么还要有DR模式与IP隧道模式呢? 首先我们来看3张图.LVS/NAT模式如下图: LVS/I ...
最新文章
- Python+Anaconda中库的安装
- 3DSSD:基于点云的single-stage物体检测模型 | CVPR2020
- C#使用post提交http请求
- 1000万存在银行,一年的利息够日常生活费吗?
- RPC Over HTTPS 访问Exchange 邮箱
- HDOJ 1713 相遇周期 (最大公约数与最小公倍数)
- SSM整合(spring mybatis)图书
- 使用procexp.exe查看线程详细信息
- GridView 样式
- 插桩valgrind_动态二进制插桩的原理和基本实现过程(一)
- FPGA之三八译码器
- 【Python】P2440 木材加工
- 小程序云函数new Date()获取的时间和new Date().getDay()获取的时间不一致 / 云函数存入的时间不对 /小程序云开发配置时区
- 【二分】Caravan Robbers
- 呼叫中心中继网关参数选型
- c语言实验选择结构程序设计
- 222222222222
- Linux课程笔记 硬盘介绍及硬盘分区
- 修改java环境变量_怎么配置java环境变量
- 跑步耳机哪种好,目前最适合运动的五款耳机推荐
热门文章
- AS3 库资源 很多非常有用的类库
- ruby学习笔记(7)
- Delphi 与 DirectX 之 DelphiX(91): TDIB.DrawMono();
- ubuntu安装时出现11:资源暂时不可用
- Apache Qpid Proton 0.16.0,轻量通信库
- 如何选择和部署长尾关键词
- 十天学会php之第二天
- 大学的最后一年有一门课程叫“人生”。
- U盘病毒“替身”大量交叉感染 打印店电脑助扩散
- SnapKit 是怎样炼成的 | 掘金技术征文