Rails的不同缓存策略

Posted AI启示录

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Rails的不同缓存策略相关的知识,希望对你有一定的参考价值。

Rails 在开发人员中享有盛誉。Rails 一度备受瞩目,是业界争论的焦点。人们对它的评价也大相径庭:从一种高生产率技术到一个小玩意,从市场定位准确到宣传过度。与很多新技术一样,Rails 也被毫无例外地被打上了 “未经验证、可扩展性有限” 的标记。与 C 和 Java™ 语言不同,Ruby 是解释性的,且存在性能上的一些固有阻碍。

实际上,Internet 上的许多大型网站都使用的是解释性语言。这些网站均引入了类似 Ruby 所采用的相同的策略:即集群式的无共享的架构。此外,缓存也是必需的。要获得尽可能好的性能,许多站点都需要采用一种有效的缓存策略。Rails 开发人员也开始跟随其后。

在 真实世界中的 Rails 系列中,在国际上享有盛誉的作家兼讲演人 Bruce Tate 将带您深入到真实世界中的 Rails 开发。作为 WellGood LLC 公司的 CTO,他主要负责设计、构建和维护慈善性质的门户网站 ChangingThePresent.org,在这里,癌症研究员可以贡献一小时的研究时间,您可以为保护热带雨林做出自己的一份贡献,您也可以资助白内障手术,让失明的人得以重见光明。许多用户都通过 ChangingThePresent 找到了数千家非赢利组织,此网站的影响和规模还在不断扩大。

现在,您可以找到数十篇帮助您构建简单 Rails 应用程序的文章。本系列无意介绍构建简单 blog 的基础知识,而是会带您深入到每个 Rails 网站都必须要面对和解决的实际问题。通过本系列文章,您将学习如何优化 Rails 以及如何让站点更加稳定。而且,您还将了解如何通过添加插件来解决 Rails 的一些基本限制。读完本系列的全部文章之后,您将对如何实际运行 Rails 站点有一个初步的了解。

几个场景

首先,让我先来带您浏览几个 ChangingThePresent.org 中的页面吧。我将显示站点中几个需要缓存的地方。然后,再指出我们为其中每个地方所做出的选择以及为实现这些页面所使用的代码或策略。尤其会重点讨论如下内容:

  • 全静态页面

  • 几乎无变化的全动态的页面

  • 动态页面片段

  • 应用程序数据

先来看看静态页面。几乎每个站点都会有静态页面,如图 1 所示,其中还有我们的条款和条件。可以通过单击 register 然后再选择是否接受用户协议来浏览相应页面。对于 ChangingThePresent 而言,我们从此页中删除了所有动态内容以便 Apache 能够对它进行缓存。按照我们 Apache 中配置的规则,这些内容永远都不会由 Rails 服务器生成。因此,我根本无需对其考虑 Rails 缓存。

图 1. 用户协议
接下来,再来看看全动态页面。理论上讲,ChangingThePresent 可以有一些动态构建的页面,但是这些页面一般很少变化。由于几乎所有页面都会显示用户是否登录,因此我们并不怎么关注这种缓存。

再下来,看看页面分段缓存。图 2 中所示的主页原来是完全静态的,现在,有一些元素变成了动态的。每天,页面都会显示一系列礼物,这些礼物有的是随机选的,有的则由我们的管理员选定。请注意在标题为 “A Few of our Special Gifts for Mother's Day” 节下的那些礼物,同时也请注意在最右边显示为 “login.” 的那个链接。此链接取决于用户是否登录。我们不能缓存整个页。页面每天只能改变一次。

图 2. 主页
最后再考虑应用程序。除非是在 15 年之前进行网络冲浪,否则您现在遇到的有趣站点全部都是动态的。现代的应用程序大都分层,而且可以通过在层间添加缓存来使这些分层更加有效。ChangingThePresent 在数据库层采用了一些缓存。接下来,我将深入讨论不同类型的缓存,还会介绍我们为 ChangingThePresent 都采用了何种缓存。

缓存静态内容

Mongrel 是一种 Web 服务器,由 Zed Shaw 利用 2500 行 Ruby 和 C 编写而成。这个小型的服务器占用内存极少,非常适合 Ruby Web 应用程序,例如 Rails、Nitro、Iowa 等等。Mongrel 可运行于 UNIX® 和 Linux™ 上,也可运行在 Win32 上。Mongrel 也经常可以作为代理运行在另一个 Web 服务器(例如 Apache 或 Litespeed)的后端,但这不是必需的 —— 因为 Mongrel 是一种 HTTP 服务器,可以与所有您偏好的 HTTP 工具结合使用。

除了图像之外,有关缓存静态数据的内容,可讲的内容不多。由于我们的网站是一个慈善性质的门户网站,这意味着我们需要更多地关注用户的感受,比如多加入一些图像或视频。但我们的 Web 服务器 Mongrel 并不能很好地服务静态数据,因此我们使用 Apache 来服务图像内容。

我们现在正在着手转向采用图形加速器 Panther Express 来缓存最经常被使用的图像以使其能够更快地被我们的客户访问到。要采取这种策略,我们将需要一个子域 images.changingThePresent.org。Panther Express 直接在图像的本地缓存中提供图像服务,然后再向我们发送请求。由于 Panther 服务并不知道我们何时会更改图像,所以我们使用 HTTP 报头来使其到期失效,如下所示:

HTTP 缓存失效报头

1

2

3

4

HTTP/1.1 200 OK

Cache-Control: max-age=86400, must-revalidate

Expires: Tues, 17 Apr 2007 11:43:51 GMT

