Rails 应用扩展开发者指南

2025-06-04

Rails 应用扩展开发者指南

拉维·杜杜库鲁

从 Airbnb 到 Zendesk,大量优秀的应用程序都是使用 Ruby 编程语言和 Rails Web 框架构建的。虽然 Rails 不如 React、Angular 和 Vuejs 等其他前端框架那么受欢迎,但它在现代软件开发中仍然拥有巨大的优势。

Ruby on Rails (RoR) 是开源的、有详尽文档的、维护良好的,并且通过新的 Gems(社区创建的开源库)不断扩展,作为 Ruby 代码标准化配置和分发的“快捷方式”。

Rails可以说是所有 RoR 中最大的瑰宝——一个易于定制和扩展的全栈服务器端 Web 应用程序框架。

Rails 是什么?快速回顾

Rails 建立在模型-视图-控制器 (MVC) 架构的基础之上。

这意味着每个 Rails 应用程序都有三个相互连接的层,分别负责一组相应的操作:

  1. 模型:数据层,包含应用程序的业务逻辑
  2. 控制器:“大脑中枢”,处理应用程序功能
  3. 视图:定义图形用户界面 (GUI) 和 UI 性能

本质上,模型层建立所需的数据结构,并包含处理 HTML、PDF、XML、RSS 和其他格式传入数据所需的代码。然后,模型层将更新传递给视图层,视图层再更新 GUI。反过来,控制器与模型和视图交互。例如,当从视图接收到更新时,它会通知模型如何处理该更新。同时,它也可以更新视图,以了解如何向用户显示结果。 (Rails 应用基本架构。图片来源:Medium)
替代文本

底层的 MVC 架构为 Rails 带来了几个重要的优势:

• 并行开发功能——一个开发人员可以开发视图,而其他开发人员可以处理模型子系统。以上特性也使得 Ruby on Rails 成为快速应用程序开发 (RAD) 方法的热门选择。

• 可重用代码组件——控制器、视图和模型可以相对轻松地打包,以便在多个功能之间共享和重用。如果操作得当,这将使代码更简洁、更易读,并缩短开发时间。此外,Ruby on Rails 遵循 DRY 原则(不要重复自己),这促使人们更频繁地重用单调功能的代码。

• 顶级安全性——该框架内置了一系列以安全为中心的功能,例如防御 SQL 注入和 XSS 攻击等。此外,社区还提供了丰富的 Grips,用于应对各种常见和新兴的网络安全威胁。

• 强大的可扩展性潜力——GitHub、Twitch 和 Fiverr 等大型 Web 应用之所以选择基于 Rails 构建,是有充分理由的。因为只要整体应用架构和部署策略得当,Rails 就能实现良好的扩展性。事实上,最古老的 Rails 应用之一 Shopify 的扩展能力可以达到每分钟处理数百万个请求(RPM)。

尽管如此,许多 Rails 指南仍然武断地声称Rails 应用难以扩展。这是真的吗?并非完全如此,本文将对此进行阐述。

Rails 应用扩展的 3 个常见问题

传统传说曾经说过,扩展 Rails 应用程序就像让骆驼穿过针眼一样——令人恼火和疲惫。

为了更好地理解这些问题的根源,让我们首先回顾一下 Web 应用程序的可扩展性。

可扩展性表示应用程序的架构在未来处理每分钟更多用户请求(RPM)的能力。

这里的关键词是“架构”,因为你对基础设施配置、连接性和整体布局的选择决定了整个系统的扩展能力。你使用的框架或编程语言对可扩展性的影响微乎其微(如果有的话)。

就 RoR 而言,开发人员实际上获得了一些优势,因为该框架提倡简洁、模块化的代码,易于与更多数据库管理系统集成。此外,添加负载均衡器来处理大量请求也相对容易。

然而,上述方法并不能完全解决 Rails 的扩展问题。坦白说,当底层基础设施不达标时,任何应用都很难扩展。

