做数学:使用编排器扩展微服务应用程序
已发表: 2022-03-11微服务应用程序架构继续侵入软件设计并不奇怪。 分配负载、创建高可用性部署和管理升级更加方便,同时简化了开发和团队管理。
但如果没有容器编排器,情况肯定就不一样了。
想要使用它们的所有关键功能很容易,尤其是自动缩放。 多么幸运,看着容器部署一整天都在波动,大小适中以处理当前负载,从而腾出我们的时间来完成其他任务。 我们对我们的容器监控工具所显示的内容感到自豪; 与此同时,我们刚刚配置了一些设置——是的,这就是(几乎)创造魔法所需要的全部!
这并不是说没有理由为此感到自豪:我们确信我们的用户拥有良好的体验,并且我们不会在超大的基础设施上浪费任何资金。 这已经相当可观了!
当然,到达那里是多么美妙的旅程啊! 因为即使最后需要配置的设置不多,但在开始之前,它比我们通常想象的要复杂得多。 最小/最大副本数、放大/缩小阈值、同步周期、冷却延迟——所有这些设置都紧密相连。 修改一个很可能会影响另一个,但您仍然必须安排一个适合您的应用程序/部署和您的基础架构的平衡组合。 然而,你不会在互联网上找到任何食谱或任何神奇的公式,因为它在很大程度上取决于你的需求。
我们大多数人首先将它们设置为“随机”或默认值,然后根据我们在监控时发现的内容进行调整。 这让我想到:如果我们能够建立一个更“数学”的程序来帮助我们找到获胜的组合呢?
计算容器编排参数
当我们考虑为应用程序自动扩展微服务时,我们实际上是在关注两个主要方面的改进:
- 确保在负载快速增加的情况下部署可以快速扩展(因此用户不会面临超时或 HTTP 500)
- 降低基础架构的成本(即防止实例负载不足)
这基本上意味着优化容器软件阈值以扩大和缩小规模。 (Kubernetes 的算法对这两者只有一个参数)。
稍后我将展示所有与实例相关的参数都与 upscale-threshold 相关联。 这是最难计算的——因此有这篇文章。
注意:关于在集群范围内设置的参数,我没有什么好的程序,但是在本文的最后,我会介绍一个软件(静态网页),在计算时会考虑到它们实例的自动缩放参数。 这样,您将能够改变它们的值以考虑它们的影响。
计算放大阈值
要使此方法起作用,您必须确保您的应用程序满足以下要求:
- 负载必须均匀分布在应用程序的每个实例中(以循环方式)
- 请求时间必须短于容器集群的负载检查间隔。
- 您必须考虑在大量用户(稍后定义)上运行该过程。
出现这些情况的主要原因是该算法不会将负载计算为每个用户,而是作为分布(稍后解释)。
得到所有高斯
首先,我们必须为负载快速增加或最坏情况下的定义制定一个定义。 对我来说,一个好的翻译方法是:让大量用户在短时间内执行消耗资源的操作——而且总是有可能在另一组用户或服务正在执行其他任务时发生这种情况。 所以让我们从这个定义开始,试着提取一些数学。 (准备好你的阿司匹林。)
引入一些变量:
- $N_{u}$,“大量用户”
- $L_{u}(t)$,单个用户执行“资源消耗操作”产生的负载($t=0$指向用户开始操作的时刻)
- $L_{tot}(t)$,总负载(由所有用户产生)
- $T_{tot}$,“短时间”
在数学世界中,谈到大量用户同时执行相同的事情,用户随时间的分布遵循高斯(或正态)分布,其公式为:
\[G(t) = \frac{1}{\sigma \sqrt{2 \pi}} e^{\frac{-(t-\mu)^2}{2 \sigma^2}}\]这里:
- µ是期望值
- σ是标准差
它的图形如下($µ=0$):
可能会让人想起你上过的一些课——没什么新鲜的。 然而,我们在这里确实面临第一个问题:为了在数学上准确,我们必须考虑从 $-\infty$ 到 $+\infty$ 的时间范围,这显然无法计算。
但是查看图表,我们注意到区间 $[-3σ, 3σ]$ 之外的值非常接近于零并且变化不大,这意味着它们的影响可以忽略不计,可以放在一边。 更是如此,因为我们的目标是测试扩展我们的应用程序,所以我们正在寻找大量用户的变化。
另外,由于区间 $[-3σ, 3σ]$ 包含了我们 99.7% 的用户,它足够接近总数来处理它,我们只需将 $N_{u}$ 乘以 1.003 即可弥补区别。 选择这个区间给我们 $µ=3σ$(因为我们要从 $t=0$ 开始工作)。
关于与 $T_{tot}$ 的对应关系,选择它等于 $6σ$ ($[-3σ, 3σ]$) 将不是一个好的近似值,因为 95.4% 的用户都在区间 $[- 2σ, 2σ]$,持续 $4σ$。 所以选择 $T_{tot}$ 等于 $6σ$ 只会为 4.3% 的用户增加一半的时间,这并不真正具有代表性。 因此我们选择$T_{tot}=4σ$,我们可以推导出:
\(σ=\frac{T_{tot}}{4}\) 和 \(µ=\frac{3}{4} * T_{tot}\)
这些价值观只是从帽子里拿出来的吗? 是的。 但这就是他们的目的,这不会影响数学过程。 这些常数是为我们准备的,并定义了与我们的假设相关的概念。 这仅意味着现在我们已经设置了它们,我们最坏的情况可以翻译为:
99.7% 的 $N{u}$ 产生的负载,执行消费操作 $L{u}(t)$,其中 95.4% 的人在 $T{tot}$ 期间执行此操作。
(在使用网络应用程序时,这是值得记住的。)
将之前的结果注入到用户分布函数(高斯)中,我们可以将方程简化如下:
\[G(t) = \frac{4 N_{u}}{T_{tot} \sqrt{2 \pi}} e^\frac{-(4t-3T_{tot})^2}{T_{tot }^2}\]从现在开始,定义了 $σ$ 和 $µ$,我们将处理区间 $t \in [0, \frac{3}{2}T_{tot}]$(持续 $6σ$)。
总用户负载是多少?
自动扩展微服务的第二步是计算 $L_{tot}(t)$。
由于$G(t)$是一个分布,要检索某个时间点的用户数,我们必须计算它的积分(或者使用它的累积分布函数)。 但由于并非所有用户都同时开始操作,因此试图引入 $L_{u}(t)$ 并将方程简化为可用公式将是一团糟。

