Edit (5.7.2014): Dieser Artikel ist etwas veraltet. Statt dem Gem Clockwork benutzen wir nun Sidetiq. Dieses funktioniert hervorragend mit Sidekiq und ist wesentlich einfacher zu verwalten.
Unsere Rails Applikation muss einige Aufgaben im Hintergrund erledigen. Zum Beispiel ruft sie die Email Accounts von Usern ab und verarbeitet diese. Wir haben dafür eine Möglichst einfache Lösung gesucht und in den beiden Gems Sidekiq und Clockwork gefunden.
Sidekiq ist zuständig für das Abarbeiten von Aufgaben, die es gestellt bekommt, während Clockwork für die regelmässige Verteilung dieser Aufgaben zuständig ist. Weil Clockwork in der jeweiligen Applikation konfiguriert wird, ist die Wartung wesentlich einfacher, als Cronjobs.
Um das Ganze auszutesten schrieb ich eine kleine Applikation, die im Hintergrund Primzahlen vor sich hin berechnet.
Voraussetzung Redis Server
Redis ist ein sehr einfacher, dafür aber hochperformanter Datenbankserver. Sidekiq verwaltet seine Queues darin, weshalb wir Redis zuerst installieren müssen. Auf dem Mac benutzt man dafür den Paketmanager seines Vertrauens.
$ brew install redis
Gemfile
Die folgenden Gems müssen im Gemfile eingetragen werden:
gem 'sidekiq' # Sidekiq
gem 'sinatra' # Für die Monitoring Applikation
gem 'slim' # Ebenfalls die für Monitoring Applikation
gem 'clockwork' # Cron Ersatz
Sidekiq Monitor
Damit wir sehen, was abgeht, machen wir den Sidekiq Monitor über einen Mount verfügbar.
require 'sidekiq/web'
WorkerTestApp::Application.routes.draw do
### mount sidekiq engine ###
mount Sidekiq::Web => '/sidekiq'
end
Worker Klasse
Pro Hintergund-Aufgabe erstellen wir eine Klasse im Verzeichnis app/workers
. Weil diese Klassen nur schwer getestet
werden können, empfiehlt es sich, den Hauptteil der Logik in die eigentliche Anwendung zu verpacken. Aus
Gründen der Einfachheit lasse ich die Berechnung für dieses Beispiel aber den Worker selbst machen. Ich habe
bei der Berechnung der Primzahlen absichtlich nicht auf einen möglichst effizienten Algorithmus geachtet.
Der Worker soll ja was zu tun haben!
class HeavyWorker
include Sidekiq::Worker
def perform(name, count)
primes = []
for i in 0..count
primes << i if isPrime(i)
end
puts "#{name} job: From 0 to #{count} there are #{primes.count} prime numbers."
end
private
def isPrime(number)
if number == 0 or number == 1
return false
end
i = 2
limit = number / i
while i < limit
return false if number % i == 0
i += 1
limit = number / i
end
return true
end
end
Jeder Worker muss eine Methode perform
haben. Diese wird von Sidekiq aufgerufen. Der Methode können beliebige
Argumente übergeben werden. Da diese Daten aber serialisiert in die Redis Queue geschrieben werden, empfiehlt es
sich, nur einfache Datentypen zu übergeben. Statt einem ganzen ActiveRecord-Objekt übergibt man also besser
nur die ID und ruft diese dann im Worker ab.
Task Scheduling
Nun brauchen wir noch jemanden, der Sidekiq beschäftigt. Und zwar wollen wir pro Minute ein Mal alle Primzahlen von 1 bis 100 wissen. Zusätzlich wollen wir alle 5 Minuten alle Primzahlen von 1 bis 1'000'000 berechnet haben. Das klingt nach Cronjob. Diese sind aber viel zu aufwändig zu pflegen. Deshalb habe ich auf den Clockwork Gem zurückgegriffen. Clockwork läuft als eigener Prozess auf dem/einem Applikationsserver und meldet zu definierten Zeiten, was Sidekiq zu tun hat.
# require boot & environment for a Rails app
require_relative "../config/boot"
require_relative "../config/environment"
require 'clockwork'
module Clockwork
every(2.minutes, 'huge_prime_numbers.job') do
HardWorker.perform_async('huge', 10000000)
end
every(10.seconds, 'light_prime_numbers.job') do
HardWorker.perform_async('light', 100)
end
end
Los geht’s
Das Starten und überwachen der Prozesse überlassen wir normalerweise God. Auf dem Entwicklungsrechner starte ich meine Prozesse aber mit Foreman. Das ist recht bequem. Redis starte ich nicht über Foreman, weil der erstens auf unserem Ubuntu Server als System-Prozess läuft und, zweitens, ich damit eine bessere Kontrolle über die Start- und vor allem Stopreihenfolge habe.
# Procfile
web: bundle exec rails server
worker: bundle exec sidekiq
clock: bundle exec clockwork config/clock.rb
Nun kann die Applikation gestartet werden. redis-server
wird in einem anderen Terminal ausgeführt.
$ redis-server
$ bundle install
$ foreman start
Die Sidekiq Konsole müsste nun folgendermassen erreichbar sein: http://localhost:3000/sidekiq.
Die Beispielapplikation habe ich auf Github bereitgestellt.