Trong các ứng dụng web hiện nay chắc hẳn các bạn đã khá quen thuộc với từ khóa Cache. Chúng ta có cache lại kết quả của các câu truy vấn (queries) để trả về kết quả nhanh hơn, ngoài ra chúng ta có thể cache lại các views để render nhanh hơn,…

Bài viết hôm nay tôi muốn giới thiệu với các bạn về HTTP Caching, về định nghĩa, cơ chế hoạt động, tại sao nên sử dụng và cách config server để thực hiện việc cache này.

Đối với một ứng dụng web nào đó thì vấn đề nhức nhối luôn được quan tâm hàng đầu đó là tốc độ. Bất kỳ ai trong chúng ta đều ghét phải chờ đợi 😛 thử tượng tượng các bạn truy cập vào một ứng dụng web nào đó, ngồi chờ cái loading indicator quay vòng tròn trước mắt, ngắm từng dòng chữ, từng hình ảnh dần dần xuất hiện, rồi một lúc lâu sau mới được chiêm ngưỡng những thông tin cần thiết. Nếu là một người không có tính kiên nhẫn, tôi đảm bảo chỉ sau một vài cú click chuột, bạn đó sẽ không bao giờ dám quay lại ứng dụng của bạn lần thứ hai.

Vậy chúng ta phải làm gì trong trường hợp này? Dù chúng ta đã rất cố gắng refactor queries phía Server để trả về kết quả nhanh nhất tuy nhiên để tốc độ ngon hơn nữa thì đây chính là lúc cần sự trợ giúp thêm từ phíaClient. Các file tĩnh như các file hình ảnh (image), các file stylesheet (CSS), các file javascript (JS), các đoạn clip,… dù đã lưu trên Server nhưng chúng ta đã có sự trợ giúp của HTTP Caching như một cầu nối giao tiếp để Client cũng có thể lưu lại những file tĩnh đó trên máy của người dùng. Làm được như vậy sẽ giảm tải khá nhiều cho Server trong việc xử lý các request đến các file tĩnh này và trả về cho Client.

HTTP Caching là gì?

Trong nội dung bài viết này tôi xin phép không giới thiệu về giao thức HTTP cũng như các tham số của HTTP Header.

Kỹ thuật HTTP Caching chính là việc bạn chuyển một bản copy các tài nguyên tĩnh phía Server xuống lưu ở dưới Client. Về cơ bản, người dùng sẽ cảm nhận thấy một độ trễ rất thấp khi yêu cầu các tài nguyên tĩnh này từ phía Server, lưu lượng truyền đi ít hơn, số request đến Server ít hơn, do vậy Server sẽ nhàn hơn để dùng sức của mình vào những việc khác.

Chúng ta hãy cùng xem qua một request ví dụ như sau: HTTP_request.png

  1. Client yêu cầu file index.html
  2. Server làm công việc đi tìm kiếm xem file index.html ở đâu
  3. Server tìm thấy và trả về cho Client
  4. Client download file và hiển thị cho người dùng.

Giả sử index.html là một file tĩnh rất ít khi thay đổi thì điều bất cập xảy ra ở đây đó là mỗi lần Client yêu cầu truy cập file này, Server đều phải lục lọi tìm kiếm rồi bắt buộc Client phải download thì mới sử dụng được. Việc làm này đang làm lãng phí thời gian của Server và thời gian của người sử dụng.

Tại sao cần sử dụng HTTP Caching?

Các lợi ích chính mà HTTP Caching mang lại có thể kể ra như sau:

  1. Giúp cho ứng dụng Web load nhanh hơn (giảm thời gian trễ).
  2. Giảm băng thông sử dụng.
  3. Giảm số lần truy cập lên Server.

HTTP Caching hoạt động như thế nào?

Tất cả các hệ thống cache đều sử dụng chung các nguyên tắc để xác định thời điểm cung cấp đối tượng (tất nhiên là các đối tượng đó đã được lưu trong Cache). Trường hợp đối tượng chưa được lưu trong Cache thì sẽ phát sinh yêu cầu đến Server để lấy thông tin của đối tượng đó.

Một số nguyên tắc được thiết lập trong giao thức HTTP (1.0, 1.1) và một số nguyên tắc khác sẽ được thiết lập bởi những người quản trị cache (hiểu nôm na có thể là người dùng Browser có thể đặt các setting cho Cache thông qua các trình duyệt như Safari, Firefox, Chrome,…)

