agenda vs bee-queue vs bree vs bull vs kue vs node-resque
Node.js 后台任务队列与调度方案选型指南
agendabee-queuebreebullkuenode-resque类似的npm包:

Node.js 后台任务队列与调度方案选型指南

这些库用于在 Node.js 应用中处理后台任务,例如发送电子邮件、生成报告或处理大量数据。它们帮助开发者将耗时操作从主线程中移走,确保应用响应速度不受影响。agendabull 适合需要高可靠性和复杂调度的场景;bree 适合需要进程隔离的简单任务;bee-queuenode-resque 适合基于 Redis 的轻量级需求;而 kue 已停止维护,不建议在新项目中使用。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
agenda09,665297 kB1020 天前MIT
bee-queue04,031107 kB486 个月前MIT
bree03,28892 kB293 个月前MIT
bull016,248309 kB1501 年前MIT
kue09,444-2889 年前MIT
node-resque01,411822 kB112 天前Apache-2.0

Node.js 后台任务队列方案深度对比

在构建现代 Web 应用时,前端开发者经常需要与后台任务系统交互。无论是发送确认邮件、处理上传的文件,还是生成复杂的报表,这些操作都不应该阻塞用户界面。agendabee-queuebreebullkuenode-resque 都是 Node.js 生态中流行的任务处理工具,但它们的设计理念和适用场景各不相同。本文将从架构、代码实现和维护状态三个方面进行深度对比。

🏗️ 核心架构与依赖:数据库 vs 进程隔离

选择任务队列时,首先要看它依赖什么存储系统,以及任务如何执行。这直接影响你的基础设施成本和运维复杂度。

agenda 依赖 MongoDB

  • 任务数据直接存在 Mongo 集合中。
  • 适合已经使用 Mongo 的项目,无需引入 Redis。
// agenda: 基于 MongoDB
const agenda = new Agenda({ db: { address: 'mongo-uri' } });
agenda.define('sendEmail', async (job) => { /*...*/ });

bull 依赖 Redis

  • 使用 Redis 列表和排序集合管理任务。
  • 性能极高,支持复杂优先级和速率限制。
// bull: 基于 Redis
const Queue = require('bull');
const queue = new Queue('email', 'redis://localhost');
queue.process(async (job) => { /*...*/ });

bee-queue 依赖 Redis

  • bull 更轻量,功能较少。
  • 适合简单的 FIFO 队列需求。
// bee-queue: 基于 Redis
const Queue = require('bee-queue');
const queue = new Queue('email', { redis: { host: 'localhost' } });
queue.process(async (job) => { /*...*/ });

node-resque 依赖 Redis

  • 遵循 Resque 协议,适合多语言互操作。
  • 结构较为传统,基于类实现。
// node-resque: 基于 Redis
const { Worker } = require('node-resque');
const worker = new Worker({ queues: ['email'], connection: { host: 'localhost' } });
worker.perform = async (job, args) => { /*...*/ };

bree 不需要外部数据库

  • 任务作为独立进程运行,通过文件配置。
  • 天然隔离,单个任务崩溃不影响主进程。
// bree: 基于独立进程
const Bree = require('bree');
const bree = new Bree({ workers: ['send-email'] });
// send-email.js 文件独立运行

kue 依赖 Redis

  • 早期流行方案,现已停止维护。
  • 架构类似 bull 但功能较旧。
// kue: 基于 Redis (已废弃)
const kue = require('kue');
const queue = kue.createQueue();
queue.process('email', (job, done) => { /*...*/ });

📝 定义与处理任务:代码风格对比

不同的库有不同的 API 设计风格。有的喜欢链式调用,有的喜欢配置对象。了解这些差异有助于团队快速上手。

agenda 使用 define 注册处理函数。

  • 逻辑清晰,支持异步函数。
  • 任务数据通过 job.attrs.data 访问。
// agenda: 定义任务
agenda.define('sendEmail', async (job) => {
  const { to, subject } = job.attrs.data;
  await emailService.send(to, subject);
});

