Sidekiq,處理背景程式的好幫手
什麼是Sidekiq?
Sidekiq是Rails框架中經常使用的背景程式處理的Gem。背景程式可以幫助我們在背景處理一些耗時較久、或是較耗資源的task。通常這些task我們會避免在HTTP的request中處理,避免網站被卡死以及使用者重複觸發消耗更多資源。
如何安裝及設定Sidekiq?
首先在Gemfile中安裝
# Gemfile
gem install "sidekiq"
$ bundle install
接著設定載入路徑、adapter類型,以及yml檔
# config/application.rb
config.eager_load_paths += %W( #{config.root}/app/jobs )
# config/environments/production.rb
# 設定queue_adapter
config.active_job.queue_adapter = :sidekiq
# config/sidekiq.yml
# 設定queues的一些資訊,比如說名稱、處理權重等等
:queues:
- default
- mailers
更多yml檔的設定可以參考文件。
在安裝了Sidekiq之後,由於Sidekiq執行時需要redis,所以記得安裝redis並啟用redis。在Mac上安裝及啟用可以透過
$ brew install redis
$ redis-server /usr/local/etc/redis.conf
在Linux上安裝則是透過
$ sudo apt-get install redis-server
最後,要啟用sidekiq還需要另外執行以下指令(註:在linux上需要另外設置sidekiq.service,後文會介紹)
$ bundle exec sidekiq
Sidekiq實作
要快速產生一個sidekiq的job,我們可以透過以下指令來產生
$ rails g job AutoExecuteJob`
這樣就會幫我在app/jobs的directory中建立auto_execute_job.rb。這個job應該會長得像
class TestJob < ApplicationJob
queue_as :default
def perform(*args)
# Do something later
end
end
在這邊你會注意到TestJob繼承了ApplicaionJob這個class,在往上去挖掘ApplicationJob你會發覺他繼承了ActiveJob::Base這個class。由於ActiveJob::Base是Rails內建的class,但當我們使用Sidekiq時我們想要使用的是Sidekiq的功能,所以可以將ApplicationJob移除。
為了正常使用sidekiq,我們會把程式碼改成
class TestJob
include Sidekiq::Job
sidekiq_options retry: 5
queue_as :default
def perform(*args)
# Do something later
end
end
這樣就可以使用sidekiq的功能了。
常見的sidekiq method
Sidekiq中最常使用的method莫過於
- perform_async
- perform_at
perform_async:是現在立刻將該段程式加到背景中排隊準備執行(queues)。加到隊伍中若是目前前方沒有其他背景程式未執行而且有空檔可以執行,那就會開始執行。
perform_at:則是指定一個時間,在該時間把程式放到queue中準備執行。
名詞及觀念解釋
sidekiq_options retry: 5: 在預設情況下,若是sidekiq的job執行過程中出現error,最多會重複執行25次。如果你手頭上同時建立了一大堆的job,但他們不巧都壞了,那他們就會重複執行25次,這可能會讓你的背景程式塞到爆。所以一般來說我們會設定重複執行的上限,避免被塞爆。
queue_as :default:我們可以為這些job分類,比如信件的job我們可以叫他們mailer、一般的job稱之為default、更重要的稱為critical。
Production設定
在linux上執行sidekiq的話,需要在 /lib/systemd/system/這個folder之下,建立 sidekiq.service這個檔案,詳細的配置可以參考這個連結。
ExecStart是用來定義unit啟用時執行什麼指令或腳本。在上面提供的配置連結的多個ExecStart範例如下,
# If you use rbenv:
# ExecStart=/bin/bash -lc 'exec /home/deploy/.rbenv/shims/bundle exec sidekiq -e production'
# If you use the system's ruby:
# ExecStart=/usr/local/bin/bundle exec sidekiq -e production
# If you use rvm in production without gemset and your ruby version is 2.6.5
# ExecStart=/home/deploy/.rvm/gems/ruby-2.6.5/wrappers/bundle exec sidekiq -e production
# If you use rvm in production with gemset and your ruby version is 2.6.5
# ExecStart=/home/deploy/.rvm/gems/ruby-2.6.5@gemset-name/wrappers/bundle exec sidekiq -e production
# If you use rvm in production with gemset and ruby version/gemset is specified in .ruby-version,
# .ruby-gemsetor or .rvmrc file in the working directory
ExecStart=/home/deploy/.rvm/bin/rvm in /opt/myapp/current do bundle exec sidekiq -e production
我們可以看到上面這段程式碼最終應該是要用bundler去啟用sidekiq,我自己的linux server上的bundler的bundle檔案的安裝路徑是在以下的檔案中
/usr/local/rvm/gems/ruby-3.0.0/wrappers/bundle
大家如果有遇到類似的bug或是設定錯誤也可以檢查一下。
Capistrano設定
Sidekiq如果要搭配Capistrano使用,必須要先在Gemfile中的development環境安裝capistrano-sidekiq這個gem
# Gemfile
group :development do
gem 'capistrano-sidekiq'
end
安裝好了之後記得到Capfile中設定
# Capfile
...
require 'capistrano/sidekiq'
install_plugin Capistrano::Sidekiq # Default sidekiq tasks
# Then select your service manager
install_plugin Capistrano::Sidekiq::Systemd
在Capfile加上以上的三行程式碼後,在Capistrano部署的過程才會觸發以下三個指令。
(1)sidekiq:quiet(2)sidekiq:stop(3)sidekiq:start
關於capistrano-sidekiq的task可以在repo中的頁面查看。
在capistrano-sidekiq的以下檔案(capistrano-sidekiq/lib/capistrano/tasks/sidekiq.rake)中,可以看到預設會觸發quiet、stop、start三個task。
namespace :sidekiq do
task :add_default_hooks do
after 'deploy:starting', 'sidekiq:quiet' if Rake::Task.task_defined?('sidekiq:quiet')
after 'deploy:updated', 'sidekiq:stop'
after 'deploy:published', 'sidekiq:start'
after 'deploy:failed', 'sidekiq:restart'
end
end
這三個task分別會在linux server中用不同systemctl指令操作sidekiq.service(1)systemctl — user quiet sidekiq(2)systemctl — user stop sidekiq(3)systemctl — user start sidekiq。
部署前可以透過以下指令安裝sidekiq
bundle exec cap production sidekiq:install
用UI介面檢視sidekiq
sidekiq提供了介面可以檢視目前的背景程式執行情況,在config/routes.rb加上以下程式碼就可以看到一個介面。
# config/routes.rb
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq'
Linux上Redis版本不一致怎麼辦?
有時候遇到Sidekiq版本升級,所以redis-server的版本可能也會不一樣。比如說原先server上的redis-server是6.0.4版,但是Sidekiq可能需要6.2.0。
我們可以先透過以下的指令檢視目前安裝的版本。
$ redis-server -v
Redis server v=6.0.4 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=aa612efc56d73632
由於伺服器上可能有多個版本,因此可以透過以下指令確認實際運行的版本
$ redis-cli INFO | grep redis_version
redis_version:6.0.4
如果遇到版本不符合,我們可以透過升級redis-server的版本,並完成接下來的設定。
sudo apt-get update && sudo apt-get upgrade redis-server
由於一開始在直接透過apt-get升級redis-server時遇到了一些問題,所以後來我改成直接手動安裝。
wget http://download.redis.io/releases/redis-6.2.0.tar.gz
tar xzf redis-6.2.0.tar.gz
cd redis-6.2.0
make
sudo make install
# 重啟redis-server
sudo service redis-server restart
安裝之後我們可以透過以下的指令確認redis-server跟redis-cli兩個文件的位置
$ which redis-server
/usr/local/bin/redis-server
$ which redis-cli
/usr/local/bin/redis-cli
$ sudo service redis-server restart
接著透過以下指令找到redis.service檔的路徑
$ sudo systemctl status redis-server
● redis.service
Loaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled
Active: inactive (dead)
loaded後的路徑就是redis.service黨的位置。之後進到redis.service的檔案中確認ExecStart的路徑是否是ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf
$ sudo vim /lib/systemd/system/redis-server.service
#/etc/systemd/system/redis.service
ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf
# reload
$ sudo systemctl daemon-reload
# restart
$ sudo service redis-server restart
# 再次確認
$ redis-server -v
$ redis-cli INFO | grep redis_version
# 兩個應該都是6.2.0
如果是就可以正常運作。