具体来说,Ruby 扩展问题经常由于以下原因出现:

• 数据库查询不佳
• 索引效率低下
• 缺乏日志记录和监控
• 数据库引擎选择不佳
• 缓存缓慢
• 代码过于复杂且杂乱

过度设计的应用程序架构

RoR 支持多线程。这意味着 Rails 框架可以同时处理不同部分的代码。

一方面,多线程是一种进步,因为它使您能够更明智地利用 CPU 时间并交付高性能应用程序。

但与此同时,在高度复杂的应用程序中,不同线程之间上下文切换的成本可能会很高。相应地,性能在某个时候会开始滞后。

如何应对

默认情况下,Ruby on Rails 优先考虑简洁、可复用的代码。如果 Rails 应用架构过于复杂(例如过于定制),确实会导致性能和可扩展性问题。

2007 年左右 Twitter 就出现过这种情况。

该团队在 Rails 上开发了一个 Twitter UI 原型,然后决定也用 Rails 编写后端代码。他们决定从头构建一个完全定制的全新后端,而不是修改一些已测试的组件。不出所料,他们的产品表现怪异,而且时间和扩展性都极具挑战性,正如团队在一次演示中承认的那样。由于代码过于复杂臃肿,他们在数据库分区时遇到了大量问题。 图片来源:SlideShare
替代文本

有趣的是,与此同时,另一个名为 Penny Arcade 的高流量 Rails Web 应用却表现良好。为什么?因为它没有过于繁琐的自定义代码,依赖关系映射清晰,并且与数据库连接良好。

记住:Ruby 支持应用内的多进程。在某些情况下,多进程应用的性能可能优于多线程应用。但进程的弊端在于它们会消耗更多内存,并且依赖关系也更复杂。如果您不小心终止了父进程,子进程将无法收到终止通知,从而变成缓慢的“僵尸”进程。这意味着它们会继续运行并消耗资源。所以要小心这些进程!

数据库设置不理想

在早期,Twitter 的写入工作负载非常密集,而读取模式组织混乱,与数据库分片不兼容。

目前,许多 Rails 开发者仍在粗略地编写合适的数据库索引,并对所有查询进行三重检查,以查找冗余请求。缓慢的数据库查询、缺乏缓存以及错综复杂的数据库索引可能会让任何优秀的 Rails 应用脱轨(此处似有误)。

有时,复杂的数据库设计也是深思熟虑的决策的一部分,就像我们的客户之一 PennyPop的情况一样。为了存储应用数据,团队向 Rails 应用程序发起了 API 请求。然后,应用程序本身将数据存储在 DynamoDB 中,并将响应发送回应用。团队没有使用 ActiveRecord,而是创建了自己的数据存储层,以实现应用与 DynamoDB 之间的通信。

但他们遇到的问题是,DynamoDB 对单个键中可存储的信息量有限制。这在技术上是一个难题,但开发团队想出了一个有趣的解决方法——将键的值压缩为 base64 编码数据的有效负载。这样做使得团队能够在应用程序和数据库之间交换更大的记录,而不会影响用户体验或应用程序性能。

当然,上述操作需要更多 CPU 资源。但由于他们使用Engine Yard来管理和优化其他基础设施,因此这些成本仍然可控。

如何应对

诚然,提升 Rails 数据库性能的方法有很多。随着应用变得越来越复杂,刻意的缓存和数据库分区(分片)是常用的方法之一。

更好的是,您有大量解决 RoR 数据库问题的优秀解决方案,例如:

• Redis——用于 Rails 应用程序的开源内存数据结构存储。

• ActiveRecord——一种数据库查询工具,通过内置缓存功能标准化对流行数据库的访问。

• Memcached — Ruby on Rails 的分布式内存缓存系统。

上述三种工具可以帮助您充分调整数据库以承受超高负载。

此外,您还可以:

• 随着数据库变得越来越复杂,将主要键的标准 ID 切换到 UUID。