bull 使用 process 注册处理器。

  • 支持并发控制,例如一次处理 5 个任务。
  • 任务数据直接在 job.data 中。
// bull: 定义任务
queue.process('sendEmail', async (job) => {
  const { to, subject } = job.data;
  await emailService.send(to, subject);
});

bee-queue 使用 process 注册处理器。

  • 回调风格或 Promise 风格均可。
  • 需要手动调用 done() 如果使用回调。
// bee-queue: 定义任务
queue.process(async (job) => {
  const { to, subject } = job.data;
  await emailService.send(to, subject);
});

node-resque 通过类方法 perform 定义。

  • 面向对象风格,适合大型系统。
  • 参数直接传递给 perform 方法。
// node-resque: 定义任务
class SendEmail {
  static async perform(args) {
    const { to, subject } = args;
    await emailService.send(to, subject);
  }
}

bree 通过独立文件定义。

  • 每个任务是一个单独的 .js 文件。
  • 默认导出一个函数,接收配置对象。
// bree: 定义任务 (workers/send-email.js)
module.exports = async (opts) => {
  const { to, subject } = opts.data;
  await emailService.send(to, subject);
};

kue 使用 process 注册处理器。

  • 类似 bull,但 API 较旧。
  • 必须调用 done() 完成任务。
// kue: 定义任务 (已废弃)
queue.process('sendEmail', (job, done) => {
  const { to, subject } = job.data;
  emailService.send(to, subject).then(done).catch(done);
});

⏰ 任务调度:即时 vs 延迟 vs 循环

有些任务需要立即执行,有些需要延迟,还有些需要每天定时运行。不同库对调度的支持程度不同。

agenda 支持强大的 scheduleevery

  • 可以直接使用 cron 表达式或自然语言。
  • 适合复杂的业务调度需求。
// agenda: 调度任务
await agenda.schedule('in 5 minutes', 'sendEmail', { to: 'user@example.com' });
agenda.every('10 minutes', 'cleanupLogs');

bull 支持 delayrepeat

  • 延迟任务简单直接。
  • 重复任务需要配置 cron 表达式。
// bull: 调度任务
await queue.add('sendEmail', data, { delay: 300000 });
await queue.add('cleanup', {}, { repeat: { cron: '*/10 * * * *' } });

bee-queue 支持 delay

  • 不支持内置的 cron 重复任务。
  • 适合一次性延迟任务。
// bee-queue: 调度任务
const job = queue.createJob(data);
job.delay(300000);
await job.save();

node-resque 支持 enqueueAt

  • 需要手动计算时间戳。
  • 重复任务需要外部调度器配合。
// node-resque: 调度任务
const time = new Date().getTime() + 300000;
await queue.enqueueAt(time, 'email', 'SendEmail', [{ to: 'user@example.com' }]);

bree 通过配置 intervalcron

  • 在初始化配置中定义调度规则。
  • 不需要代码调用,配置即运行。
// bree: 调度任务 (配置文件中)
const bree = new Bree({
  workers: [{ name: 'cleanup', interval: '10m' }]
});

kue 支持 delay

  • 类似 bee-queue
  • 重复任务支持较弱。
// kue: 调度任务 (已废弃)
const job = queue.create('sendEmail', data);
job.delay(300000);
job.save();

⚠️ 维护状态与风险提示

在选择库之前,必须确认它是否还在维护。使用废弃的库会给项目带来安全风险和兼容性问题。

  • kue已废弃。最后更新停留在多年前,社区不再推荐。不要在新项目中使用。
  • bull维护中。但官方推荐使用新一代 bullmq。如果是新项目,建议评估 bullmq
  • agenda活跃维护。适合 Mongo 用户。
  • bree活跃维护。适合需要进程隔离的场景。
  • bee-queue低维护。功能稳定但更新缓慢。
  • node-resque稳定。适合特定协议需求。

📊 总结对比表

特性agendabullbreebee-queuenode-resquekue
存储后端MongoDBRedis无 (文件)RedisRedisRedis
进程隔离
重复任务强大 (cron)支持 (cron)支持 (配置)不支持需外部配合弱支持
维护状态✅ 活跃✅ 活跃✅ 活跃⚠️ 低频✅ 稳定❌ 废弃
学习曲线

