Redis Queue 安装与使用

文章目录


一、Redis Queue

介绍

RQ(Redis Queue)是一个基于 Redis 的简单、轻量级的 Python 任务队列库,常用于将耗时任务(如发送邮件、图像处理、数据导入等)放到后台异步执行,避免阻塞 Web 请求。

  • 使用 Redis 作为消息中间件(broker)
  • 用 Python 函数作为任务单元
  • 启动 worker 进程监听队列并执行任务

安装与配置

安装

shell 复制代码
pip install rq

配置

示例:.env.dev

shell 复制代码
### redis 配置
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0

示例:app\config\settings.py

python 复制代码
	# ...
	### redis 配置
    REDIS_HOST: str = "127.0.0.1"
    REDIS_PORT: int = 6379
    REDIS_PASSWORD: Optional[str] = None
    REDIS_DB: int = 0
    REDIS_URL: Optional[str] = None  # 可选的完整Redis URL,优先级最高

    @property
    def REDIS_CONNECTION_URL(self) -> str:
        """动态生成Redis连接字符串"""
        if self.REDIS_URL:
            return self.REDIS_URL
        
        password_part = f":{self.REDIS_PASSWORD}@" if self.REDIS_PASSWORD else ""
        return f"redis://{password_part}{self.REDIS_HOST}:{self.REDIS_PORT}/{self.REDIS_DB}"    

rq常用工具函数示例:app\config\redis_queue.py

python 复制代码
from redis import Redis, ConnectionPool
from rq import Queue
from typing import Optional
from .settings import settings

# 全局连接池,避免重复创建连接
_redis_pool: Optional[ConnectionPool] = None
_default_queue: Optional[Queue] = None


def get_redis_connection() -> Redis:
    """
    获取Redis连接实例
    使用连接池以提高性能和资源管理
    """
    global _redis_pool

    if _redis_pool is None:
        _redis_pool = ConnectionPool.from_url(settings.REDIS_CONNECTION_URL)

    return Redis(connection_pool=_redis_pool)


def get_queue(name: str = "default", **kwargs) -> Queue:
    """
    获取指定名称的队列实例
    :param name: 队列名称,默认为 'default'
    :param kwargs: 传递给Queue的额外参数
    :return: Queue实例
    """
    redis_conn = get_redis_connection()
    return Queue(name=name, connection=redis_conn, **kwargs)


def get_default_queue() -> Queue:
    """
    获取默认队列实例
    使用单例模式避免重复创建
    """
    global _default_queue

    if _default_queue is None:
        _default_queue = get_queue()

    return _default_queue


def enqueue_job(
    func,
    *args,
    queue_name: str = "default",
    timeout: str = "10m",
    result_ttl: int = 600,
    **kwargs
) -> dict:
    """
    将任务加入队列
    :param func: 要执行的函数
    :param args: 函数位置参数
    :param queue_name: 队列名称
    :param timeout: 任务超时时间,支持格式'1h', '30m', '10s' 等
    :param result_ttl: 结果保存时间(秒)
    :param kwargs: 函数关键字参数
    :return: 包含任务ID和状态的字典
    """
    queue = get_queue(queue_name)
    job = queue.enqueue(
        func, *args, job_timeout=timeout, result_ttl=result_ttl, **kwargs
    )

    return {
        "job_id": job.id,
        "status": "queued",
        "queue_name": queue_name,
        "timeout": timeout,
    }

编写任务函数

示例:app\api\v1\module_test\docs\tasks.py

  • 任务函数必须是可被 pickle 序列化的顶层函数(不能是类方法或嵌套函数)。
python 复制代码
# tasks.py
import time

def send_email(to: str, subject: str, body: str):
    print(f"Sending email to {to}...")
    time.sleep(3)  # 模拟耗时操作
    print("Email sent!")
    return f"Email to {to} with subject '{subject}' sent."

注意:修改任务函数后,要重启RQ Worker

提交任务到队列

示例:main.py

python 复制代码
# 连接 Redis(默认 localhost:6379)
redis_conn = Redis(host='localhost', port=6379, db=0)
q = Queue(connection=redis_conn)

