分享到

简介

疑似数据库中断事件对每个人来说都可能是一个充满压力的时刻。用户会感到沮丧并担心他们的数据可能会丢失,开发人员会争先恐后地寻找原因,而公司利益相关者则会计算每分钟可能造成的收入损失。

在数据库中断事件中,将数据库恢复到健康状态是首要任务。然而,在如此紧张的事件中,甚至很难推断出问题可能是什么。本文旨在帮助您了解数据库可能宕机的一些最常见原因以及您可以采取哪些措施来修复它。

本文涵盖了在处理数据库中断时需要注意的各个方面,包括:

问题潜在问题操作
数据库似乎不接受连接数据库用户凭据问题、连接字符串问题、连接数达到限制检查数据库服务器日志以确定原因
网络通信似乎受损VPC 和防火墙问题,应用程序与数据库之间的延迟和超时检查防火墙规则,查找延迟和超时,检查是否达到连接限制
数据库服务器或应用服务器崩溃潜在的数据量问题导致数据库服务器和应用服务器达到资源限制查找大量返回数据,优化查询,添加索引
数据库服务器正常运行但应用程序不显示数据潜在的近期代码更改,包括模式更改、未应用的迁移、格式错误的查询检查源代码管理历史以查找近期更改,并在必要时回滚

阅读本文的最佳时机是在数据库宕机之前。如果您的数据库已经宕机,那么次佳时机就是现在。

数据库连接问题

应用日志中的线索

如果您的数据库似乎已宕机,您的应用程序日志可能会为您提供有关接受请求或连接数据库时出现任何问题的见解。由于应用服务器处理客户端请求和数据库请求,如果数据库出现问题,它通常会记录错误。

应用服务器是否成功处理连接?

一个暴露 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。

数据库问题排查

既然您知道您的应用服务器正在运行但无法连接到数据库,下一步就是确定原因。最好的方法是排除问题的潜在来源。

如果您正在使用数据库的托管服务,请首先检查您的云服务提供商的状态页面。大多数云服务提供商都提供一种方法来检查其基础设施是否正常运行,例如 Digital OceanGoogle Cloud PlatformAWS

一旦您排除了云平台的问题,接下来要检查的是您的数据库实例的日志或状态。

托管数据库日志

如果您正在使用的托管数据库提供商似乎运行正常,那么下一步就是检查您的特定数据库实例的日志。

大多数云平台除了吞吐量和查询统计等指标外,还提供查看数据库日志的方式。日志的布局和详细程度因云提供商而异。然而,大多数云提供商都提供了足够详细的信息来帮助您查找问题。

每个云提供商访问数据库日志的方式也不同。大多数都可以在部署管理页面轻松访问日志。

例如,DigitalOcean 提供一个名为“日志与查询 (Logs & Queries)”的选项卡,可直接从部署管理菜单访问。

DigitalOcean Logs & Queries Menu Item

本节包含多种不同的日志信息,包括一个通用的“近期日志 (Recent Logs)”部分。您可能会发现连接数据库问题或其他可能发生的迹象。

DigitalOcean Recent Logs section

网络问题

网络相关问题可能导致您的数据库不可用或看似宕机。在由客户端层、应用层(后端)和数据层(数据库)组成的三层应用程序中,网络问题可能出现在这三层之间。

网络相关问题包括:

当您的数据库似乎宕机或无响应,并且您怀疑这可能与网络问题有关时,最好首先确定流量是被防火墙策略阻塞还是存在实际的网络问题。

注意:以下建议基于可能不适用于您架构的假设。因此,在调试数据库问题时,建议您了解故障模式及其原因。

VPC

在云平台上配置数据库等云资源时,它们被隔离在虚拟私有云 (VPC) 中。实际上,VPC 充当您应用程序资源的私有网络,并与公共互联网隔离。如果您的数据库和应用程序不在同一个 VPC(或同一个云)中,您通常需要配置 VPC 的防火墙规则,以允许应用程序的 IP 访问数据库。