• 当你的数据库变得非常大时,可以尝试其他 ActiveRecord 的 ORM 替代方案。一些不错的方案包括 Sequel、DataMapper 和 ORM Adapter。

• 使用数据库性能分析工具,尽早诊断和检测速度和性能问题。常用的工具有:rack-mini-profiler、bullet、rails_panel 等。

服务器带宽不足

最后一个问题虽然基本,但仍然普遍存在。如果资源不足,你根本无法将 Rails 应用加速到数百万 RPM。

当然,有了云计算,配置额外的实例只需点击几下鼠标即可。然而,您仍然需要了解并考虑以下事项:

• 特定应用程序/子系统对额外资源的要求

云计算成本(即金钱与速度的权衡)

理想情况下,您需要工具来不断扫描您的系统并识别性能缓慢、资源配置不足(和过度)的情况,以及不同应用程序的整体性能基准。

如果没有这些,就好比开车时没有速度计:你只能依靠直觉来判断车速是太慢还是太快。

如何应对

在 Kubernetes 上构建和扩展 Engine Yard 时,我们学到的一个教训是,容器平台没有为托管容器设置默认资源限制。因此,您的应用程序可能会消耗无限的 CPU 和内存,这可能会导致“吵闹的邻居”情况,即某些应用程序占用过多资源,从而拖累其他应用程序的性能。

解决方案:从一开始就编排您的容器。使用 Kubernetes 调度程序来调整 Pod 的节点大小,限制最大资源分配,并定义 Pod 的抢占行为。

此外,如果您正在运行容器,请务必设置自己的日志记录和监控,因为目前没有现成的解决方案。在 Kubernetes 中添加日志聚合功能,可以更好地了解您的应用行为。

在我们的例子中,我们使用:

• Fluent Bit 用于分布式日志收集

• Kibana + Elasticsearch 用于日志分析

• Prometheus + Grafana 用于指标警报和可视化

总结一下:确保可扩展性的关键是剔除滞后的模块,并分别优化不同的基础设施和架构元素,以获得更大的累积效益。

扩展 Rails 应用:两种主要方法

与其他应用程序类似,Rails 应用程序以两种方式扩展——垂直和水平。

两种方法在各自的案例中都有其优点。

垂直扩展

垂直扩展,即为应用配置更多服务器资源,可以增加 RPM 的数量。其基本前提与其他框架相同。您可以添加额外的处理器、RAM 等,直到技术上可行且经济合理为止。可以理解的是,垂直扩展只是一种临时的“补丁”解决方案。

垂直扩展 Rails 应用对于适应线性或可预测的增长是合理的,因为这样也更容易控制成本。此外,垂直扩展也是升级数据库服务器的一个好选择。毕竟,将速度较慢的数据库部署到更好的硬件上可以显著加速。

硬件是垂直扩展的明显限制因素。即使使用云资源,垂直扩展 Rails 应用仍然充满挑战。

例如,如果您计划在 Kubernetes 上实现垂直 Pod 自动缩放 (VPA),则会面临一些限制。

在我们对 Ruby 应用程序进行扩展的实验中,我们发现:

• VPA 是一种相当具有破坏性的方法,因为它会破坏原始 Pod,然后重新创建其垂直扩展版本。这可能会造成很大的破坏。

• 您不能将 VPA 与 Horizo​​ntal Pod Autoscaling 配对。

因此,最好尽可能优先考虑水平扩展。

水平扩展

水平扩展,即在多个服务器上重新分配工作负载,是一种更具未来性的 Rails 应用程序扩展方法。

本质上,您可以将应用程序转换为具有以下特点的三层架构:

• 用于连接应用程序的 Web 服务器和负载均衡器
• Rails 应用程序实例(本地或云端)
• 数据库实例(也是本地或基于云的)

主要思想是将负载分配到不同的机器上,以公平地获得最佳性能。