@app.post("/send-email")
def enqueue_email(to: str, subject: str = "Hello", body: str = "Test"):
    job = q.enqueue(tasks.send_email, to, subject, body)
    return {"job_id": job.id, "status": "queued"}

启动 RQ Worker

注意:修改任务函数后,要重启RQ Worker

在项目根目录下执行:

shell 复制代码
# 终端1:启动 FastAPI
uvicorn main:app --reload

# 终端2:启动 RQ Worker (Windows系统要设置环境变量)
$env:RQ_WORKER_CLASS = 'rq.worker.SimpleWorker'    
rq worker default

默认队列名是 default。如果你用了自定义队列名(如 q = Queue('email')),则要运行:rq worker email

关闭RQ Worker

若按CTRL+C无法关闭,在CMD执行下面命令进行关闭

shell 复制代码
# 42716 替换为RQ Worker的进程。RQ Worker启动时,会显示PID
taskkill /pid 42716 /f

排错

启动 RQ Worker 排错,错误提示:

shell 复制代码
09:02:14 Worker 9e06dc6f1f014706a766fea572c5c312: found an unhandled exception, quitting...
Traceback (most recent call last):
  File "D:\workspace_ai\my-fastapi\venv\Lib\site-packages\rq\worker.py", line 607, in work
    self.execute_job(job, queue)
  File "D:\workspace_ai\my-fastapi\venv\Lib\site-packages\rq\worker.py", line 1695, in execute_job
    self.fork_work_horse(job, queue)
  File "D:\workspace_ai\my-fastapi\venv\Lib\site-packages\rq\worker.py", line 1602, in fork_work_horse
    child_pid = os.fork()
                ^^^^^^^
AttributeError: module 'os' has no attribute 'fork'

分析:RQ默认尝试使用Unix系统的fork()调用来创建子进程执行任务,而Windows操作系统并不支持这个系统调用

解决方法

shell 复制代码
$env:RQ_WORKER_CLASS = 'rq.worker.SimpleWorker'
rq worker default

查询任务状态

可以通过 Job ID 查询任务状态:

python 复制代码
from rq.job import Job

@app.get("/job/{job_id}")
def get_job_status(job_id: str):
    job = Job.fetch(job_id, connection=redis_conn)
    return {
        "job_id": job.id,
        "status": job.get_status(),
        "result": job.result,
        "enqueued_at": job.enqueued_at,
        "started_at": job.started_at,
        "ended_at": job.ended_at,
    }

二、RQ监控

介绍

rq-dashboard 是一个基于 Web 的可视化监控工具,用于查看 RQ(Redis Queue)任务队列的状态,包括:

  • 当前正在运行的任务
  • 已完成的任务
  • 失败的任务
  • 队列长度、Worker 状态等

安装与启动

安装

shell 复制代码
pip install rq-dashboard

启动

  • 默认会连接到本地 Redis(localhost:6379,db=0),并启动 Web 服务在 http://127.0.0.1:9181
  • 打开浏览器访问:http://127.0.0.1:9181
shell 复制代码
# 在终端中直接运行
rq-dashboard

启动参数

shell 复制代码
rq-dashboard \
  --host localhost \
  --port 6379 \
  --db 0 \
  --web-port 9181 \
  --web-host 0.0.0.0
