FaunaDB是一个分布式系统。与所有分布式系统一样,我们也有一些令人烦恼的问题,即不可靠的网络和故障节点(不是拜占庭故障,只是正常的慢速或死节点)。当一个请求的结果需要来自多个节点的数据组合时,故障节点接收到请求中的工作份额的可能性被极大地放大。在这种情况下,分布式系统很常见-故障节点可能会影响许多请求。因此,在出现故障节点的情况下提高系统可靠性的方法是必不可少的。
当您需要通过可能不可靠的链路请求数据,并且多个节点拥有数据时,有一种简单的方法可以将一组不可靠的链路转换为单个虚拟链路,该虚拟链路在尾部总体上比任何单个链路都更可靠、速度更快。只是冗余地发出请求。可用路径中最快的路径将首先返回。当然,发出冗余请求也有其自身的问题:增加了服务请求的机器的服务器负载,增加了双方的网络流量。
我们可以尝试确定何时必须发送冗余消息,而不是在每个场景中用冗余消息淹没集群。要做到这一点,最直接的方法就是简单地等待,看看结果是否足够快。节点不是立即发送冗余请求,而是等待特定的延迟,并在给定的时间范围内没有收到应答时发送备份消息。
事实上,如果我们延迟发出第二个请求,直到等待响应分布的某个高百分位时,几乎所有冗余请求的收益都可以得到。Tail at Scale白皮书讲述了在发出冗余请求之前等待约98%的响应时间(他们测量的固定延迟为10ms)是如何将99.9%的响应时间从1800ms减少到74ms的,同时只会适度增加2%的利用率。
从理论上讲,这一切都很好。当然,有一个明显紧迫和实际的问题:如何计算出应该等待多长时间才能发送对冲请求?理想情况下,它足够小,因为每个需要备份消息的请求至少需要延迟时间。但它也不能太小,因为群集将被备份消息淹没。一个简单而明显的答案是衡量它。我们可以确定需要备份消息的邮件百分比,直接测量它,并根据需要进行调整。
让我们假设我们的目标是像报纸上那样等待98%的百分位数。通过收集所有请求的延迟测量,我们可以确定确切的“备份请求延迟”,以确保只有2%的请求触发冗余请求。
实际上,情况要复杂得多,当我们根据测量结果设置延迟时,延迟可能已经因外部因素而改变。此外,通过设置此延迟,测量结果将发生更改,从而影响我们采取行动所依赖的测量结果,这可能会产生不良的副作用。
在一种天真的方法中,我们每次都精确地根据我们的测量来设置延迟,我们可能会意外地使事情变得更糟。如果由于NetSplit之类的异常,我们的延迟严重扭曲,只有一瞬间,我们可能会设置极高的延迟。设置如此高的延迟可能会影响我们的测量,从而导致更高的延迟,这再次对百分位数产生影响,进而导致更高的延迟,这再次导致…。。。。
我相信你明白这一点,如果一个系统在一个循环中对受其行为影响的测量采取行动,那么它可能很快就会变得无赖,而不是稳定在一个最佳值上。有许多糟糕的情况需要避免:
不断变化:系统不断地上下跳跃,而不是稳定,这通常是由于响应过快造成的。
从本质上讲,处理这样一个移动的目标会引发更多的问题:我们应该在什么时间段内进行衡量?如果我们连续测量延迟,我们应该如何调整等待时间?加权平均?指数型?什么权重?
理想的解决方案应该对网络条件的变化做出快速反应,并收敛到正确的值,而不会超调。现在,在我们的例子中,任何一方稍微出错都不是世界末日,但它确实会产生后果:收敛缓慢或超调,这取决于它到达的方向,要么意味着请求服务过度繁忙,要么意味着计算资源过度消耗。
幸运的是,这类问题属于一个众所周知的研究领域,这在电子学中很常见,但在计算机算法中很少遇到。事实上,你家里的恒温器也要解决类似的问题。它需要通过对受自身行为(开始供暖)和外部刺激(敞开的门让寒冷进入)在持续循环中影响的测量做出反应来实现目标。
看看这个视觉类比,马达正在控制球的位置,以响应外部刺激。这个系统必须根据对不断移动的目标的测量来决定行动路线,该目标既受到外部刺激(将球移出中心的手)的影响,也受到其自身动作(倾斜盘子)的影响。
这个平板控制器正在做我们想让我们的软件做的事情。它有一个设置点(将球保持在板的中心),它有一个误差(球到设置点的距离),它有一个减少误差的机制(倾斜板)。我们的设置点是触发备份读取的请求与不触发备份读取的请求的比率,我们的错误是观察到的比率与目标比率之间的差距,而影响该比率的机制是发出备份请求之前的延迟。
PID控制器是典型的闭环控制系统,每个节拍收集信息并采取行动使系统达到所需的状态。PID是对这类问题的数学解答,发明于17世纪,目的是保持风车以固定速度运行。
PID是比例、积分、导数的缩写,它们是用来驯服系统的部件,PID控制器是这一功能的体现,多年来采取了多种形式:机械(气动装置)、电气(芯片),最后是可用于优化分布式系统的编程实现。
该函数将误差(P)、随时间累积的误差(I)和预测误差(D)分别乘以可调常数。这为什么说得通呢?
通过指定这三个因素的权重(使用常数Kp、Ki和Kd),我们可以微调我们的系统应该如何运行,以防止超调、累积错误或不稳定的情况。
成比例:我们根据误差大小调整设定点。这意味着要引发快速反应。
积分:考虑误差在一段时间内的表现,这有助于消除系统误差。随着时间的推移,误差累积的时间越长,积分部分受到的影响越大。这意味着系统误差最终将得到缓解。
导数:可以被视为预测未来的变量,并有助于防止超过目标。如果我们朝目标走得太快,导数会减慢过程。
如果你想真正投入其中,请看这个视频系列。这正是我们想要的:当我们的比率不正常时,我们希望安全而快速地更改延迟以收敛到我们想要的状态,使系统保持响应,同时最小化资源使用。
引入备份请求以及在我们实际发出这些请求之前的延迟只是该方法的第一步。通过引入PID控制器作为第三个元件,我们可以更容易地适应环境变化时的延迟。将这三个洞察力结合到一个算法中,我们希望在发出备份请求的同时保留发出请求的所有好处,同时减少请求。
如果您喜欢我们的博客,并且想要研究与无服务器数据库、GraphQL和JAMstack相关的系统和挑战,那么Fauna正在招聘!
获取最新的博客帖子、开发提示和技巧,以及直接发送到您的收件箱的最新JAMStack材料