-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcaching_with_rails.html
554 lines (499 loc) · 37.3 KB
/
caching_with_rails.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN" lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Rails 缓存简介 — Ruby on Rails 指南</title>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print" />
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shCore.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shThemeRailsGuides.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/fixes.css" />
<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
</head>
<body class="guide">
<div id="topNav">
<div class="wrapper">
<strong class="more-info-label">更多内容 <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong>
<span class="red-button more-info-button">
更多内容
</span>
<ul class="more-info-links s-hidden">
<li class="more-info"><a href="http://rubyonrails.org/">综览</a></li>
<li class="more-info"><a href="http://rubyonrails.org/download">下载</a></li>
<li class="more-info"><a href="http://rubyonrails.org/deploy">部署</a></li>
<li class="more-info"><a href="https://github.com/rails/rails">源码</a></li>
<li class="more-info"><a href="http://rubyonrails.org/screencasts">视频</a></li>
<li class="more-info"><a href="http://rubyonrails.org/documentation">文件</a></li>
<li class="more-info"><a href="http://rubyonrails.org/community">社群</a></li>
<li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li>
</ul>
</div>
</div>
<div id="header">
<div class="wrapper clearfix">
<h1><a href="index.html" title="回首页">Guides.rubyonrails.org</a></h1>
<ul class="nav">
<li><a class="nav-item" href="index.html">首页</a></li>
<li class="guides-index guides-index-large">
<a href="index.html" id="guidesMenu" class="guides-index-item nav-item">指南目录</a>
<div id="guides" class="clearfix" style="display: none;">
<hr />
<dl class="L">
<dt>入门</dt>
<dd><a href="getting_started.html">Rails 入门</a></dd>
<dt>模型</dt>
<dd><a href="active_record_basics.html">Active Record 基础</a></dd>
<dd><a href="active_record_migrations.html">Active Record 数据库迁移</a></dd>
<dd><a href="active_record_validations.html">Active Record 数据验证</a></dd>
<dd><a href="active_record_callbacks.html">Active Record 回调</a></dd>
<dd><a href="association_basics.html">Active Record 关联</a></dd>
<dd><a href="active_record_querying.html">Active Record 查询</a></dd>
<dt>视图</dt>
<dd><a href="layouts_and_rendering.html">Rails 布局和视图渲染</a></dd>
<dd><a href="form_helpers.html">Action View 表单帮助方法</a></dd>
<dt>控制器</dt>
<dd><a href="action_controller_overview.html">Action Controller 简介</a></dd>
<dd><a href="routing.html">Rails 路由全解</a></dd>
</dl>
<dl class="R">
<dt>深入</dt>
<dd><a href="active_support_core_extensions.html">Active Support 核心扩展</a></dd>
<dd><a href="i18n.html">Rails 国际化 API</a></dd>
<dd><a href="action_mailer_basics.html">Action Mailer 基础</a></dd>
<dd><a href="active_job_basics.html">Active Job 基础</a></dd>
<dd><a href="security.html">Rails 安全指南</a></dd>
<dd><a href="debugging_rails_applications.html">调试 Rails 程序</a></dd>
<dd><a href="configuring.html">设置 Rails 程序</a></dd>
<dd><a href="command_line.html">Rails 命令行</a></dd>
<dd><a href="asset_pipeline.html">Asset Pipeline</a></dd>
<dd><a href="working_with_javascript_in_rails.html">在 Rails 中使用 JavaScript</a></dd>
<dd><a href="constant_autoloading_and_reloading.html">Constant Autoloading and Reloading</a></dd>
<dt>扩展 Rails</dt>
<dd><a href="rails_on_rack.html">Rails on Rack</a></dd>
<dd><a href="generators.html">客制与新建 Rails 产生器</a></dd>
<dd><a href="rails_application_templates.html">Rails 应用程式模版</a></dd>
<dt>贡献 Ruby on Rails</dt>
<dd><a href="contributing_to_ruby_on_rails.html">贡献 Ruby on Rails</a></dd>
<dd><a href="api_documentation_guidelines.html">API 文件准则</a></dd>
<dd><a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南准则</a></dd>
<dt>维护方针</dt>
<dd><a href="maintenance_policy.html">维护方针</a></dd>
<dt>发布记</dt>
<dd><a href="upgrading_ruby_on_rails.html">升级 Ruby on Rails</a></dd>
<dd><a href="4_2_release_notes.html">Ruby on Rails 4.2 发布记</a></dd>
<dd><a href="4_1_release_notes.html">Ruby on Rails 4.1 发布记</a></dd>
<dd><a href="4_0_release_notes.html">Ruby on Rails 4.0 发布记</a></dd>
<dd><a href="3_2_release_notes.html">Ruby on Rails 3.2 发布记</a></dd>
<dd><a href="3_1_release_notes.html">Ruby on Rails 3.1 发布记</a></dd>
<dd><a href="3_0_release_notes.html">Ruby on Rails 3.0 发布记</a></dd>
<dd><a href="2_3_release_notes.html">Ruby on Rails 2.3 发布记</a></dd>
<dd><a href="2_2_release_notes.html">Ruby on Rails 2.2 发布记</a></dd>
</dl>
</div>
</li>
<!-- <li><a class="nav-item" href="//github.com/docrails-tw/wiki">参与翻译</a></li> -->
<li><a class="nav-item" href="https://github.com/ruby-china/guides/blob/master/CONTRIBUTING.md">贡献</a></li>
<li><a class="nav-item" href="credits.html">致谢</a></li>
<li class="guides-index guides-index-small">
<select class="guides-index-item nav-item">
<option value="index.html">指南目录</option>
<optgroup label="入门">
<option value="getting_started.html">Rails 入门</option>
</optgroup>
<optgroup label="模型">
<option value="active_record_basics.html">Active Record 基础</option>
<option value="active_record_migrations.html">Active Record 数据库迁移</option>
<option value="active_record_validations.html">Active Record 数据验证</option>
<option value="active_record_callbacks.html">Active Record 回调</option>
<option value="association_basics.html">Active Record 关联</option>
<option value="active_record_querying.html">Active Record 查询</option>
</optgroup>
<optgroup label="视图">
<option value="layouts_and_rendering.html">Rails 布局和视图渲染</option>
<option value="form_helpers.html">Action View 表单帮助方法</option>
</optgroup>
<optgroup label="控制器">
<option value="action_controller_overview.html">Action Controller 简介</option>
<option value="routing.html">Rails 路由全解</option>
</optgroup>
<optgroup label="深入">
<option value="active_support_core_extensions.html">Active Support 核心扩展</option>
<option value="i18n.html">Rails 国际化 API</option>
<option value="action_mailer_basics.html">Action Mailer 基础</option>
<option value="active_job_basics.html">Active Job 基础</option>
<option value="security.html">Rails 安全指南</option>
<option value="debugging_rails_applications.html">调试 Rails 程序</option>
<option value="configuring.html">设置 Rails 程序</option>
<option value="command_line.html">Rails 命令行</option>
<option value="asset_pipeline.html">Asset Pipeline</option>
<option value="working_with_javascript_in_rails.html">在 Rails 中使用 JavaScript</option>
<option value="constant_autoloading_and_reloading.html">Constant Autoloading and Reloading</option>
</optgroup>
<optgroup label="扩展 Rails">
<option value="rails_on_rack.html">Rails on Rack</option>
<option value="generators.html">客制与新建 Rails 产生器</option>
<option value="rails_application_templates.html">Rails 应用程式模版</option>
</optgroup>
<optgroup label="贡献 Ruby on Rails">
<option value="contributing_to_ruby_on_rails.html">贡献 Ruby on Rails</option>
<option value="api_documentation_guidelines.html">API 文件准则</option>
<option value="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南准则</option>
</optgroup>
<optgroup label="维护方针">
<option value="maintenance_policy.html">维护方针</option>
</optgroup>
<optgroup label="发布记">
<option value="upgrading_ruby_on_rails.html">升级 Ruby on Rails</option>
<option value="4_2_release_notes.html">Ruby on Rails 4.2 发布记</option>
<option value="4_1_release_notes.html">Ruby on Rails 4.1 发布记</option>
<option value="4_0_release_notes.html">Ruby on Rails 4.0 发布记</option>
<option value="3_2_release_notes.html">Ruby on Rails 3.2 发布记</option>
<option value="3_1_release_notes.html">Ruby on Rails 3.1 发布记</option>
<option value="3_0_release_notes.html">Ruby on Rails 3.0 发布记</option>
<option value="2_3_release_notes.html">Ruby on Rails 2.3 发布记</option>
<option value="2_2_release_notes.html">Ruby on Rails 2.2 发布记</option>
</optgroup>
</select>
</li>
</ul>
</div>
</div>
</div>
<hr class="hide" />
<div id="feature">
<div class="wrapper">
<h2>Rails 缓存简介</h2><p>本文要教你如果避免频繁查询数据库,在最短的时间内把真正需要的内容返回给客户端。</p><p>读完本文,你将学到:</p>
<ul>
<li>页面和动作缓存(在 Rails 4 中被提取成单独的 gem);</li>
<li>片段缓存;</li>
<li>存储缓存的方法;</li>
<li>Rails 对条件 GET 请求的支持;</li>
</ul>
<div id="subCol">
<h3 class="chapter"><img src="images/chapters_icon.gif" alt="" />Chapters</h3>
<ol class="chapters">
<li>
<a href="#%E7%BC%93%E5%AD%98%E5%9F%BA%E7%A1%80">缓存基础</a>
<ul>
<li><a href="#%E9%A1%B5%E9%9D%A2%E7%BC%93%E5%AD%98">页面缓存</a></li>
<li><a href="#%E5%8A%A8%E4%BD%9C%E7%BC%93%E5%AD%98">动作缓存</a></li>
<li><a href="#%E7%89%87%E6%AE%B5%E7%BC%93%E5%AD%98">片段缓存</a></li>
<li><a href="#%E5%BA%95%E5%B1%82%E7%BC%93%E5%AD%98">底层缓存</a></li>
<li><a href="#sql-%E7%BC%93%E5%AD%98">SQL 缓存</a></li>
</ul>
</li>
<li>
<a href="#%E7%BC%93%E5%AD%98%E7%9A%84%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F">缓存的存储方式</a>
<ul>
<li><a href="#%E8%AE%BE%E7%BD%AE">设置</a></li>
<li><a href="#activesupport::cache::store">ActiveSupport::Cache::Store</a></li>
<li><a href="#activesupport::cache::memorystore">ActiveSupport::Cache::MemoryStore</a></li>
<li><a href="#activesupport::cache::filestore">ActiveSupport::Cache::FileStore</a></li>
<li><a href="#activesupport::cache::memcachestore">ActiveSupport::Cache::MemCacheStore</a></li>
<li><a href="#activesupport::cache::ehcachestore">ActiveSupport::Cache::EhcacheStore</a></li>
<li><a href="#activesupport::cache::nullstore">ActiveSupport::Cache::NullStore</a></li>
<li><a href="#%E8%87%AA%E5%BB%BA%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F">自建存储方式</a></li>
<li><a href="#%E7%BC%93%E5%AD%98%E9%94%AE">缓存键</a></li>
</ul>
</li>
<li><a href="#%E6%94%AF%E6%8C%81%E6%9D%A1%E4%BB%B6-get-%E8%AF%B7%E6%B1%82">支持条件 GET 请求</a></li>
</ol>
</div>
</div>
</div>
<div id="container">
<div class="wrapper">
<div id="mainCol">
<h3 id="缓存基础">1 缓存基础</h3><p>本节介绍三种缓存技术:页面,动作和片段。Rails 默认支持片段缓存。如果想使用页面缓存和动作缓存,要在 <code>Gemfile</code> 中加入 <code>actionpack-page_caching</code> 和 <code>actionpack-action_caching</code>。</p><p>在开发环境中若想使用缓存,要把 <code>config.action_controller.perform_caching</code> 选项设为 <code>true</code>。这个选项一般都在各环境的设置文件(<code>config/environments/*.rb</code>)中设置,在开发环境和测试环境默认是禁用的,在生产环境中默认是开启的。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.action_controller.perform_caching = true
</pre>
</div>
<h4 id="页面缓存">1.1 页面缓存</h4><p>页面缓存机制允许网页服务器(Apache 或 Nginx 等)直接处理请求,不经 Rails 处理。这么做显然速度超快,但并不适用于所有情况(例如需要身份认证的页面)。服务器直接从文件系统上伺服文件,所以缓存过期是一个很棘手的问题。</p><div class="note"><p>Rails 4 删除了对页面缓存的支持,如想使用就得安装 <a href="https://github.com/rails/actionpack-page_caching">actionpack-page_caching gem</a>。最新推荐的缓存方法参见 <a href="http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works">DHH 对键基缓存过期的介绍</a>。</p></div><h4 id="动作缓存">1.2 动作缓存</h4><p>如果动作上有前置过滤器就不能使用页面缓存,例如需要身份认证的页面,这时需要使用动作缓存。动作缓存和页面缓存的工作方式差不多,但请求还是会经由 Rails 处理,所以在伺服缓存之前会执行前置过滤器。使用动作缓存可以执行身份认证等限制,然后再从缓存中取出结果返回客户端。</p><div class="note"><p>Rails 4 删除了对动作缓存的支持,如想使用就得安装 <a href="https://github.com/rails/actionpack-action_caching">actionpack-action_caching gem</a>。最新推荐的缓存方法参见 <a href="http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works">DHH 对键基缓存过期的介绍</a>。</p></div><h4 id="片段缓存">1.3 片段缓存</h4><p>如果能缓存整个页面或动作的内容,再伺服给客户端,这个世界就完美了。但是,动态网页程序的页面一般都由很多部分组成,使用的缓存机制也不尽相同。在动态生成的页面中,不同的内容要使用不同的缓存方式和过期日期。为此,Rails 提供了一种缓存机制叫做“片段缓存”。</p><p>片段缓存把视图逻辑的一部分打包放在 <code>cache</code> 块中,后续请求都会从缓存中伺服这部分内容。</p><p>例如,如果想实时显示网站的订单,而且不想缓存这部分内容,但想缓存显示所有可选商品的部分,就可以使用下面这段代码:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<% Order.find_recent.each do |o| %>
<%= o.buyer.name %> bought <%= o.product.name %>
<% end %>
<% cache do %>
All available products:
<% Product.all.each do |p| %>
<%= link_to p.name, product_url(p) %>
<% end %>
<% end %>
</pre>
</div>
<p>上述代码中的 <code>cache</code> 块会绑定到调用它的动作上,输出到动作缓存的所在位置。因此,如果要在动作中使用多个片段缓存,就要使用 <code>action_suffix</code> 为 <code>cache</code> 块指定前缀:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<% cache(action: 'recent', action_suffix: 'all_products') do %>
All available products:
</pre>
</div>
<p><code>expire_fragment</code> 方法可以把缓存设为过期,例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
expire_fragment(controller: 'products', action: 'recent', action_suffix: 'all_products')
</pre>
</div>
<p>如果不想把缓存绑定到调用它的动作上,调用 <code>cahce</code> 方法时可以使用全局片段名:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<% cache('all_available_products') do %>
All available products:
<% end %>
</pre>
</div>
<p>在 <code>ProductsController</code> 的所有动作中都可以使用片段名调用这个片段缓存,而且过期的设置方式不变:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
expire_fragment('all_available_products')
</pre>
</div>
<p>如果不想手动设置片段缓存过期,而想每次更新商品后自动过期,可以定义一个帮助方法:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
module ProductsHelper
def cache_key_for_products
count = Product.count
max_updated_at = Product.maximum(:updated_at).try(:utc).try(:to_s, :number)
"products/all-#{count}-#{max_updated_at}"
end
end
</pre>
</div>
<p>这个方法生成一个缓存键,用于所有商品的缓存。在视图中可以这么做:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<% cache(cache_key_for_products) do %>
All available products:
<% end %>
</pre>
</div>
<p>如果想在满足某个条件时缓存片段,可以使用 <code>cache_if</code> 或 <code>cache_unless</code> 方法:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<% cache_if (condition, cache_key_for_products) do %>
All available products:
<% end %>
</pre>
</div>
<p>缓存的键名还可使用 Active Record 模型:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<% Product.all.each do |p| %>
<% cache(p) do %>
<%= link_to p.name, product_url(p) %>
<% end %>
<% end %>
</pre>
</div>
<p>Rails 会在模型上调用 <code>cache_key</code> 方法,返回一个字符串,例如 <code>products/23-20130109142513</code>。键名中包含模型名,ID 以及 <code>updated_at</code> 字段的时间戳。所以更新商品后会自动生成一个新片段缓存,因为键名变了。</p><p>上述两种缓存机制还可以结合在一起使用,这叫做“俄罗斯套娃缓存”(Russian Doll Caching):</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<% cache(cache_key_for_products) do %>
All available products:
<% Product.all.each do |p| %>
<% cache(p) do %>
<%= link_to p.name, product_url(p) %>
<% end %>
<% end %>
<% end %>
</pre>
</div>
<p>之所以叫“俄罗斯套娃缓存”,是因为嵌套了多个片段缓存。这种缓存的优点是,更新单个商品后,重新生成外层片段缓存时可以继续使用内层片段缓存。</p><h4 id="底层缓存">1.4 底层缓存</h4><p>有时不想缓存视图片段,只想缓存特定的值或者查询结果。Rails 中的缓存机制可以存储各种信息。</p><p>实现底层缓存最有效地方式是使用 <code>Rails.cache.fetch</code> 方法。这个方法既可以从缓存中读取数据,也可以把数据写入缓存。传入单个参数时,读取指定键对应的值。传入代码块时,会把代码块的计算结果存入缓存的指定键中,然后返回计算结果。</p><p>以下面的代码为例。程序中有个 <code>Product</code> 模型,其中定义了一个实例方法,用来查询竞争对手网站上的商品价格。这个方法的返回结果最好使用底层缓存:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Product < ActiveRecord::Base
def competing_price
Rails.cache.fetch("#{cache_key}/competing_price", expires_in: 12.hours) do
Competitor::API.find_price(id)
end
end
end
</pre>
</div>
<div class="note"><p>注意,在这个例子中使用了 <code>cache_key</code> 方法,所以得到的缓存键名是这种形式:<code>products/233-20140225082222765838000/competing_price</code>。<code>cache_key</code> 方法根据模型的 <code>id</code> 和 <code>updated_at</code> 属性生成键名。这是最常见的做法,因为商品更新后,缓存就失效了。一般情况下,使用底层缓存保存实例的相关信息时,都要生成缓存键。</p></div><h4 id="sql-缓存">1.5 SQL 缓存</h4><p>查询缓存是 Rails 的一个特性,把每次查询的结果缓存起来,如果在同一次请求中遇到相同的查询,直接从缓存中读取结果,不用再次查询数据库。</p><p>例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ProductsController < ApplicationController
def index
# Run a find query
@products = Product.all
...
# Run the same query again
@products = Product.all
end
end
</pre>
</div>
<h3 id="缓存的存储方式">2 缓存的存储方式</h3><p>Rails 为动作缓存和片段缓存提供了不同的存储方式。</p><div class="info"><p>页面缓存全部存储在硬盘中。</p></div><h4 id="设置">2.1 设置</h4><p>程序默认使用的缓存存储方式可以在文件 <code>config/application.rb</code> 的 <code>Application</code> 类中或者环境设置文件(<code>config/environments/*.rb</code>)的 <code>Application.configure</code> 代码块中调用 <code>config.cache_store=</code> 方法设置。该方法的第一个参数是存储方式,后续参数都是传给对应存储方式构造器的参数。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.cache_store = :memory_store
</pre>
</div>
<div class="note"><p>在设置代码块外部可以调用 <code>ActionController::Base.cache_store</code> 方法设置存储方式。</p></div><p>缓存中的数据通过 <code>Rails.cache</code> 方法获取。</p><h4 id="activesupport::cache::store">2.2 ActiveSupport::Cache::Store</h4><p>这个类提供了在 Rails 中和缓存交互的基本方法。这是个抽象类,不能直接使用,应该使用针对各存储引擎的具体实现。Rails 实现了几种存储方式,介绍参见后几节。</p><p>和缓存交互常用的方法有:<code>read</code>,<code>write</code>,<code>delete</code>,<code>exist?</code>,<code>fetch</code>。<code>fetch</code> 方法接受一个代码块,如果缓存中有对应的数据,将其返回;否则,执行代码块,把结果写入缓存。</p><p>Rails 实现的所有存储方式都共用了下面几个选项。这些选项可以传给构造器,也可传给不同的方法,和缓存中的记录交互。</p>
<ul>
<li><p><code>:namespace</code>:在缓存存储中创建命名空间。如果和其他程序共用同一个存储,可以使用这个选项。</p></li>
<li><p><code>:compress</code>:是否压缩缓存。便于在低速网络中传输大型缓存记录。</p></li>
<li><p><code>:compress_threshold</code>:结合 <code>:compress</code> 选项使用,设定一个阈值,低于这个值就不压缩缓存。默认为 16 KB。</p></li>
<li><p><code>:expires_in</code>:为缓存记录设定一个过期时间,单位为秒,过期后把记录从缓存中删除。</p></li>
<li><p><code>:race_condition_ttl</code>:结合 <code>:expires_in</code> 选项使用。缓存过期后,禁止多个进程同时重新生成同一个缓存记录(叫做 dog pile effect),从而避免条件竞争。这个选项设置一个秒数,在这个时间之后才能再次使用重新生成的新值。如果设置了 <code>:expires_in</code> 选项,最好也设置这个选项。</p></li>
</ul>
<h4 id="activesupport::cache::memorystore">2.3 ActiveSupport::Cache::MemoryStore</h4><p>这种存储方式在 Ruby 进程中把缓存保存在内存中。存储空间的大小由 <code>:size</code> 选项指定,默认为 32MB。如果超出分配的大小,系统会清理缓存,把最不常使用的记录删除。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.cache_store = :memory_store, { size: 64.megabytes }
</pre>
</div>
<p>如果运行多个 Rails 服务器进程(使用 mongrel_cluster 或 Phusion Passenger 时),进程间无法共用缓存数据。这种存储方式不适合在大型程序中使用,不过很适合只有几个服务器进程的小型、低流量网站,也可在开发环境和测试环境中使用。</p><h4 id="activesupport::cache::filestore">2.4 ActiveSupport::Cache::FileStore</h4><p>这种存储方式使用文件系统保存缓存。缓存文件的存储位置必须在初始化时指定。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.cache_store = :file_store, "/path/to/cache/directory"
</pre>
</div>
<p>使用这种存储方式,同一主机上的服务器进程之间可以共用缓存。运行在不同主机上的服务器进程之间也可以通过共享的文件系统共用缓存,但这种用法不是最好的方式,因此不推荐使用。这种存储方式适合在只用了一到两台主机的中低流量网站中使用。</p><p>注意,如果不定期清理,缓存会不断增多,最终会用完硬盘空间。</p><p>这是默认使用的缓存存储方式。</p><h4 id="activesupport::cache::memcachestore">2.5 ActiveSupport::Cache::MemCacheStore</h4><p>这种存储方式使用 Danga 开发的 <code>memcached</code> 服务器,为程序提供一个中心化的缓存存储。Rails 默认使用附带安装的 <code>dalli</code> gem 实现这种存储方式。这是目前在生产环境中使用最广泛的缓存存储方式,可以提供单个缓存存储,或者共享的缓存集群,性能高,冗余度低。</p><p>初始化时要指定集群中所有 memcached 服务器的地址。如果没有指定地址,默认运行在本地主机的默认端口上,这对大型网站来说不是个好主意。</p><p>在这种缓存存储中使用 <code>write</code> 和 <code>fetch</code> 方法还可指定两个额外的选项,充分利用 memcached 的特有功能。指定 <code>:raw</code> 选项可以直接把没有序列化的数据传给 memcached 服务器。在这种类型的数据上可以使用 memcached 的原生操作,例如 <code>increment</code> 和 <code>decrement</code>。如果不想让 memcached 覆盖已经存在的记录,可以指定 <code>:unless_exist</code> 选项。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
</pre>
</div>
<h4 id="activesupport::cache::ehcachestore">2.6 ActiveSupport::Cache::EhcacheStore</h4><p>如果在 JRuby 平台上运行程序,可以使用 Terracotta 开发的 Ehcache 存储缓存。Ehcache 是使用 Java 开发的开源缓存存储,同时也提供企业版,增强了稳定性、操作便利性,以及商用支持。使用这种存储方式要先安装 <code>jruby-ehcache-rails3</code> gem(1.1.0 及以上版本)。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.cache_store = :ehcache_store
</pre>
</div>
<p>初始化时,可以使用 <code>:ehcache_config</code> 选项指定 Ehcache 设置文件的位置(默认为 Rails 程序根目录中的 <code>ehcache.xml</code>),还可使用 <code>:cache_name</code> 选项定制缓存名(默认为 <code>rails_cache</code>)。</p><p>使用 <code>write</code> 方法时,除了可以使用通用的 <code>:expires_in</code> 选项之外,还可指定 <code>:unless_exist</code> 选项,让 Ehcache 使用 <code>putIfAbsent</code> 方法代替 <code>put</code> 方法,不覆盖已经存在的记录。除此之外,<code>write</code> 方法还可接受 <a href="http://ehcache.org/apidocs/net/sf/ehcache/Element.html">Ehcache Element 类</a>开放的所有属性,包括:</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>参数类型</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>elementEvictionData</td>
<td>ElementEvictionData</td>
<td>设置元素的 eviction 数据实例</td>
</tr>
<tr>
<td>eternal</td>
<td>boolean</td>
<td>设置元素是否为 eternal</td>
</tr>
<tr>
<td>timeToIdle, tti</td>
<td>int</td>
<td>设置空闲时间</td>
</tr>
<tr>
<td>timeToLive, ttl, expires_in</td>
<td>int</td>
<td>设置在线时间</td>
</tr>
<tr>
<td>version</td>
<td>long</td>
<td>设置 ElementAttributes 对象的 <code>version</code> 属性</td>
</tr>
</tbody>
</table>
<p>这些选项通过 Hash 传给 <code>write</code> 方法,可以使用驼峰式或者下划线分隔形式。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Rails.cache.write('key', 'value', time_to_idle: 60.seconds, timeToLive: 600.seconds)
caches_action :index, expires_in: 60.seconds, unless_exist: true
</pre>
</div>
<p>关于 Ehcache 更多的介绍,请访问 <a href="http://ehcache.org/">http://ehcache.org/</a>。关于如何在运行于 JRuby 平台之上的 Rails 中使用 Ehcache,请访问 <a href="http://ehcache.org/documentation/jruby.html">http://ehcache.org/documentation/jruby.html</a>。</p><h4 id="activesupport::cache::nullstore">2.7 ActiveSupport::Cache::NullStore</h4><p>这种存储方式只可在开发环境和测试环境中使用,并不会存储任何数据。如果在开发过程中必须和 <code>Rails.cache</code> 交互,而且会影响到修改代码后的效果,使用这种存储方式尤其方便。使用这种存储方式时调用 <code>fetch</code> 和 <code>read</code> 方法没有实际作用。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.cache_store = :null_store
</pre>
</div>
<h4 id="自建存储方式">2.8 自建存储方式</h4><p>要想自建缓存存储方式,可以继承 <code>ActiveSupport::Cache::Store</code> 类,并实现相应的方法。自建存储方式时,可以使用任何缓存技术。</p><p>使用自建的存储方式,把 <code>cache_store</code> 设为类的新实例即可。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.cache_store = MyCacheStore.new
</pre>
</div>
<h4 id="缓存键">2.9 缓存键</h4><p>缓存中使用的键可以是任意对象,只要能响应 <code>:cache_key</code> 或 <code>:to_param</code> 方法即可。如果想生成自定义键,可以在类中定义 <code>:cache_key</code> 方法。Active Record 根据类名和记录的 ID 生成缓存键。</p><p>缓存键也可使用 Hash 或者数组。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# This is a legal cache key
Rails.cache.read(site: "mysite", owners: [owner_1, owner_2])
</pre>
</div>
<p><code>Rails.cache</code> 方法中使用的键和保存到存储引擎中的键并不一样。保存时,可能会根据命名空间或引擎的限制做修改。也就是说,不能使用 <code>memcache-client</code> gem 调用 <code>Rails.cache</code> 方法保存缓存再尝试读取缓存。不过,无需担心会超出 memcached 的大小限制,或者违反句法规则。</p><h3 id="支持条件-get-请求">3 支持条件 GET 请求</h3><p>条件请求是 HTTP 规范的一个特性,网页服务器告诉浏览器 GET 请求的响应自上次请求以来没有发生变化,可以直接读取浏览器缓存中的副本。</p><p>条件请求通过 <code>If-None-Match</code> 和 <code>If-Modified-Since</code> 报头实现,这两个报头的值分别是内容的唯一 ID 和上次修改内容的时间戳,在服务器和客户端之间来回传送。如果浏览器发送的请求中内容 ID(ETag)或上次修改时间戳和服务器上保存的值一样,服务器只需返回一个空响应,并把状态码设为未修改。</p><p>服务器负责查看上次修改时间戳和 <code>If-None-Match</code> 报头的值,决定是否返回完整的响应。在 Rails 中使用条件 GET 请求很简单:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
# If the request is stale according to the given timestamp and etag value
# (i.e. it needs to be processed again) then execute this block
if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key)
respond_to do |wants|
# ... normal response processing
end
end
# If the request is fresh (i.e. it's not modified) then you don't need to do
# anything. The default render checks for this using the parameters
# used in the previous call to stale? and will automatically send a
# :not_modified. So that's it, you're done.
end
end
</pre>
</div>
<p>如果不想使用 Hash,还可直接传入模型实例,Rails 会调用 <code>updated_at</code> 和 <code>cache_key</code> 方法分别设置 <code>last_modified</code> 和 <code>etag</code>:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
respond_with(@product) if stale?(@product)
end
end
</pre>
</div>
<p>如果没有使用特殊的方式处理响应,使用默认的渲染机制(例如,没有使用 <code>respond_to</code> 代码块,或者没有手动调用 <code>render</code> 方法),还可使用十分便利的 <code>fresh_when</code> 方法:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ProductsController < ApplicationController
# This will automatically send back a :not_modified if the request is fresh,
# and will render the default template (product.*) if it's stale.
def show
@product = Product.find(params[:id])
fresh_when last_modified: @product.published_at.utc, etag: @product
end
end
</pre>
</div>
<h3>反馈</h3>
<p>
欢迎帮忙改善指南质量。
</p>
<p>
如发现任何错误,欢迎修正。开始贡献前,可先行阅读<a href="http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation">贡献指南:文档</a>。
</p>
<p>翻译如有错误,深感抱歉,欢迎 <a href="https://github.com/ruby-china/guides/fork">Fork</a> 修正,或至此处<a href="https://github.com/ruby-china/guides/issues/new">回报</a>。</p>
<p>
文章可能有未完成或过时的内容。请先检查 <a href="http://edgeguides.rubyonrails.org">Edge Guides</a> 来确定问题在 master 是否已经修掉了。再上 master 补上缺少的文件。内容参考 <a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南准则</a>来了解行文风格。
</p>
<p>最后,任何关于 Ruby on Rails 文档的讨论,欢迎到 <a href="http://groups.google.com/group/rubyonrails-docs">rubyonrails-docs 邮件群组</a>。
</p>
</div>
</div>
</div>
<hr class="hide" />
<div id="footer">
<div class="wrapper">
<p>本著作采用<a href="https://creativecommons.org/licenses/by-sa/4.0/">创用 CC 姓名标示-相同方式分享 4.0 国际授权条款</a>授权。</p>
<p>“Rails”、“Ruby on Rails”,以及 Rails logo 为 David Heinemeier Hansson 的商标。版权所有。</p>
</div>
</div>
<script type="text/javascript" src="javascripts/jquery.min.js"></script>
<script type="text/javascript" src="javascripts/responsive-tables.js"></script>
<script type="text/javascript" src="javascripts/guides.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shCore.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushRuby.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushXml.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushSql.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushPlain.js"></script>
<script type="text/javascript">
SyntaxHighlighter.all();
$(guidesIndex.bind);
</script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
// ga('create', '', 'ruby-china.github.io');
ga('require', 'displayfeatures');
ga('send', 'pageview');
</script>
</body>
</html>