参数 说明
--host Redis 主机(默认 127.0.0.1
--port Redis 端口(默认 6379
--db Redis 数据库编号(默认 0
--password Redis 密码(如有)
--web-port Web 服务端口(默认 9181
--web-host Web 绑定地址(设为 0.0.0.0 可外网访问)

三、RQ进阶

任务函数最佳实战

保持任务函数轻量且幂等

  • 幂等性:确保任务即使被多次执行,也不会产生副作用或重复结果(例如:使用数据库唯一约束、状态检查等)。
  • 避免副作用:不要依赖外部状态(如全局变量),除非明确可控。
python 复制代码
# ✅ 好例子:幂等任务
def send_email(user_id, email_type):
    user = User.objects.get(id=user_id)
    if not EmailLog.objects.filter(user=user, type=email_type).exists():
        send_actual_email(user.email, email_type)
        EmailLog.objects.create(user=user, type=email_type)

避免长时间运行的单个任务

  • 如果任务可能耗时很长(> 几分钟),考虑将其拆分为多个子任务,或使用进度跟踪机制。
  • 长时间任务会阻塞工作进程,影响队列吞吐量。

使用类型提示和文档字符串

  • 提高代码可读性和可维护性,便于调试和团队协作。
python 复制代码
from typing import Optional

def process_file(file_path: str, retries: int = 3) -> bool:
    """
    处理指定路径的文件,最多重试 retries 次。
    返回 True 表示成功,False 表示失败。
    """

合理处理异常

  • 在任务开始/结束/关键步骤记录日志。
  • 捕获预期异常并记录日志,避免任务静默失败。
  • 对于不可恢复的错误,允许任务失败(RQ 会自动重试,如果配置了 retry)。
python 复制代码
import logging

logger = logging.getLogger(__name__)

def fetch_data(url: str):
    try:
        response = requests.get(url, timeout=10)
        response.raise_for_status()
        return response.json()
    except requests.RequestException as e:
        logger.error(f"Failed to fetch {url}: {e}")
        raise  # 让 RQ 捕获并可能重试

使用 RQ 的重试机制(Retry)

python 复制代码
from rq import Retry

queue.enqueue(fetch_data, url, retry=Retry(max=3, interval=[10, 30, 60]))

任务参数必须可序列化

  • RQ 使用 pickle(默认)或 JSON 序列化任务参数。
  • 确保传入的参数是简单类型(str, int, dict, list 等),不要传递模型实例、文件句柄、lambda 函数等。
python 复制代码
# 错误:传递模型实例
queue.enqueue(process_user, user_instance)

# 正确:传递 ID
queue.enqueue(process_user, user_id=user_instance.id)

与Fastapi集成注意事项

数据库连接池独立

  • FastAPI 使用 异步数据库驱动(如 asyncpg + SQLAlchemy async session),连接池由 AsyncEngine 管理。
  • RQ 任务使用 同步数据库驱动 (如 psycopg2 + SQLAlchemy sync session),连接池由 Engine 管理。
  • 即使连接的是同一个 PostgreSQL/MySQL 实例,连接池是分开的。

进程/线程隔离

  • FastAPI 应用:运行在 ASGI 服务器(如 Uvicorn)中,使用 事件循环(event loop) + 异步 I/O 处理 HTTP 请求。
  • RQ Worker:是 独立的 Python 进程(通过 rq worker 启动),与 FastAPI 进程 完全分离。
  • 它们之间没有共享线程或事件循环,因此:
    • RQ 任务中的同步数据库操作 不会阻塞 FastAPI 的 event loop;
    • FastAPI 的 await db.execute(...) 也不会被 RQ 任务"卡住"。

建议:RQ任务,尽量减少数据库操作

相关推荐
SiYuanFeng41 分钟前
Colab复现 NanoChat:从 Tokenizer(CPU)、Base Train(CPU) 到 SFT(GPU) 的完整踩坑实录
python·colab
炸炸鱼.2 小时前
Python 操作 MySQL 数据库
android·数据库·python·adb
_深海凉_2 小时前
LeetCode热题100-颜色分类
python·算法·leetcode
AC赳赳老秦3 小时前
OpenClaw email技能:批量发送邮件、自动回复,高效处理工作邮件
运维·人工智能·python·django·自动化·deepseek·openclaw
zhaoshuzhaoshu3 小时前
Python 语法之数据结构详细解析
python
AI问答工程师3 小时前
Meta Muse Spark 的"思维压缩"到底是什么?我用 Python 复现了核心思路(附代码)
人工智能·python
zfan5204 小时前
python对Excel数据处理(1)
python·excel·pandas
小饕4 小时前
我从零搭建 RAG 学到的 10 件事
python
老歌老听老掉牙4 小时前
PyQt5+Qt Designer实战:可视化设计智能参数配置界面,告别手动布局时代!
python·qt
格鸰爱童话5 小时前
向AI学习项目技能(六)
java·人工智能·spring boot·python·学习