文章目录
一、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任务,尽量减少数据库操作