1 การใช้แคชเบื้องต้น
นี่คือการแนะนำในการใช้เทคนิคแคชสามประเภท: แคชหน้า แคชแอคชัน และแคชฟรากเมนต์ โดยค่าเริ่มต้น Rails จะให้แคชฟรากเมนต์ ในการใช้แคชหน้าและแคชแอคชันคุณจะต้องเพิ่ม actionpack-page_caching
และ actionpack-action_caching
เข้าไปใน Gemfile
ของคุณ
โดยค่าเริ่มต้นแคชจะเปิดใช้งานเฉพาะในสภาพแวดล้อมการพัฒนาของคุณ คุณสามารถทดลองใช้แคชในเครื่องคอมพิวเตอร์ของคุณโดยการเรียกใช้ rails dev:cache
หรือโดยการตั้งค่า config.action_controller.perform_caching
เป็น true
ใน config/environments/development.rb
หมายเหตุ: เปลี่ยนค่าของ config.action_controller.perform_caching
จะมีผลเฉพาะกับแคชที่ Action Controller ให้ ตัวอย่างเช่น มันจะไม่มีผลต่อการแคชระดับต่ำที่เรากล่าวถึงด้านล่าง
1.1 แคชหน้า
แคชหน้าเป็นกลไกของ Rails ที่ช่วยให้คำขอสำหรับหน้าที่สร้างขึ้นสามารถได้รับการบริการจากเว็บเซิร์ฟเวอร์ (เช่น Apache หรือ NGINX) โดยไม่ต้องผ่านทั้งสแต็กของ Rails ทั้งหมด แม้ว่าสิ่งนี้จะเร็วมาก แต่ไม่สามารถใช้กับทุกสถานการณ์ได้ (เช่นหน้าที่ต้องการการรับรองตัวตน) นอกจากนี้ เนื่องจากเว็บเซิร์ฟเวอร์กำลังให้บริการไฟล์โดยตรงจากไฟล์ระบบไฟล์คุณจะต้องดำเนินการหมดอายุแคชด้วยตนเอง
ข้อมูล: แคชหน้าถูกลบออกจาก Rails 4 ดู actionpack-page_caching gem
1.2 แคชแอคชัน
แคชหน้าไม่สามารถใช้สำหรับแอคชันที่มีตัวกรองก่อน - ตัวอย่างเช่นหน้าที่ต้องการการรับรองตัวตน นี่คือที่แคชแอคชันเข้ามาช่วย แคชแอคชันทำงานเหมือนกับแคชหน้า ยกเว้นคำขอเว็บที่เข้ามาจะถูกส่งไปยังสแต็กของ Rails เพื่อให้สามารถเรียกใช้ตัวกรองก่อนแคชได้ นี่ช่วยให้สามารถเรียกใช้การรับรองตัวตนและข้อจำกัดอื่น ๆ ได้ในขณะที่ยังคงให้ผลลัพธ์จากการแคช
ข้อมูล: แคชแอคชันถูกลบออกจาก Rails 4 ดู actionpack-action_caching gem ดู ภาพรวมการหมดอายุแคชตามคีย์ของ DHH สำหรับวิธีการที่แนะนำใหม่
1.3 แคชฟรากเมนต์
แอปพลิเคชันเว็บแบบไดนามิกทั่วไปมักจะสร้างหน้าด้วยส่วนประกอบต่าง ๆ ที่มีลักษณะการแคชที่แตกต่างกัน หากต้องการแคชและหมดอายุส่วนประกอบต่าง ๆ ของหน้าแยกกันคุณสามารถใช้แคชฟรากเมนต์ได้
แคชฟรากเมนต์ช่วยให้ส่วนประกอบของตัวตนมุมมองถูกห่อหุ้มในบล็อกแคชและถูกเก็บไว้ในร้านค้าแคชเมื่อคำขอถัดไปมาถึง ตัวอย่างเช่นหากคุณต้องการเก็บแคชสินค้าแต่ละรายการในหน้าเว็บ คุณสามารถใช้โค้ดนี้ได้:
<% @products.each do |product| %>
<% cache product do %>
<%= render product %>
<% end %>
<% end %>
เมื่อแอปพลิเคชันของคุณได้รับคำขอแรกสำหรับหน้านี้ Rails จะเขียนแคชเข้าสู่ระบบด้วยคีย์ที่ไม่ซ้ำกัน คีย์จะมีลักษณะเช่นนี้:
views/products/index:bea67108094918eeba42cd4a6e786901/products/1
สตริงของตัวอักษรที่อยู่ตรงกลางคือการเข้ารหัสแบบต้นไม้ของเทมเพลต มันเป็นการเข้ารหัสแบบต้นไม้ที่คำนวณขึ้นจากเนื้อหาของเฟรกเมนต์วิวที่คุณกำลังเก็บแคช หากคุณเปลี่ยนเฟรกเมนต์วิว (เช่น HTML เปลี่ยนแปลง) เครื่องหมายการเข้ารหัสจะเปลี่ยนแปลง ทำให้ไฟล์ที่มีอยู่ในแคชหมดอายุ
เวอร์ชันแคชที่ได้มาจากบันทึกสินค้าจะถูกเก็บไว้ในรายการแคช หากสินค้าถูกแตะ (touched) เวอร์ชันแคชจะเปลี่ยนแปลง และเฟรกเมนต์ที่ถูกแคชที่มีเวอร์ชันก่อนหน้านั้นจะถูกละเลย
เคล็ดลับ: ร้านค้าแคชเช่น Memcached จะลบไฟล์แคชเก่าๆอัตโนมัติ
หากคุณต้องการเก็บแคชเฟรกเมนต์ภายใต้เงื่อนไขบางอย่าง คุณสามารถใช้ cache_if
หรือ cache_unless
:
<% cache_if admin?, product do %>
<%= render product %>
<% end %>
1.3.1 การเก็บแคชของคอลเลกชัน
เฮลเปอร์ render
ยังสามารถเก็บแคชเทมเพลตแต่ละรายการที่ถูกเรนเดอร์สำหรับคอลเลกชันได้ มันยังสามารถทำได้ดีกว่าตัวอย่างก่อนหน้านี้ด้วย each
โดยอ่านเทมเพลตแคชทั้งหมดในครั้งเดียวแทนที่จะทำหนึ่งต่อหนึ่ง สามารถทำได้โดยการส่ง cached: true
เมื่อเรนเดอร์คอลเลกชัน:
<%= render partial: 'products/product', collection: @products, cached: true %>
เทมเพลตแคชที่เก็บไว้จากการเรนเดอร์ก่อนหน้านี้จะถูกดึงขึ้นทั้งหมดในครั้งเดียวโดยมีความเร็วที่ดีกว่ามาก นอกจากนี้ เทมเพลตที่ยังไม่ได้รับแคชจะถูกเขียนลงในแคชและดึงขึ้นมาพร้อมกันในการเรนเดอร์ครั้งถัดไป
1.4 การเก็บแคชแบบรัสเซียนดอลล์
คุณอาจต้องการซ้อนเฟรกเมนต์แคชภายในเฟรกเมนต์แคชอื่น ซึ่งเรียกว่าการเก็บแคชแบบรัสเซียนดอลล์
ข้อดีของการเก็บแคชแบบรัสเซียนดอลล์คือหากมีการอัปเดตสินค้าเพียงรายการเดียว แฟรกเมนต์ภายในที่เหลืออาจถูกใช้ซ้ำเมื่อสร้างแฟรกเมนต์ภายนอกใหม่
ตามที่อธิบายในส่วนก่อนหน้านี้ ไฟล์แคชจะหมดอายุหากมีการเปลี่ยนแปลงค่าของ updated_at
สำหรับเรคคอร์ดที่ไฟล์แคชขึ้นอยู่ อย่างไรก็ตาม สิ่งนี้จะไม่ทำให้แคชที่ฟรากเมนต์ถูกซ้ำในกรณีที่ฟรากเมนต์อยู่ภายใน
ตัวอย่างเช่น ดูวิวต่อไปนี้:
<% cache product do %>
<%= render product.games %>
<% end %>
ซึ่งในลำดับต่อมาจะเรนเดอร์วิวนี้:
<% cache game do %>
<%= render game %>
<% end %>
หากมีการเปลี่ยนแปลงคุณสมบัติใดๆของเกม เวลาที่ updated_at
จะถูกตั้งค่าเป็นเวลาปัจจุบัน ทำให้แคชหมดอายุ อย่างไรก็ตาม เนื่องจาก updated_at
จะไม่เปลี่ยนแปลงสำหรับวัตถุสินค้า แคชนั้นจะไม่หมดอายุและแอปของคุณจะให้บริการข้อมูลที่เก่า ในการแก้ไขปัญหานี้ เราจะผูกโมเดลเข้าด้วยกันด้วยวิธี touch
:
class Product < ApplicationRecord
has_many :games
end
class Game < ApplicationRecord
belongs_to :product, touch: true
end
เมื่อตั้งค่า touch
เป็น true
การกระทำใดๆที่เปลี่ยนค่า updated_at
สำหรับเรคคอร์ดเกมจะเปลี่ยนค่า updated_at
สำหรับสินค้าที่เกี่ยวข้องด้วย ทำให้แคชหมดอายุ
1.5 การแบ่งปันการใช้งานแคชบางส่วน
สามารถแบ่งปันแคชบางส่วนและการแคชที่เกี่ยวข้องระหว่างไฟล์ที่มีประเภท MIME ต่างกันได้ ตัวอย่างเช่น การแบ่งปันแคชบางส่วนช่วยให้ผู้เขียนเทมเพลตสามารถแบ่งปันส่วนบางส่วนระหว่างไฟล์ HTML และไฟล์ JavaScript ได้ โดยเมื่อเก็บรวบรวมเทมเพลตในไฟล์เส้นทางการแก้ปัญหาเทมเพลต มันจะรวมเฉพาะส่วนขยายของภาษาเทมเพลตเท่านั้นและไม่รวมประเภท MIME เนื่องจากเหตุนี้เทมเพลตสามารถใช้สำหรับประเภท MIME หลายประเภท การร้องขอ HTML และ JavaScript จะตอบสนองต่อโค้ดต่อไปนี้:
render(partial: 'hotels/hotel', collection: @hotels, cached: true)
จะโหลดไฟล์ที่ชื่อ hotels/hotel.erb
.
ตัวเลือกอื่น ๆ คือการรวมชื่อไฟล์แบบเต็มของส่วนบางส่วนที่จะแสดงผล
render(partial: 'hotels/hotel.html.erb', collection: @hotels, cached: true)
จะโหลดไฟล์ที่ชื่อ hotels/hotel.html.erb
ในประเภท MIME ไฟล์ใดก็ได้ เช่นคุณสามารถรวมส่วนนี้ในไฟล์ JavaScript ได้
1.6 การจัดการความขึ้นต่อกันของข้อมูลที่ต้องการ
เพื่อที่จะยกเลิกแคชได้อย่างถูกต้อง คุณต้องกำหนดความขึ้นต่อกันของข้อมูลที่ต้องการอย่างถูกต้อง Rails สามารถจัดการกรณีที่พบบ่อยเองได้เพื่อให้คุณไม่ต้องระบุอะไรเลย อย่างไรก็ตาม บางครั้ง เมื่อคุณมีการใช้งานเฮลเปอร์ที่กำหนดเอง เช่น คุณจะต้องระบุเอง
1.6.1 ความขึ้นต่อกันแบบอัตโนมัติ
ส่วนใหญ่ความขึ้นต่อกันของเทมเพลตสามารถได้รับจากการเรียกใช้ render
ในเทมเพลตเอง ตัวอย่างเช่นการเรียกใช้ render ที่ ActionView::Digestor
รู้จักแปลงเป็นรหัส:
render partial: "comments/comment", collection: commentable.comments
render "comments/comments"
render 'comments/comments'
render('comments/comments')
render "header" จะแปลงเป็น render("comments/header")
render(@topic) จะแปลงเป็น render("topics/topic")
render(topics) จะแปลงเป็น render("topics/topic")
render(message.topics) จะแปลงเป็น render("topics/topic")
อย่างไรก็ตาม บางครั้งคุณต้องเปลี่ยนการเรียกใช้เพื่อให้การแคชทำงานอย่างถูกต้อง ตัวอย่างเช่น หากคุณกำลังส่งค่าคอลเลกชันที่กำหนดเอง คุณจะต้องเปลี่ยนจาก:
render @project.documents.where(published: true)
เป็น:
render partial: "documents/document", collection: @project.documents.where(published: true)
1.6.2 ความขึ้นต่อกันแบบชัดเจน
บางครั้งคุณอาจมีความขึ้นต่อกันของเทมเพลตที่ไม่สามารถได้มาจากการแปลงเอง นี่เป็นกรณีที่พบบ่อยเมื่อการแสดงผลเกิดขึ้นในเฮลเปอร์ เช่น ตัวอย่างนี้:
<%= render_sortable_todolists @project.todolists %>
คุณจะต้องใช้รูปแบบคอมเมนต์พิเศษเพื่อเรียกใช้:
<%# Template Dependency: todolists/todolist %>
<%= render_sortable_todolists @project.todolists %>
ในบางกรณี เช่น การตั้งค่าการสืบทอดตารางเดียว คุณอาจมีความขึ้นต่อกันแบบชัดเจนมากมาย แทนที่จะเขียนเทมเพลตทุกตัวคุณสามารถใช้เครื่องหมายแทนที่เพื่อจับคู่กับเทมเพลตในไดเรกทอรี:
<%# Template Dependency: events/* %>
<%= render_categorizable_events @person.events %>
ในกรณีของการแคชคอลเลกชัน หากเทมเพลตส่วนบางส่วนไม่เริ่มต้นด้วยการเรียกแคชที่สะอาด คุณยังสามารถใช้ประโยคคอมเมนต์พิเศษในที่ใดก็ได้ในเทมเพลต เช่น:
<%# Template Collection: notification %>
<% my_helper_that_calls_cache(some_arg, notification) do %>
<%= notification.name %>
<% end %>
1.6.3 ความขึ้นต่อกันภายนอก
หากคุณใช้เมธอดเฮลเปอร์ เช่น ภายในบล็อกแคชและคุณอัปเดตเมธอดช่วยเหลือนั้น คุณต้องอัปเดตแคชเช่นกัน วิธีที่คุณทำก็ไม่สำคัญ แต่ MD5 ของไฟล์เทมเพลตต้องเปลี่ยน แนะนำคือให้เป็นชัดเจนในคอมเมนต์ เช่น:
<%# Helper Dependency Updated: Jul 28, 2015 at 7pm %>
<%= some_helper_method(person) %>
1.7 การเก็บแคชระดับต่ำ
บางครั้งคุณต้องการเก็บแคชค่าหรือผลลัพธ์ของคำสั่งค้นหาแทนการเก็บแคชส่วนของมุมมอง (view fragments) กลไกการเก็บแคชของ Rails ทำงานได้ดีสำหรับการเก็บข้อมูลที่สามารถแปลงเป็นรหัสได้
วิธีที่มีประสิทธิภาพที่สุดในการนำเอาการเก็บแคชระดับต่ำมาใช้คือการใช้เมธอด Rails.cache.fetch
วิธีนี้ใช้ทั้งการอ่านและเขียนข้อมูลลงในแคช ถ้าส่งอาร์กิวเมนต์เดียว จะดึงคีย์และคืนค่าจากแคช หากส่งบล็อกมา บล็อกนั้นจะถูกทำงานเมื่อไม่พบข้อมูลในแคช ค่าที่คืนจากบล็อกนั้นจะถูกเขียนลงในแคชใต้คีย์แคชที่กำหนด และค่าที่คืนนั้นจะถูกคืนค่าออกไป ในกรณีที่พบข้อมูลในแคช ค่าที่เก็บในแคชจะถูกคืนค่าโดยไม่ต้องทำงานบล็อก
พิจารณาตัวอย่างต่อไปนี้ แอปพลิเคชันมีโมเดล Product
ที่มีเมธอดอินสแตนซ์ที่ใช้ค้นหาราคาสินค้าในเว็บไซต์แข่งขัน ข้อมูลที่คืนค่าจากเมธอดนี้เหมาะกับการเก็บแคชระดับต่ำ:
class Product < ApplicationRecord
def competing_price
Rails.cache.fetch("#{cache_key_with_version}/competing_price", expires_in: 12.hours) do
Competitor::API.find_price(id)
end
end
end
หมายเหตุ: สังเกตว่าในตัวอย่างนี้เราใช้เมธอด cache_key_with_version
เพื่อให้ได้คีย์แคชเช่น products/233-20140225082222765838000/competing_price
ซึ่ง cache_key_with_version
จะสร้างสตริงขึ้นมาจากชื่อคลาสของโมเดล แอตทริบิวต์ id
และ updated_at
นี่เป็นแนวทางที่พบบ่อยและมีประโยชน์ในการทำให้แคชเกิดการตัดสินใจใหม่เมื่อมีการอัปเดตสินค้า โดยทั่วไปเมื่อคุณใช้การเก็บแคชระดับต่ำคุณต้องสร้างคีย์แคช
1.7.1 หลีกเลี่ยงการเก็บแคชอินสแตนซ์ของออบเจกต์ Active Record
พิจารณาตัวอย่างนี้ซึ่งเก็บรายการออบเจกต์ Active Record ที่แทนผู้ดูแลระบบ (superusers) ในแคช:
# super_admins is an expensive SQL query, so don't run it too often
Rails.cache.fetch("super_admin_users", expires_in: 12.hours) do
User.super_admins.to_a
end
คุณควร หลีกเลี่ยง รูปแบบนี้ เพราะอินสแตนซ์อาจเปลี่ยนแปลง ในโปรดักชันอาจมีแอตทริบิวต์ที่แตกต่างกันหรือบันทึกอาจถูกลบ และในการพัฒนา มันทำงานได้ไม่เสถียรกับร้านค้าแคชที่โหลดโค้ดเมื่อคุณทำการเปลี่ยนแปลง
แทนนั้นให้เก็บรหัสไอดีหรือชนิดข้อมูลพื้นฐานอื่น ๆ ตัวอย่างเช่น:
# super_admins is an expensive SQL query, so don't run it too often
ids = Rails.cache.fetch("super_admin_user_ids", expires_in: 12.hours) do
User.super_admins.pluck(:id)
end
User.where(id: ids).to_a
1.8 การเก็บแคช SQL
การเก็บแคชคิวรีเป็นคุณสมบัติของ Rails ที่เก็บผลลัพธ์ที่คืนมาจากแต่ละคิวรี หาก Rails พบคิวรีเดียวกันอีกครั้งในคำขอเดียวกัน จะใช้ผลลัพธ์ที่เก็บไว้ในแคชแทนที่จะเรียกใช้คิวรีกับฐานข้อมูลอีกครั้ง
ตัวอย่าง:
class ProductsController < ApplicationController
def index
# รันคิวรีค้นหา
@products = Product.all
# ...
# รันคิวรีเดียวกันอีกครั้ง
@products = Product.all
end
end
ครั้งที่สองที่คิวรีเดียวกันถูกรันต่อฐานข้อมูลจริง แต่จริง ๆ แล้วไม่ได้เข้าถึงฐานข้อมูล ครั้งแรกผลลัพธ์จะถูกเก็บไว้ในแคชคิวรี (ในหน่วยความจำ) และครั้งที่สองจะถูกดึงมาจากหน่วยความจำ
อย่างไรก็ตาม ควรระวังว่าแคชคิวรีจะถูกสร้างขึ้นตอนเริ่มต้นของแอ็กชันและถูกทำลายเมื่อสิ้นสุดแอ็กชัน ดังนั้นแคชคิวรีจะอยู่ในระยะเวลาเดียวกับแอ็กชันเท่านั้น หากคุณต้องการเก็บผลลัพธ์ของคิวรีในรูปแบบที่มีอยู่ต่อไปได้ คุณสามารถใช้การเก็บแคชระดับต่ำได้
2 การจัดเก็บแคช
Rails มีการจัดเก็บแคชข้อมูลที่แตกต่างกัน (นอกเหนือจาก SQL และการจัดเก็บหน้า).
2.1 การกำหนดค่า
คุณสามารถกำหนดค่าการจัดเก็บแคชเริ่มต้นของแอปพลิเคชันของคุณโดยการตั้งค่าตัวเลือก config.cache_store
. คุณสามารถส่งพารามิเตอร์อื่นๆเป็นอาร์กิวเมนต์ให้กับคอนสตรัคเตอร์ของการจัดเก็บแคช:
config.cache_store = :memory_store, { size: 64.megabytes }
หรือคุณสามารถกำหนดค่า ActionController::Base.cache_store
นอกบล็อกการกำหนดค่าได้
คุณสามารถเข้าถึงแคชได้โดยเรียกใช้ Rails.cache
.
2.1.1 ตัวเลือก Connection Pool
โดยค่าเริ่มต้น :mem_cache_store
และ
:redis_cache_store
ถูกกำหนดค่าให้ใช้
การจัดกลุ่มการเชื่อมต่อ (connection pooling). นั่นหมายความว่าหากคุณกำลังใช้ Puma หรือเซิร์ฟเวอร์ที่มีเธรดหลายเธรด
คุณสามารถมีเธรดหลายเธรดที่ดำเนินการคิวรีไปยังการจัดเก็บแคชพร้อมกันได้
หากคุณต้องการปิดการจัดกลุ่มการเชื่อมต่อ ให้ตั้งค่า :pool
เป็น false
เมื่อกำหนดค่าการจัดเก็บแคช:
config.cache_store = :mem_cache_store, "cache.example.com", pool: false
คุณยังสามารถแทนที่การตั้งค่าการจัดกลุ่มเริ่มต้นโดยการให้ตัวเลือกแต่ละตัวกับตัวเลือก :pool
:
config.cache_store = :mem_cache_store, "cache.example.com", pool: { size: 32, timeout: 1 }
:size
- ตัวเลือกนี้กำหนดจำนวนการเชื่อมต่อต่อกระบวนการ (ค่าเริ่มต้นคือ 5).:timeout
- ตัวเลือกนี้กำหนดจำนวนวินาทีที่ต้องรอการเชื่อมต่อ (ค่าเริ่มต้นคือ 5). หากไม่มีการเชื่อมต่อที่พร้อมใช้งานภายในเวลาที่กำหนด จะเกิดข้อผิดพลาดTimeout::Error
.
2.2 ActiveSupport::Cache::Store
ActiveSupport::Cache::Store
ให้พื้นฐานสำหรับการติดต่อกับแคชใน Rails. นี่เป็นคลาสแบบนามธรรมและคุณไม่สามารถใช้งานได้เอง แต่คุณต้องใช้การประมวลผลที่เชื่อมโยงกับเครื่องมือจัดเก็บ. Rails มาพร้อมกับการประมวลผลหลายอย่างที่เอกสารแสดงด้านล่าง.
เมธอด API หลักคือ read
, write
, delete
, exist?
, และ fetch
.
ตัวเลือกที่ส่งผ่านไปยังคอนสตรัคเตอร์ของการจัดเก็บแคชจะถูกจัดการเป็นตัวเลือกเริ่มต้นสำหรับเมธอด API ที่เหมาะสม
2.3 ActiveSupport::Cache::MemoryStore
ActiveSupport::Cache::MemoryStore
เก็บรายการในหน่วยความจำในกระบวนการ Ruby เดียวกัน. การจัดเก็บแคช
มีขนาดที่ถูกกำหนดโดยการส่ง :size
เป็นตัวเลือกให้กับ
ตัวกำหนดค่า (ค่าเริ่มต้นคือ 32Mb). เมื่อแคชเกินขนาดที่กำหนด การทำความสะอาดจะเกิดขึ้นและรายการที่ไม่ได้ใช้ล่าสุดจะถูกลบออก.
config.cache_store = :memory_store, { size: 64.megabytes }
หากคุณกำลังเรียกใช้กระบวนการเซิร์ฟเวอร์ Ruby on Rails หลายกระบวนการ (ซึ่งเป็นกรณี หากคุณกำลังใช้ Phusion Passenger หรือโหมดการจัดกลุ่มของ puma), กระบวนการเซิร์ฟเวอร์ Rails จะไม่สามารถแบ่งปันข้อมูลแคชกันได้. การจัดเก็บแคชนี้ไม่เหมาะสำหรับการใช้งานแอปพลิเคชันขนาดใหญ่. อย่างไรก็ตาม มันสามารถทำงานได้ดีสำหรับเว็บไซต์ขนาดเล็กที่มีการเข้าชมต่ำและมีเพียงไม่กี่กระบวนการเซิร์ฟเวอร์ รวมถึงสภาพแวดล้อมการพัฒนาและทดสอบ.
โปรเจค Rails ใหม่ถูกกำหนดค่าให้ใช้การประมวลผลนี้ในสภาพแวดล้อมการพัฒนาโดยค่าเริ่มต้น.
หมายเหตุ: เนื่องจากกระบวนการจะไม่แบ่งปันข้อมูลแคชเมื่อใช้ :memory_store
,
จะไม่สามารถอ่าน, เขียน, หรือลบแคชด้วยตนเองผ่านคอนโซลของ Rails ได้.
2.4 ActiveSupport::Cache::FileStore
ActiveSupport::Cache::FileStore
ใช้ระบบไฟล์เพื่อจัดเก็บรายการ. ต้องระบุเส้นทางไปยังไดเรกทอรีที่เก็บไฟล์ของการจัดเก็บเมื่อเริ่มต้นแคช.
config.cache_store = :file_store, "/path/to/cache/directory"
ด้วยการจัดเก็บแคชนี้ กระบวนการเซิร์ฟเวอร์หลายๆกระบวนการบนโฮสต์เดียวกันสามารถแบ่งปันแคชกันได้. การจัดเก็บแคชนี้เหมาะสำหรับเว็บไซต์ที่มีการเข้าชมระดับต่ำถึงปานกลางที่ให้บริการจากหนึ่งหรือสองโฮสต์. กระบวนการเซิร์ฟเวอร์ที่ทำงานบนโฮสต์ที่แตกต่างกันสามารถแบ่งปันแคชได้โดยใช้ระบบไฟล์ที่ใช้ร่วมกัน แต่ไม่แนะนำให้ติดตั้งแบบนั้น. เนื่องจากแคชจะเติบโตจนกระทั่งดิสก์เต็ม แนะนำให้ล้างรายการเก่าออกเป็นระยะๆ
นี่คือการดำเนินการเก็บแคชเริ่มต้น (ที่ "#{root}/tmp/cache/"
) ถ้าไม่ได้ระบุ config.cache_store
โดยเฉพาะ
2.5 ActiveSupport::Cache::MemCacheStore
ActiveSupport::Cache::MemCacheStore
ใช้เซิร์ฟเวอร์ memcached
ของ Danga เพื่อให้บริการแคชที่เซ็นทรัลสำหรับแอปพลิเคชันของคุณ รู้จักกับ dalli
gem ที่ใช้เป็นค่าเริ่มต้น นี่เป็นการเก็บแคชที่ได้รับความนิยมสูงสุดสำหรับเว็บไซต์ในการดำเนินการจริง สามารถใช้เพื่อให้มีแคชเดียวที่ใช้ร่วมกันในคลัสเตอร์แคชที่มีประสิทธิภาพสูงและความเสถียรสูง
เมื่อเริ่มต้นแคชคุณควรระบุที่อยู่สำหรับเซิร์ฟเวอร์ memcached ทั้งหมดในคลัสเตอร์ของคุณ หรือตรวจสอบให้แน่ใจว่าตัวแปรสภาพแวดล้อม MEMCACHE_SERVERS
ถูกตั้งค่าอย่างเหมาะสม
config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
หากไม่ได้ระบุเซิร์ฟเวอร์จะถือว่า memcached กำลังทำงานบนเครื่อง localhost ที่พอร์ตเริ่มต้น (127.0.0.1:11211
) แต่นี่ไม่ใช่การตั้งค่าที่เหมาะสำหรับเว็บไซต์ขนาดใหญ่
config.cache_store = :mem_cache_store # จะถูกสลับกลับไปใช้ $MEMCACHE_SERVERS แล้ว 127.0.0.1:11211
ดูเอกสาร Dalli::Client
สำหรับประเภทที่รองรับ
เมธอด write
(และ fetch
) ในแคชนี้ยอมรับตัวเลือกเพิ่มเติมที่ใช้ประโยชน์จากคุณสมบัติที่เฉพาะเจาะจงของ memcached
2.6 ActiveSupport::Cache::RedisCacheStore
ActiveSupport::Cache::RedisCacheStore
ใช้ Redis เพื่อรองรับการลบอัตโนมัติเมื่อมีการใช้หน่วยความจำสูงสุด ทำให้มีพฤติกรรมคล้ายกับเซิร์ฟเวอร์แคช Memcached
หมายเหตุการติดตั้ง: Redis ไม่ได้ตั้งค่าให้หมดอายุของคีย์โดยค่าเริ่มต้น ดังนั้นควรใช้เซิร์ฟเวอร์แคช Redis ที่มีการกำหนดเฉพาะ อย่าเต็มพื้นที่เซิร์ฟเวอร์ Redis ถาวรของคุณด้วยข้อมูลแคชที่เปลี่ยนแปลงได้! อ่านคู่มือการติดตั้งเซิร์ฟเวอร์แคช Redis อย่างละเอียด
สำหรับเซิร์ฟเวอร์ Redis ที่ใช้แค่แคชเท่านั้น ตั้งค่า maxmemory-policy
เป็นหนึ่งในตัวเลือกของ allkeys Redis 4+ รองรับการลบตามการใช้งานที่น้อยที่สุด (allkeys-lfu
) เป็นตัวเลือกที่เหมาะสมที่สุด ส่วน Redis 3 และต่ำกว่าควรใช้การลบตามการใช้งานล่าสุด (allkeys-lru
)
ตั้งค่าเวลาที่อ่านแคชและเขียนแคชให้สั้นลง การสร้างค่าแคชใหม่บ่อยกว่าการรอให้เวลาเกิน 1 วินาทีในการเรียกคืน การอ่านและเขียนแคชเริ่มต้นทั้งคู่เป็น 1 วินาที แต่สามารถตั้งค่าให้ต่ำกว่านั้นได้หากเครือข่ายของคุณมีเวลาตอบสนองต่ำตลอดเวลา
โดยค่าเริ่มต้นแคชสโตร์จะไม่พยายามเชื่อมต่อกับ Redis หากการเชื่อมต่อล้มเหลวในระหว่างคำขอ หากคุณพบการตัดการเชื่อมต่อบ่อยคุณอาจต้องเปิดใช้งานการเชื่อมต่ออีกครั้ง
การอ่านและเขียนแคชไม่เกิดข้อผิดพลาด; แทนที่จะเกิดข้อยกเว้นพวกมันจะส่งคืน nil
แสดงพฤติกรรมเหมือนว่าไม่มีอะไรอยู่ในแคช หากต้องการประเมินว่าแคชของคุณมีข้อยกเว้นคุณสามารถให้ error_handler
รายงานไปยังบริการรวบรวมข้อยกเว้น ต้องรับอาร์กิวเมนต์คีย์เวิร์ดสามตัว: method
เมธอดของแคชสโตร์ที่เรียกเริ่มต้น; returning
ค่าที่ส่งคืนให้ผู้ใช้ โดยทั่วไปคือ nil
; และ exception
ข้อยกเว้นที่ถูกแก้ไข
เพื่อเริ่มต้นให้เพิ่ม gem redis ใน Gemfile:
gem 'redis'
ในที่สุดเพิ่มการกำหนดค่าในไฟล์ config/environments/*.rb
ที่เกี่ยวข้อง:
config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }
การกำหนดค่าแคช Redis ที่ซับซ้อนมากขึ้นสามารถดูได้ดังนี้:
cache_servers = %w(redis://cache-01:6379/0 redis://cache-02:6379/0)
config.cache_store = :redis_cache_store, { url: cache_servers,
connect_timeout: 30, # ค่าเริ่มต้น 20 วินาที
read_timeout: 0.2, # ค่าเริ่มต้น 1 วินาที
write_timeout: 0.2, # ค่าเริ่มต้น 1 วินาที
reconnect_attempts: 1, # ค่าเริ่มต้น 0
error_handler: -> (method:, returning:, exception:) {
# รายงานข้อผิดพลาดไปยัง Sentry เป็นคำเตือน
Sentry.capture_exception exception, level: 'warning',
tags: { method: method, returning: returning }
}
}
2.7 ActiveSupport::Cache::NullStore
ActiveSupport::Cache::NullStore
ถูกกำหนดขอบเขตในแต่ละคำขอเว็บ และล้างค่าที่เก็บไว้ที่สิ้นสุดของคำขอ มันถูกออกแบบมาสำหรับใช้ในสภาพแวดล้อมการพัฒนาและทดสอบ มันสามารถเป็นประโยชน์อย่างมากเมื่อคุณมีโค้ดที่มีปฏิสัมพันธ์โดยตรงกับ Rails.cache
แต่การใช้แคชขัดขวางการเห็นผลลัพธ์จากการเปลี่ยนแปลงของโค้ด
config.cache_store = :null_store
2.8 การเก็บแคชที่กำหนดเอง
คุณสามารถสร้างเก็บแคชที่กำหนดเองได้โดยการขยาย ActiveSupport::Cache::Store
และการดำเนินการที่เหมาะสม นี่คือวิธีที่คุณสามารถสลับเทคโนโลยีการแคชใดก็ได้เข้าสู่แอปพลิเคชัน Rails ของคุณ
ในการใช้เก็บแคชที่กำหนดเอง คุณเพียงแค่ตั้งค่าเก็บแคชเป็นอินสแตนซ์ใหม่ของคลาสที่กำหนดเอง
config.cache_store = MyCacheStore.new
3 คีย์แคช
คีย์ที่ใช้ในแคชสามารถเป็นอ็อบเจ็กต์ใดก็ได้ที่ตอบสนองกับ cache_key
หรือ to_param
คุณสามารถสร้างเมธอด cache_key
บนคลาสของคุณหากคุณต้องการสร้างคีย์ที่กำหนดเอง Active Record จะสร้างคีย์ขึ้นมาจากชื่อคลาสและรหัสบันทึก
คุณสามารถใช้ Hashes และ Arrays ของค่าเป็นคีย์แคชได้
# นี่คือคีย์แคชที่ถูกต้อง
Rails.cache.read(site: "mysite", owners: [owner_1, owner_2])
คีย์ที่คุณใช้ใน Rails.cache
จะไม่เหมือนกับคีย์ที่ใช้จริงกับเก็บข้อมูล อาจถูกแก้ไขด้วยเนมสเปซหรือถูกแก้ไขเพื่อเข้ากับข้อจำกัดของเทคโนโลยีที่ใช้ นั่นหมายความว่า เช่น คุณไม่สามารถบันทึกค่าด้วย Rails.cache
แล้วพยายามดึงออกมาด้วย gem dalli
อย่างไรก็ตาม คุณก็ไม่ต้องกังวลเกินไปเกี่ยวกับการเกินขีดจำกัดขนาดของ memcached หรือการละเมิดกฎไวยากรณ์ไว้ใจ
4 การสนับสนุนการรับเงื่อนไข GET
การรับเงื่อนไข GET เป็นคุณสมบัติของข้อกำหนด HTTP ที่ให้วิธีในการบอกเซิร์ฟเวอร์เว็บว่าการตอบสนองกับคำขอ GET ไม่เปลี่ยนแปลงตั้งแต่คำขอล่าสุดและสามารถดึงจากแคชของเบราว์เซอร์ได้อย่างปลอดภัย
มันทำงานโดยใช้ส่วนหัว HTTP_IF_NONE_MATCH
และ HTTP_IF_MODIFIED_SINCE
เพื่อส่งกลับและส่งผ่านไปมาทั้งตัวแยกแยะเนื้อหาที่ไม่ซ้ำกันและเวลาที่เนื้อหาเปลี่ยนครั้งล่าสุด หากเบราว์เซอร์ทำคำขอที่เนื้อหาแยกแยะ (ETag) หรือเวลาแก้ไขล่าสุดตรงกับเวอร์ชันของเซิร์ฟเวอร์ แล้วเซิร์ฟเวอร์จะต้องส่งกลับคำขอที่ว่างเปล่าพร้อมสถานะไม่ได้เปลี่ยนแปลง
เป็นหน้าที่ของเซิร์ฟเวอร์ (เช่นของเรา) ที่จะค้นหาเวลาแก้ไขล่าสุดและส่วนหัว if-none-match และกำหนดว่าจะส่งคำขอที่เต็มกลับหรือไม่ ด้วยการรับเงื่อนไข GET ใน Rails นี้เป็นงานที่ง่ายพอ:
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
# หากคำขอเก่าตามเวลาและค่า etag ที่กำหนดไม่ตรงกับค่าปัจจุบัน (คือต้องทำงานใหม่) ให้ดำเนินการบล็อกนี้
if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key_with_version)
respond_to do |wants|
# ... การประมวลผลคำขอปกติ
end
end
# หากคำขอเป็นเวลาใหม่ (คือไม่ได้เปลี่ยนแปลง) คุณไม่ต้องทำอะไรเลย การเรนเดอร์เริ่มต้นตรวจสอบสิ่งนี้โดยใช้พารามิเตอร์
# ที่ใช้ในการเรียกใช้ stale? ก่อนหน้านี้และจะส่งอัตโนมัติ :not_modified ดังนั้นนั่นคือทั้งหมด คุณเสร็จสิ้นแล้ว
end
end
แทนที่ options hash คุณยังสามารถส่ง model เข้าไปได้อีกวิธี Rails จะใช้เมธอด updated_at
และ cache_key_with_version
เพื่อตั้งค่า last_modified
และ etag
:
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
if stale?(@product)
respond_to do |wants|
# ... normal response processing
end
end
end
end
หากคุณไม่มีการประมวลผลการตอบกลับที่เฉพาะเจาะจงและใช้กลไกการเรนเดอร์เริ่มต้น (เช่นคุณไม่ได้ใช้ respond_to
หรือเรียกใช้ render เอง) คุณสามารถใช้เฮลเปอร์ fresh_when
ได้ง่ายๆ:
class ProductsController < ApplicationController
# นี่จะส่งกลับ :not_modified โดยอัตโนมัติหากคำขอเป็นเวอร์ชันล่าสุด
# และจะเรนเดอร์เทมเพลตเริ่มต้น (product.*) หากคำขอไม่ได้เป็นเวอร์ชันล่าสุด
def show
@product = Product.find(params[:id])
fresh_when last_modified: @product.published_at.utc, etag: @product
end
end
บางครั้งเราต้องการแคชการตอบกลับ เช่นหน้าสถิติที่ไม่มีวันหมดอายุ ในการทำเช่นนี้ เราสามารถใช้เฮลเปอร์ http_cache_forever
และด้วยการทำเช่นนั้น เบราว์เซอร์และพร็อกซีจะแคชไว้ตลอดเวลา
โดยค่าเริ่มต้นการแคชคำตอบจะเป็นแบบส่วนตัว เก็บแคชไว้เฉพาะในเบราว์เซอร์ของผู้ใช้ หากต้องการให้พร็อกซีแคชคำตอบได้ ให้ตั้งค่า public: true
เพื่อระบุว่าพร็อกซีสามารถให้คำตอบแคชกับผู้ใช้ทั้งหมดได้
ในการใช้เฮลเปอร์นี้ เราจะตั้งค่า last_modified
ให้กับเฮดเดอร์เป็น Time.new(2011, 1, 1).utc
และตั้งค่าเฮดเดอร์ expires
เป็น 100 ปี
คำเตือน: ใช้เมธอดนี้อย่างระมัดระวัง เนื่องจากเบราว์เซอร์/พร็อกซีจะไม่สามารถยกเลิกการแคชคำตอบได้เว้นแต่จะล้างแคชของเบราว์เซอร์โดยบังคับ
class HomeController < ApplicationController
def index
http_cache_forever(public: true) do
render
end
end
end
4.1 ETag แบบแข็งและแบบอ่อน
Rails สร้าง ETag แบบอ่อนโดยค่าเริ่มต้น ETag แบบอ่อนช่วยให้คำตอบที่มีความหมายเท่ากันสามารถมี ETag เดียวกันได้ แม้ว่าเนื้อหาของคำตอบจะไม่ตรงกันอย่างยิ่ง นี้เป็นประโยชน์เมื่อเราไม่ต้องการที่จะสร้างหน้าใหม่สำหรับการเปลี่ยนแปลงเล็กน้อยในเนื้อหาของคำตอบ
ETag แบบอ่อนจะมีตัวอักษร W/
นำหน้าเพื่อแยกจาก ETag แบบแข็ง
W/"618bbc92e2d35ea1945008b42799b0e7" → ETag แบบอ่อน
"618bbc92e2d35ea1945008b42799b0e7" → ETag แบบแข็ง
ในขณะที่ ETag แบบอ่อนไม่เกี่ยวข้องกับเนื้อหาของคำตอบ ETag แบบแข็งแสดงว่าคำตอบควรเหมือนกันและตรงตามตำแหน่งไบต์ที่เหมือนกัน มีประโยชน์เมื่อทำ Range requests ภายในไฟล์วิดีโอหรือไฟล์ PDF ขนาดใหญ่ บาง CDN รองรับเฉพาะ ETag แบบแข็งเท่านั้น เช่น Akamai หากคุณจำเป็นต้องสร้าง ETag แบบแข็ง คุณสามารถทำได้ดังนี้
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
fresh_when last_modified: @product.published_at.utc, strong_etag: @product
end
end
คุณยังสามารถตั้งค่า ETag แบบแข็งโดยตรงบนคำตอบได้
response.strong_etag = response.body # => "618bbc92e2d35ea1945008b42799b0e7"
5 การแคชในโหมดการพัฒนา
เป็นสิ่งที่ธรรมดาที่คุณต้องการทดสอบกลยุทธ์การแคชของแอปพลิเคชันของคุณในโหมดการพัฒนา Rails มีคำสั่ง dev:cache
เพื่อเปิด/ปิดการแคชได้อย่างง่ายดาย
$ bin/rails dev:cache
Development mode is now being cached.
$ bin/rails dev:cache
Development mode is no longer being cached.
โดยค่าเริ่มต้นเมื่อการแคชในโหมดการพัฒนาถูกปิด Rails จะใช้ :null_store
6 อ้างอิง
ข้อเสนอแนะ
คุณสามารถช่วยปรับปรุงคุณภาพของคู่มือนี้ได้
กรุณาช่วยเพิ่มเติมหากพบข้อผิดพลาดหรือข้อผิดพลาดทางความจริง เพื่อเริ่มต้นคุณสามารถอ่านส่วน การสนับสนุนเอกสาร ของเราได้
คุณอาจพบเนื้อหาที่ไม่สมบูรณ์หรือเนื้อหาที่ไม่ได้อัปเดต กรุณาเพิ่มเอกสารที่ขาดหายไปสำหรับเนื้อหาหลัก โปรดตรวจสอบ Edge Guides ก่อนเพื่อตรวจสอบ ว่าปัญหาได้รับการแก้ไขหรือไม่ในสาขาหลัก ตรวจสอบ คู่มือแนวทาง Ruby on Rails เพื่อดูรูปแบบและกฎเกณฑ์
หากคุณพบข้อผิดพลาดแต่ไม่สามารถแก้ไขได้เอง กรุณา เปิดปัญหา.
และสุดท้าย การสนทนาใด ๆ เกี่ยวกับ Ruby on Rails เอกสารยินดีต้อนรับที่สุดใน เว็บบอร์ดอย่างเป็นทางการของ Ruby on Rails.