Dramatiq安装及应用(一)

文章目录

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)

函数解释:

  1. RedisBroker:负责和 Redis 建立连接,是任务队列的「载体」,所有提交的任务都会暂存在 Redis 中;
  2. @dramatiq.actor:装饰器是核心,把普通函数转换成「可异步执行的任务」,只有被装饰的函数才能用 .send() 提交到队列;
  3. .send() 方法:提交任务到队列的核心方式,非阻塞 (主程序不会等任务执行完),返回一个 Message 对象,包含任务 ID 等信息;
  4. 工作进程:必须单独启动 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 默认采用「主进程 + 子进程」的模型:

  1. 主进程:负责监听队列、分发任务、管理子进程;
  2. 子进程 :实际执行任务,每个子进程对应一个 --workers 参数指定的 Worker;
  3. 线程 :每个子进程内部用线程池(--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
相关推荐
liulanba2 小时前
深入理解 Python 异步编程:async、await 与同步函数详解
服务器·网络·python
BBB努力学习程序设计2 小时前
从文本中精准提取手机号并脱敏:Python 正则 + 文件流的实战进阶
python
BBB努力学习程序设计2 小时前
Python文件操作完全指南:读写文件与数据处理
python·pycharm
vv_Ⅸ2 小时前
打卡day47
python
zhongtianhulian2 小时前
陶瓷行业大会资讯:掌握行业动态,洞察未来趋势
大数据·人工智能·python
小鸡吃米…2 小时前
Python的人工智能-入门指南
python
LT>_<2 小时前
flink遇到的问题
大数据·python·flink
写代码的【黑咖啡】3 小时前
面向对象编程入门:从类与对象到构造函数
开发语言·python
luo_yu_11063 小时前
安装chroma的时候报错
python·chroma