Node.js Background jobs backed by redis.
Distributed delayed jobs in nodejs. Resque is a background job system backed by Redis (version 2.6.0 and up required). It includes priority queues, plugins, locking, delayed jobs, and more! This project is a very opinionated but API-compatible with Resque and Sidekiq (caveats). We also implement some of the popular Resque plugins, including resque-scheduler and resque-retry
The full API documentation for this package is automatically generated from the main via typedoc branch and published to https://node-resque.actionherojs.com/
Resque is a queue based task processing system that can be thought of as a "Kanban" style factory. Workers in this factory can each only work one Job at a time. They pull Jobs from Queues and work them to completion (or failure). Each Job has two parts: instructions on how to complete the job (the perform function), and any inputs necessary to complete the Job.
In our factory example, Queues are analogous to conveyor belts. Jobs are placed on the belts (Queues) and are held in order waiting for a Worker to pick them up. There are three types of Queues: regular work Queues, Delayed Job Queues, and the Failed Job Queue. The Delayed Job Queues contains Job definitions that are intended to be worked at or in a specified time. The Failed Job Queue is where Workers place any Jobs that have failed during execution.
Our Workers are the heart of the factory. Each Worker is assigned one or more Queues to check for work. After taking a Job from a Queue the Worker attempts to complete the Job. If successful, they go back to check out more work from the Queues. However, if there is a failure, the Worker records the job and its inputs in the Failed Jobs Queue before going back for more work.
The Scheduler can be thought of as a specialized type of Worker. Unlike other Workers, the Scheduler does not execute any Jobs, instead it manages the Delayed Job Queue. As Job definitions are added to the Delayed Job Queue they must specify when they can become available for execution. The Scheduler constantly checks to see if any Delayed Jobs are ready to execute. When a Delayed Job becomes ready for execution the Scheduler places a new instance of that Job in its defined Queue.
You can read the API docs for Node Resque @ node-resque.actionherojs.com. These are generated automatically from the master branch via TypeDoc
I learn best by examples:
…There are 3 main classes in node-resque: Queue, Worker, and Scheduler
error queue.multiWorker in Node Resque which will run many workers at once for you (see below).queue.enqueueIn or queue.enqueueAt) should be processed, but it performs some other jobs like checking for 'stuck' workers and general cluster cleanup.node-resque can not guarantee when a job is going to be executed, only when it will become available for execution (added to a Queue).new queue requires only the "queue" variable to be set. If you intend to run plugins with beforeEnqueue or afterEnqueue hooks, you should also pass the jobs object to it.new worker has some additional options:options = {
looping: true,
timeout: 5000,
queues: "*",
name: os.hostname() + ":" + process.pid,
};Note that when using "*" queue:
The configuration hash passed to new NodeResque.Worker, new NodeResque.Scheduler or new NodeResque.Queue can also take a connection option.
const connectionDetails = {
pkg: "ioredis",
host: "127.0.0.1",
password: "",
port: 6379,
database: 0,
namespace: "resque", // Also allow array of strings
};
const worker = new NodeResque.Worker(
{ connection: connectionDetails, queues: "math" },
jobs,
);
worker.on("error", (error) => {
// handler errors
});
await worker.connect();
worker.start();
// and when you are done
// await worker.end()You can also pass redis client directly.
// assume you already initialized redis client before
// the "redis" key can be IORedis.Redis or IORedis.Cluster instance
const redisClient = new Redis();
const connectionDetails = { redis: redisClient };
// or
const redisCluster = new Cluster();
const connectionDetails = { redis: redisCluster };
const worker = new NodeResque.Worker(
{ connection: connectionDetails, queues: "math" },
jobs,
);
worker.on("error", (error) => {
// handler errors
});
await worker.connect();
worker.start();
// and when you are done
await worker.end();await worker.end(), await queue.end() and await scheduler.end() before shutting down your application if you want to properly clear your worker status from resque.keyPrefix if you are using the ioredis (default) redis driver in this project (see https://github.com/actionhero/node-resque/issues/245 for more information.)beforeEnqueue or afterEnqueue, be sure to pass the jobs argument to the new NodeResque.Queue() constructorfailed queue. You can then inspect these jobs, write a plugin to manage them, move them back to the normal queues, etc. Failure behavior by default is just to enter the failed queue, but there are many options. Check out these examples from the ruby ecosystem for inspiration:
hostname:pid+unique_id. For example:If you want to learn more about running Node-Resque with docker, please view the examples here: https://github.com/actionhero/node-resque/tree/master/examples/docker
const name = os.hostname() + ":" + process.pid + "+" + counter;
const worker = new NodeResque.Worker(
{ connection: connectionDetails, queues: "math", name: name },
jobs,
);DO NOT USE THIS IN PRODUCTION. In tests or special cases, you may want to process/work a job in-line. To do so, you can use worker.performInline(jobName, arguments, callback). If you are planning on running a job via #performInline, this worker should also not be started, nor should be using event emitters to monitor this worker. This method will also not write to redis at all, including logging errors, modify resque's stats, etc.
const queue = new NodeResque.Queue({ connection: connectionDetails, jobs });
await queue.connect();API documentation for the main methods you will be using to enqueue jobs to be worked can be found @ node-resque.actionherojs.com.
From time to time, your jobs/workers may fail. Resque workers will move failed jobs to a special failed queue which will store the original arguments of your job, the failing stack trace, and additional metadata.
You can work with these failed jobs with the following methods:
let failedCount = await queue.failedCount()
failedCount is the number of jobs in the failed queuelet failedJobs = await queue.failed(start, stop)
failedJobs is an array listing the data of the failed jobs. Each element looks like:
{"worker": "host:pid", "queue": "test_queue", "payload": {"class":"slowJob", "queue":"test_queue", "args":[null]}, "exception": "TypeError", "error": "MyImport is not a function", "backtrace": [' at Worker.perform (/path/to/worker:111:24)', ' at <anonymous>'], "failed_at": "Fri Dec 12 2014 14:01:16 GMT-0800 (PST)"}await queue.failed(0, -1)We use a try/catch pattern to catch errors in your jobs. If any job throws an uncaught exception, it will be caught, and the job's payload moved to the error queue for inspection. Do not use domain, process.on("exit"), or any other method of "catching" a process crash.
The error payload looks like:
{ worker: 'busted-worker-3',
queue: 'busted-queue',
payload: { class: 'busted_job', queue: 'busted-queue', args: [ 1, 2, 3 ] },
exception: 'ERROR_NAME',
error: 'I broke',
failed_at: 'Sun Apr 26 2015 14:00:44 GMT+0100 (BST)' }await queue.removeFailed(failedJob)
failedJob is an expanded node object representing the failed job, retrieved via queue.failedawait queue.retryAndRemoveFailed(failedJob)
failedJob is an expanded node object representing the failed job, retrieved via queue.failedBy default, the scheduler will check for workers which haven't pinged redis in 60 minutes. If this happens, we will assume the process crashed, and remove it from redis. If this worker was working on a job, we will place it in the failed queue for later inspection. Every worker has a time
No open issues yet, or sync has not completed.