Last-Modified: Mon, 16 Apr 2007 11:43:51 GMT


注意这些不是 html 报头。它们与 Web 页面内容独立构建。Web 服务器将负责构建这些 HTTP 报头。像这样一篇有关 Rails 的文章系列若详细介绍 Web 服务器配置,未免有点偏题,所以我将直接切入可用 Rails 框架进行控制的缓存内容这一主题(有关 Web 服务器配置的更多内容,请参见 参考资料 中的相关链接)。

页面缓存

如果动态页面不经常更改,可以使用页面级的缓存。比如,Blog 和公告牌使用的就是这种缓存。通过页面缓存,Rails 就可以用来构建动态 HTML 页,并将此页存储在公共目录,这样,应用程序服务器就可以像服务其他静态页面一样来服务这个动态页。

如果页面已经被缓存,那么就不需要引入 Rails,页面缓存是 Rails 内速度最快的一种缓存。在最底层,页面缓存实际上在 Rails 中非常容易实现。页面和分段缓存二者均在控制器级别发生。您需要告知 Rails 如下内容:

  • 想要缓存哪些页面?

  • 当页面内容更改时,您如何能在缓存中让该页面到期失效?

可以通过在控制器类中使用 caches_page 指令来启用页面缓存。例如,若要在 about_us_controller 缓存 privacy_policy和 user_agreement 页面,可以输入如下代码:

清单 2. 启用页面缓存

1

2

3

class AboutController < ApplicationController

  caches_page :privacy_policy, :user_agreement

end


让页面到期失效则可以通过 expire_page 指令来实现。若要在 Rails 调用 new_pages 动作时使上述页面到期失效,可以使用如下代码:

清单 3. 使页面失效

1

2

3

4

5

6

7

8

9

class AboutController < ApplicationController

  caches_page :privacy_policy, :user_agreement

   

  def new_pages

    expire_page :action => :privacy_policy

    expire_page :action => :user_agreement

  end

   

end


另外,有几个小问题需要注意,比如 URL。URL 不能依赖于 URL 参数。例如,应该使用 gifts/water/1 而非 gifts/water?page=1。在 routes.rb 中使用这类 URL 将非常容易。比如,我们的页面中总是有一个选项卡参数用来显示哪个选项卡被当前选中。若要将此选项卡作为 URL 的一部分,我们会有如下的路由规则:

清单 4. 选项卡的路由规则

1

map.connect 'member/:id/:tab', :controller => 'profiles', :action => 'show'


对于具有页面参数的那些列表以及依赖于 URL 参数的其他页面,也需要采用相同的做法。此外,还需要考虑安全性问题。

如果页面已经在缓存内,那么就不会用到 Rails 框架,服务器并不能为您管理安全性。Web 服务器将更乐于在缓存内呈现任何页面,而不管用户是否对其拥有查看的权限。所以,如果您很关心页面可由谁查看,那么就不要使用页面缓存。

如果只是想缓存简单的静态页面,那么了解上述内容就应该足够了。只要内容简单,实现起来就不难。

当想要缓存更为复杂的内容时,就需要进行一些权衡取舍了。由于想要缓存的页面高度动态,所以到期失效逻辑就会变得更加复杂。要处理复杂的到期失效逻辑,将需要编写和配置定制清理器(sweeper)。在某些控制器击发时,这些类会从缓存内删除选定的元素。

多数定制清理器都会观察某些模型对象,并根据更改击发逻辑来使一个或多个缓存页面到期失效。清单 5 显示了一种典型的缓存清理器。在此清理器中,开发人员可以定义一个活动记录事件,比如 after_save。当此事件击发时,清理器也会击发,并可让缓存内的特定页面到期失效。这个事例所显示的到期失效基于 expire_page 方法。而很多严格的应用程序大都直接使用 Ruby 优秀的文件系统实用工具来显式地删除所缓存的页面。

清单 5. 一个典型的观察器

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

class CauseController < ApplicationController

   cache_sweeper :cause_sweeper

...

 

class CauseSweeper < ActionController::Caching::Sweeper

  observe Cause

     

  def after_save(record)

    expire_page(:controller => 'causes', :action => 'show',

               :id => record.id)

    cause.nonprofits.each do |nonprofit|

     expire_page(:controller => 'nonprofits', :action => 'show',

                  :id => nonprofit.id)

     end

   end

end


现在,您可能会开始感觉到页面缓存的些许缺点了:复杂性。您虽然可以很好地进行页面级的缓存,但固有的复杂性却让应用程序很难测试,进而会增加系统内出现 bug 的可能性。而且,如果页面针对每个用户都会有所不同,或者希望缓存进行过身份验证的页面,那么将需要使用页面缓存之外的方式。对于 ChangingThePresent,我们必须处理两种情况,原因是我们必须基于用户是否登录来更改基本布局上的链接。对于大多数页面,我们甚至都不会考虑使用页面级缓存。为了让您能够深入了解页面级的缓存,在本文的 参考资料 部分,我特意给出了到一系列有关页面级缓存的优秀文章的链接。接下来,将来深入探究另一种形式的整页缓存 —— 动作缓存。

以上是关于Rails的不同缓存策略的主要内容,如果未能解决你的问题,请参考以下文章

如何使控制台中的视图缓存片段过期?

在每个用户的Rails中使用片段缓存

Rails:旧数据与新数据不匹配时如何更新片段缓存

为 memcached 和 Rails 组合片段和对象缓存的最佳方式

Rails - 使用特定属性值缓存所有用户

以下代码片段是不是容易受到 Rails 5 中 SQL 注入的影响?