为了有效地在服务器实例之间重新路由 Rails 进程,您必须选择最佳的 Web 服务器和负载均衡解决方案。然后,根据新解耦的工作负载调整实例的规模。

负载均衡

负载均衡器是横向扩展架构的关键结构元素。本质上,它们执行路由功能,并帮助在连接的实例之间以最佳方式分配传入流量。

大多数云计算服务都自带原生软件负载均衡解决方案(例如AWS 上的Elastic Load Balancing)。此类解决方案还支持动态主机端口映射。这有助于在已注册的 Web 均衡器和容器实例之间建立无缝配对。

对于 Rails 应用程序,最常见的两个选项是使用 Web 服务器和应用程序服务器的组合(或融合服务)来确保最佳性能。

• Web 服务器将用户请求转发到您的网站,然后将其传递给 Rails 应用(如果适用)。本质上,它们会过滤掉不必要的 CSS、SSL 或 JavaScript 组件请求(这些请求服务器可以自行处理),从而将发往 Rails 应用的请求数量减少到最低限度。

  • Rails Web 服务器的示例:Ngnix 和 Apache。

• 应用服务器是将应用维护在内存中的程序。这样,当来自 Web 服务器应用的传入请求出现时,它会直接路由到该应用进行处理。然后,响应会被返回到 Web 服务器,最终返回给用户。在生产环境中与 Web 服务器配合使用时,这样的设置可以让你更快地将请求渲染到多个应用。

  • Rails 应用服务器示例:Unicorn、Puma、Thin、Rainbows。

最后,还有一些“融合”服务,例如 Passenger App(Phusion Passenger)。该服务与流行的 Web 服务器(Ngnix 和 Apache)集成,并引入了一个应用服务器层——可独立使用,也可与 Web 服务器组合使用。 图片来源:Phusion Passenger
替代文本

如果您想要一次性为大量应用程序推出统一的应用服务器设置,而不需要为每个应用程序单独设置应用服务器,那么 Passenger 是一个绝佳的选择。

简而言之,使用 Web 和应用服务器背后的主要思想是在不同的实例之间最佳地跨越不同的 Rails 进程。

专业提示:我们在构建产品时发现,AWS Elastic Load Balancer 经常无法满足需求。一个主要缺点是 ELB 无法处理多个虚拟主机。

在我们的案例中,我们继续配置基于 NGINX 的负载均衡器,并在其上配置了自动伸缩功能以支持 ELB。您也可以尝试使用 HAProxy。

应用程序实例

横向扩展架构的下一步是配置不同应用程序实例之间的通信,您的 Rails 工作负载将分配到这些实例中。

应用服务器(Unicorn、Puma 等)有助于确保 Web 服务器之间正常通信,从而提高每秒处理的请求吞吐量。在 Rails 上,您可以分配一个应用服务器来处理多个应用实例,这些实例可以拥有独立的“工作”进程或线程(具体取决于您使用的应用服务器服务类型)。

然而,确保不同的应用服务器能够与 Web 服务器良好通信至关重要。Rack 接口在这里非常有用,因为它有助于统一独立应用服务器之间的通信标准。

在为容器配置正确的实例时,请记住以下几点:

  1. 有四个变量可以控制 CPU 的最小/最大容量以及内存的最小/最大容量,从而调节 Pod 的大小
  2. 使用[最低要求+ 20%]公式限制资源
  3. 使用平均 CPU 利用率和平均内存利用率作为扩展指标
  4. 注意时间。Pod 和集群在 Kubernetes 上扩展需要 4 到 12 分钟。

PS:如果您不想在每次构建新的 pod/集群时都进行上述猜测,Engine Yard 提供了预测集群扩展功能,可以帮助您及时扩展基础设施,而不会增加成本。

数据库扩展

将数据库转移到所有应用程序实例使用的单独服务器是扩展 Rails 应用程序最巧妙的举措之一

首先,这是一个很好的练习,可以隔离数据并实现数据库复制,从而提高业务连续性。其次,这样做可以减少查询时间,因为请求不必经过存储不同数据的多个数据库实例。相反,它将直接进入合并的存储库。

