ITPub博客

首页 > 数据库 > 数据库开发技术 > 节流器剖析,第1部分 — PlanetScale

节流器剖析,第1部分 — PlanetScale

数据库开发技术 作者:ITPUB社群 时间:1970-01-01 08:33:44 0 删除 编辑

限流器是一种服务或组件,它会对传入的请求流进行反压,以确保系统有能力处理所有收到的请求而不至于过载。在本系列文章中,我们将阐述数据库系统限流器的设计考量,其目的是保持数据库系统的整体健康。我们将讨论指标选择、粒度、行为、影响、优先级以及其他话题。

你要对哪些请求进行限流?

在数据库中对请求进行限流有多种不同的方法。我们关注的是对非时间关键的异步、批量和海量操作进行限流。例如 ETL、数据导入、在线 DDL 操作、大规模数据清除、重新分片等等。限流器会对那些可能持续数分钟、数小时甚至数天的操作进行反压。其他形式的限流可能会对 OLTP 生产流量进行反压。本讨论对两者同样适用。

举例说明,假设有一个任务需要向数据库导入 1000 万行数据。该任务不会尝试一次性写入全部 1000 万行,而是将任务分解为更小的子任务:每次尝试导入(写入)100 行。在每次导入之前,它会向限流器请求访问权限。

有些限流器实现是协作式的,意味着它们假定客户端会遵守其指令。另一些则充当应用与数据库之间的屏障。无论哪种方式,如果限流器指示数据库已过载,任务应暂停一段时间,然后再次请求访问权限。此过程重复进行,直到获得许可。每个子任务应足够小,以免单凭自身就拖垮数据库的服务能力;同时又要足够大,以补偿额外的限流器开销并实现有意义的进展。

限流器基于什么进行限流?

一些通用限流器只允许受调控的请求速率,预期消费任务能够以某个已知的固定速率处理这些请求。对于数据库而言,情况则不那么清晰。数据库在任意时间点或某段时间内只能处理有限数量的查询。然而,并非所有查询都是平等的。数据库服务查询的能力取决于查询的范围、受影响数据中的热点或冷点、页缓存的状态、查询所服务数据的重叠与否等诸多因素。

因此,我们需要能够确定:我们如何认定数据库处于“健康”状态?我们如何判断它是否正在过载?

为此,我们寻找能够定义或预测数据库服务等级目标(SLO)的指标。但事情并不总是那么简单。让我们从一个被广泛用作限流指标的流行指标开始,看看这个指标有什么特别之处。

复制延迟

复制常用于数据库集群,尤其是采用主从(即 leader-follower)架构的集群。有时复制被设置为异步,有时则基于组通信。在这些场景中,复制延迟被定义为从主服务器上的写入到该写入在副本/从服务器上被应用或可见之间经过的时间。

在 MySQL 世界中,复制延迟可能是最常用的单一限流指标,因为多个第三方和社区工具都使用它来对长时间运行的任务进行反压。这有充分的理由:它易于测量,并且对产品和业务有明确影响。例如,在数据库故障转移的情况下,复制延迟会影响副本/备用服务器被提升并可用于接收写请求所需的时间。当复制延迟较低时,写后读可以简化,从而允许从服务器处理部分读流量。

因此,我们可以对可接受的复制延迟设置业务限制,并将其用于限流器:低于此延迟,允许请求。高于此延迟,进行反压。

其他数据库指标:指标中有什么?

MySQL 世界中另一个常见指标是 threads_running 的值。在任何给定服务器上,这是并发、正在执行的查询数量(不要与并发打开的事务混淆,其中一些事务可能在查询之间处于空闲状态)。这个指标经常出现在数据库仪表盘上,是数据库负载的一个指示器。

但是,可接受的值是多少?50 个并发查询可以吗?100 个可以吗?随便选一个数字,你很快就会发现它站不住脚。有些值在清晨是可以接受的,而另一些在高峰流量时段则只是正常水平。随着产品演进和采用率提高,数据库上的查询也会增加。3 个月前成立的事情今天未必成立。而且,并非所有查询都是平等的。

这个指标与复制延迟的不同之处在于,它更像是一种症状,而不是实际原因。如果我们突然看到活跃查询急剧飙升,这可能表明一些可能的原因:也许它们都被提交队列持有,而该队列由于某种原因停滞了。或者,这些查询恰好竞争某个特定热点并等待锁。或者,它们并非如此,而是恰好竞争非常不同的页面,其中没有一个在内存中,并且它们都在等待页面缓存时发生拥塞,等等。那么,我们到底需要监控什么?这个指标本身没有用吗?

