Computer >> Máy Tính >  >> Lập trình >> Ruby

HTTP Caching trong các ứng dụng Ruby on Rails

Một cách chung để mô tả bộ nhớ đệm là lưu trữ một số dữ liệu để chúng ta có thể nhanh chóng truy xuất nó sau này. Đôi khi, điều này có nghĩa là lưu trữ dữ liệu đã tính toán để không cần tính toán lại, nhưng nó cũng có thể đề cập đến việc lưu trữ dữ liệu cục bộ để tránh phải tìm nạp lại. Máy tính của bạn thực hiện việc này liên tục vì hệ điều hành của bạn cố gắng giữ dữ liệu được truy cập thường xuyên trong RAM để không phải tìm nạp lại từ ổ cứng hoặc SSD.

Tương tự, trình duyệt của bạn cố gắng sử dụng lại các tài nguyên mà nó đã tải xuống. Bạn có thể đã tự mình thấy điều này khi truy cập một trang web mới lần đầu tiên. Quá trình tải ban đầu mất nhiều thời gian hơn vì trình duyệt của bạn phải kéo xuống mọi thứ nó cần, bao gồm tất cả hình ảnh, javascript và bảng định kiểu. Một sự thật thú vị là khi bạn mới tải xuống trang chủ CNN, trình duyệt của bạn tìm nạp nhiều dữ liệu hơn so với trò chơi Doom gốc vào khoảng năm 1993. Đối với những người tò mò, tại thời điểm viết bài blog này, CNN chỉ tải xuống hơn 3MB trên máy của tôi, được nén từ ~ 15MB và đó là khi đã bật trình chặn quảng cáo, trong khi trình cài đặt Doom ban đầu là ~ 2,2MB.

Để trình duyệt lưu vào bộ đệm dữ liệu này, cần có sự phối hợp nhất định với máy chủ. Trình duyệt cần biết những gì nó có thể lưu vào bộ nhớ cache và trong bao lâu; nếu không, nó có thể hiển thị cho bạn nội dung cũ khi máy chủ có sẵn phiên bản mới hơn. Trong bài viết này, chúng ta sẽ xem xét cách phối hợp bộ nhớ đệm máy khách-máy chủ này được thực hiện và những gì Rails cung cấp để thay đổi nó.

Mặc dù trọng tâm là cách Ruby on Rails xử lý điều này, nhưng cơ chế thực tế là một phần của các đặc tả HTTP. Nói cách khác, bộ nhớ đệm mà chúng ta đang nói đến ở đây được đưa vào cơ sở hạ tầng của Internet, điều này làm cho nó trở thành nền tảng của cách các trang web và khuôn khổ hiện đại được phát triển. Các khung công tác khác nhau, chẳng hạn như Rails, các ứng dụng một trang (SPA) và thậm chí cả các trang web tĩnh, đều có thể sử dụng các cơ chế này để giúp cải thiện hiệu suất.

HTTP Request-Response

Có thể bạn đã quen thuộc với vòng đời phản hồi yêu cầu, ít nhất là ở cấp độ cao:bạn nhấp vào liên kết trên một trang web, trình duyệt của bạn gửi yêu cầu đến máy chủ cho nội dung đó và máy chủ gửi lại nội dung đó (Lưu ý rằng tôi tôi cố ý che đậy rất nhiều sự phức tạp ở đây).

Hãy cùng tìm hiểu một chút về dữ liệu thực tế được gửi trong giao dịch qua lại này. Mỗi thông báo HTTP có phần đầu và phần thân (không nên nhầm lẫn với <head><body> Thẻ HTML). Tiêu đề yêu cầu cho máy chủ biết bạn đang cố gắng truy cập đường dẫn nào và sử dụng phương thức HTTP nào (ví dụ:GET / PUT / PATCH / POST). Nếu cần, bạn có thể tìm hiểu các tiêu đề này bằng cách sử dụng công cụ dành cho nhà phát triển của trình duyệt hoặc công cụ dòng lệnh, chẳng hạn như curl :