Một số nguyên tắc chung có thể được liệt kê ra như sau:

  1. Nếu trong Response Header từ Server có thông tin không cho phép lưu cache, Browser sẽ không được quyền lưu.
  2. Nếu đối tượng trong request có yêu cầu xác thực hay bảo mật (VD: HTTPS), nó sẽ không được phép lưu trong shared cache.
  3. Nếu một đối tượng được gọi là “mới” được phép lưu cache ở Browser mà không cần xác thực với Server thì nó cần phải có thông tin về khoảng thời gian tồn tại.
  4. Nếu một đối tượng được gọi là “cũ”, Server sẽ nhận được yêu cầu xác thực đối tượng và báo cho Client biết nó còn giá trị sử dụng hay không. Trong trường hợp này ClientServer chỉ trao đổi với nhau thông qua việc truyền và gửi message để confirm nên lưu lượng sẽ giảm đi rất nhiều so với việc gửi cả một đối tượng có dung lượng xác định.

Như vậy các bạn có thể thấy thay bằng việc Client cứ phải download những file hàng trăm KB hay đơn vị MB với mỗi request lên Server thì giờ đây chỉ bằng việc trao đổi với nhau vài message với dung lượng cực kỳ nhỏ, Client đã có thông tin mà mình cần, đồng thời Server cũng đỡ cực hơn trong việc xử lý request từ Client.Message mà tôi nói đến chính là những tham số mà HTTP Header hỗ trợ. Bây giờ chúng ta sẽ cùng xem chi tiết đó là những tham số gì và nó hoạt động ra sao.

Các giải pháp HTTP Caching

Sau đây tôi xin giới thiệu với các bạn một số giải pháp thực hiện HTTP Caching sử dụng các tham số của HTTP Header. Các bạn nên lưu ý rằng các tham số này chỉ mang tính chất truyền thông điệp giữa Client và Server, đồng thời cũng là thông tin đối sánh để Client biết lấy dữ liệu từ Cache hay lại phải request lên Server để lấy dữ liệu mà mình cần. Cache mà tôi nói đến ở đây chính là một vùng lưu trữ nào đó trên ổ cứng của máy bạn mà Client (hay chính xác là Browser) sử dụng để lưu trữ các file tĩnh từ phía Server trả về.

Tham số Last-Modified

Khi lần đầu tiên Client request một file (VD: logo.png) từ Server, file này chưa hề được lưu trong Cache ở Client, Server sẽ trả về cho Client file đó kèm theo tham số Last-Modified trên Response Headers để xác định thời điểm cuối cùng file đó được sửa đổi.

header_last_modified.png

Nhìn vào hình ảnh trên ta có thể thấy file được chỉnh sửa lần cuối cùng vào Thứ 2, ngày 1/6/2015, lúc 2 giờ 57 phút, Status Code200 OK. Sau đó Client sẽ lưu lại file này vào Cache và đồng thời lưu luôn thông tin ngày thay đổi cuối cùng của file.

Tiếp theo, Client lại thực hiện request một lần nữa lên Server. Trước hết hãy cùng xem qua sơ đồ hoạt động ví dụ sau:

HTTP-caching-last-modified_1.png (Thông số trong ảnh chỉ mang tính chất minh họa)

  1. Client gửi yêu cầu lấy file logo kèm theo điều kiện ngày thay đổi của file đó phải chính xác là Thứ 2, ngày 1/6/2015, lúc 2 giờ 57 phút (tham số If-Modified-Since trong Request Headers).
  2. Server đi tìm file logo và kiểm tra xem ngày thay đổi có đúng với thông tin Client gửi lên không.
  3. Nếu Server check OK, Client sẽ nhận được Status Code304 Not Modified.
  4. Client không phải download file logo đó một lần nữa từ Server mà chui thẳng vào Cache để lấy file đó ra dùng.

Hình ảnh thực tế của request này như sau: header_last_modified_1.png

Như vậy câu hỏi đặt ra là: Nếu If-Modified-Since trong Request HeadersLast-Modified trongResponse Headers trước đó không trùng nhau thì sao?

Câu trả lời hết sức đơn giản, Client sẽ download file mới, lưu Cache đồng thời lưu thông tin ngày thay đổi mới. Quy trình sẽ lại lặp lại từ bước đầu tiên tôi nói phía trên.

  • Ưu điểm:
    • Nếu file không có sự thay đổi, Server chỉ phải gửi về status 304 với thông báo Not Modified mà không cần phải gửi lại toàn bộ nội dung file cho Client.
  • Nhược điểm:
    • Vẫn phải kết nối đến Server để nhận thông điệp.
    • Server vẫn phải xử lý kiểm tra thời gian thay đổi file.
    • Nếu có phát sinh sai lệch thời gian trên Server thì nội dung file vẫn được gửi xuống Client download mặc dù file đó không hề thay đổi.

Tham số Etag

Cơ chế hoạt động của tham số Etag cũng gần gần tương tự với Last-Modified chỉ khác ở điểm Etag có giá trị là một chuỗi ký tự đại diện cho file.

