Chúng ta đều biết, hiệu năng của ứng dụng và website là yếu tố quan trọng quyết định thành công của một doanh nghiệp. Tuy nhiên, quá trình làm cho ứng dụng hoặc website hoạt động tốt hơn thường không rõ ràng. Chất lượng code và cơ sở hạ tầng tất nhiên là quan trọng. Nhưng trong nhiều trường hợp, bạn có thể cải thiện trải nghiệm của người dùng một cách rõ rệt bằng cách triển khai và tối ưu hóa caching trong application stack. Bài viết hôm nay sẽ chia sẻ các kỹ thuật từ căn bản đến nâng cao giúp tận dụng tối đa sức mạnh của tính năng cache mà NGINX hỗ trợ để nâng cao hiệu suất.
Nội dung
Tổng quan về NGINX cache
Một hệ thống cache nội dung (content cache) nằm giữa người dùng (client) và server (origin server), và lưu trữ lại mọi nội dung nó nhìn thấy. Nếu một client requests content đã được lưu trữ trong cache, nó sẽ return cho người dùng trực tiếp mà không cần phải liên hệ với origin server để lấy nội dung. Điều này giúp nâng cao hiệu suất vì server cache nằm gần người dùng hơn, và sử dụng application server hiệu quả hơn bởi vì nó không cần phải xử lý để tạo ra kết quả trả lời cho mỗi lần user request.
Có nhiều vị trí tiềm năng để cache giữa web browser và server chạy ứng dụng: cache ở browser của client, các lớp cache trung gian, CDN, hệ thống load balancer và reverse proxy nằm phía trước server ứng dụng. Caching, thậm chí khi chỉ được đặt ở load balancer hoặc reverse proxy level cũng giúp tăng hiệu năng rất lớn.
Một ví dụ, năm ngoái khi tôi nhận nhiệm vụ tối ưu hóa tốc độ cho một website đang bị load chậm. Một trong những thứ đầu tiên tôi chú ý là nó mất hơn 1 giây để load trang home page. Sau khi debug, tôi phát hiện ra lý do là gì page bị đánh dấu là not cachable (không được phép cache), website được generate dynamically mỗi khi có request. Bản thân home page nó không được thay đổi content thường xuyên và cũng không được cá nhân hóa (personalized), nên việc phải generate lại nội dung mỗi khi có truy cập là điều không cần thiết. Dựa theo kinh nghiệm, tôi cấu hình để cache home page 5s trên load balancer, và chỉ với việc này, hiệu suất được cải thiện một cách đáng kinh ngạc. Time to first by (TTFB) hạ xuống chỉ còn vài miliseconds và page load nhanh hơn thấy rõ.
NGINX thường được triển khai như một reverse proxy hoặc một load balancer trong một application stack và được trang bị đầy đủ các tính năng cache. Phần tiếp theo, chúng ta sẽ cùng xem cách để cấu hình caching căn bản với NGINX
Cách cài đặt và cấu hình NGINX cache cơ bản
Chỉ có 2 directive cần sử dụng để enable basic caching: proxy_cache_path và proxy_cache.
- proxy_cache_path directive thiết lập đường dẫn (path) và cấu hình (configuration) của cache.
- proxy_cache directive kích hoạt cache.
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
server {
# ...
location / {
proxy_cache my_cache;
proxy_pass http://my_upstream;
}
}
Các tham số cấu hình của proxy_cache_path directive bao gồm:
- Đường dẫn trên local disk đến vị trí lưu file cache được gọi là /path/to/cache
- levels: Thiết lập một cấu trúc thư mục hai cấp (two-level) trong /path/to/cache. Nếu có một số lượng lớn file ở trong 1 thư mục thì việc truy cập vào thư mục sẽ bị chậm. Do đó, NGINX khuyến nghị nên sử dụng cấu trúc thư mục two-level khi sử dụng cache. Nếu tham số levels không được cấu hình, NGINX sẽ đặt tất cả file cache vào cùng 1 thư mục.
- keys_zone thiết lập một vùng shared memory để lưu trữ cache keys và metadata như là usage timers. Duy trì một bản copy của keys trong bộ nhớ cho phép NGINX nhanh chóng xác định một request là HIT hay là MISS mà không cần phải kiểm tra dữ liệu trên ổ cứng, điều này giúp tăng tốc độ kiểm tra đáng kể. Zone với dung lượng 1MB có thể chứa khoảng 8000 key, zone với dung lượng 10MB như trong ví dụ có thể chứa khoảng 80,000 keys.
- max_size thiết lập chặn trên (upper limit) của cache size (10GB trong ví dụ phía trên). Tham số này không bắt buộc (optional), nếu không được khai báo đồng nghĩa với việc cho phép cache sử dụng toàn bộ dung lượng ổ cứng còn trống. Khi cache size đạt đến limit, một process có tên gọi cache manager sẽ xóa những file ít được sử dụng nhất để giữ cho cache size trở lại thấp hơn limit.
- inactive chỉ định thời gian tối đa một item có thể tồn tại trong cache mà không được sử dụng. Trong ví dụ, nếu một file không được request trong vòng 60 phút sẽ tự động bị xóa bởi process cache manager, không cần quan tâm nó đã hết hạn hay chưa. Giá trị mặc định là 10 phút (10m). Inactive content khác với expired content. NGINX không tự động xóa content đã hết hạn (expired) như nó được khai báo bởi cache control header (ví dụ: Cache-Control: max-age=120). Expired (stale) content được xóa chỉ khi nó không được sử dụng trong khoảng thời gian được chỉ định bởi inactive. Khi expired content được truy cập, NGINX sẽ refresh nó từ origin server và reset inactive timer.
- NGINX sẽ ghi những file được chỉ định cho cache vào trong một khu vực lưu trữ tạm (temporary storage) trước, và sử dụng directive use_temp_path=off sẽ chỉ định NGINX write cache trực tiếp vào thư mục chứa cache mà không cần lưu vào file tạm. Khuyến nghị nên cấu hình tham số này bằng off để tránh thao tác copy data không cần thiết giữ file system. use_temp_path được giới thiệu ở NGINX version 1.7.10.
Và cuối cùng proxy_cache directive kích hoạt cache tất cả content match URL của block location chứa nó (trong ví dụ là là location /). Bạn cũng có thể sử dụng proxy_cache directive ở server block, it sẽ apply cache chung cho tất cả location block trong server nếu location đó không có proxy_cache directive của riêng nó.
Phục vụ cached content khi Origin server bị Down
Một tính năng của NGINX content caching là NGINX có thể được cấu hình để phục vụ các content đã hết hạn (stale content) trong cache của nó khi nó không thể lấy được content mới từ origin server. Trường hợp này xảy ra khi tất cả origin server của cached resources bị down hoặc không đang quá tải. Thay vì trả về error code cho client, NGINX sẽ phục vụ nội dung cũ đang có trong cache cho client. Điều này cung cấp thêm 1 level chịu lỗi cho server chạy ứng dụng mà NGINX đang làm proxy cho nó, giúp nâng cao uptime trong trường hợp server bị lỗi hoặc traffic tăng cao đột biến. Để kích hoạt tính năng này, sử dụng proxy_cache_use_stale directive:
location / {
# ...
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
}
Với cấu hình trên, nếu NGINX nhận phải lỗi error, timeout, hoặc bất kỹ lỗi 5xx nào được liệt kê phía trên từ origin server và nó có stale version của URL được request trong cache, nó sẽ dùng nội dung của stale file (cache cũ) trả về cho client thay vì trả về lỗi.
Tối ưu cache và cải thiện hiệu suất
NGINX có rất nhiều options giúp fine-tuning hiệu suất của cache. Dưới đây là một vài ví dụ:
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
server {
# ...
location / {
proxy_cache my_cache;
proxy_cache_revalidate on;
proxy_cache_min_uses 3;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;
proxy_pass http://my_upstream;
}
}
Những directive trên có tác dụng:
- proxy_cache_revalidate ra lệnh cho NGINX sử dụng thêm GET requests khi làm mới (refreshing) content từ phía origin server. Nếu một client request một item đã được cache nhưng expired bởi cache control header, NGINX đính kèm If-Modified-Since vào header của GET request mà nó gửi đến origin server. Điều này giúp tiết kiệm băng thông, bởi vì server chỉ gửi lại full nội dung của item chỉ khi nội dung đó đã bị thay đổi kể từ thời điểm được ghi nhận trong header Last-Modified trong file cache đang được lưu.
- proxy_cache_min_uses thiết lập số lần mà item phải được request bởi client trước khi NGINX quyết định cache lại chúng. Điều này sẽ rất có ích khi mà hệ thống cache thường hay bị đầy, vì nó giúp đảm bảo chỉ có những items thường được truy cập mới được add vào cache. Mặc định, proxy_cache_min_use có gía trị là 1.
- Tham số updating của proxy_cache_use_stale directive, kết hợp với việc kích hoạt proxy_cache_background_update directive, cho phép NGINX sử dụng stale content để phục vụ cho client khi một item expired hoặc nó đang trong quá trình được update nội dung mới từ origin server. Tất cả việc cập nhật sẽ được thực hiện ở background. Stale file sẽ được sử dụng cho đến khi quá trình update hoàn tất và file cache được download hoàn chỉnh.
- Với proxy_cache_lock được enable, nếu nhiều client cùng request một file mà file đó đang không có trong cache (MISS), chỉ có request đầu tiên trong số những request đó được phép đẩy về origin server. Những request còn lại sẽ phải chờ cho đến khi request đầu tiên hoàn tất và dữ liệu được lưu vào cache, và chúng sẽ sử dụng dữ liệu từ file cache này. Nếu không có proxy_cache_lock được bật, tất cả request bị cache miss sẽ đi thẳng về origin server.
Theo dõi trạng thái cache
Ta có thể theo dõi trạng thái cache của một request bằng cách sử dụng add_header directive:
add_header X-Cache-Status $upstream_cache_status;
Ví dụ trên add một HTTP header có tên X-Cache-Status vào response của client cho phép client theo dõi được trạng thái cache của request. Những giá trị có thể có của $uptream_cache_status:
- MISS – dữ liệu cần phản hồi không có trong cache và nó được fetch từ origin server. Dữ liệu phản hồi có thể sẽ được cache lại.
- BYPASS – phản hồi được fetch từ origin server thay vì được phục vụ từ cache bởi vì request match proxy_cache_bypass directive. Phản hồi sau đó có thể sẽ được cache lại.
- EXPIRED – entry trong cache đã expired. Dữ liệu phản hồi được fetch từ origin server.
- STALE – dữ liệu nhận dược là dữ liệu cũ vì origin server không phản hồi chính xác hoặc proxy_cache_use_stale được cấu hình.
- UPDATING – Nội dung đã cũ vì cache entry hiện đang được update trong response của request trước đó, và proxy_cache_use_stale updating được cấu hình.
- REVALIDATED – proxy_cache_revalidate directive được kích hoạt và NGINX xác nhận content đang được cache hiện tại vẫn còn hợp lệ (If-Modified-Since hoặc If-None-Match).
- HIT – dữ liệu phản hồi hoàn toàn hợp lệ và được phục vụ từ cache.
NGINX quyết định cache hoặc không cache như thế nào ?
Mặc định, NGINX tuân thủ theo header Cache-Control của origin servers. Nó không cache response mà có Cache-Control set thành Private, No-Cache, hoặc No-Store hoặc với Set-Cookie trong response header. NGINX chỉ cache GET hoặc HEAD requests. NGINX cũng không cache response nếu proxy_buffering được set thành off. Giá trị này mặc định là on.
Có thể bỏ qua Cache-Control header hay không?
Có, sử dụng proxy_ignore_headers directive. Ví dụ, với cấu hình như sau:
location /images/ {
proxy_cache my_cache;
proxy_ignore_headers Cache-Control;
proxy_cache_valid any 30m;
# ...
}
NGINX sẽ bỏ qua Cache-Control header cho mọi thứ nằm trong /images/. proxy_cache_valid directive thiết lập thời gian hết hạn (expiration) của dữ liệu được cache và nó cần phải được cấu hình nếu chúng ta bỏ qua (ignore) Cache-Control header. NGINX sẽ không cache file nếu không có thời gian hết hạn (expiration).
NGINX có thể cache content với Set-Cookie trong Header không?
Có, bằng cách sử dụng proxy_ignore_headers directive như đã được bàn phía trên.
NGINX có thể cache POST request được không?
Có, sử dụng proxy_cache_methods directive:
proxy_cache_methods GET HEAD POST;
Request có thể bỏ qua hệ thống cache được không?
Có, sử dụng proxy_cache_bypass directive:
location / {
proxy_cache_bypass $cookie_nocache $arg_nocache;
# ...
}
Directive trên khai báo những loại request mà NGINX sẽ request content trực tiếp từ origin server thay vì kiểm tra và tìm chúng trong hệ thống cache. Điều này còn thường được gọi là “đào lỗ” (punching a hole) xuyên qua hệ thống cache. Trong ví dụ này, NGINX thực hiện điều đó cho những request có nocache cookie hoặc argument, ví dụ: https://vietnix.vn/?nocache=true. NGINX có thể vấn sẽ cache kết quả response để phục vụ cho những request tiếp theo nếu chúng không bypass.
NGINX sử dụng cache key như thế nào?
Mặc định, NGINX sử dụng key có giá trị tương tự như kết quả hash MD5 của những biến NGINX sau: $scheme$proxy_host$request_uri
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
server {
# ...
location / {
proxy_cache my_cache;
proxy_pass http://my_upstream;
}
}
Trong cấu hình trên, cache key cho https://vietnix.vn/firewall-anti-ddos/ được tính toán bằng hàm md5(“http://my_upstream:80/firewall-anti-ddos)
Lưu ý rằng biến $proxy_host được sử dụng để hash thay vì dùng hostname thực sự (vietnix.vn). $proxy_host được khai báo bằng tên và port của proxied server được chỉ định trong directive proxy_pass.
Để thay đổi những biến được sử dụng để làm key, sử dụng directive proxy_cache_key.
Có thể sử dụng Cookie thành một phần của cache key không?
Hoàn toàn có thể, cache key có thể được cấu hình bởi bất cứ giá trị nào, ví du:
proxy_cache_key $proxy_host$request_uri$cookie_jessionid;
Ví dụ trên sử dụng giá trị của cookie JSESSIONID thành một phần của cache key. Item của những request có cùng URI nhưng khác JSESSIONID được cache thành những item khác nhau.
Biên dịch: Vietnix – Theo NGINX