因此,请考虑为您的关系数据库设置专用的 MySQL 或 PostgreSQL 服务器。然后清理它们并确保最佳实例大小以节省成本。

例如,AWS RDC 允许您在 18 种类型的数据库实例中进行选择,并进行细粒度的配置。选择将数据托管在更便宜的云区域可以大幅节省成本(有时可节省高达 40%!)。

以下是不同 AWS 区域按需每小时成本的差异:

美国东部(俄亥俄州)

• db.t3.small — 每小时 0.034 美元
• db.t3.xlarge — 每小时 0.272 美元
• db.t3.2xlarge — 每小时 0.544 美元

美国西部(洛杉矶)

• db.t3.small — 每小时 0.0408 美元
• db.t3.xlarge — 每小时 0.3264 美元
• db.t3.2xlarge — 每小时 0.6528 美元

欧洲(法兰克福)

• db.t3.small — 每小时 0.04 美元
• db.t3.large — 每小时 0.16 美元
• db.t3.2xlarge — 每小时 0.64 美元

亚太地区(首尔)

• db.t3.small — 每小时 0.052 美元
• db.t3.large — 每小时 0.208 美元
• db.t3.2xlarge — 每小时 0.832 美元

另一个专业提示:尽可能选择预留实例而不是按需实例,以进一步削减每小时成本。

缓存

数据库缓存的实现是加速 Rails 应用的另一个核心步骤,尤其是在数据库性能方面。鉴于 RoR 自带的原生查询缓存功能可以缓存每个查询返回的结果集,如果不利用这一点,真是可惜!
缓存可以帮助你加速那些慢查询。但在实施之前,请先调查一下!找到“罪魁祸首”后,可以考虑尝试不同的策略,例如:

• 低级缓存——适用于任何类型的缓存,用于检索数据库查询。
• Redis 缓存存储——允许您在内存中存储高达 512 MB 的键值对,并提供原生数据复制功能。
• Memcache 存储——另一种易于实现的内存数据存储,其值限制为 1 MB。与 Redis 不同,它支持多线程架构。

最终,缓存可以提高数据可用性,并通过代理提高应用程序的查询速度和性能。

数据库分片

最后,在数据库扩展过程中的某个时刻,您将不可避免地面临分片关系数据库的决定。

数据分片是指将数据库记录水平或垂直切分成更小的块(分片),并将它们存储在数据库节点集群中。其最大的优势在于查询速度更快,因为大型数据库被一分为二,运行所需的内存、I/O 和 CPU 资源增加了一倍。

然而,分片的弊端在于它会显著影响应用的逻辑。现在,每个查询的范围仅限于数据库 1 或数据库 2,不会出现数据混合的情况。因此,在添加新的应用功能时,您需要仔细考虑如何跨分片访问数据、数据共享与基础架构的关系,以及如何在不影响应用逻辑的情况下扩展支持基础架构的最佳方法。

总结:是否有更简单的解决方案来扩展 Rails 应用程序?

扩展 Rails 应用需要谨慎权衡,确保实例分配优化、资源及时调配,并精心编排容器。手动监控一系列应用和子服务的所有相关指标并非易事,也不应该如此。

您可以尝试 Engine Yard Kontainers (EYK)——我们为容器化应用提供的NoOps PaaS 自动扩容服务。本质上,我们充当您隐形的 DevOps 团队。您只需编写应用代码并将其部署到 EYK,我们便会接管自动扩容实施、容器编排以及其他基础设施优化任务。

了解有关Engine Yard 的更多信息。

文章来源:https://dev.to/devgraph/the-developers-guide-to-scaling-rails-apps-3kln
PREV
Divjoy:在💪 Steroids💉上创建 React 应用
NEXT
通过构建简单的东西来学习 CSS 伪元素 - 第 1 部分:按钮和链接悬停效果。