文章目录
- Dramatiq
-
- [什么是 Dramatiq?](#什么是 Dramatiq?)
- [Dramatiq 任务的核心特性](#Dramatiq 任务的核心特性)
- [Dramatiq 进阶特性](#Dramatiq 进阶特性)
- [Dramatiq Worker(工作进程)](#Dramatiq Worker(工作进程))
-
- [1.什么是 Worker?](#1.什么是 Worker?)
- [2.Worker 的核心启动参数(命令行)](#2.Worker 的核心启动参数(命令行))
- [3.Worker 的进程模型(理解执行原理)](#3.Worker 的进程模型(理解执行原理))
Dramatiq
什么是 Dramatiq?
Dramatiq 是一个轻量、高性能的 Python 异步任务队列库,专门用于处理「需要异步执行的任务」(比如发送邮件、生成报表、处理大数据、调用第三方 API 等)
- 依赖 Redis/RabbitMQ 作为消息中间件(推荐 Redis,部署更简单);
- 支持任务重试、优先级、延迟执行、任务结果存储等核心特性;
- 进程级并发,性能优于基于线程的方案。
主要的执行流程则是:
python
定义中间件 → 装饰任务函数 → 主程序提交任务 → 工作进程执行任务
| 概念 | 说明 |
|---|---|
| 任务(Task) | 被异步执行的函数,由 @dramatiq.actor 装饰器标记 |
| 消息中间件 | 存储待执行任务的「队列」,常用 Redis/RabbitMQ,负责任务的暂存和分发 |
| 工作进程 | 运行 dramatiq 命令启动的进程,专门监听队列、取出任务并执行 |
依赖安装:
首先安装 Dramatiq 核心库和 Redis 依赖(Redis 是最常用的中间件,优先学这个):
python
# 安装 dramatiq 核心库 + redis 后端
pip install dramatiq[redis]
代码示例:
python
# 导入 dramatiq 核心模块
import dramatiq
# 导入 Redis 后端(用于连接 Redis 队列)
from dramatiq.brokers.redis import RedisBroker
# 1. 初始化 Redis 消息中间件(连接本地 Redis,默认端口 6379)
redis_broker = RedisBroker(host="localhost", port=6379)
# 2. 将这个中间件设置为 Dramatiq 的默认中间件
dramatiq.set_broker(redis_broker)
# 3. 定义异步任务:用 @dramatiq.actor 装饰器标记函数,使其成为可异步执行的任务
@dramatiq.actor
def send_email(to: str, subject: str, content: str):
"""模拟发送邮件的异步任务"""
print(f"开始给 {to} 发送邮件...")
# 模拟耗时操作(比如真实发送邮件需要 3 秒)
import time
time.sleep(3)
print(f"邮件发送完成:{to} - {subject}")
return f"Success: {to}"
# 4. 主程序:触发异步任务
if __name__ == "__main__":
# 调用任务的 .send() 方法,将任务提交到队列(非阻塞)
task = send_email.send("user@example.com", "欢迎学习 Dramatiq", "这是异步任务示例")
# 打印任务 ID(用于追踪任务状态)
print(f"任务已提交,ID:{task.message_id}")
print("主程序继续执行,不等待邮件发送完成")
打开新的终端,进入代码所在目录,运行以下命令启动 Dramatiq 工作进程(监听队列并执行任务):
bash
dramatiq tasks # tasks 是你的 py 文件名(不含 .py)
函数解释:
RedisBroker:负责和 Redis 建立连接,是任务队列的「载体」,所有提交的任务都会暂存在 Redis 中;@dramatiq.actor:装饰器是核心,把普通函数转换成「可异步执行的任务」,只有被装饰的函数才能用.send()提交到队列;.send()方法:提交任务到队列的核心方式,非阻塞 (主程序不会等任务执行完),返回一个Message对象,包含任务 ID 等信息;- 工作进程:必须单独启动
dramatiq命令,它是真正执行任务的「工人」,主程序只负责「发任务」,不负责「做任务」。
Dramatiq 任务的核心特性
第二个核心模块 ------Dramatiq 任务的核心特性(重试、延迟、优先级),这些是实际开发中最常用的进阶基础功能。
实际场景中,异步任务不会只满足「简单执行」,还需要处理:
- 网络波动导致任务失败 → 自动重试
- 定时发送消息 / 定时生成报表 → 延迟执行
- 紧急任务优先处理 → 任务优先级
- 追踪任务执行结果 → 结果存储
1.任务自动重试(解决临时失败)
比如调用第三方 API 时网络超时、数据库临时连接失败,这类问题通常重试几次就能解决,Dramatiq 支持配置重试策略。
python
import dramatiq
from dramatiq.brokers.redis import RedisBroker
import time
# 初始化 Redis 中间件(复用模块一的配置)
redis_broker = RedisBroker(host="localhost", port=6379,db=0)
dramatiq.set_broker(redis_broker)
# 配置重试策略:@dramatiq.actor 中通过 max_retries、time_limit、min_backoff 等参数设置
@dramatiq.actor(
max_retries=3, # 最大重试次数(失败后重试 3 次,总共执行 4 次)
min_backoff=1000, # 最小重试间隔(1000 毫秒 = 1 秒)
max_backoff=10000, # 最大重试间隔(10000 毫秒 = 10 秒)
time_limit=30000, # 任务超时时间(毫秒)
)
def call_third_party_api(api_url: str):
"""模拟调用第三方 API,故意让其随机失败"""
print(f"尝试调用 API:{api_url}")
# 模拟随机失败(比如 70% 概率失败)
import random
if random.random() < 0.7:
raise Exception("API 调用超时/网络波动")
# 模拟成功执行
time.sleep(1)
print(f"API 调用成功:{api_url}")
return "Success"
# 主程序提交任务
if __name__ == "__main__":
task = call_third_party_api.send("https://api.example.com/data")
print(f"任务提交,ID:{task.message_id}")
print("主程序继续执行...")
2.任务延迟执行(定时任务)
比如「5 分钟后给用户发送下单提醒」「每天凌晨 1 点生成昨日报表」,Dramatiq 支持通过 delay 参数实现延迟执行。
python
from datetime import datetime
import dramatiq
from dramatiq.brokers.redis import RedisBroker
import time
# 初始化 Redis 中间件(复用模块一的配置)
redis_broker = RedisBroker(host="localhost", port=6379,db=0)
dramatiq.set_broker(redis_broker)
# 接上面的代码,新增一个延迟任务
@dramatiq.actor
def send_delayed_notification(user_id: str, message: str):
"""延迟发送通知的任务"""
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 给用户 {user_id} 发送通知:{message}")
# 主程序中添加延迟任务提交逻辑
if __name__ == "__main__":
# 延迟任务:delay=5000 表示 5 秒后执行(单位:毫秒)
task = send_delayed_notification.send_with_options(
args=("1001", "你的订单即将超时,请尽快支付"),
delay=5000 # 核心参数:延迟 5 秒执行
)
print(f"任务提交,ID:{task.message_id}")
print("延迟任务已提交,5 秒后执行")
# 指定时间运行
# 目标执行时间
target_time = datetime(2025, 12, 24, 8, 0, 0)
# 计算延迟毫秒数
delay_ms = int((target_time - datetime.now()).total_seconds() * 1000)
# 确保延迟为正(避免立即执行)
task = send_delayed_notification.send_with_options(
args=("1001", "你的订单即将超时,请尽快支付"),
delay=delay_ms
)
print(f"任务提交,ID:{task.message_id}")
print("延迟任务已提交,到指定时间后执行")
3.任务指定队列
比如说不同的任务会进入不同的队列中进行 等待
python
import dramatiq
from dramatiq.brokers.redis import RedisBroker
import time
# 1. 初始化 Redis 中间件
redis_broker = RedisBroker(
host="localhost",
port=6379,
db=0
)
dramatiq.set_broker(redis_broker)
# 2. 在启动 worker 时指定队列和优先级
# 注意:这里的队列优先级是通过 worker 消费顺序实现的
# 3. 定义不同优先级的任务
@dramatiq.actor(queue_name="high")
def withdraw_money(user_id: str, amount: float):
print(f"[高优先级] 处理用户 {user_id} 提现 {amount} 元")
time.sleep(2)
print(f"[高优先级] 提现完成:{user_id} - {amount} 元")
@dramatiq.actor(queue_name="low")
def stat_logs(date: str):
print(f"[低优先级] 统计 {date} 的日志数据")
time.sleep(5)
print(f"[低优先级] 日志统计完成:{date}")
# 默认队列的任务(中等优先级)
@dramatiq.actor(queue_name="default")
def send_email(email: str, content: str):
print(f"[默认优先级] 发送邮件到 {email}")
time.sleep(3)
print(f"[默认优先级] 邮件发送完成:{email}")
# 主程序提交不同优先级任务
if __name__ == "__main__":
print("提交任务...")
# 先提交低优先级任务
stat_logs.send("2025-12-23")
print("已提交:低优先级任务 - 日志统计")
# 提交默认优先级任务
send_email.send("user@example.com", "欢迎邮件")
print("已提交:默认优先级任务 - 发送邮件")
# 最后提交高优先级任务
withdraw_money.send("1001", 500.0)
print("已提交:高优先级任务 - 提现处理")
4.队列优先级的工作原理
1.队列没有固定优先级 ,队列名称只是字符串标识符,比如 "high"、"default"、"low",这些名字本身没有任何优先级含义。
2.优先级由 worker 的消费顺序决定,优先级是通过以下方式实现的:
bash
# worker 会按顺序检查这些队列
dramatiq my_module --queues high,default,low
worker 的执行逻辑:
bash
1. 先检查 'high' 队列是否有消息
↓ 如果有,处理它
2. 再检查 'default' 队列是否有消息
↓ 如果有,处理它
3. 最后检查 'low' 队列是否有消息
↓ 如果有,处理它
4. 回到第1步,循环检查
5.任务结果存储(追踪执行结果)
模块一中的任务执行后,主程序无法获取结果,Dramatiq 可以通过 Results 扩展存储任务结果,方便主程序查询。
python
import time
import dramatiq
from dramatiq.results import Results
from dramatiq.results.backends.redis import RedisBackend
from dramatiq.brokers.redis import RedisBroker
# 1. 配置Redis broker
redis_broker = RedisBroker(host="localhost", port=6379)
dramatiq.set_broker(redis_broker)
# 2. 配置结果后端
result_backend = RedisBackend(host="localhost", port=6379)
redis_broker.add_middleware(Results(backend=result_backend))
# 3. 定义任务
@dramatiq.actor(store_results=True)
def calculate_sum(a: int, b: int):
"""计算两数之和,存储结果"""
print(f"[Worker] 计算 {a} + {b}")
time.sleep(2) # 模拟耗时操作
result = a + b
print(f"[Worker] 计算完成: {result}")
return result
# 4. 启动worker(需要在新终端运行)
# 在命令行执行: dramatiq 你的文件名
if __name__ == "__main__":
# 确保先启动worker进程
print("提交任务...")
task = calculate_sum.send(10, 20)
print(f"任务 ID:{task.message_id}")
#注意踩坑:这个获取结果的这一步你得让上述任务完成了之后再去获取结果,如果说你直接去获取可能还没执行就获取就会报错。
time.sleep(4)
result = task.get_result(timeout=5000)
print(f"✅ 任务结果:{result}")
Dramatiq 进阶特性
第三个模块 ------Dramatiq 进阶特性(任务取消、限速、错误处理、监控),这些特性主要解决生产环境中的复杂场景问题。
生产环境中,除了基础的重试、延迟,还需要处理:
- 提交错误的任务需要手动取消;
- 防止任务执行过快压垮第三方服务 → 任务限速;
- 不同类型的错误需要自定义处理逻辑;
- 监控任务执行状态、排查问题 → 日志与监控。
Dramatiq Worker(工作进程)
1.什么是 Worker?
Worker 是 Dramatiq 的「任务执行进程」,你之前运行的 dramatiq tasks 命令本质就是启动一个或多个 Worker 进程:
- 它的核心作用是监听指定队列,从队列中取出待执行的任务,创建子进程 / 线程执行;
- 一个 Worker 可以监听多个队列,也可以启动多个 Worker 进程实现任务并行执行;
- Worker 的配置直接决定任务执行的效率、资源占用、容错能力,是生产环境调优的核心。
2.Worker 的核心启动参数(命令行)
之前你用的 dramatiq tasks 是最简启动方式,实际生产中需要通过参数定制 Worker 行为,所有参数可通过 dramatiq --help 查看,核心参数如下:
| 参数 | 示例 | 说明 |
|---|---|---|
-w/--workers |
dramatiq -w 4 tasks |
指定 Worker 进程数(默认 1),建议设置为 CPU 核心数(比如 4 核 CPU 设为 4) |
-t/--threads |
dramatiq -t 8 tasks |
每个 Worker 进程的线程数(默认 8),IO 密集型任务可适当增大 |
-q/--queues |
dramatiq -q high,default tasks |
指定 Worker 监听的队列(默认监听所有),比如只处理高优先级队列 |
--timeout |
dramatiq --timeout 30 tasks |
任务超时时间(秒),超过则强制终止任务 |
--reload |
dramatiq --reload tasks |
开发模式:代码修改后自动重启 Worker(生产环境禁用) |
--pidfile |
dramatiq --pidfile /tmp/dramatiq.pid tasks |
生成 PID 文件,方便管理进程(比如停止 Worker) |
--log-file |
dramatiq --log-file /var/log/dramatiq.log tasks |
日志输出到文件(替代控制台) |
常用启动示例(生产环境):
bash
# 启动 4 个 Worker 进程,每个进程 16 个线程,只监听 high/default 队列,任务超时 60 秒
dramatiq -w 4 -t 16 -q high,default --timeout 60 --pidfile /tmp/dramatiq.pid tasks
3.Worker 的进程模型(理解执行原理)
Dramatiq 默认采用「主进程 + 子进程」的模型:
- 主进程:负责监听队列、分发任务、管理子进程;
- 子进程 :实际执行任务,每个子进程对应一个
--workers参数指定的 Worker; - 线程 :每个子进程内部用线程池(
--threads)执行任务,线程数决定单个 Worker 同时执行的任务数。
示意图:
管理
管理
管理
线程池
线程池
线程池
线程池
主进程
Worker 1(子进程)
Worker 2(子进程)
Worker 3(子进程)
线程 1
线程 2
线程 1
线程 2
代码示例:
第一步:编写任务文件(tasks.py)
python
import dramatiq
from dramatiq.brokers.redis import RedisBroker
# 1. 初始化 Redis 消息队列(Dramatiq 2.0.0 推荐 Redis 作为 broker)
redis_broker = RedisBroker(host="localhost", port=6379, db=0)
dramatiq.set_broker(redis_broker)
# 2. 定义异步任务(绑定到 default 队列)
@dramatiq.actor
def process_data(data: str):
"""示例任务:处理字符串数据"""
print(f"Worker 开始处理任务,数据:{data}")
# 模拟任务执行耗时
import time
time.sleep(2)
print(f"Worker 完成任务,数据:{data}")
return f"处理完成:{data}"
# 3. 定义高优先级任务(绑定到 high_priority 队列)
@dramatiq.actor(queue_name="high_priority")
def urgent_task(urgent_data: str):
"""高优先级任务"""
print(f"【高优先级】Worker 处理紧急任务:{urgent_data}")
return f"紧急任务完成:{urgent_data}"
第二部:命令行启动(最常用)
bash
# 启动 Worker,监听 default 队列(默认)
dramatiq tasks
# 启动 Worker,同时监听 default + high_priority 队列(优先级:high_priority > default)
dramatiq tasks -Q high_priority,default
# 启动 4 个 Worker 进程(并行处理任务)
dramatiq tasks -p 4 -Q high_priority,default
# 配置 Worker 子进程数(每个 Worker 最多启动 8 个子进程执行任务)
dramatiq tasks -w 8 -p 4