💡 最终建议

agenda 是 MongoDB 用户的最佳伙伴 — 如果你已经在用 Mongo,它能让你少维护一个 Redis 实例。它的调度功能非常强大,适合复杂的业务逻辑。

bull 是 Redis 用户的首选 — 性能强劲,功能丰富。但请注意,如果是全新项目,可以看看 bullmq,它是 bull 的现代升级版。

bree 是隔离需求的独特方案 — 如果你担心某个任务内存泄漏会搞挂整个服务,bree 的独立进程模型能让你睡个安稳觉。它不需要额外的数据库,部署简单。

bee-queuenode-resque 适合特定场景 — 前者适合简单 Redis 队列,后者适合需要 Resque 协议兼容的环境。

kue 请避免使用 — 它已经完成了历史使命,现在有更安全、更强大的替代品。

核心思考:不要为了用队列而用队列。如果你的任务很简单,setTimeoutnode-cron 可能就够了。但如果任务需要可靠性、重试机制和监控,那么选择一个维护良好的队列库是必要的投资。

如何选择: agenda vs bee-queue vs bree vs bull vs kue vs node-resque

  • agenda:

    如果你已经在使用 MongoDB,并且需要灵活的 cron 表达式或基于日期的调度,agenda 是最佳选择。它将任务数据存储在 Mongo 中,便于查询和管理,适合需要持久化任务状态的场景。

  • bee-queue:

    如果你需要一个基于 Redis 的简单队列,且不需要复杂的优先级或速率限制,bee-queue 是一个轻量级选项。它的 API 非常直观,适合快速搭建小型后台任务系统。

  • bree:

    如果你希望每个任务都在独立的进程中运行,以避免内存泄漏或阻塞主线程,bree 是独特的选择。它不需要 Redis 或 Mongo,仅通过配置文件管理任务,适合 cron 风格的任务调度。

  • bull:

    如果你需要强大的 Redis 队列功能,包括优先级、速率限制和事件监听,bull 是成熟的选择。注意新版本推荐使用 bullmq,但 bull 依然广泛使用,适合高并发任务处理。

  • kue:

    不建议在新项目中使用 kue。该库已停止维护多年,存在未修复的安全隐患和兼容性问题。请考虑迁移到 bullagenda 以获得长期支持。

  • node-resque:

    如果你需要兼容 Resque 协议或与 Ruby on Rails 的 Resque 系统互操作,node-resque 是合适选择。它基于 Redis,适合需要标准化队列协议的多语言环境。

agenda的README

Agenda

Agenda

A light-weight job scheduling library for Node.js

NPM Version NPM Downloads

Migrating from v5? See the Migration Guide for all breaking changes.

Agenda 6.x

Agenda 6.x is a complete TypeScript rewrite with a focus on modularity and flexibility:

  • Pluggable storage backends - Choose from MongoDB, PostgreSQL, Redis, or implement your own. Each backend is a separate package - install only what you need.

  • Pluggable notification channels - Move beyond polling with real-time job notifications via Redis, PostgreSQL LISTEN/NOTIFY, or other pub/sub systems. Jobs get processed immediately when saved, not on the next poll cycle.

  • Modern stack - ESM-only, Node.js 18+, full TypeScript with strict typing.

See the 6.x Roadmap for details and progress.

Features

  • Minimal overhead job scheduling
  • Pluggable storage backends (MongoDB, PostgreSQL, Redis)
  • TypeScript support with full typing
  • Scheduling via cron or human-readable syntax
  • Configurable concurrency and locking
  • Real-time job notifications (optional)
  • Sandboxed worker execution via fork mode
  • TypeScript decorators for class-based job definitions

Installation

Install the core package and your preferred backend:

# For MongoDB
npm install agenda @agendajs/mongo-backend

# For PostgreSQL
npm install agenda @agendajs/postgres-backend

# For Redis
npm install agenda @agendajs/redis-backend

