Active Job – Xử lý công việc nền trong Rails

Tram Ho

1. Đặt vấn đề

Giả sử bạn muốn gửi email tới các user sau khi đăng ký tài khoản để xác nhận tài khoản ở trang Sample app (Trang web đa số các bạn học Rails sẽ làm qua) của bạn. Nếu như đồng thời có tới hàng ngàn User cùng đăng ký tại một thời điểm, bạn sẽ phải đồng thời gửi tới hàng ngàn cái Email với nội dung xác thực tài khoản, điều này sẽ dẫn tới việc phản hồi giữa server và client bị ảnh hưởng đáng kể.

=> Chúng ta cần phải tìm ra một giải pháp làm sao để xử lý riêng biệt giữa các tác vụ, thì Background Job sẽ giúp chúng ta trong việc thực hiện các tác vụ theo một luồng riêng biệt và không ảnh hưởng tới trải nghiệm của người dùng mà các công việc sẽ đều được xử lý hết. => Ở bài viết này thì mình sẽ xử lý việc gửi mail đưa vào Background Job nhé

2. Background Job là gì ?

Background job – “công việc phía sau” : là những công việc hay tác vụ được xử lý ngoài luồng request – response thông thường trong các ứng dụng web.

Bình thường thì các trang web nhận request từ người dùng và trả về một response nhưng Background job thì có khác một ít => Vẫn là từ một request đến Website nhưng đòi hỏi thời gian thực thi lâu hơn so với bình thường (có thể tưởng tưởng như là những request này không thể xử lý ngay lập tức) thì chúng ta sẽ cần sử dụng đến Background job, chuyển phần xử lý phức tạp ấy vào Job, nó sẽ xử lý bất đồng bộ trên một luồng riêng biệt, trả về response cho người dùng.

Ví dụ

Chúng ta có một request từ người dùng để cập nhật thông tin quê quán mới của họ :

Các bạn có thể thấy, thời gian bắt đầu từ lúc người dùng submit form cho đến khi server trả về trang người dùng và thông báo update thành công sẽ mất 3.7s

Khi chúng ta sử dụng Background job thì sao : quá trình mình nêu ở trên chỉ còn mất 0.3 s, các quá trình như refresh cache, send maild, send noti, recommend new friends sẽ được Background job xử lý ở một luồng riêng biệt, kết quả vẫn sẽ trả về như nguời dùng mong muốn

3. Rails xử lý Backgound Job như thế nào ?

Có khá nhiều cách để xử lý Background Jobcho ứng dụng Rails của bạn:

  1. Sử dụng Active job
  2. Sử dụng Gem Sidekiq
  3. Sử dụng Gem Delayed job
  4. Sử dụng Gem Resque

Mặc định Rails cung cấp cho chúng ta Active job (Một framework để xử lý Background job mà chúng ta không cần cài thêm bất cứ cái gì) built-in queue process (tích hợp sẵn hàng đợi) .

Điều đáng tiếc là những process trong queue được lưu vào RAM nên nếu server bị shutdown thì những job chưa hoặc đang thực hiện sẽ bị mất, điều này có thể gây ảnh hưởng tiêu cực đến người dùng và dịch vụ của chúng ta, vì vậy nó chỉ thích hợp cho những ứng dụng nhỏ hoặc những công việc không quan trọng.

Tuy nhiên hầu như các ứng dụng lớn đều không muốn điều này xảy ra nên đã sử dụng các 3rd-party adapter như Sidekiq, Delayed_Job, Resque (sử dụng Redis để lưu các queue, và có những cơ chế riêng để xử lý job khi server bị sập và khởi động lại sẽ không bị mất job)

Ở bài này mình chủ yếu tìm hiểu về Active job để xử lý Background Job, 3 cách còn lại các bạn có thể tìm hiểu thêm ở các bài viết hoặc lên doc của nó để đọc nhé

Mình cũng đã từng sử dụng Sidekiq (còn 2 cái còn lại thì chưa): ngoài việc xử lý job khá hoàn hảo thì nó cũng có một số khác biệt với các gem khác là Sidekiq sử dụng đa luồng và Redis để xử lý nhiều jobs đồng thời .Sidekiq và Rescue có trang dashboard để quản lý các job, còn Active job và Delayed_job thì không (Delayed_job có thể cài thêm gem để sử dụng trang dashboard). Ngoài ra việc truyền tham số của Sidekiq sẽ khác so với Active job, mình sẽ trình bày phía dưới nhé

Để xử lý các job sử dụng các adapter như Sidekiq, Delayed Job, Rescue ta có thể config như sau:

  • Thay đổi queue_adapter ở file application.rb

  • Định cấu hình cho nó ở file …_job.rb

  • Ngoài ra bạn muốn quản lý rõ ràng hơn hàng đợi mà job sẽ chạy, bạn có thể dùng method #set như sau :

