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任务,尽量减少数据库操作

相关推荐
人工智能AI技术2 小时前
【Agent从入门到实践】26 使用Chroma搭建本地向量库,实现Agent的短期记忆
人工智能·python
不想写bug呀2 小时前
Redis集群介绍
数据库·redis·缓存
赤狐先生2 小时前
第三步--根据python基础语法完成一个简单的深度学习模拟
开发语言·python·深度学习
victory04312 小时前
pytorch函数使用规律-不必再死记硬背
人工智能·pytorch·python
rjc_lihui2 小时前
LightGBM 从入门到精通 (来自deepseek)
python
YUISOK2 小时前
如何使用uiautomator2+Weditor 可视化查看一个app组件的vm树
python·软件工程
yangSnowy2 小时前
Redis数据类型
数据库·redis·wpf
Pyeako2 小时前
opencv计算机视觉--图形旋转&图形可视化&均衡化
人工智能·python·opencv·计算机视觉·图形旋转·图形可视化·均衡化
人工智能AI技术2 小时前
【Agent从入门到实践】28 开发第一个Agent——开发准备:环境搭建(Python、依赖库、大模型API密钥)
人工智能·python