如何从 1 个用户扩展到 100 个用户

许多初创公司都经历过这样的情况:每天都有大量新用户注册,开发团队努力维持服务运行。

这是一个很好的问题,但是网络上几乎没有关于如何小心地将 Web 应用程序从无扩展到拥有数十万用户的明确信息。 通常有火灾解决方案或瓶颈解决方案(通常两者都有)。 因此,人们使用相当陈词滥调的技术将他们的业余项目扩展到真正严肃的事情。

让我们尝试过滤信息并写下基本公式。 我们将逐步将新照片共享网站 Graminsta 的用户规模从 1 名扩大到 100 名。

我们来写一下当观众增加到10、100、1000、10和000人时需要采取哪些具体行动。

1 个用户:1 台机器

几乎每个应用程序,无论是网站还是移动应用程序,都具有三个关键组件:

  • API
  • 数据库
  • 客户端(移动应用程序本身或网站)

数据库存储持久数据。 API 为针对此数据以及围绕此数据的请求提供服务。 客户端将数据传输给用户。

我得出的结论是,如果从架构的角度来看,客户端和 API 实体完全分离,那么谈论扩展应用程序就会容易得多。

当我们第一次开始构建应用程序时,所有三个组件都可以在同一服务器上运行。 在某些方面,这类似于我们的开发环境:一名工程师在同一台机器上运行数据库、API 和客户端。

理论上,我们可以将其部署在云中的单个 DigitalOcean Droplet 或 AWS EC2 实例上,如下所示:
如何从 1 个用户扩展到 100 个用户
话虽如此,如果一个站点上有多个用户,那么专用一个数据库层几乎总是有意义的。

10 个用户:将数据库移动到单独的级别

将数据库拆分为 Amazon RDS 或 Digital Ocean Managed Database 等托管服务将为我们提供长期良好的服务。 它比在单台机器或 EC2 实例上自托管要贵一些,但通过这些服务,您可以获得许多开箱即用的有用扩展,这些扩展在将来会派上用场:多区域备份、只读副本、自动备份备份等等。

这就是系统现在的样子:
如何从 1 个用户扩展到 100 个用户

100 个用户:将客户端移至单独的级别

幸运的是,我们的第一批用户非常喜欢我们的应用程序。 流量变得更加稳定,因此是时候将客户端移至单独的级别了。 应当指出的是 分离 实体是构建可扩展应用程序的一个关键方面。 当系统的某一部分接收更多流量时,我们可以对其进行分区,以根据特定流量模式控制服务的扩展方式。

这就是为什么我喜欢将客户端与 API 分开。 这使得我们很容易考虑为多个平台进行开发:网络、移动网络、iOS、Android、桌面应用程序、第三方服务等。它们都只是使用相同 API 的客户端。

例如,现在我们的用户最常要求发布移动应用程序。 如果将客户端和 API 实体分开,这会变得更容易。

这样的系统是这样的:

如何从 1 个用户扩展到 100 个用户

1000 个用户:添加负载均衡器

事情在好转。 Graminsta 用户上传的照片越来越多。 注册数量也在不断增长。 我们唯一的 API 服务器很难跟上所有流量。 需要更多的铁!

负载均衡器是一个非常强大的概念。 关键思想是我们在 API 前面放置一个负载均衡器,它将流量分配到各个服务实例。 这就是我们水平扩展的方式,这意味着我们使用相同的代码添加更多服务器,从而增加我们可以处理的请求数量。

我们将在 Web 客户端和 API 前面放置单独的负载均衡器。 这意味着您可以运行多个运行 API 代码和 Web 客户端代码的实例。 负载均衡器会将请求定向到负载较少的服务器。

在这里我们得到了另一个重要的优势——冗余。 当一个实例失败(可能过载或崩溃)时,我们剩下其他实例继续响应传入的请求。 如果只有一个实例在工作,那么一旦出现故障,整个系统就会崩溃。

负载均衡器还提供自动缩放功能。 我们可以将其配置为在峰值负载之前增加实例数量,并在所有用户都睡眠时减少实例数量。

使用负载均衡器,API 级别几乎可以无限扩展,只需随着请求数量的增加添加新实例即可。

如何从 1 个用户扩展到 100 个用户

笔记。 目前,我们的系统与 AWS 上的 Heroku 或 Elastic Beanstalk 等 PaaS 公司提供的开箱即用的系统非常相似(这就是它们如此受欢迎的原因)。 Heroku 将数据库放在单独的主机上,管理自动扩展负载均衡器,并允许您将 Web 客户端与 API 分开托管。 这是在早期项目或初创公司中使用 Heroku 的一个重要原因 - 您可以立即获得所有基本服务。

10 个用户:CDN

也许我们从一开始就应该这样做。 处理请求和接受新照片开始给我们的服务器带来太大压力。

