Làm việc với background worker trong rails
Bài đăng này đã không được cập nhật trong 3 năm
Background worker luôn là điều cần thiết cho tất cả các dự án, và nhiều ứng dựng, mã chạy ngầm (background code) nó có tầm quan trọng tương đương với những đoạn mã mặt ngoài của web( các mã để xây dựng nên mặt ngoài của 1 ứng dụng web).
Việc viết background worker lần đầu tiên có thể khá khó khăn, đó là một chủ đề không giống như được đề cập thường xuyên, và yêu cầu bạn phải tìm hiểu về nhiều gems khác nhau. Ở đây chúng ta sẽ tập trung vào các công cụ mà hiện tại đang được sử dụng nhiều nhất: Redis, Sidekiq và Foreman.
Cài đặt Sidekiq
Sidekiq là một thư việc xử lý việc chạy ngầm (background processing) của Ruby. Nó đã thêm một số phương thức để làm cho việc xử lí tiến trình chạy ngầm một cách đơn giản hơn nhiều.
Sidekiq dựa trên Redis để duy trùy một hàng đợi công việc ( job queue). Vì vậy điều đầu tiên bạn cần chính là cái đặt redis.
Trên Mac brew install
Sau khi cài đặt Redis và chạy chó, việc đơn giản là thêm gem "sidekiq"
vào Gemfile và chạy bundle
Một khi Sidekiq gem đã được cài đặt, bạn có thể khởi động nó bằng cách chạy bundle exec sidekiq
Phân loại các kiểu trong Background Workers
Nhìn chung, công việc của bạn sẽ được chạy một trong số các kiểu như sau:
- Ngay lập tức sau khi hành động được thực thi
- Sau một khoảng thời gian nhất định sau khi hành động được thực thi
- Thường xuyên, vào những khoảng thời gian nhất định
Bây giờ chúng ta sẽ xem làm thế nào để thực hiện các trường hợp trên
Thực hiện chạy công việc ngay sau khi hành động được thực thi
Nhiều khi người dùng sẽ thực hiện hành động với ứng dụng mà phải tốn một khoảng thời gian đáng kể để hoàn thành. Một số ví dụ có thể nêu ra như: việc gửi mail, xử lí hình ảnh.v..v. Chúng ta không muốn người dùng phải ngồi chờ đợi cho đến khi hành động kết thúc, vì vậy chúng ta cần gửi việc thực thi này đến background worker.
Việc gửi mail là một ví dụ điển hình. Để đưa chúng vào background worker, đơn giản chỉ cần add delay và drop deliver
# UserMailer.welcome_email.deliver ## NOT BACKGROUND
UserMailer.delay.welcome_email
Thực thi một phương thức tiêu tốn nhiều thời gian của ActivceRecord object trong background worker như sau:
# Project.do_long_running_action(@project.id) ## NOT BACKGROUND
Project.delay.do_long_running_action(@project.id)
Với nhiều công việc phức tạp, bạn có thể tạo một class và includes Sidekiq::Worker vào trong module đó
# app/workers/project_cleanup_worker.rb
class ProjectCleanupWorker
include Sidekiq::Worker
def perform(project_id)
# do lots of project cleanup stuff here
end
end
# ProjectCleanupWorker.new.perform(@project.id) ## NOT BACKGROUND
ProjectCleanupWorker.perform_async(@project.id)
Chú ý: Hãy chắc chắn rằng chỉ những param đơn giản được đưa vào trong worker. ví dụ .perform_async(@project.id). Các tham số đư vào phải tuần tự và được đặt vào trong Redis queue, vì vậy việc cố gắng đưa các thực thể ActiveRecord phức tạp vào praram là không hề tốt tí nào và bất khả thi.
Thực thi các công việc được đinh thời gian nhất định sau khi thực hiện hành động
Việc trì hoãn thực thi một công việc với khoảng thời gian nhất định khá giống với việc chạy ngầm nó Gửi email:
# UserMailer.welcome_email.deliver ## NOT BACKGROUND
UserMailer.delay_for(5.minutes).welcome_email
# OR
UserMailer.delay_until(1.week.from_now).welcome_email
Sử dụng cho long task trong ActiveRecord
# Project.do_long_running_action(@project.id) ## NOT BACKGROUND
Project.delay_for(10.minutes).do_long_running_action(@project.id)
OR
Project.delay_until(2.days.from_now).do_long_running_action(@project.id)
Việc thực hiện công viêc theo khoảng thời gian khác nhau
Với Sidekiq Worker
# app/workers/project_cleanup_worker.rb
class ProjectCleanupWorker
include Sidekiq::Worker
def perform(project_id)
# do lots of project cleanup stuff here
end
end
# ProjectCleanupWorker.new.perform(@project.id) ## NOT BACKGROUND
ProjectCleanupWorker.perform_in(10.minutes, @project.id)
# OR
ProjectCleanupWorker.perform_at(2.days.from_now, @project.id)
Đôi khi bạn muốn thiêt lập một lịch trình các công việc được thực hiện theo một khoảng thời gian đều đặn.
Ví dụ : Trong quy trình đăng kí người dùng mới, nếu ai đó bắt đầu đăng kí nhưng không hoàn tất, bạn muốn gửi mail nhắc nhỡ họ.
Heroku's khuyên bạn nên xử lí vấn đề này bằng cách tạo ra 1 rake task như sau
desc "Remind users if they haven't completed registration"
task :remind_of_registration => :environment do
puts "Reminding users of registration"
# ...
puts "done."
end
Hãy nhớ :environment
trong định nghĩa task rất quan trọng. Nó tải môi trường rails tương ứng cho Rake task của bạn.
Để cho việc kiểm tra dễ dàng hơn, có lẽ bạn muốn tạo một đối tượng riêng để hoàn thành nhiệm vụ của bạn. Mặc dù thực tế là loại công việc dự kiến này không hề liên quan đến Sidekiq, Chúng tôi thường đặt các đối tượng này ( complete object) vào thường dẫn thư mục app/workers
với tên của chúng theo format [...]_worker.rb
Việc thiết lập như sau
desc "Remind users if they haven't completed registration"
task :remind_of_registration => :environment do
puts "Reminding users of registration"
RegistrationReminderWorker.new.perform
puts "done."
end
class RegistrationReminderWorker
def perform
...
end
end
Ở đây, bạn có thể gọi `rake remind_of_registration` trong command line để chạy worker, đấy là những gì bạn cần để chạy vào những thời điểm cố định
Nếu bạn đang sử dụng Heroku, bạn có thể sử dụng Scheduler addon, nó là free và dễ sử dụng. Nhược điểm duy nhất là không có sự linh hoạt trong khoảng thời gian bạn có thể chọn.
![](https://images.viblo.asia/08d10218-7426-457d-bf4b-477031090aed.png)
Nếu bạn đang chạy trên sever ảo (VPS) thì bạn cần làm thêm một số vieeck. Bạn sẽ cần thiết lập một vài thứ để giống [cron](https://en.wikipedia.org/wiki/Cron) , gem [whenever](https://github.com/javan/whenever) sẽ cần thiết cho bạn để handle nó
## Firing it up - Foreman
Phần này mình sẽ cập nhật sau. Thân
All rights reserved