# curl -v honeybadger.io
...
> GET / HTTP/1.1
> Host: honeybadger.io
> User-Agent: curl/7.64.1
> Accept: */*

Phần đầu tiên của đầu ra là tiêu đề yêu cầu. Chúng tôi đang phát hành GET tới honeybadger.io . Tiếp theo là những gì máy chủ gửi lại ("tiêu đề phản hồi"):

>
< HTTP/1.1 301 Moved Permanently
< Cache-Control: public, max-age=0, must-revalidate
< Content-Length: 39
< Content-Security-Policy: frame-ancestors 'none'
...
< Content-Type: text/plain

Phản hồi bao gồm mã HTTP (ví dụ:200 để thành công hoặc 404 vì không tìm thấy). Trong ví dụ này, nó là một chuyển hướng vĩnh viễn (301) vì curl đang cố gắng liên hệ với http URL chuyển hướng đến https an toàn URL. Tiêu đề phản hồi cũng bao gồm loại nội dung, đó là text/plain ở đây, nhưng một số tùy chọn phổ biến khác là text/html , text/css , text/javascriptapplication/json .

Nội dung phản hồi theo sau tiêu đề. Trong trường hợp của chúng tôi, phần nội dung trống vì 301 chuyển hướng không cần phần thân. Nếu chúng tôi đã thử lại với curl -v https://www.honeybadger.io , bạn sẽ thấy nội dung trang chủ ở đây, giống như thể bạn đang xem nguồn trong trình duyệt.

Nếu bạn muốn tự mình thử nghiệm điều này, đây là hai mẹo:

  1. Để chỉ hiển thị tiêu đề phản hồi có cuộn tròn (ví dụ:không có tiêu đề yêu cầu hoặc nội dung phản hồi), hãy sử dụng -I tùy chọn, như trong curl -I localhost:3000 .
  2. Theo mặc định, Rails không lưu vào bộ nhớ cache trong môi trường phát triển; bạn có thể cần chạy rails dev:cache đầu tiên.

Tiêu đề HTTP kiểm soát bộ nhớ cache

Tiêu đề chính mà chúng tôi quan tâm, theo như bộ nhớ đệm, là Cache-Control đầu trang. Điều này giúp xác định máy nào có thể lưu vào bộ nhớ cache một phản hồi từ máy chủ Rails của chúng tôi và khi nào dữ liệu được lưu trong bộ nhớ cache đó hết hạn. Trong Cache-Control tiêu đề, có một số trường, hầu hết trong số đó là tùy chọn. Chúng ta sẽ xem xét một số mục có liên quan nhất ở đây, nhưng để biết thêm thông tin, bạn có thể kiểm tra thông số HTTP chính thức tại w3.org.

Đây là mẫu từ tiêu đề phản hồi Rails cơ bản có sẵn:

< Content-Type: text/html; charset=utf-8
< Etag: W/"b41ce6c6d4bde17fd61a09e36b1e52ad"
< Cache-Control: max-age=0, private, must-revalidate

max-age

max-age trường là một số nguyên chứa số giây phản hồi hợp lệ. Theo mặc định, phản hồi Rails cho một chế độ xem sẽ có giá trị này được đặt thành 0 (tức là phản hồi sẽ hết hạn ngay lập tức và trình duyệt sẽ luôn nhận được phiên bản mới).

public / private

Bao gồm public hoặc private trong bộ tiêu đề mà máy chủ được phép lưu vào bộ nhớ cache phản hồi. Nếu tiêu đề bao gồm private , nó chỉ được lưu vào bộ nhớ đệm bởi ứng dụng khách yêu cầu (ví dụ:trình duyệt), không phải bất kỳ máy chủ nào khác mà nó có thể đã đi qua để đến đó, chẳng hạn như mạng phân phối nội dung (CDN) hoặc proxy. Nếu tiêu đề bao gồm public thì các máy chủ trung gian này được phép lưu phản hồi vào bộ nhớ cache. Rails đặt mỗi tiêu đề thành private theo mặc định.

phải xác thực lại

Rails cũng đặt must-revalidate trường theo mặc định. Điều này có nghĩa là máy khách phải liên hệ với máy chủ để xác nhận rằng phiên bản đã lưu trong bộ nhớ cache của nó vẫn còn hiệu lực trước khi nó được sử dụng. Để xác định xem phiên bản đã lưu trong bộ nhớ cache có hợp lệ hay không, máy khách và máy chủ sử dụng ETags.

ETags

ETags là một tiêu đề HTTP tùy chọn được máy chủ thêm vào khi nó gửi phản hồi đến máy khách. Thông thường, đây là một số loại tổng kiểm tra trên chính phản hồi. Khi ứng dụng khách (tức là trình duyệt của bạn) cần yêu cầu lại tài nguyên này, nó sẽ bao gồm Etag mà nó nhận được (giả sử nó có một phản hồi trước đó được lưu trong bộ nhớ cache) trong If-None-Match Tiêu đề HTTP. Sau đó, máy chủ có thể phản hồi bằng 304 Mã HTTP ("Không được sửa đổi") và phần nội dung trống. Điều này có nghĩa là phiên bản trên máy chủ không thay đổi, vì vậy máy khách nên sử dụng phiên bản đã lưu trong bộ nhớ cache của nó.

Có hai loại ETags:mạnh và yếu (thẻ yếu được ký hiệu bằng W/ tiếp đầu ngữ). Chúng hoạt động theo cùng một cách, nhưng ETag mạnh có nghĩa là hai bản sao của tài nguyên (phiên bản trên máy chủ và một trong bộ nhớ cache cục bộ) giống nhau 100% từng byte. Tuy nhiên, ETags yếu chỉ ra rằng hai bản sao có thể không giống nhau từng byte, nhưng vẫn có thể sử dụng phiên bản được lưu trong bộ nhớ cache. Một ví dụ phổ biến về điều này là Rails 'csrf_meta_tags helper, tạo ra một mã thông báo thay đổi liên tục; do đó, ngay cả khi bạn có một trang tĩnh trong ứng dụng của mình, nó sẽ không giống nhau từng byte khi được làm mới do mã thông báo Cross-Site-Request-Forgery (CSRF). Rails sử dụng ETags yếu theo mặc định.

ETags trong Rails

Rails xử lý ETags tự động trên các khung nhìn. Nó bao gồm ETag trong các tiêu đề gửi đi và có phần mềm trung gian để kiểm tra các tiêu đề ETag đến và trả về 304 (Không sửa đổi) mã khi thích hợp. Tuy nhiên, đáng chú ý là vì Rails tạo các khung nhìn động nên nó vẫn phải thực hiện tất cả các công việc kết xuất trước khi có thể tìm ra ETag cho chế độ xem đó. Điều này có nghĩa là ngay cả khi ETags khớp, bạn chỉ tiết kiệm thời gian và băng thông cần thiết để gửi dữ liệu qua mạng, trái ngược với một thứ như bộ nhớ đệm chế độ xem, nơi bạn có thể bỏ qua hoàn toàn bước kết xuất nếu có phiên bản được lưu trong bộ nhớ cache. Tuy nhiên, Rails cung cấp một số cách để tinh chỉnh ETag đã tạo.

cũ?

Một cách để khắc phục mã thông báo CSRF luôn thay đổi từ việc thay đổi thẻ ETag là sử dụng stale? trợ giúp trong ActionController . Điều này cho phép bạn đặt ETag (mạnh hoặc yếu) trực tiếp. Tuy nhiên, bạn cũng có thể chỉ cần chuyển cho nó một đối tượng, chẳng hạn như mô hình ActiveRecord và nó sẽ tính toán ETag dựa trên updated_at của đối tượng dấu thời gian hoặc sử dụng updated_at tối đa nếu bạn vượt qua một bộ sưu tập:

class UsersController < ApplicationController
  def index
    @users = User.includes(:posts).all

    render :index if stale?(@users)
  end
end

Bằng cách đánh trang bằng cách cuộn tròn, chúng ta có thể thấy kết quả:

# curl -I localhost:3000 -- first page load
ETag: W/"af9ae8f2d66b9b6c4d0513f185638f1a"
# curl -I localhost:3000 -- reload (change due to CSRF token)
ETag: W/"f06158417f290334f47ea2124e08d89d"

-- Add stale? to controller code

# curl -I localhost:3000 -- reload
ETag: W/"04b9b99835c359f36551720d8e3ca6fe" -- now using `@users` to generate ETag
# curl -I localhost:3000 -- reload
ETag: W/"04b9b99835c359f36551720d8e3ca6fe" -- no change

Điều này cho phép chúng tôi kiểm soát nhiều hơn khi máy khách phải tải xuống lại toàn bộ tải trọng, nhưng nó vẫn phải kiểm tra với máy chủ mọi lúc để xác định xem bộ nhớ cache của nó có còn hợp lệ hay không. Điều gì sẽ xảy ra nếu chúng ta muốn bỏ qua hoàn toàn việc kiểm tra đó? Đây là nơi max-age trường trong tiêu đề đi kèm.

expires_in và http_cache_forever

Rails cung cấp cho chúng ta một số phương thức trợ giúp trong ActionController để điều chỉnh max-age trường:expires_inhttp_cache_forever . Cả hai đều hoạt động theo cách bạn mong đợi dựa trên tên của chúng:

class UsersController < ApplicationController
  def index
    @users = User.includes(:posts).all

    expires_in 10.minutes
  end
end
# curl -I localhost:3000
Cache-Control: max-age=600, private

Rails đã đặt max-age đến 600 (10 phút trong giây) và xóa must-revalidate đồng ruộng. Bạn cũng có thể thay đổi private bằng cách chuyển public: true đối số được đặt tên.

http_cache_forever chủ yếu chỉ là một trình bao bọc xung quanh expires_in đặt max-age đến 100 năm và có một khối:

class UsersController < ApplicationController
  def index
    @users = User.includes(:posts).all

    http_cache_forever(public: true) do
      render :index
    end
  end
end
# curl -I localhost:3000
Cache-Control: max-age=3155695200, public

Loại bộ nhớ đệm cực kỳ dài hạn này là lý do tại sao nội dung Rails có "dấu vân tay" được thêm vào chúng, là hàm băm của nội dung tệp và tạo tên tệp, chẳng hạn như packs/js/application-4028feaf5babc1c1617b.js . "Dấu vân tay" ở cuối liên kết hiệu quả nội dung của tệp với tên của tệp. Nếu nội dung thay đổi, tên tệp sẽ thay đổi. Điều này có nghĩa là các trình duyệt có thể lưu trữ tệp này một cách an toàn mãi mãi bởi vì nếu nó luôn thay đổi, dù chỉ một cách nhỏ, dấu vân tay cũng sẽ thay đổi; Theo như trình duyệt có liên quan, đó là một tệp hoàn toàn riêng biệt cần được tải xuống.

Tầm ảnh hưởng

Bây giờ chúng ta đã đề cập đến một số tùy chọn bộ nhớ đệm, lời khuyên của tôi có vẻ hơi kỳ quặc, nhưng tôi khuyên bạn nên cố gắng tránh sử dụng bất kỳ phương pháp nào trong bài viết này! ETags và bộ nhớ đệm HTTP là những điều cần biết và Rails cung cấp cho chúng ta một số công cụ cụ thể để giải quyết các vấn đề cụ thể. Tuy nhiên, điều cần lưu ý và quan trọng là tất cả các bộ nhớ đệm này xảy ra bên ngoài ứng dụng của bạn và do đó, phần lớn nằm ngoài tầm kiểm soát của bạn. Nếu bạn đang sử dụng bộ nhớ đệm dạng xem hoặc bộ nhớ đệm cấp thấp trong Rails, như đã đề cập trong các phần trước của loạt bài này và gặp phải các vấn đề về việc vô hiệu hóa, bạn có các tùy chọn; bạn có thể touch mô hình, đẩy mã cập nhật hoặc thậm chí truy cập vào Rails.cache trực tiếp từ bảng điều khiển nếu bạn phải làm như vậy, nhưng không phải với bộ đệm HTTP. Cá nhân tôi muốn chạy Rails.cache.clear trong quá trình sản xuất thay vì phải đối mặt với sự cố trong đó trang web bị hỏng đối với người dùng cho đến khi họ xóa bộ nhớ cache của trình duyệt (nhóm dịch vụ khách hàng của bạn cũng sẽ yêu bạn vì điều đó).

Kết luận

Đây là phần cuối của loạt bài về bộ nhớ đệm trong Rails; hy vọng, nó hữu ích và nhiều thông tin. Lời khuyên của tôi cho bộ nhớ đệm tiếp tục như sau:làm ít nhất bạn có thể, nhưng nhiều nhất bạn phải làm. Nếu bạn gặp vấn đề về hiệu suất, hãy bắt đầu bằng cách tìm kiếm các phương pháp thường gặp; có lẽ, chúng có thể được ghi nhớ. Cần giá trị để duy trì trên các yêu cầu? Có thể phương pháp được sử dụng nhiều đó có thể sử dụng một số bộ nhớ đệm cấp thấp. Hoặc, có lẽ, nó không phải là bất kỳ phương pháp cụ thể nào; nó chỉ chạy qua tất cả những phần tử lồng vào nhau, đang làm mọi thứ chậm lại; trong trường hợp này, có thể bộ nhớ đệm cấp chế độ xem có thể giúp ích. Rails cung cấp cho chúng tôi "con dao sắc bén" để nhắm vào từng vấn đề này và chúng tôi chỉ cần biết khi nào nên sử dụng chúng.