Etag có thể là một chuỗi Hash hay Footprint. Trong nội dung bài viết này tôi xin phép không đi sâu vào việcFootprint được sinh ra thế nào hay theo thuật toán nào, ta chỉ hiểu đơn giản là với mỗi file, Server sẽ sinh ra 1 chuỗi ký tự riêng và duy nhất để đại diện cho file đó (mà chúng ta gọi là tham số Etag). Nếu bạn thay đổi nội dung file thì Etag chắc chắn sẽ thay đổi giá trị.

Chúng ta hãy cùng phân tích sơ đồ giả định sau: HTTP_caching_if_none_match.png(Thông tin trong ảnh chỉ mang tính chất minh họa)

  1. Client yêu cầu file logo từ Server, và gửi kèm theo tham số If-None-Match (chính là giá trị của Etag) trongRequest Headers đã lưu trước đó (có thể hiểu là từ lần request đầu tiên).
  2. Server tìm kiếm file logo và kiểm tra mã Etag.
  3. Nếu Server check OK, Client sẽ nhận được Status Code304 Not Modified.
  4. Client không phải download file logo đó một lần nữa từ Server mà chui thẳng vào Cache để lấy file đó ra dùng.

Hình ảnh thực tế với Request HeadersResponse Headers như sau: header_etag.png

Ta có thể nhận thấy ưu và nhược điểm của tham số Etag này cũng gần tương tự với tham số Last-Modified, ta vẫn cứ phải chui lên Server để kiểm tra xem file có sự thay đổi hay không.

Tham số Expires

Để khắc phục nhược điểm của 2 tham số trên, chúng ta hãy cùng tiếp tục tìm hiểu về tham số Expires. Để tránh việc request một file nào đó từ lần thứ 2 trở đi cứ phải chui lên Server hỏi và kiểm tra, với lần đầu tiên Client request, Server sẽ trả về nội dung file kèm theo thông tin Expires trong Response Headers.

Thông tin trong tham số Expires chính là thời điểm hết hạn của file đó, sau thời điểm này nếu Client vẫn muốn sử dụng file thì phải lên Server để download lại cũng như cập nhật thông tin Expires mới, còn nếu chưa hết hạn thì Client sẽ chỉ việc lấy thông tin từ Cache để sử dụng mà không cần hỏi lại Server.

Sơ đồ giả định như sau: HTTP_caching_expires.png

  1. Client cần request file logo, tuy nhiên kiểm tra thấy thời hạn hết hiệu lực chưa đến.
  2. Client tự lấy file đó từ Cache mà không cần lên Server hỏi.
  • Ưu điểm của cách làm này đó là chúng ta hạn chế được việc Client sẽ phải chui lên để hỏi Server, khi đã làm được như vậy thì Server sẽ rảnh hơn để dành sức và tài nguyên phục vụ những thao tác khác. Ngoài ra thì tham số Expires cũng đã được hỗ trợ từ chuẩn HTTP 1.0 nên nó được sử dụng khá rộng rãi hiện nay.
  • Nhược điểm hay câu hỏi đặt ra cho cách làm này đó là nhỡ đâu trong thời điểm file đó vẫn chưa hết hạn (tức là Client vẫn đương nhiên lấy ở Cache) thì phía Server đã thực hiện thay đổi file đó. Như vậy trong trường hợp này Client sẽ không nhận được thông tin file mới ít nhất cho đến khi qua thời điểm hết hạn của file.

Có một giải pháp đã được đưa ra để giải quyết nhược điểm của cách làm này đó là đánh version cho file. Các bạn có thể thấy trong một số ứng dụng họ có đánh version cho các assets (js, css) dựa trên sự thay đổi của các file đó. Version có thể là một thành phần trong tên file (VD: A.123456.js) hoặc có thể ở dạng query string(VD: a.js?v=123456).

Cách đánh version như vậy sẽ giải quyết được nhược điểm của phương pháp này ở chỗ khi Server cập nhật file mới với version mới, Client sẽ download file mới kèm theo Expires của file mới và từ đó làm việc với file mới chứ không phải file cũ đã được Cache.

Tham số Cache-Control