防火墙规则

当您将应用程序和数据库部署到不同的云平台或不同的 VPC 时,您需要配置防火墙以允许从应用程序访问数据库。

虽然某些防火墙策略使用*黑洞*策略静默丢弃不允许的流量,但其他策略会发送拒绝响应,这可以帮助您确定防火墙是否是导致连接中断的原因。同样,如果您遇到“无主机路由 (no route to host)”类型的错误,这可能意味着网络段已宕机或路由逻辑不正确。

此外,如果您的应用程序 IP 是动态的,它随时可能更改。当应用程序的 IP 更改时,它可能无法连接到数据库,直到防火墙更新以允许新的 IP 访问。

防火墙规则补救措施

通常最好将应用程序和数据库部署在同一个 VPC 和同一个区域中,以便两者通过私有网络进行通信。这还可以防止公共网络可能导致的瓶颈。但是,如果无法实现,则有几种可能的修复方法。

第一种方法是为您的应用程序使用专用 IP,并添加一条防火墙规则,允许该 IP 的访问。这种方法确保您不需要更新防火墙规则,因为 IP 保持不变。

如果由于云平台限制或部署方法(例如无服务器)而无法使用专用 IP,请考虑通过公共网络启用对数据库的访问。虽然这种方法会降低数据库的安全性,但它能确保您的应用程序在您更新防火墙规则以适应应用程序 IP 更改之前不会遇到任何突然的停机。此外,您的数据库的认证机制仍然是主要的防御措施。

请注意,不建议从公共互联网打开到数据库的连接。在可能的情况下,我们建议将连接限制到特定的 IP 地址或使用 VPC 对等连接等技术。

延迟和超时

当应用程序和数据库之间的地理和网络距离较大时,请求延迟增加和超时错误可能会成为问题。在这种情况下,可能需要在应用程序中增加数据库连接超时时间以避免超时错误。

ORM查询构建器维护着到数据库的连接池。这些连接通常具有连接超时配置,用于控制建立连接时在超时前等待多长时间。

建议设置初始基线超时值并对应用程序进行负载测试以查看其性能。根据负载测试中失败请求的数量,您可能需要调整超时并再次尝试,直到您确信超时不是问题的原因。如果您的连接超时配置过低,则存在这些超时导致请求失败的风险。

连接数耗尽

基于连接的数据库(如 MySQLPostgreSQL)的另一个常见挑战是您可能会很快耗尽数据库的连接限制。面向连接的数据库对到数据库的开放连接数施加了限制。

当您使用传统的长运行进程模型部署应用程序时,您可以在连接池中将许多传入请求复用到更少的数据库连接上。例如,单个服务器实例可能使用 20 个数据库连接池处理 100 个并发 HTTP 请求。

然而,在无服务器函数上,每个函数实例一次只能处理 1 个 HTTP 请求。由于每个传入请求至少需要一个到数据库的连接,因此您无法复用数据库连接。

鉴于此,建议使用数据库连接限制,然后以不会耗尽数据库连接限制的方式在连接到它的服务器实例之间分配这些连接。例如,假设您的数据库连接限制为 20。如果您有两个应用程序服务器实例,您会希望将每个服务器实例的连接池设置为最多 10 个连接。

对于无服务器部署,考虑运行一个外部连接池,如 PgBouncer——这是一个额外的基础设施组件,它维护着到数据库的连接,并复用来自无服务器函数的传入数据库查询。

数据量问题

随着应用程序的增长,其数据量也很可能会随之增长。性能和数据库正常运行时间的一个关键考虑因素是为满足给定请求而处理的数据量。当总数据量较小时为应用程序编写的低效查询,在数据量增长时可能会变成瓶颈,从而导致性能急剧下降并引发中断。

大量数据可能在三个位置产生影响:

  • 数据库服务器
  • 应用服务器
  • 客户端

