简介
疑似数据库宕机事件可能对每个人来说都是一个压力事件。用户会感到沮丧并担心他们的数据可能会丢失,开发人员争先恐后地寻找原因,而公司利益相关者则会计算每一分钟可能带来的收入损失。
在发生宕机事件时,让您的数据库恢复到健康状态是重中之重。但是,在如此紧张的事件中,甚至很难推断问题可能是什么。本文旨在帮助您了解数据库宕机的一些最常见原因,以及您可以采取的解决方法。
本文涵盖了处理数据库宕机时需要查找的多个方面,包括
问题 | 潜在问题 | 行动 |
---|---|---|
数据库似乎无法接受连接 | 数据库用户凭据问题、连接字符串问题、已达连接限制 | 检查数据库服务器日志以确定原因 |
网络通信似乎受损 | VPC 和防火墙问题、应用程序和数据库之间的延迟和超时 | 检查防火墙规则、查找延迟和超时、检查是否耗尽连接限制 |
数据库服务器或应用程序服务器崩溃 | 潜在的数据量问题导致数据库服务器和应用程序服务器达到资源限制 | 查找大量返回的数据、优化查询、添加索引 |
数据库服务器已启动,但应用程序未显示数据 | 潜在的近期代码更改,包括模式更改、未应用的迁移、格式错误的查询 | 检查源代码控制历史记录以查找近期更改,并在必要时进行回滚 |
阅读本文的最佳时间是在数据库宕机之前很久。如果您的数据库已经宕机,第二好的时间就是现在。
数据库连接问题
应用程序日志中的线索
如果您的数据库似乎宕机,您的应用程序日志可能会让您深入了解接受请求或连接到数据库的任何问题。由于应用程序服务器处理客户端请求和数据库请求,因此如果数据库存在问题,它通常会记录错误。
如果您使用的是 Prisma Client,您可以 配置日志记录 以控制日志的生成方式。
应用程序服务器是否成功处理连接?
一个公开 API 并通过查询数据库来服务请求的应用程序服务器有两种类型的连接
- 用户请求到应用程序服务器公开的 API
- 应用程序服务器请求到数据库
在调试宕机事件时,请记住,这两种类型的连接问题都可能导致宕机。
应用程序服务器框架通常带有内置的记录器。当服务器启动后,记录服务器可以接受连接是一种常见的做法。
例如,请查看此日志行
{"level":30,"time":1617808854673,"pid":96741,"hostname":"do-server-1","msg":"Server listening at http://0.0.0.0:8000"}
日志显示服务器已启动并在端口 8000 处接受连接。但不足以确定服务器启动后是否发生了错误。
下一步将查看最新的日志,并检查服务器最近请求的 HTTP 状态代码。通常,请求日志如下所示
{"level":30,"time":1617809865718,"pid":97326,"hostname":"do-server-1","reqId":5,"req":{"method":"POST","url":"/graphql","hostname":"0.0.0.0:3000","remoteAddress":"127.0.0.1","remotePort":53540},"msg":"incoming request"}{"level":30,"time":1617809865719,"pid":97326,"hostname":"do-server-1","reqId":5,"res":{"statusCode":200},"responseTime":1.1810400485992432,"msg":"request completed"}
每个传入请求日志都包含时间戳、URL 以及有关请求的其他信息。第一个日志指示传入的请求,而第二个日志显示请求已成功,HTTP 状态代码为 200。
最近是否有任何数据库错误?
由于您正在尝试调试与数据库相关的错误,因此应专门查找失败的请求。您可以通过过滤响应状态代码为 5xx(例如 500)的请求来找到这些请求,这表示服务器错误。
如果您发现多个状态代码为 500 的请求,请查找这些请求开始的时间点,并检查任何其他已记录的错误。
如果数据库宕机或数据库凭据错误,您会看到已记录的内容。例如
Can't reach database server at `db-postgresql-563564.b.db.ondigitalocean.com`:`5432`
上面的日志表明您的应用程序服务器无法连接到数据库。在这种情况下,请检查应用程序服务器中的数据库 URL。
如果您使用的是 Prisma Client,错误代码参考 可以帮助您诊断错误的含义以及如何修复它们。
数据库问题排查
既然您已经知道您的应用程序服务器正在运行,但无法连接到数据库,下一步就是确定原因。最好通过消除潜在问题源来解决这个问题。
如果您使用的是托管服务来托管您的数据库,请首先检查您的云提供商的状态页面。大多数云提供商提供了一种方式来检查其基础设施是否正常运行,例如 Digital Ocean、Google Cloud Platform 和 AWS。
排除了云平台的问题后,接下来需要检查的是数据库实例的日志或状态。
托管数据库日志
如果您的托管数据库提供商看起来正常运行,下一步就是检查您特定数据库实例的日志。
大多数云平台除了提供一些指标(如吞吐量和查询统计信息)之外,还提供了一种查看数据库日志的方式。不同云提供商的日志布局和详细程度各不相同。但是,大多数云提供商提供的详细程度足以帮助您搜索问题。
每个云提供商也有不同的访问数据库日志的方式。大多数情况下,可以通过部署管理页面轻松访问日志。
例如,DigitalOcean 提供了一个名为“日志和查询”的选项卡,可以直接从部署管理菜单访问。
此部分包含几种不同的日志信息,包括一个通用的“最近日志”部分。您可能会发现连接到数据库或其他可能出现的问题的指示。
网络问题
与网络相关的故障会导致您的数据库不可用或看起来已关闭。在一个由客户端层、应用程序层(后端)和数据层(数据库)组成的三层应用程序中,网络问题可能出现在这三层之间。
与网络相关的故障包括
- VPC 和防火墙策略问题
- 延迟 和应用程序与数据库之间的超时
当您的数据库看起来已关闭或无响应,并且您怀疑这可能与网络问题有关时,最好先确定流量是否被防火墙策略阻止,或者是否存在实际的网络问题。
注意:以下建议基于可能不适用于您的体系结构的假设。因此,在调试数据库问题时,建议您了解故障模式及其原因。
VPC
在云平台上配置云资源(例如数据库)时,它们被隔离在虚拟私有云 (VPC) 中。实际上,VPC 充当应用程序资源的专用网络,并与公共互联网隔离。如果您的数据库和应用程序不在同一个 VPC 中(或同一个云中),您通常需要配置 VPC 的防火墙规则以允许应用程序的 IP 访问数据库。
防火墙规则
将应用程序和数据库部署到不同的云平台或不同的 VPC 时,需要配置防火墙以允许应用程序访问数据库。
虽然一些防火墙策略使用黑洞策略来静默丢弃不允许的流量,但其他策略会发送拒绝响应,这可以帮助您确定防火墙是否是导致连接中断的原因。同样,如果您遇到“主机不可达”类型的错误,这可能意味着网络段已关闭或路由逻辑不正确。
此外,如果应用程序的 IP 是动态的,它可能会随时更改。当应用程序的 IP 更改时,它可能无法连接到数据库,直到防火墙更新为允许新的 IP 访问。
防火墙规则解决方法
通常,最好将应用程序和数据库部署在同一个 VPC 和同一个区域中,以便两者可以通过专用网络进行通信。它还可以防止公共网络可能带来的瓶颈。但是,如果这不可行,有一些可能的解决方法。
第一种方法是为您的应用程序使用专用 IP,并添加一条允许来自该 IP 的访问的防火墙规则。这种方法确保您无需更新防火墙规则,因为 IP 保持不变。
如果由于云平台限制或部署方法(例如,无服务器)而无法使用专用 IP,请考虑通过公共网络启用对数据库的访问。虽然这种方法降低了数据库的安全性,但它确保您的应用程序不会遇到任何突然的停机时间,直到您在应用程序的 IP 更改时更新防火墙规则。此外,数据库的 身份验证 机制仍然是主要的防御手段。
请注意,不建议从公共互联网打开对数据库的连接。在可能的情况下,我们建议将连接限制为特定 IP 地址或使用诸如 VPC 对等连接之类的技术。
延迟和超时
当应用程序和数据库之间的地理距离和网络距离很大时,增加的请求延迟和超时错误可能会成为问题。在这种情况下,可能需要在应用程序中增加数据库连接超时以避免超时错误。
ORM 和 查询构建器 保持一个连接到数据库的池。这些连接通常具有一个连接超时配置,该配置控制在建立连接时等待多长时间才会超时。
建议设置一个初始基线超时值,并对应用程序进行负载测试以查看其性能。根据负载测试中失败请求的数量,您可能需要调整超时并重试,直到您确信超时不是问题的原因。如果您的连接超时配置过低,您就有可能出现这些超时导致请求失败的风险。
连接限制已耗尽
与连接型数据库(如 MySQL 和 PostgreSQL)相关的另一个常见问题是,您可能会很快耗尽数据库的连接限制。连接型数据库对数据库的开放连接数量有限制。
当您使用传统的长时间运行进程模型部署应用程序时,您可以将许多传入请求多路复用到池中较少的 DB 连接上。例如,单个服务器实例可能使用 20 个 DB 连接的池处理 100 个并发 HTTP 请求。
但是,在 无服务器 函数中,每个函数实例一次只能处理 1 个 HTTP 请求。由于每个传入请求都需要至少一个连接到数据库,因此您无法对数据库连接进行多路复用。
鉴于此,建议使用数据库连接限制,然后将这些限制分配到连接到它的服务器实例之间,这样不会耗尽数据库的连接限制。例如,假设您的数据库的连接限制为 20。如果您有两个应用程序服务器实例,您将希望将每个服务器实例的连接池设置为最多 10 个连接。
对于无服务器部署,请考虑运行一个外部 连接池(如 PgBouncer),这是一个额外的基础设施组件,它保持连接到数据库并多路复用来自无服务器函数的传入数据库查询。
数据量问题
随着应用程序的增长,应用程序的数据量很可能也会随之增长。对于性能和数据库正常运行时间来说,一个关键的考量因素是处理给定请求所需的数据量。当总数据量很小时为应用程序编写的低效查询可能会变成 瓶颈,当数据量增长时,这些瓶颈会导致性能下降并引发故障。
大量数据会对三个地方产生影响。
- 数据库服务器
- 应用程序服务器
- 客户端
数据库服务器
当从应用程序服务器发出数据库查询时,数据库服务器的职责是检索请求的数据并将其发送回应用程序服务器。在执行此操作时,数据库服务器必须首先将检索到的数据扫描到自己的内存中。当数据量很小时,将数据扫描到内存中并将其转发到应用程序服务器的过程微不足道。但是,如果查询返回了大量数据,那么配置不足的数据库服务器可能会在负载下崩溃。
这种情况通常发生在最初在数据库总大小较小时足够使用的查询在数据大小增长时没有得到充分优化时。
假设一个应用程序需要显示用户联系人的列表。一个典型的数据库查询,用于提供此数据,可能会选择针对该用户的范围数据。
SELECT * from contacts WHERE userId = 123;
根据应用程序的需求,应用程序服务器可能除了将结果转发到客户端之外没有其他需要做的。此查询不太可能导致数据量问题。
但是,考虑一下如果上述查询中没有 WHERE
子句会发生什么。
SELECT * from contacts;
如果查询要求数据库中的所有联系人,而应用程序服务器随后负责根据 userId
筛选列表,然后将结果转发到客户端,那么数据量的差异可能是天文数字的。
在总数据量相对较小的情况下,这种不充分的查询可能无法被发现。随着数据量的增长,性能下降可能会变得明显,但可能不会完全破坏应用程序。但是,当这种情况发生时,数据库服务器的负载将超过其可能配置的负载。随着数据量进一步增长,可能会达到一个临界点,数据库服务器将无法再处理负载。
应用程序服务器
就像数据库服务器没有无限的能力来处理大量数据一样,应用程序服务器也是如此。您可能对应用程序服务器的容量和大小比对数据库服务器的容量和大小有更多控制,但增加应用程序服务器的容量可能是没有必要的。
从数据库服务器返回的大量数据需要在应用程序服务器上花费时间和资源进行处理。如果应用程序服务器正在响应来自客户端应用程序的请求,那么应用程序服务器上的长时间等待会很成问题。许多云托管提供商在经过几分钟后会强制执行请求超时。Web 浏览器会在请求处于“挂起”状态几分钟后自动重试请求,这会给系统带来进一步的压力。
客户端
客户端应用程序可能是最容易受到大量数据造成的瓶颈的影响。与应用程序服务器和数据库服务器不同,您可能无法增加其容量,在浏览器或移动设备上运行的客户端应用程序会受到浏览器、操作系统或两者的限制。
向客户端应用程序发送过大的数据量会导致处理延迟,并可能严重降低响应速度。一旦达到给定数据大小,浏览器可能会完全无响应,最终崩溃。
如果从数据库到客户端一直转发非常大的数据量,那么性能下降将在每一步都会感受到。这种三方效应会导致长时间的加载时间,并且可能在任何一个点上都会导致感知到的故障。更糟糕的是,排查瓶颈所在的位置会变得很困难。
数据大小补救措施
解决数据量问题导致的性能下降和故障的方法几乎总是限制从数据库服务器返回的数据量。这样做将缓解数据库服务器、应用程序服务器和客户端的问题。
使用 WHERE
子句限制数据范围
在上面的虚构查询示例中,查询了数据库的整个联系人表。这意味着数据库服务器需要将整个表扫描到内存中,然后才能筛选到所需的结果,这会给数据库服务器和应用程序服务器带来巨大负载。
首先寻找查询范围过广的查询。这些查询很可能是数据库服务器处理时间过长。查看运行时间过长的查询日志可以指示问题出在哪里。与其返回许多结果,然后使用应用程序代码进行筛选,不如构建查询以返回真正需要的结果子集。在 SQL 中,使用 WHERE
子句来明确您实际需要的数据。
使用 LIMIT
和 OFFSET
分页数据
如果客户端应用程序确实需要大量数据,建议引入分页。
分页是一种设计模式,它限制了在给定时间内要查询和返回的记录总数。如果用户想查看更多记录,他们必须明确地请求它们。
这种设计模式最常使用 LIMIT
和 OFFSET
子句来实现。通过选择跳过特定数量的记录,并限制返回结果的数量,客户端和应用程序服务器可以实现分页体验。
第一页的查询可能如下所示
SELECT * from contacts WHERE userId = 123 LIMIT 25 OFFSET 0;
然后,第二页的查询只需将 LIMIT
的值设置为 OFFSET
的值,即可获取下一“页”。
SELECT * from contacts WHERE userId = 123 LIMIT 25 OFFSET 25;
排查数据量问题
大多数数据库提供工具来帮助排查数据量问题。特别是,许多数据库提供 EXPLAIN
命令,该命令将显示查询的执行计划,并提供有关任何相关问题的见解。
例如,在 PostgreSQL 中,EXPLAIN
命令显示正在考虑的语句的查询计划,并显示估计的执行成本。这是对查询运行需要多长时间的估计。
此信息有助于缩小范围,确定导致瓶颈的特定表或查询语句。
除了手动调用 EXPLAIN
命令之外,许多云数据库提供商还提供一种自动可视化导致问题查询的方法。此信息通常可以在云提供商的“慢查询”视图中找到。查看数据库提供商的“慢查询”部分,以查找哪些查询可能对您的应用程序产生负面影响。
添加索引
与大量数据相关的问题通常可以通过使用 索引 来解决。
数据库表的索引可以理解为书中的索引。如果您想在书中查找特定主题的信息,那么通读整本书来查找它将需要很长时间。相反,您可以查阅索引以查找特定主题。如果它存在,它将指向书中讨论该主题的特定页面。
同样的概念也适用于数据库索引。根据常见的访问模式在数据库表中添加索引可以实现快速查找。
例如,如果您有一个包含 email
列的用户数据库表,并且该表被查询以根据用户的电子邮件查找用户,则基于 email
列为该表创建索引将有助于提高性能。如果没有索引,将扫描整个表以通过电子邮件地址查找用户。如果表大小非常大,这会导致严重的性能问题。但是,如果为该列创建索引,查找将很快,因为搜索特定电子邮件将立即指向所需的精确行。
在数据库中添加索引需要规划和考虑,应该在数据库导致应用程序中断之前完成。但是,如果您能找到导致中断的特定查询或查询,则添加索引有助于立即减轻压力。
代码变更导致中断
可能发生的数据库中断可能追溯到最近对客户端或服务器代码的重大更改。在这些情况下,很可能不是数据库本身出现故障,而是用于检索数据、处理数据并将数据返回给客户端的代码可能已损坏。
可能导致中断的代码更改通常分为三类
- 数据库查询
- 服务器代码
- 客户端代码
数据库查询
即使是微小的数据库查询更改也会影响应用程序。如果使用原始 SQL,则查询语句可能在最近的代码更改中变得无效。如果查询本身仍然有效,它可能已被更改为返回应用程序其他部分不再可处理的结果。
检查源代码控制更改以查找最近对数据库访问的更改(SQL 语句或 ORM 使用)。尝试隔离与应用程序受影响特定区域相关的查询。
如果没有数据库访问更改的迹象,另一种可能是数据库模式已更改,但未运行迁移。查找可能仍需要应用于生产数据库的任何最近迁移。
客户端和服务器代码
即使是微小的客户端或服务器应用程序代码更改,也可能导致看似数据库中断的问题。有许多具体的问题可能是问题根源,但一些例子包括
- 客户端可能正在调用不再存在的服务器端点
- 服务器端点已更改有效负载或查询参数的验证规则
- 服务器正在尝试从返回的数据中访问属性,这些属性已因最近的模式更改而变得无效
结论
无论根本原因如何,数据库中断都可能是非常令人沮丧的事件。在努力使应用程序恢复运行的过程中,很难正确判断数据库问题可能来自哪里。
每次数据库中断都是独一无二的,发生中断时没有万能的解决方案。但是,熟悉可能出现的问题及其相关解决方案可以帮助您更有效地确定问题的原因,并减少恢复运行所需的时间。
与大多数问题解决一样,最好在问题发生之前熟悉潜在的解决方案。我们希望本指南能帮助您实现这一目标。