因此,为了使这更容易,我们将使用黎曼和,这是一种使用小形状的有限和来近似积分的数学方法(我们将在这里使用矩形)。 形状(细分)越多,结果就越准确。 使用细分的另一个好处来自于我们可以认为一个细分中的所有用户同时开始他们的操作。
回到黎曼和,它具有以下与积分相关的性质:
\[\int_{a}^{b} f( x )dx = \lim_{n \rightarrow \infty } \sum_{k=1}^{n} ( x_{k} - x_{k-1} ) f( x_{k} )\]$x_k$ 定义如下:
\[x_{ k } = a + k\frac{ b - a }{ n }, 0 \leq k \leq n\]这是真的:
- $n$ 是细分的数量。
- $a$ 是下限,这里是 0。
- $b$ 是上限,这里是 $\frac{3}{2}*T_{tot}$。
- $f$ 是近似其面积的函数(此处为 $G$)。
注意:细分中存在的用户数不是整数。 这是两个先决条件的原因:拥有大量用户(因此小数部分不会造成太大影响),以及需要将负载均匀分布在每个实例上。
另请注意,我们可以在黎曼和定义的右侧看到细分的矩形形状。
现在我们有了黎曼和公式,我们可以说时间 $t$ 的负载值是每个细分的用户数乘以相应时间的用户负载函数的总和。 这可以写成:
\[L_{ tot }( t ) = \lim_{n \rightarrow \infty} \sum_{ k=1 }^{ n } ( x_{k} - x_{k-1} )G( x_{k} ) L_{ u }( t - x_{k} )\]替换变量并简化公式后,变为:
\[L_{ tot }( t ) = \frac{6 N_{u}}{\sqrt{2 \pi}} \lim_{n \rightarrow \infty} \sum_{ k=1 }^{ n } (\ frac{1}{n}) e^{-{(\frac{6k}{n} - 3)^{2}}} L_{ u }( t - k \frac{3 T_{tot}}{2n })\]瞧! 我们创建了加载函数!
寻找放大阈值
最后,我们只需要运行一个二分法算法,该算法会改变阈值,以找到每个实例的负载在整个负载函数中永远不会超过其最大限制的最大值。 (这是应用程序所做的。)
推导其他编排参数
一旦找到放大阈值 ($S_{up}$),其他参数就很容易计算。
从 $S_{up}$ 您将知道您的最大实例数。 (您还可以在负载函数上查找最大负载,然后除以每个实例的最大负载,四舍五入。)
必须根据您的基础架构定义实例的最小数量 ($N_{min}$)。 (我建议每个 AZ 至少有一个副本。)但它还需要考虑负载函数:由于高斯函数增加得非常快,负载分布在开始时(每个副本)更加强烈,所以你可能需要增加最小副本数来缓冲这种影响。 (这很可能会增加您的 $S_{up}$。)
最后,一旦定义了最小副本数,就可以计算缩减阈值 ($S_{down}$),考虑以下因素: 因为缩减单个副本对其他实例的影响比缩减$N_{min}+1$ 到 $N_{min}$,我们必须确保在缩小后不会立即触发放大阈值。 如果允许,这将产生溜溜球效果。 换一种说法:
\[( N_{ min } + 1) S_{ down } < N_{ min }S_{ up }\]要么:
\[S_{ 下 } < \frac{N_{ 分钟 }}{N_{min}+1}S_{ 上 }\]此外,我们可以承认,您的集群配置为在缩减之前等待的时间越长,将 $S_{down}$ 设置为更接近上限就越安全。 再一次,你必须找到一个适合你的平衡点。
请注意,当使用带有自动缩放器的 Mesosphere Marathon 编排系统时,可以从缩减中一次删除的最大实例数与AS_AUTOSCALE_MULTIPLIER
($A_{mult}$) 相关联,这意味着:
用户加载功能呢?
是的,这有点问题,而且不是最容易用数学解决的问题——如果它甚至可能的话。
要解决此问题,想法是运行应用程序的单个实例,并增加重复执行相同任务的用户数量,直到服务器负载达到分配的最大值(但不超过)。 然后除以用户数,计算请求的平均时间。 对要集成到用户加载功能中的每个操作重复此过程,添加一些时间,然后就可以了。
我知道这个过程意味着考虑到每个用户请求在其处理过程中都有一个恒定的负载(这显然是不正确的),但是大量用户会产生这种效果,因为他们每个人都不是同时在同一个处理步骤. 所以我想这是一个可以接受的近似值,但它再次暗示你正在与大量用户打交道。
您也可以尝试使用其他方法,例如 CPU 火焰图。 但我认为创建一个将用户操作与资源消耗联系起来的准确公式将非常困难。
介绍app-autoscaling-calculator
现在,对于贯穿始终提到的小型 Web 应用程序:它将您的加载函数、容器编排器配置和其他一些通用参数作为输入,并返回扩展阈值和其他与实例相关的数据。
该项目托管在 GitHub 上,但它也有一个可用的实时版本。
这是 Web 应用程序给出的结果,针对测试数据运行(在 Kubernetes 上):
扩展微服务:不再在黑暗中摸索
当涉及到微服务应用架构时,容器部署成为整个基础设施的中心点。 并且编排器和容器配置得越好,运行时就会越流畅。
我们这些 DevOps 服务领域的人一直在寻找更好的方法来为我们的应用程序调整编排参数。 让我们采用更数学的方法来自动扩展微服务!