uWSGI cheaper子系统 —— 适应性进程生成¶
uWSGI通过可插拔算法,提供了动态扩展运行的worker数量的能力。使用 uwsgi --cheaper-algos-list
来获取可用算法的列表。
用法¶
要启用cheaper模式,则添加 cheaper = N
选项到uWSGI配置文件中,其中,N是uWSGI可以运行的worker的最小数目。
cheaper
必须小于配置的worker最大数目
(workers
或者 processes
选项)。
# set cheaper algorithm to use, if not set default will be used
cheaper-algo = spare
# minimum number of workers to keep at all times
cheaper = 2
# number of workers to spawn at startup
cheaper-initial = 5
# maximum number of workers that can be spawned
workers = 10
# how many workers should be spawned at a time
cheaper-step = 1
这个配置将会告诉WSGI负载之下最多运行10个worker。如果应用处于idle状态,那么uWSGI将会停止worker,但它总是会让至少2个worker在运行。使用 cheaper-initial
,你可以控制在启动的时候应该生成几个worker。如果你的平均负载要求比最小数量的worker还要多,那么你可以让它们立即生成,然后在负载足够低的情况下,”省省” (杀死它们)。当cheaper算法决定它需要更多的worker时,它会生成它们的 cheaper-step
。这在你有一个高的最大worker数的时候有用 —— 否则在突然尖峰负载的情况下,它会花费大量的时间来一个一个生成足够的worker。
设置内存限制¶
自1.9.16起,可以设置rss内存限制,从而在没有达到进程数量限制,但是所有worker使用的rss内存的总数量达到指定限制的时候停止cheapter生成新worker。
# soft limit will prevent cheaper from spawning new workers
# if workers total rss memory is equal or higher
# we use 128MB soft limit below (values are in bytes)
cheaper-rss-limit-soft = 134217728
# hard limit will force cheaper to cheap single worker
# if workers total rss memory is equal or higher
# we use 160MB hard limit below (values are in bytes)
cheaper-rss-limit-hard = 167772160
注意:
- 硬限制是可选的,可以单独使用软限制。
- 硬限制值必须比软限制值高,这两个值都不应该彼此太接近。
- 硬限制值应该是软限制值 + 至少是用于给定应用的平均worker内存使用。
- 软限制值是cheaper的限制器,它不会生成更多的worker,但已运行中的worker的内存使用可能会增长,要处理它,也可以设置reload-on-rss。建议为应用内存使用cgroups设置牢不可破的限制。
spare
cheaper算法¶
这是默认的算法。如果所有的worker在 cheaper_overload
秒内都处于忙碌状态,那么uWSGI将会生成新的worker。当负载消失的时候,它将开始一次停止一个进程。
spare2
cheaper算法¶
这个算法与spare类似,但适用于更快增长worker数,并且较慢降低worker数的大规模。
当idle状态的worker数比 cheaper
指定的数量小的时候,它生成 (cheaper
-
idle状态的worker数) worker。一次生成worker的数量的最大值可以通过 cheaper-step
来限制。例如。 cheaper
是4,有2个idle状态的worker,并且 cheaper-step
为1,它会生成1个worker。
当idle状态的worker数比 cheaper
大时,它会增加内部计数器。当idle状态的worker数小于或等于 cheaper
时,会重置计数器。
当计数器等于 cheaper-idle
时,省掉一个worker,然后重置计数器。
样例配置:
workers = 64 # maximum number of workers
cheaper-algo = spare2
cheaper = 8 # tries to keep 8 idle workers
cheaper-initial = 8 # starts with minimal workers
cheaper-step = 4 # spawn at most 4 workers at once
cheaper-idle = 60 # cheap one worker per minute while idle
backlog
cheaper算法¶
注解
backlog
只适用于Linux以及TCP socket (不是UNIX域的socket)。
如果socket的监听队列有超过 cheaper_overload
个请求在等待处理,那么uWSGI将会生成新的worker。如果积压降低,它将会开始一次杀掉一个进程。
busyness
cheaper算法¶
注解
这个算法是可选的,只有在编译并加载了 cheaper_busyness
插件的情况下,才可以用它。
该插件实现了这样一个算法,基于给定时间周期内的平均利用率来添加或移除worker。它的目标是保持在任意有比需要的最小值的worker数还多的worker可用,这样,应用将总是能够处理新的请求。如果你只想运行最小数量的worker,那么使用spare或者backlog算法。
使用该插件主要是因为spare和backlog插件工作的方式引发非常激进的缩放行为。如果你设置一个低的 cheaper
值
(例如,1),那么uWSGI将会一直只运行1个worker,然后只在运行中的那个worker过载的时候才生成新的worker。如果应用要求更多的worker,那么uWSGI将会一直生成停止worker。只有在非常低的负载期间,最小数量的worker才够。
Busyness算法试着与其相反:按需生成worker,然后只有在很有肯能不需要它们的时候才停止一些。这应该会使得worker数量更加稳定,并且更少进行重新生成。由于大部分的时间里,我们比实际需要的拥有更多的worker,因此平均应用响应时间应该比使用其他插件更低。
选项:
cheaper-overload¶
指定窗口,以秒为单位,用于追踪worker的平均busyness。例如:
cheaper-overload = 30
这个选项将会每30秒检查busyness。如果在上一个30秒期间,所有worker都是运行3秒,并且在剩下的27秒内出于idle状态,那么计算所得的busyness将会是10% (3/30)。这个值将会决定uWSGI可以多快响应负载尖峰。至少每 cheaper-overload
秒会生成新的worker (除非你在Linux上运行uWSGI —— 详情请见
cheaper-busyness-backlog-alert
)。
如果你想要更快地对负载尖峰进行响应,那么为这个值取一个小的值,这样,就会更频繁地计算busyness。记住,这可能会导致比需要的更频繁地启动/停止worker,因为每一个小的尖峰都会生成新的worker。使用一个高的 cheaper-overload
值,则worker数量将会更少发生改变,因为较长的周期将会吞掉所有负载短尖峰和极端值。默认是3,对于busyness插件,最好使用较高的值 (10-30)。
cheaper-step¶
当算法决策需要worker的时候,要生成worker的数目。默认是1。
cheaper-initial¶
启动应用的时候,启动的worker数。在应用启动之后,如果需要的话,算法可以停止或者启动worker。
cheaper-busyness-max¶
这是允许的最大busyness。每次上一个 cheaper-overload
秒计算的busyness比这个值高的时候,uWSGI将会生成
cheaper-step
新worker。默认是50.
cheaper-busyness-min¶
这是最小的busyness。如果当前的busyness位于该值之下,那么应用就会被认为处在一个“idle周期”中,而uWSGI将会开始对它们进行计数。一旦到达idle周期的所需数目,uWSGI将会杀掉一个worker。默认是25.
cheaper-busyness-multiplier¶
这个选项告诉uWSGI在停止一个worker之前,我们需要多少个idle周期。 在到达这个限制之后,uWSGI将会停止一个worker,并且设置这个计数器。
例如:
cheaper-overload = 10
cheaper-busyness-multiplier = 20
cheaper-busyness-min = 25
如果连续20次检查,每10秒执行一次(总共200秒),平均worker busyness低于25%,那么将会停止一个worker。如果平均busyness跳到 cheaper-busyness-max
以上,idle周期计数器将会被重置,并且我们生成新的worker。如果在idle周期计数期间,平均busyness跳到 cheaper-busyness-min
以上,但仍然低于 cheaper-busyness-max
,那么idle周期计数器将会被调整,而我们需要等待另外一个idle周期。如果在idle周期计数期间,平均busyness跳到 cheaper-busyness-min
以上,但仍然低于 cheaper-busyness-max
连续3次,那么会重置idle周期计数器。
cheaper-busyness-penalty¶
当worker因为足够的idle周期停止,然后快速(比worker所需的同等时间少)又生成回来的时候,uWSGI将会自动调整停止worker所需的idle周期数,然后我们会增加 cheaper-busyness-multiplier
值这个值。默认是1.
例如:
cheaper-overload = 10
cheaper-busyness-multiplier = 20
cheaper-busyness-min = 25
cheaper-busyness-penalty = 2
如果连续20次检查,每10秒执行一次(总共200秒),平均worker busyness低于25%,那么将会停止一个worker。如果新的worker在少于200秒内生成 (时间从我们生成它之前的最后一个worker算起),那么 cheaper-busyness-multiplier
值将会被增加到22 (20+2)。现在,我们将需要等待220秒 (22*10)来cheap另一个worker。这个选项用于防止worker一直启动和停止,因为一旦我们停止了一个worker,busyness也许会提供以致于够着 cheaper-busyness-max
。没有这个,或者如果被不充分地调整,我们会陷入一个停止/启动反馈循环。
cheaper-busyness-verbose¶
这个选项启用 cheaper_busyness
插件的调试日志。
cheaper-busyness-backlog-alert¶
这个选项只有在Linux上能用。它用于允许快速响应负载峰值,即使使用了高的 cheaper-overload
值。在每个uWSGI master周期(默认是1秒)中,会检查当前的监听队列。如果它比这个值高,那么会生成一个紧急worker。当使用这个选项的时候,使用高的 cheaper-overload
值以拥有更平滑的worker数缩放是安全的。默认是33.
cheaper-busyness-backlog-multiplier¶
这个选项只有在Linux上能用。它就像 cheaper-busyness-multiplier
,除了它只用于监听队列高于 cheaper-busyness-backlog-alert
时的紧急worker生成。
紧急worker是在大负载峰值下生成的,用以防止当前运行的worker过载。有时,负载峰值是随机并且短暂的,这会生成大量的紧急worker。在这种情况下,获得那些worker之前,我们会需要等待几个周期。这提供了一个交替的multiplier来更快的获得这些进程。默认是3.
cheaper-busyness-backlog-step¶
这个选项只有在Linux上能用。 它设置当监听队列高于 cheaper-busyness-backlog-alert
时生成的紧急worker数。默认是1.
cheaper-busyness-backlog-nonzero¶
这个选项只有在Linux上能用。如果超过N秒,请求监听队列都>0,那么它将会生成新的紧急worker。它用来保护服务器不受这样的边缘情况之害:只有单个worker运行,并且这个worker在处理一个长时间运行的请求。如果uWSGI收到了新的请求,那么它们将会待在请求队列中,直到那个长时间运行请求完成。使用这个选项,我们可以检测这样一个条件,并生成新的worker来防止入队请求超时。默认是60.
关于Busyness的一些注意事项¶
通过实验确定设置。对于每个人而言,没有哪个万金油值应该被使用。测试并挑选对你来说的最佳值。监控uWSGI统计数据 (例如,通过Carbon) 会使得决定使用哪个值简单一些。
不要指望busyness恒久不变。它会经常改变。最终,真正的用户是以非常随机的方式与你的应用进行交互的。推荐使用更长的–cheaper-overload值 (>=30) ,使得尖峰更少。
如果你想要运行这个插件的一些基准,那么你应该使用添加随机性到工作负载的工具。
使用少量的worker (2-3),启动新的worker,或者停止一个worker或许会大大影响busyness。如果你有2个worker,带50%的busyness,那么停止其中一个将会增加busyness到100%。记住,当挑选最小和最大程度的时候,如果大部分时间只有少量的worker在运行,那么max应该超过min的两倍,否则,每当停止一个worker的时候,它将会增加busyness到超过max程度。
使用少量的worker (1-4) 和默认设置,期望这个插件将会保持平静busyness低于最小程度;调整程度来补偿。
使用处理负载所需的更多数量的worker,worker计数器将会稳定在接近最小busyness程度的某处,在这个值附近上下波动。
在对这个插件进行实验的时候,建议启用
--cheaper-busyness-verbose
,从而知道它正在做什么。一个样例日志如下。# These messages are logged at startup to show current settings [busyness] settings: min=20%, max=60%, overload=20, multiplier=15, respawn penalty=3 [busyness] backlog alert is set to 33 request(s) # With --cheaper-busyness-verbose enabled You can monitor calculated busyness [busyness] worker nr 1 20s average busyness is at 11% [busyness] worker nr 2 20s average busyness is at 11% [busyness] worker nr 3 20s average busyness is at 20% [busyness] 20s average busyness of 3 worker(s) is at 14% # Average busyness is under 20%, we start counting idle cycles # we have overload=20 and multiplier=15 so we need to wait 300 seconds before we can stop worker # cycle we just had was counted as idle so we need to wait another 280 seconds # 1 missing second below is just from rounding, master cycle is every 1 second but it also takes some time, this is normal [busyness] need to wait 279 more second(s) to cheap worker # We waited long enough and we can stop one worker [busyness] worker nr 1 20s average busyness is at 6% [busyness] worker nr 2 20s average busyness is at 22% [busyness] worker nr 3 20s average busyness is at 19% [busyness] 20s average busyness of 3 worker(s) is at 15% [busyness] 20s average busyness is at 15%, cheap one of 3 running workers # After stopping one worker average busyness is now higher, which is no surprise [busyness] worker nr 2 20s average busyness is at 36% [busyness] worker nr 3 20s average busyness is at 24% [busyness] 20s average busyness of 2 worker(s) is at 30% # 30% is above our minimum (20%), but it's still far from our maximum (60%) # since this is not idle cycle uWSGI will ignore it when counting when to stop worker [busyness] 20s average busyness is at 30%, 1 non-idle cycle(s), adjusting cheaper timer # After a while our average busyness is still low enough, so we stop another worker [busyness] 20s average busyness is at 3%, cheap one of 2 running workers # With only one worker running we won't see per worker busyness since it's the same as total average [busyness] 20s average busyness of 1 worker(s) is at 16% [busyness] 20s average busyness of 1 worker(s) is at 17% # Shortly after stopping second worker and with only one running we have load spike that is enough to hit our maximum level # this was just few cycles after stopping worker so uWSGI will increase multiplier # now we need to wait extra 3 cycles before stopping worker [busyness] worker(s) respawned to fast, increasing cheaper multiplier to 18 (+3) # Initially we needed to wait only 300 seconds, now we need to have 360 subsequent seconds when workers busyness is below minimum level # 10*20 + 3*20 = 360 [busyness] worker nr 1 20s average busyness is at 9% [busyness] worker nr 2 20s average busyness is at 17% [busyness] worker nr 3 20s average busyness is at 17% [busyness] worker nr 4 20s average busyness is at 21% [busyness] 20s average busyness of 4 worker(s) is at 16% [busyness] need to wait 339 more second(s) to cheap worker