在此阶段,您需要使用云服务来存储静态内容 - 图像、视频等(AWS S3 或 Digital Ocean Spaces)。 一般来说,我们的 API 应避免处理诸如提供图像和将图像上传到服务器之类的事情。

云托管的另一个优势是 CDN(AWS 将此附加组件称为 Cloudfront,但许多云存储提供商都提供开箱即用的服务)。 CDN 自动将我们的图像缓存在世界各地的各个数据中心。

尽管我们的主数据中心可能位于俄亥俄州,但如果有人从日本请求图像,云提供商将制作副本并将其存储在日本数据中心。 下一个在日本请求此图像的人会更快地收到它。 当我们处理大文件(例如照片或视频)时,这一点非常重要,这些文件需要很长时间才能下载并在全球范围内传输。

如何从 1 个用户扩展到 100 个用户

100 个用户:扩展数据层

CDN 帮了大忙:流量正在全速增长。 正如他们所说,著名视频博主马维德·莫布里克刚刚在我们注册并发布了他的“故事”。 感谢负载均衡器,API 服务器上的 CPU 和内存使用率保持在较低水平(运行 XNUMX 个 API 实例),但我们开始出现大量请求超时……这些延迟从何而来?

深入研究一下指标,我们发现数据库服务器上的 CPU 负载率为 80-90%。 我们已经到了极限。

扩展数据层可能是方程式中最困难的部分。 API 服务器提供无状态请求,因此我们只需添加更多 API 实例即可。 鼻子 以多数票 数据库无法做到这一点。 我们将讨论流行的关系数据库管理系统(PostgreSQL、MySQL 等)。

缓存

提高数据库性能的最简单方法之一是引入一个新组件:缓存层。 最常见的缓存方法是内存中的键值记录存储,例如 Redis 或 Memcached。 大多数云都有这些服务的托管版本:AWS 上的 Elasticache 和 Google Cloud 上的 Memorystore。

当服务多次重复调用数据库以检索相同信息时,缓存非常有用。 本质上,我们只访问数据库一次,将信息存储在缓存中,并且不会再次触及它。

例如,在我们的 Graminsta 服务中,每次有人访问明星 Mobrik 的个人资料页面时,API 服务器都会从数据库中查询其个人资料中的信息。 这种情况一再发生。 由于 Mobrik 的配置文件信息不会随每个请求而改变,因此非常适合缓存。

我们将数据库中的结果按key缓存在Redis中 user:id 有效期为30秒。 现在,当有人访问 Mobrik 的个人资料时,我们首先检查 Redis,如果数据存在,我们只需直接从 Redis 传输即可。 现在,对网站上最受欢迎的配置文件的请求实际上不会加载我们的数据库。

大多数缓存服务的另一个优点是它们比数据库服务器更容易扩展。 Redis内置了Redis集群模式。 类似于负载均衡器1,它允许您将 Redis 缓存分布在多台计算机上(如果需要,可以分布在数千台服务器上)。

几乎所有大型应用程序都使用缓存;它绝对是快速 API 不可或缺的一部分。 更快的查询处理和更高效的代码都很重要,但如果没有缓存,几乎不可能将服务扩展到数百万用户。

读取副本

当数据库的查询数量大幅增加时,我们还能做的另一件事就是在数据库管理系统中添加只读副本。 通过上述托管服务,只需单击一下即可完成此操作。 只读副本将在主数据库中保持最新状态,并且可用于 SELECT 语句。

现在这是我们的系统:

如何从 1 个用户扩展到 100 个用户

下一步

随着应用程序不断扩展,我们将继续分离服务以独立扩展它们。 例如,如果我们开始使用 Websockets,那么将 Websockets 处理代码拉入单独的服务中是有意义的。 我们可以将其放置在我们自己的负载均衡器后面的新实例上,该负载均衡器可以根据开放的 Websockets 连接进行扩展和缩减,而不管 HTTP 请求的数量如何。

我们还将继续对抗数据库级别的限制。 到了这个阶段,就该研究数据库分区和分片了。 这两种方法都需要额外的开销,但允许您几乎无限地扩展数据库。

我们还想安装 New Relic 或 Datadog 等监控和分析服务。 这将帮助您识别缓慢的查询并了解哪里需要改进。 随着规模的扩大,我们希望专注于寻找瓶颈并消除它们——通常使用前几节中的一些想法。

来源

这篇文章的灵感来自于其中之一 我最喜欢的关于高可扩展性的帖子。 我想让这篇文章更具体地介绍项目的初始阶段,并将其与一个供应商分开。 如果您对此主题感兴趣,请务必阅读。

脚注

  1. 尽管在多个实例之间的负载分配方面类似,但 Redis 集群的底层实现与负载均衡器有很大不同。 [返回]

如何从 1 个用户扩展到 100 个用户

来源: habr.com

添加评论