Server side cache with Go

1273

Go rất nhanh và ai cũng biết điều này. Nhưng làm như thế nào để nó nhanh hơn nữa khi chạy web applications viết trên Go?

Khi nói về vấn đề tốc độ, mọi cuộc thảo luận thường đi đến các chiến lược cache. Trong bài viết này tôi sẽ cố gắng bao quát làm thế nào chúng ta có thể đạt được thời gian phản hồi nhanh hơn cho web application sử dụng server side cache.

Server side cache là gì?

Hầu hết các developer đều quen thuộc với browser cache sử dụng HTTP header Cache-Control, về cơ bản đây là cách máy chủ có thể chỉ thị cho browser khi nào và thời gian bao lâu để trình duyệt có thể lưu trữ tài nguyên. Điều này cực kỳ hữu ích và chúng ta nên luôn sử dụng nó cho các tài nguyên tĩnh như JavaScript, CSS and Images.

Nhưng các trang HTML thì sao? (lamsao) Chúng ta cache chúng như thế nào? Trong một số trường hợp thì không hữu ích nếu cache nó trên browser. bởi vì nếu trang của chúng ta thay đổi về nội dung, người dùng sẽ không thấy được nội dung mới nhất ngay lập tức. Ví dụ các trang báo như CNN, làm như nào họ có thể phục vụ hàng triệu lượt truy cập vào trang chủ của mình? nếu một bài viết mới được đăng tải, tất cả mọi người cần phải xem nó trong lần làm mới tiếp theo.

Đây là nơi mà server cache phát huy tác dụng. Để server có thể render ra một trang nội dung gửi về cho người dùng yêu cầu nhiều hoạt động IO như truy vấn database, tính toán logic và render nội dung. Sau đó nội dung ở dạng HTML của trang này được build, cache lại ở server và sử dụng lại cho việc trả về cho tất cả các request để trang này. Khi làm điều này trên server, chúng ta sẽ hoàn toàn kiểm soát được khi nào dọn dẹp nội dung bị cache khi một bài viết mới được đăng tải.

Có thể bạn quan tâm

  Golang là gì? 9 Framework tối ưu “cực căng” cho Golang

Thế giờ làm như nào với Go?

Go không chỉ nhanh, mà nó còn dễ học và do đó implement cái server side cache này không khó.

Phần khó nhất nhà quyết định nơi để lưu trữ cache. Các chiến lược phổ biến thường là lưu trữ trên process memory, disk hoặc database. Một trong những cách tiếp cận này là ổn, nhưng hiểu được nhược điểm của từng phương pháp rất quan trọng để quyết định dùng các nào.

In-Memory: Tất cả các trang được cache được lưu trữ trên bộ nhớ tiến trình của web application khi nó chạy (hiểu như nó lưu trên biến ý), cách này siêu nhanh và dễ implement. Hạn chế là nếu có nhiều instance server chạy cùng lúc nó sẽ tạo ra N bản copy của nội dung được cache (nói chung cho nhiều server chạy nó không share cache cho nhau được, phải tạo lại). Nếu tiến trình bị khởi động lại hoặc bị chết do bug thì sẽ mất nôi dung được cache và như thế sẽ làm chậm nhưng request đầu (từ request sau lại nhanh vì lúc đó nó cache ròi).

Disk: Trang được cache sẽ được lưu trữ trên đĩa (disk). Cách này chắc chắn không phải là nhanh nhất rồi vì server cần tìm và đọc từ đĩa. Lợi thế lớn nhất của cách này là chi phí rẻ hơn và bền vì khi server bị tắt khởi động lại vẫn còn.

Database: Trang cache được lưu trữ trên database, có thể là key-value storage. Thực tế thì Redis vô đối trong việc này. Nó là có hiệu suất cao trong các bài test về in-memory database. Nó không nhanh như process in-memory, bởi vì cần thực hiện truyền dữ liệu giữa server và redis server, nhưng được cái là nội dung có thể chia sẻ chéo giữa tất cả các instance server, do đó không bị lặp tài nguyên trên trên server.

Implement!

Tất cả source demo đã đẩy lên repo GitHub trang demo Heroku. Tôi sẽ highlight ở đây một số phần quan trọng trong project này.

Đầu tiên tôi tạo một interface Storage để ứng dụng có thể sử dụng get/set các trang cache. Viết thành interface để có thể sửa dụng nhiều chiến lược lưu trữ khác nhau.

Do đó chúng ta có hai struct đã implement cái interface này, memory.Storage dùng chiến lược process in-memory và redis.Storage sử dụng chiến lược lưu trữ trên Redis.

Việc thực hiện implement khá đơn giản, vì thế tôi sẽ skip và đi đến phần quan trọng hơn. Nếu bạn muốn thử chiến lược lưu trên disk, chỉ cần tạo struct mới và implement interface nhưn bên trên tương tự mấy cái kia.

cached là một http middleware chạy trước http handler và trả về nội dung ngay lập tức nếu trang đã được cache. Nếu không, handler sẽ thực hiện render nội dung rồi cache lại với thời gian ta thiết lập. Vì nó là middleware nên nó rất dễ dàng thiết lập cho từng router (cái nào cần cache thì thêm thôi).

Tôi sử dụng RequestURI làm key để lưu trữ vì tôi muốn cache trang dựa trên sự khác nhau về path và querystring. Điều này có nghĩa là trang có url /users?page=1 và /users?page=2 sẽ được cache độc lập.

Video: Cách triển khai & tracking hiệu quả chiến dịch Mobile

Đoạn middleware như sau:

Để dùng nó ta chỉ cần wrap HTTP handler bên trong cached như bên dưới. Nhờ thằng time package, mà chúng ta có thể sử dụng chuỗi để biểu diễn thời hạn một cách human hơn. Ví dụ, 10s dễ hiểu hơn là 10 * 1000. Trong ví dụ này chúng ta chỉ cache trang index.

Theo dõi kết quả ở ảnh sau. Request đầu tiên mất đến 2 seconds trong khi request thứ hai chỉ tốn 27ms với cùng một nội dung và kích thước.

Sau 10 giây từ request đầu tiên, request tiếp theo sẽ lâu hơn vì trang được cache đã hết hạn.

Điều gì cần quan tâm khi implement server side cache

Đầu tiên là đừng bao giờ cache các trang sử dụng method POST, PUT hoặc DELETE request để thay đổi tài nguyên. Chỉ nên cache các GET request.

Nếu trang yêu cầu đăng nhập để cung cập nội dung cho từng người dùng tương ứng thì vẫn có thể cache đươc nhưng cần lưu ý dùng định danh của người dùng làm key cache, nếu trả dữ liều của người dùng này cho người dùng khác thì vỡ mồm.

Và cuối cùng, bạn chắc chắn cần phải implement tính năng cho phép vô hiệu nó trên môi trường development, vì sao thì bạn tự hiểu.

  Chiến trường sinh tử phiên bản lập trình : Python vs Ruby vs Golang

TopDev via Viblo

SHARE