12/08/2018, 15:00

Làm việc với background worker trong rails

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ó ...

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://viblo.asia/uploads/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

0