未必。经验丰富的管理员可能只需要看一眼数据库仪表盘上的这个指标,就能说“我们遇到问题了”。

更仔细的审视:队列

软件严重依赖队列。它们不仅在软件设计中是基础性的,在硬件访问中也是如此。请求会在网络访问上排队。它们会在磁盘访问上排队。它们会在 CPU 访问上排队。它们会在锁上排队。

回到复制延迟,就像并发查询一样,它是一种症状。例如,副本上的磁盘 I/O 已饱和,因此副本无法跟上重放 changelog,从而累积延迟。或者延迟可能是由慢网络引起的。或者两者兼有!无论情况如何,有趣的是复制机制本身就是一个队列:changelog 事件队列。主服务器上的新写入表现为一个“写入事件”,它被发送到副本,并等待被副本消费(处理、重放)。复制延迟是该事件在队列中花费的时间,其中我们的队列是网络队列、本地磁盘写队列、实际等待时间以及最终事件处理时间的组合。其中每一项都可能成为整体复制延迟的主要贡献者,然而,我们仍然可以把复制延迟作为一个整体来看待——作为数据库健康状况的清晰指标。

有了这一洞察,我们再次尝试理解其他指标。在并发写入的情况下,我们理解并发查询飙升的一个主要贡献者是它们无法完成。通常,这意味着它们在提交时被阻塞,即它们等待被写入事务(redo)日志。这意味着它们处于事务队列中,因此我们可以测量事务队列延迟(即队列延迟)。

但是,什么样的阈值才算好呢?事务提交延迟通常由磁盘写入/刷新时间引起,而这一时间在不同硬件上差异巨大。这取决于你是否了解自己的指标。同样,有经验的管理员应该知道预期值是多少。但现在这些值与硬件的绑定更紧密,受应用的影响略小一些。

队列延迟并不是唯一的指标。另一个常见的指标是队列长度:即队列中等待的条目数量。机场排长队本身并不是坏事,有些队列移动得相当快,但它往往是等待时间的一个预测指标。在等待时间无法或难以衡量的情况下,队列长度可以作为替代指标。

操作系统的 Load Average(平均负载)指标评估包含了等待 CPU 时间的进程数量。这会随可用 CPU 数量的变化而变化。一个常见的粗略指标是(平均负载)/(CPU 数量)的阈值为 1。这同样是一个必须与你自己的系统相匹配的指标。一些数据库部署以将服务器推向极限而闻名,其平均负载远超每 CPU 为 1 的水平。

连接池使用率

另一个指标是连接池使用率。与数据库相关的最常见的连接池必然是应用的数据库连接池。要执行查询,应用会从连接池中取出一个连接,用它来执行查询,然后将连接归还到连接池。如果连接池有空闲连接,获取连接就没有代价。但如果连接池已耗尽,那么应用要么需要等待创建新连接,要么被拒绝。与并发查询类似,高连接池使用率表明操作出现了拥塞。然而,池化连接可以在一个事务中的多个查询之间使用,也可以跨多个事务使用,并且应用在持有连接的同时,可能在运行查询之间执行自己的逻辑。

连接池耗尽是负载过高的强烈信号,而 60% 和 80% 使用率之间的差异则不那么明确。退一步说,连接池耗尽意味着什么?又是谁首先决定了连接池的大小?如果有人选了 50 或 100 这样的数字,这个数字难道不是人为设定的吗?

很可能是这样,但连接池大小很可能是出于某些合理的理由而选择的。它或许源自某个数据库配置,而该配置本身又源自某个硬件限制。虽然指标的选择可能会随意变化,但就限流而言,当连接池耗尽时进行推回仍然是合理的。限流器因此依赖于更大的系统配置,而不会引入任何新的人为阈值。

多指标并用的理由

限流器应该能够基于多个指标的组合来进行推回,而不是仅限于单一指标。我们在上文已经说明了一些指标,而每个环境可能还有自己的负载预测指标。管理员应该能够选择一组适合限流器使用的指标,能够为每个这样的指标设置特定的阈值,并且可能能够通过编程方式或动态方式引入新的指标。

被限流的系统是什么样子?

许多软件开发者会熟悉下面这个场景:你有一个多线程或其他高度并发的应用。存在一个 bug,很可能是竞态条件或同步问题,你想找到它。你选择将信息性调试消息打印到标准输出,希望通过检查日志来找到这个 bug。可惜的是,当你这样做时,bug 不再复现了。或者它可能在别处显现。