Cách dùng tham số Cache-Control về cơ bản lại giống với Expires tuy nhiên cách cấu hình uyển chuyển và linh hoạt hơn. Thay vì việc đặt ra thời gian cụ thể hết hạn thì tham số Cache-Control này có thể khai báo thay cho một thông báo kiểu như: “File này sẽ hết hạn sử dụng trong vòng 1 tháng tính từ thời điểm hiện tại”. Cụ thể thì tham số này hỗ trợ cấu hình các thông tin như sau:

  • max-age=[seconds]: định nghĩa khoảng thời gian tối đa mà file được cache (tính từ thời điểm request file), tính bằng đơn vị giây, một số thông tin chuyển đổi cơ bản như sau:
    • 1 ngày = 86400 giây
    • 1 tuần = 604800 giây
    • 1 tháng = 2629000 giây
    • 1 năm = 31636000 giây
  • s-maxage=[seconds]: tương tự như max-age nhưng thông số này chỉ được sử dụng cho shared caches (VD:Proxy caches).
  • public: cho phép file được cache bởi các Proxy và các server trung gian.
  • private: file có giá trị khác nhau cho các Client khác nhau, Browser có thể cache nhưng Proxy thì không.
  • no-cache: không được phép cache file này, thường được sử dụng cho những trang kết quả tìm kiếm, mặc dù URL là giống nhau nhưng nội dung có thể thay đổi khác nhau.
  • no-store: chỉ thị cho Client biết không được phép tạo một bản copy của file và lưu trữ dưới bất kỳ hình thức nào.
  • must-revalidate: chỉ thị bắt buộc phải validate lại với Server khi sử dụng file đã được cache.
  • proxy-revalidate: tương tự như must-revalidate nhưng chỉ áp dụng cho các Proxy caches.

Ví dụ sử dụng:

Default
Cache-Control: max-age=3600, must-revalidate

Cách config server hoạt động với HTTP Caching

Phần này tôi xin giới thiệu cách config HTTP Caching hoạt động với Server Apache (hệ điều hành UBUNTU). Cách config với Nginx về cơ bản cũng tương tự (các bạn vui lòng Google để biết thêm chi tiết).

Điều kiện cần đầu tiên đó là 2 module: mod_headersmod_expires của Apache phải được enable. Các bạn cần làm theo các bước như sau:

  • Bước 1: kiểm tra xem 2 mod đó đã được enable chưa
    SH
    
    apachectl -t -D DUMP_MODULES
  • Bước 2: enable 2 mod nói ở trên (nếu kiểm tra fail ở bước 1), sau đó nhớ restart server
    SH
    
     sudo a2enmod headers
    sudo a2enmod expires
     Sau khi đã có 2 mod cần thiết chúng ta tiến hành cấu hình cho HTTP Caching hoạt động như sau:
  • Sử dụng Expires Header:
    SH
    
     <IfModule mod_expires.c>
        ExpiresActive On
        ExpiresByType image/jpg "access 1 year"
        ExpiresByType image/jpeg "access 1 year"
        ExpiresByType image/gif "access 1 year"
        ExpiresByType image/png "access 1 year"
        ExpiresByType text/css "access 1 month"
        ExpiresByType text/html "access 1 month"
        ExpiresByType application/x-javascript "access 1 month"
        ExpiresByType application/javascript "access 1 month"
        ExpiresByType text/javascript "access 1 month"
        ExpiresByType image/x-icon "access 1 year"
        ExpiresDefault "access 1 month"
    </IfModule>
  • Sử dụng Cache-Control Header:
    SH
    
     <IfModule mod_headers.c>
        <FilesMatch "\.(ico|jpg|jpeg|png|gif|css)$">
            Header set Cache-Control "max-age=2678400, public"
        </FilesMatch>
        <FilesMatch "\.(html|htm)$">
            Header set Cache-Control "max-age=7200, private, must-revalidate"
        </FilesMatch>
        <FilesMatch "\.(pdf)$">
            Header set Cache-Control "max-age=86400, public"
        </FilesMatch>
        <FilesMatch "\.js$">
            Header set Cache-Control "max-age=2678400, private"
        </FilesMatch>
    </IfModule>

Có một số chú ý như sau:

Kết luận

  • Trong các cách sử dụng tham số cho HTTP Headers để thực hiện HTTP Caching có thể chia làm 2 loại:
    • Strong Caching Header: ExpiresCache-Control.
    • Weak Caching Header: EtagLast-Modified.
  • Ứng dụng của bạn chỉ nên sử dụng một Strong Caching Header và một Weak Caching Header vì cơ chế tương tự nhau, nếu sử dụng cả hai cùng một lúc sẽ dư thừa.
  • Nếu đồng thời sử dụng ExpiresCache-Control thì Cache-Control sẽ được ưu tiên hơn.
  • Nên sử dụng Cache-Control hơn là Expires vì tính linh động của nó.
  • Nên sử dụng Last-Modified hơn là Etag vì có thể Etag sẽ gặp vấn đề khi config với Apache2.
  • Với các file tĩnh ít khi thay đổi thì nên đặt thời gian còn hiệu lực là khoảng 1 tháng đến 1 năm.
  • Nên sử dụng thêm việc đánh version cho các file tĩnh để khắc phục nhược điểm của Expires cũng nhưCache-Control.

Comments

comments