Requirements:

  • Node.js 18+
  • Database of your choice (MongoDB 4+, PostgreSQL, or Redis)

Quick Start

import { Agenda } from 'agenda';
import { MongoBackend } from '@agendajs/mongo-backend';

const agenda = new Agenda({
  backend: new MongoBackend({ address: 'mongodb://localhost/agenda' })
});

// Define a job
agenda.define('send email', async (job) => {
  const { to, subject } = job.attrs.data;
  await sendEmail(to, subject);
});

// Start processing
await agenda.start();

// Schedule jobs
await agenda.every('1 hour', 'send email', { to: 'user@example.com', subject: 'Hello' });
await agenda.schedule('in 5 minutes', 'send email', { to: 'admin@example.com', subject: 'Report' });
await agenda.now('send email', { to: 'support@example.com', subject: 'Urgent' });

Official Backend Packages

PackageBackendNotificationsInstall
@agendajs/mongo-backendMongoDBPolling onlynpm install @agendajs/mongo-backend
@agendajs/postgres-backendPostgreSQLLISTEN/NOTIFYnpm install @agendajs/postgres-backend
@agendajs/redis-backendRedisPub/Subnpm install @agendajs/redis-backend

Backend Capabilities

BackendStorageNotificationsNotes
MongoDB (MongoBackend)Storage only. Combine with external notification channel for real-time.
PostgreSQL (PostgresBackend)Full backend. Uses LISTEN/NOTIFY for notifications.
Redis (RedisBackend)Full backend. Uses Pub/Sub for notifications.
InMemoryNotificationChannelNotifications only. For single-process/testing.

Backend Configuration

MongoDB

import { Agenda } from 'agenda';
import { MongoBackend } from '@agendajs/mongo-backend';

// Via connection string
const agenda = new Agenda({
  backend: new MongoBackend({ address: 'mongodb://localhost/agenda' })
});

// Via existing MongoDB connection
const agenda = new Agenda({
  backend: new MongoBackend({ mongo: existingDb })
});

// With options
const agenda = new Agenda({
  backend: new MongoBackend({
    mongo: db,
    collection: 'jobs'        // Collection name (default: 'agendaJobs')
  }),
  processEvery: '30 seconds', // Job polling interval
  maxConcurrency: 20,         // Max concurrent jobs
  defaultConcurrency: 5       // Default per job type
});

PostgreSQL

import { Agenda } from 'agenda';
import { PostgresBackend } from '@agendajs/postgres-backend';

const agenda = new Agenda({
  backend: new PostgresBackend({
    connectionString: 'postgresql://user:pass@localhost:5432/mydb'
  })
});

Redis

import { Agenda } from 'agenda';
import { RedisBackend } from '@agendajs/redis-backend';

const agenda = new Agenda({
  backend: new RedisBackend({
    connectionString: 'redis://localhost:6379'
  })
});

Real-Time Notifications

For faster job processing across distributed systems:

import { Agenda, InMemoryNotificationChannel } from 'agenda';
import { MongoBackend } from '@agendajs/mongo-backend';

const agenda = new Agenda({
  backend: new MongoBackend({ mongo: db }),
  notificationChannel: new InMemoryNotificationChannel()
});

Mixing Storage and Notification Backends

You can use MongoDB for storage while using a different system for real-time notifications:

import { Agenda } from 'agenda';
import { MongoBackend } from '@agendajs/mongo-backend';
import { RedisBackend } from '@agendajs/redis-backend';

// MongoDB for storage + Redis for real-time notifications
const redisBackend = new RedisBackend({ connectionString: 'redis://localhost:6379' });
const agenda = new Agenda({
  backend: new MongoBackend({ mongo: db }),
  notificationChannel: redisBackend.notificationChannel
});

This is useful when you want MongoDB's proven durability and flexible queries for job storage, but need faster real-time notifications across multiple processes.

API Overview

Defining Jobs

// Simple async handler
agenda.define('my-job', async (job) => {
  console.log('Processing:', job.attrs.data);
});

