Cron job with gem whenever
Bài đăng này đã không được cập nhật trong 6 năm

1. Cron job là gì:
- Đôi khi chúng ta cần implement các job cần được thực hiện theo 1 schedule cho trước, các job này được gọi chung là cron job.
- Các job này có thể là gửi remind email cho user hằng ngày, gọi API crawl data của third party để import hoặc update data của app mỗi tuần 1 lần, xóa cache, xóa log, backup database, ...
- Trong Rails chúng ta có thể sử dụng gem
wheneverđể lên schedule cho cronjob.
2. Tạo demo app:
- Chạy các command sau để tạo 1 app đơn giản.
rails new demo_whenever_revise - Chạy các command migration cho user.
rails g model user email name rails db:migrate - Thêm gem
ffakervàoGemfile.# Gemfile gem "ffaker" - Chạy command
bundle installđể install gemffaker.bundle install - Tạo seed data cho user.
# db/seeds.rb 10.times do User.create email: FFaker::Internet.email, name: FFaker::Name.name end - Chạy command
rails db:seedđể tạo seed data.rails db:seed - Tạo rake task tạo random user.
# lib/tasks/user/create_random.rake namespace :user do desc "Create random user" task create_random: :environment do puts "RUNNING rake user:create_random" user = User.create email: FFaker::Internet.email, name: FFaker::Name.name puts "CREATED user: #{user.inspect}" end end - Chạy command
rake user:create_randomđể test rake task vừa tạo.rake user:create_random - Tiếp theo chúng ta sẽ dùng gem
wheneverđể tạo cronjob cho rake task này. Let's go!
3. Gem whenever:
a. Install:
- Thêm gem
whenevervàoGemfile.# Gemfile gem "whenever" - Chạy command
bundle installđể install gemwhenever.bundle install - Chạy command
wheneverizeđể generate fileconfig/schedule.rb.wheneverize
b. Update file config/schedule.rb:
- Update lại file
config/schedule.rbnhư sau.# config/schedule.rb every 1.minutes do rake "user:create_random" end - Mỗi lần update file
config/schedule.rbta cần chạy commandwhenever --update-crontabđể update lại crontab.whenever --update-crontab - Chạy lại command
wheneverđể check lại crontab.whenever - Check lại outout của crontab ta được kết quả như sau.
* * * * * /bin/bash -l -c 'cd /home/hcm-102-0003/Desktop/demo_whenever_revise && RAILS_ENV=production bundle exec rake user:create_random --silent' ## [message] Above is your schedule file converted to cron syntax; your crontab file was not updated. ## [message] Run `whenever --help' for more options. - Ta thấy
RAILS_ENVđang được set giá trị làproduction, có nghĩa là crontab này chỉ được chạy ở môi trường production, không được chạy ở môi trường development hay staging. - Nguyên nhân là môi trường chạy crontab được lưu trong biến
@environmentvà biến này có giá trọ mặc định là production. - Ta có thể check lại bằng cách update lại file
config/schedule.rb, in ra màn hình giá trị của biến@environment.# config/schedule.rb puts "Crontab is run on #{@environment}" - Sau đó chạy lại command
whenever --update-crontabwhenever --update-crontab - Output
Crontab is run on production [write] crontab file updated - Ta có thể set giá trị của biến
@environmentbằng cách thêm đoạn code sau vào đầu fileconfig/schedule.rb# config/schedule.rb set :environment, :development - Sau đó chạy lại command
whenever --update-crontabwhenever --update-crontab - Output
Crontab is run on development [write] crontab file updated - Tuy nhiên ta đang hard code giá trị của biến
@environment, ta cần phải set giá trị của biến@environmentthay đổi theo môi trường. - Để làm vậy ta cần sử dụng
Rails.env, update lại fileconfig/schedule.rbnhư sau# config/schedule.rb require_relative "environment" set :environment, Rails.env - Chúng ta cũng có thể quy định outout log của crontab bằng cách thêm đoạn code sau vào file
config/schedule.rb# config/schedule.rb set :output, "log/cron_job.log" - File
config/schedule.rbhoàn chỉnh# config/schedule.rb require_relative "environment" set :environment, Rails.env set :output, "log/cron_job.log" # puts "Crontab is run on #{@environment}" # puts "Crontab is logged on #{@output}" every 1.minutes do rake "user:create_random" end - Chạy command để update crontab và check lại
whenever --update-crontab whenever - Output
* * * * * /bin/bash -l -c 'cd /home/hcm-102-0003/Desktop/demo_whenever_revise && RAILS_ENV=development bundle exec rake user:create_random --silent >> log/cron_job.log 2>&1' ## [message] Above is your schedule file converted to cron syntax; your crontab file was not updated. ## [message] Run `whenever --help' for more options. - Bạn có thể check lại kết quả bằng cách mở file
log/cron_job.logvà xem lại log, dưới đây là log của mìnhRUNNING rake user:create_random CREATED user: #<User id: 12, email: "deanna.braun@brekke.ca", name: "Claretha Hintz", created_at: "2019-06-26 08:58:03", updated_at: "2019-06-26 08:58:03"> RUNNING rake user:create_random CREATED user: #<User id: 13, email: "kathyrn.kris@king.name", name: "Debera Purdy", created_at: "2019-06-26 08:59:03", updated_at: "2019-06-26 08:59:03"> RUNNING rake user:create_random CREATED user: #<User id: 14, email: "leontine@russel.biz", name: "Erminia Weber", created_at: "2019-06-26 09:00:03", updated_at: "2019-06-26 09:00:03">
c. Jobtypes:
-
Mặc định whenever cung cấp cho chúng ta 4 loại
jobtypeđể chạy các crontab khác nhau.-
command: chạy các command của linux hoặc rails app, ví dụ
command "echo 'example command'" -
rake: chạy các rake task của rails app, crontab trong bài viết này cũng có jobtype là rake
rake "user:create_random" -
runner: chạy các class method, trong source code tham khảo có bổ sung thêm 1 crontab có jobtype là runner, bạn có thể tham khảo thêm
runner "User.create_random" -
script: chạy script file, ví dụ
script "example_script"
-
-
Bạn cũng có thể tự define thêm jobtype mới, tham khảo document của gem whenever.
d. Date and time:
i. Gem Chronic:
- Gem whenever đang sử dụng gem Chronic để parse date time.
- Theo mặc định Chronic sử dụng thời gian theo option 12h và có hậu tố
amhoặcpmđể phân biệt. - Bạn có thể sử dụng thời gian theo option 24h và không cần sử dụng hậu tố
amhoặcpmđể phân biệt bằng cách setchronic_optionsnhư sau.set :chronic_options, hours24: true - Khi đó thay vì viết
3:00 ambạn có thể viết ngắn gọn là3:00hoặc15:00thay cho3:00 pm. - Các option date time trong
wheneverbao gồmevery n.minutes # or every :minute with n = 1 every n.hours # or every :hour with n = 1 every n.days # or every :day with n = 1 every n.months # or every :month with n = 1 every n.years # or every :year with n = 1 # hours in a day every "1:00am" ... every "11:00am" # days in a week every :monday ... every :sunday # days in a month every "1st" ... every "31st" # months in a year every :jan ... every :dec - Bạn cũng có thể kết hợp các option để tạo ra các option phức tạp hơn.
every :day, at: "3:00am" every :month, at: "1st" every :year, at: :jan ...
ii. Timezone:
- Hãy thử tưởng tượng Timezone Rails app của bạn là
Tokyo UTC +09:00nhưng server của bạn lại được deploy và set Timezone làUTC UTC +00:00. - Khi đó crontab
11:00 amcủa bạn sẽ được chạy lúc11:00 amcủa Timzone nào nhỉ. - Expectation của chúng ta là
11:00 amcủa timezoneTokyo UTC +09:00. - Nhưng sự thật là
11:00 amcủa timezoneUTC UTC +00:00, tức là08:00 pmcủa timezoneTokyo UTC +09:00. - Để tránh tình trạng này chúng ta có 2 solution.
Solution 1: set timezone của server deploy trùng với timezone của app - Với solution này thì chúng ta không cần implement thêm code.
Solution 2: convert timezone của rails app sang timezone của server - Với solution này thì chúng ta cần implement thêm hàm convert time của app sang timezone của server
def server_timezone time Time.zone.parse(time).localtime end every :day, at: server_timezone("11:00 am") do command "echo 'example command'" end - Bạn có thểm tham khảo thêm ở issue#239.
e. Các vấn đề khác:
i. Deploy:
- Nếu bạn sử dụng capistrano, bạn có thể tham khảo document của whenever.
- Nếu bạn sử dụng mina, bạn có thể tham khảo thêm gem mina-whenever.
- Nếu bạn sử dụng heroku, bạn có thể tham khảo thêm add-ons Heroku Scheduler.
ii. Test:
- Gem whenever-test được recommend để viết test cho gem whenever.
- Bạn có thể tham khảo thêm về gem shoulda-whenever và example code của gem shoulda-whenever.
4. Document:
All rights reserved