4. Active Job – Xử lý Background Job trong Rails

Trước tiên mình sẽ xử lý bài toán đưa phần gửi mail kích hoạt tài khoản người dùng vào job cái đã nhé

Trước tiên ta phải tạo ra một job mà bạn muốn sử dụng câu lệnh rails g job SendMail

File send_mail_job.rb sẽ như sau:

Mặc địnhActive jobsử dụng queue_as :default tức là adapter sử dụng là :async để lưu job , ngoài ra còn có adapter khác là :inline, để sử dụng ta phải config ở application.rb như sau:

=> Khi sử dụng adapter :inline thì job sẽ thực hiện ngay lập tức trong luồng chính, điều đó chả khác gi so với việc không sử dụng Background job cả, nên mình nghĩ là các bạn không nên dùng

=> Vậy với adapter :async mặc định của Active job thì sao: Job sẽ thực hiện trong nhóm luồng khác, phù hợp với môi trường dev/test vì nó không cần cơ sở hạ tầng bên ngoài, nhưng nó không phù hợp trong môi trường production, vì nó sẽ loại bỏ những công việc đang chờ xử lý khi khởi động lại. Nếu các job cùng sử dụng chung một nhóm luồng, thì các job phía sau phải chờ job đang thực hiện chạy xong (vì vậy nếu job đang chờ mà sever bị reset thì sẽ bị mất.

Note: Để khắc phục nhược điểm trên thì các bạn có thể config queue_adapter = :sidekiq nhé, tất nhiên phải cài thêm gem sidekiq rồi, bật server của sidekiq lên và sài thôi, chắc chắn là job của bạn sẽ không bị mất khi khởi động lại server đâu, mình thử rồi

Chúng ta có thể config số luồng :async hoạt động như sau :

Với Active job thì bạn có thể gửi bao nhiêu tham số tùy ý, có thể cả một object, cụ thể :

Đối với Sidekiq: các đối số truyền vào phải là các kiểu JSON đơn giản như string, integer, boolean, float, null(nil), hash, array.

Chút nữa lan man sau, giải quyết xong bài toán gửi mail đã nhé, chỉ cần thực hiện như sau:

Vậy là xong , dưới đây là phần bonus thêm của mình

4.1. Các cách gọi job

Thực hiện job ngay sau khi hàng đợi trống :

Thực hiện sau khoảng thời gian:

Thực hiện job vào buổi trưa ngày mai:

Note: Ngoài perform_later còn có perform_now, khi bạn sử dụng perform_now thì thời gian bạn set để thực hiện job trở nên vô nghĩa.

4.2. Callbacks của Active job

Active Job cung cấp các callbacksđể kích hoạt logic trong vòng đời của một job. Hoạt động tương tự như các callbackskhác trong model Rails như sau:

=> được gọi trước, trong, và sau khi đưa job vào hàng đợi

=> được gọi tới trước, trong và sau khi job được thực hiện

4.3. Xử lý exceptions

Trong lúc thực hiện job không thể lúc nào cũng trơn tru được, Active Job cung cấp cho chúng ta các cách để bắt các ngoại lệ được nêu ra trong quá trình thực thi công việc:

  1. Xử lý ngoại lệ trong thân hàm perform

  1. discard_on(*exceptions) : Loại bỏ luôn job mà bạn không muốn nó thử lại khi raise exceptions, điều này hữu ích khi record đã bị xóa chẳng hạn

  1. retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil)Lên lịch chạy lại job khi job raise exception, nếu exception tăng lên vượt quá số attempts mà bạn cài đặt => có thể có cơ chế thử lại của riêng exceptions hoặc đặt nó vào hàng đợi để kiểm tra
  • :wait – xếp lại job vào hàng đợi vời thời gian dc xét
  • :attempts – cho vào lại hàng đợi chờ được xử lý bao nhiêu lần
  • :queue – Xếp job vào hàng đợi khác
  • :priority – Xếp lại job theo mức độ ưu tiên

5. Tài liệu tham khảo

Bài viết khá dài, tuy nhiên đó là những gì mà mình đã tìm hiểu được về Active Job và mong rằng phần nào đó giúp bạn có cái nhìn tổng quát về Background job và cơ chế hoạt động của Active Job. Cảm ơn mỏi nguời đã đọc tới đây ạ

Guide Active job:

https://guides.rubyonrails.org/active_job_basics.html

https://api.rubyonrails.org/classes/ActiveJob/Exceptions/ClassMethods.html

Chia sẻ bài viết ngay

Nguồn bài viết : Viblo