Khả năng mở rộng cho người mới - Phần 4: Bất đồng bộ (Asynchronism)
Mở đầu bằng một hình ảnh
Phần thứ 4 của loạt bài này bắt đầu bằng một hình ảnh: hãy tưởng tượng bạn muốn mua bánh mì ở tiệm bánh yêu thích của mình.
Bạn bước vào tiệm, hỏi mua một ổ bánh mì, nhưng chẳng có ổ bánh nào cả! Thay vào đó, người ta bảo bạn quay lại sau 2 tiếng nữa khi ổ bánh bạn đặt đã xong.
Bực mình đúng không?
Để tránh rơi vào tình huống "xin vui lòng chờ một lát" như thế, ta cần làm mọi thứ bất đồng bộ. Và cái gì tốt cho tiệm bánh thì có lẽ cũng tốt cho web service hay web app của bạn.
Nhìn chung, có hai cách - hai mô hình - để làm bất đồng bộ.
Bất đồng bộ kiểu 1: Làm sẵn từ trước
Hãy cứ ở lại với hình ảnh tiệm bánh. Cách xử lý bất đồng bộ thứ nhất là kiểu "nướng bánh ban đêm, bán vào buổi sáng". Không phải chờ đợi gì ở quầy thu ngân, và khách hàng vui vẻ.
Áp vào một web app, điều này nghĩa là: làm phần việc tốn thời gian từ trước, rồi phục vụ kết quả đã hoàn thành với thời gian đáp ứng cực thấp.
Mô hình này rất hay được dùng để biến nội dung động thành nội dung tĩnh. Các trang của một website - có thể được dựng bằng một framework hay CMS đồ sộ - sẽ được render sẵn và lưu cục bộ thành các file HTML tĩnh mỗi khi có thay đổi.
Thường thì các tác vụ tính toán này được chạy định kỳ, chẳng hạn bằng một script được cronjob gọi mỗi giờ. Việc tính trước dữ liệu chung này có thể cải thiện website và web app cực kỳ mạnh, khiến chúng mở rộng tốt và chạy nhanh.
Cứ thử tưởng tượng khả năng mở rộng của website nếu script đó còn upload luôn các trang HTML đã render sẵn lên AWS S3, CloudFront hay một CDN nào khác! Website của bạn sẽ phản hồi cực nhanh và gánh được hàng triệu lượt truy cập mỗi giờ!
Kiểu 1 tính toán trước, phục vụ ngay khi có request. Kiểu 2 đẩy việc vào hàng đợi, worker xử lý rồi báo lại sau.
Bất đồng bộ kiểu 2: Nhận việc rồi trả kết quả sau
Quay lại tiệm bánh. Đáng tiếc là đôi khi khách có những yêu cầu đặc biệt, kiểu bánh sinh nhật có dòng chữ "Chúc mừng sinh nhật, Steve!" ở trên.
Tiệm bánh không thể đoán trước những mong muốn kiểu này, nên buộc phải bắt đầu làm khi khách đang ở trong tiệm, rồi hẹn khách hôm sau quay lại.
Áp vào web service, điều đó nghĩa là xử lý tác vụ một cách bất đồng bộ.
Đây là một luồng làm việc điển hình:
Một người dùng vào website của bạn và khởi động một tác vụ tính toán rất nặng, phải mất vài phút mới xong.
Phần frontend của website đẩy một job vào hàng đợi job (job queue) và lập tức báo lại cho người dùng: công việc của bạn đang được xử lý, mời bạn tiếp tục lướt trang.
Hàng đợi job liên tục được một nhóm worker kiểm tra xem có job mới không.
Nếu có job mới, worker sẽ làm job đó, và sau vài phút thì phát tín hiệu báo job đã xong.
Frontend - vốn liên tục kiểm tra xem có tín hiệu "job đã xong" nào mới không - thấy job đã hoàn tất và báo lại cho người dùng.
Tôi biết đây là một ví dụ đã được đơn giản hóa rất nhiều.
Worker càng nhiều, hàng đợi vơi càng nhanh — nhưng nếu job đổ vào nhanh hơn tốc độ xử lý, hàng đợi vẫn phình; lúc đó cần back pressure: giới hạn kích thước hàng đợi và báo bận khi đầy.
Học tiếp ở đâu
Nếu giờ bạn muốn đào sâu vào chi tiết và thiết kế kỹ thuật thực tế, tôi khuyên bạn xem 3 bài hướng dẫn đầu tiên trên website của RabbitMQ.
RabbitMQ là một trong nhiều hệ thống giúp triển khai xử lý bất đồng bộ. Bạn cũng có thể dùng ActiveMQ, hoặc đơn giản là một list của Redis. Ý tưởng cốt lõi là có một hàng đợi các tác vụ/job để worker xử lý.
Bất đồng bộ nghe thì có vẻ phức tạp, nhưng chắc chắn đáng để bạn dành thời gian tìm hiểu và tự tay triển khai.
Backend trở nên gần như mở rộng vô hạn, còn frontend thì nhanh nhẹn hẳn lên - điều đó tốt cho trải nghiệm người dùng nói chung.
Nếu bạn làm việc gì đó tốn thời gian, hãy luôn cố làm nó một cách bất đồng bộ.