数据库服务器

当应用服务器发出数据库查询时,数据库服务器的职责是检索请求的数据并将其发送回应用服务器。在此过程中,数据库服务器必须首先将检索到的数据扫描到其自身内存中。当数据量较小时,将数据扫描到内存并转发到应用服务器的过程微不足道。然而,如果查询返回的数据量非常大,配置不足的数据库服务器可能会在负载下崩溃。

当最初在数据库总规模较小时足够使用的查询,随着数据规模的增长而未得到充分优化时,这种情况经常发生。

考虑一个场景,应用程序需要为用户显示联系人列表。用于提供此数据的典型数据库查询可能会选择限于该用户的数据。

SELECT * from contacts WHERE userId = 123;

根据应用程序的需求,应用服务器可能除了将结果转发给客户端外,无需执行其他操作。此查询不太可能导致数据量问题。

然而,考虑一下如果上述查询中没有 WHERE 子句会发生什么。

SELECT * from contacts;

如果查询改为请求数据库中所有联系人,然后由应用服务器负责在将结果转发到客户端之前根据 userId 过滤列表,那么数据量的差异将是天文数字。

在总体数据量相对较小的情况下,这种不完善的查询可能不会被发现。随着数据量的增长,性能下降可能会变得明显,但可能不会完全破坏应用程序。然而,随着这种情况的发生,数据库服务器将超出其可能配置的负载。随着数据量进一步增长,可能会达到一个临界点,数据库服务器将无法再处理负载。

应用服务器

正如数据库服务器处理大量数据的能力并非无限一样,应用服务器也同样如此。您可能对应用服务器的容量和规模有比数据库服务器更多的控制,但增加应用服务器的容量可能没有必要。

从数据库服务器返回的大量数据需要在应用服务器上花费时间和资源进行处理。如果应用服务器正在响应来自客户端应用程序的请求,那么在应用服务器上的长时间等待可能会导致问题。许多云托管提供商会在几分钟后强制执行请求超时。Web 浏览器会在请求处于“待处理”状态几分钟后自动重试请求,这会进一步给系统带来压力。

客户端

客户端应用程序可能最容易受到大量数据量导致的瓶颈影响。与应用服务器和数据库服务器不同(您可能能够增加其容量),在浏览器或移动设备上运行的客户端应用程序受限于浏览器、操作系统或两者的限制。

向客户端应用程序发送过大的数据量将导致处理延迟,并可能严重降低响应速度。一旦达到给定数据大小,浏览器可能会完全无响应并最终崩溃。

如果非常大的数据量从数据库一直转发到客户端,那么在整个过程中都会感受到性能下降。这种三重效应将导致漫长的加载时间,并可能在这些点中的任何一个地方出现感知到的中断。使问题复杂化的是,排查瓶颈所在变得困难。

数据量补救措施

解决数据量问题导致的性能下降和中断的办法几乎总是限制从数据库服务器返回的数据量。这样做将缓解数据库服务器、应用服务器和客户端的问题。

使用 WHERE 子句限制数据范围

在上述虚构的查询示例中,查询了数据库的整个联系人表。这意味着数据库服务器需要将整个表扫描到内存中,然后才过滤到所需的结果,这会给数据库服务器和应用服务器带来巨大的负载。

首先查找范围过大的查询。很可能这些查询在数据库服务器上处理时间很长。查看长时间运行查询的日志可以指示这种情况发生在哪里。与其返回大量结果然后用应用程序代码过滤它们,不如构建您的查询以返回真正需要的结果子集。在 SQL 中,使用 WHERE 子句来精确获取您实际需要的数据。

使用 LIMITOFFSET 分页数据

如果客户端应用程序确实需要大量数据,建议引入分页。

分页是一种设计模式,它限制在给定时间内查询和返回的记录总数。如果用户希望查看更多记录,他们必须明确请求。