在添加对标准输出的写入时,你引入了新的锁。你的调试消息现在要竞争这些锁,这反过来又导致了不同的上下文切换。

在你的基础设施中引入限流器与这个同步示例有相似之处。突然间,数据库上的争用减少了,而某些原本运行良好的应用却表现出争用/延迟行为。一个新任务的出现突然影响了另一个任务的进展。但以前你可以清楚地分析数据库查询来找到根本原因,现在数据库却几乎无法告诉你任何信息。现在只能依靠限流器来提供这些信息。但即便是限流器也是有限的,因为所有应用所做的只是检查限流器的健康状态。它们实际上还没有做任何事情。

假设我们基于复制延迟进行限流,再假设我们想要运行一个规模极大的操作,如果放任不管,必然会导致复制延迟飙升。然而,在限流器的控制下,该操作只会运行小批量的子任务。但一个有趣的行为出现了:该操作会将复制延迟推高到限流器的阈值,然后回落,再次推高。当我们启动该操作时,我们预期看到复制延迟图表跳升到阈值,然后在该值附近大致稳定下来,略高或略低,持续整个操作期间,这可能长达数小时。

在这段时间内,该操作将被授予访问权限数千次或更多,同样也会被拒绝访问数千次或更多。这就是限流器启用时健康系统的样子。无论我们运行多少额外的并发操作,我们都期望将复制延迟控制在阈值上下大致相同的微小偏移范围内。关于这一点,我们将在讨论粒度时进一步展开。

系统长时间运行一两个操作并不罕见,这意味着我们所认为的限流阈值(比如 5 秒的复制延迟)变成了实际的标准。幸运的是,并非所有操作和工作负载都如此激进,以至于必然将指标推高到其阈值。

检查间隔与指标粒度

限流器通过检查请求异步收集指标,以便在收到请求时能立即给出答案。限流器收集指标的间隔对限流器的使用方式有重大影响。让我们考虑一种情况:限流器以较大的间隔收集指标,比如每 5 秒一次。在这 5 秒内,指标可能发生任何变化,但只有在该时间段结束时进行的特定采样才起作用。

类似地,其他指标也可能有一定的粒度。也就是说,复制延迟可以通过不同的方法测量,最常见的方法是在主库上故意注入心跳事件,并在副本上捕获它们。更多内容将在另一篇文章中讨论,但心跳事件生成的间隔决定了所测量延迟的粒度或准确性。假设我们以一秒的间隔注入心跳,并且我们恰好在正午注入了一次心跳。再假设我们每秒采样一次指标,而我们恰好在 12:00:00.995 进行采样。该采样仍然读取的是 12:00:00.000,因为那是我们最后注入的指标。然后客户端在 12:00:01.990 检查限流器。此时应该已经有了新的指标值,但我们还没有采样到它。限流器使用它最后一次采样来响应,该采样几乎但不完全是一秒前的,而其本身代表的指标现在已经几乎但还不完全是两秒前的。

较长的心跳间隔和过时的信息对我们的系统健康以及限流器的利用率都有负面影响。

一方面,在间隔期间,我们可能错过注意到系统负载的显著上升。我们只会在几秒后才发现,那时限流器才会介入。然而,到那时,系统性能已经下降了。需要几秒钟才能降到可接受的值。但话说回来,一旦我们发现指标超过阈值,在下一个间隔期间,我们会拒绝所有进一步的请求。如果指标比这更早恢复健康,那就是错失了取得进展的机会。因此,我们降低了数据库的操作容量。

当多个操作同时尝试取得进展时,当指标高于阈值时所有操作都会被限流,而当指标恢复到低值时可能所有操作同时被放行,从而同时推高指标。

借鉴网络硬件领域的经验,建议指标间隔和粒度对允许的阈值范围进行过采样。例如,如果可接受的复制延迟是 5 秒,那么最好将心跳/采样间隔设为 1-2 秒。

更低的间隔和更准确的指标可以减少峰值并更有效地分散工作负载。这也有代价,我们将在后续文章中讨论。

未完待续

在本系列的下一部分中,我们将探讨单点与分布式限流器设计,以及限流器本身可能对您的环境产生的影响。


转载声明:本文为转载整理,原文版权归原作者及原站点所有。

原文来源:PlanetScale Blog

原创地址:https://planetscale.com/blog/anatomy-of-a-throttler-part-1

原文作者:PlanetScale Blog;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2022-11-21

  • 博文量
    1090
  • 访问量
    546595