// With options
agenda.define('my-job', async (job) => { /* ... */ }, {
  concurrency: 10,
  lockLimit: 5,
  lockLifetime: 10 * 60 * 1000, // 10 minutes
  priority: 'high'
});

Defining Jobs with Decorators

For a class-based approach, use TypeScript decorators:

import { JobsController, Define, Every, registerJobs, Job } from 'agenda';

@JobsController({ namespace: 'email' })
class EmailJobs {
  @Define({ concurrency: 5 })
  async sendWelcome(job: Job<{ userId: string }>) {
    console.log('Sending welcome to:', job.attrs.data.userId);
  }

  @Every('1 hour')
  async cleanupBounced(job: Job) {
    console.log('Cleaning up bounced emails');
  }
}

registerJobs(agenda, [new EmailJobs()]);
await agenda.start();

// Schedule using namespaced name
await agenda.now('email.sendWelcome', { userId: '123' });

See Decorators Documentation for full details.

Scheduling Jobs

// Run immediately
await agenda.now('my-job', { userId: '123' });

// Run at specific time
await agenda.schedule('tomorrow at noon', 'my-job', data);
await agenda.schedule(new Date('2024-12-25'), 'my-job', data);

// Run repeatedly
await agenda.every('5 minutes', 'my-job');
await agenda.every('0 * * * *', 'my-job'); // Cron syntax

Job Control

// Cancel jobs matching a filter (removes from database)
await agenda.cancel({ name: 'my-job' });
await agenda.cancel({ name: 'my-job', data: { userId: 123 } });

// Cancel ALL jobs unconditionally
await agenda.cancelAll();

// Disable/enable jobs globally (by query)
await agenda.disable({ name: 'my-job' });  // Disable all jobs matching query
await agenda.enable({ name: 'my-job' });   // Enable all jobs matching query

// Disable/enable individual jobs
const job = await agenda.create('my-job', data);
job.disable();
await job.save();

// Progress tracking
agenda.define('long-job', async (job) => {
  for (let i = 0; i <= 100; i += 10) {
    await doWork();
    await job.touch(i); // Report progress 0-100
  }
});

Stopping / Draining

// Stop immediately - unlocks running jobs so other workers can pick them up
await agenda.stop();

// Drain - waits for running jobs to complete before stopping
await agenda.drain();

// Drain with timeout (30 seconds) - for cloud platforms with shutdown deadlines
const result = await agenda.drain(30000);
if (result.timedOut) {
    console.log(`${result.running} jobs still running after timeout`);
}

// Drain with AbortSignal - for external control
const controller = new AbortController();
setTimeout(() => controller.abort(), 30000);
await agenda.drain({ signal: controller.signal });

Use drain() for graceful shutdowns where you want in-progress jobs to finish their work.

Events

agenda.on('start', (job) => console.log('Job started:', job.attrs.name));
agenda.on('complete', (job) => console.log('Job completed:', job.attrs.name));
agenda.on('success', (job) => console.log('Job succeeded:', job.attrs.name));
agenda.on('fail', (err, job) => console.log('Job failed:', job.attrs.name, err));

// Job-specific events
agenda.on('start:send email', (job) => { /* ... */ });
agenda.on('fail:send email', (err, job) => { /* ... */ });

Use fail listeners to capture richer error context, such as stack traces, without storing large payloads in job.attrs.failReason:

agenda.on('fail', async (err, job) => {
	await saveJobError({
		jobId: job.attrs._id,
		jobName: job.attrs.name,
		message: err.message,
		stack: err.stack
	});
});

Custom Backend

For databases other than MongoDB, PostgreSQL, or Redis, implement AgendaBackend:

import { AgendaBackend, JobRepository } from 'agenda';

class SQLiteBackend implements AgendaBackend {
  readonly repository: JobRepository;
  readonly notificationChannel = undefined; // Or implement NotificationChannel

  async connect() { /* ... */ }
  async disconnect() { /* ... */ }
}

const agenda = new Agenda({
  backend: new SQLiteBackend({ path: './jobs.db' })
});

See Custom Backend Driver for details.

Documentation

Related Packages

Official Backend Packages:

Tools:

License

MIT