这种设计模式最常使用 LIMITOFFSET 子句来实现。通过选择跳过一定数量的记录并限制返回结果的数量,客户端和应用服务器可以实现分页体验。

第一页的查询可能如下所示:

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 命令外,许多云数据库提供商还提供一种自动可视化导致问题的查询的方法。这些信息通常可以在云提供商的“慢查询 (Slow Queries)”视图中找到。检查您的数据库提供商的慢查询部分,以找出哪些查询可能对您的应用程序产生不利影响。

添加索引

与大数据量相关的问题通常可以使用索引来解决。

数据库表的索引可以像书的索引一样理解。如果您想在一本书中查找特定主题的信息,通读整本书来找到它将花费很长时间。相反,您可以查阅索引来查找感兴趣的特定主题。如果存在,它将指向书中讨论该主题的具体页面。

相同的概念也适用于数据库索引。根据常见的访问模式为数据库表添加索引可以实现快速查找。

例如,如果您有一个用户数据库表,其中包含一个 email 列,并且该表根据用户的电子邮件进行查询,那么基于 email 列为该表创建索引有助于提高性能。如果没有索引,将扫描整个表以通过电子邮件地址查找用户。如果表大小非常大,这可能会导致严重的性能问题。然而,如果为该列创建了索引,则查找将非常快速,因为搜索特定电子邮件将立即指向所需的精确行。

向数据库添加索引需要规划和考虑,并且应该在您的数据库导致应用程序中断之前完成。但是,如果您能找到导致中断的特定查询,则立即添加索引可能会有所帮助以减轻压力。

破坏性代码变更

疑似数据库中断可能追溯到最近对客户端或服务器代码进行的破坏性更改。在这种情况下,很可能不是数据库本身发生了中断,而是用于检索数据、处理数据并将其返回给客户端的代码可能已损坏。

可能导致中断的代码更改通常分为三类:

  • 数据库查询
  • 服务器代码
  • 客户端代码

数据库查询

对数据库查询的更改,即使很小,也可能影响应用程序。如果使用原始 SQL,查询语句可能在最近的代码更改中变得无效。如果查询本身仍然有效,它可能已被更改为返回应用程序其他部分无法再处理的结果。

检查源代码控制更改,查找数据库访问(SQL 语句或 ORM 使用)的近期更改。尝试隔离与受影响应用程序特定区域相关的查询。

如果没有迹象表明数据库访问发生了变化,另一种可能性是数据库模式已更改,但尚未运行迁移。查找可能仍需要应用于生产数据库的任何近期迁移。

客户端和服务器代码

对客户端或服务器应用程序代码的更改,即使很小,也可能导致看起来像是数据库中断的问题。导致此问题的具体问题有很多,但一些示例包括:

  • 客户端可能正在调用不再存在的服务器端点
  • 服务器端点更改了有效负载或查询参数的验证规则
  • 服务器正在尝试访问因近期模式更改而失效的返回数据中的属性

结论

数据库中断,无论其根本原因是什么,都可能是令人非常紧张的事件。在急于让应用程序恢复运行的过程中,可能很难正确地推断数据库问题可能源于何处。

每一次数据库中断都是独一无二的,发生中断时没有一劳永逸的解决方案。然而,熟悉可能的问题及其相关解决方案可以帮助您更有效地确定问题原因,并缩短恢复运行所需的时间。

与大多数解决问题的方法一样,最好在问题发生之前熟悉其潜在解决方案。我们希望本指南能帮助您实现这一目标。

关于作者
Ryan Chenkie

Ryan Chenkie

Ryan 是一名全栈开发人员,对数据库和 API 特别感兴趣。他是 CourseLift 的创始人,这是一个帮助作者进行营销和销售的课程托管平台。
Daniel Norman

Daniel Norman

Daniel 是一名软件工程师,在现代网络和云环境方面拥有丰富的经验。他从事数据库开发超过 15 年,对开源和现代开发工具充满热情。
© . All rights reserved.