Python aetosky-job-processing 包完全指南
一、包概述
aetosky-job-processing 是一个轻量级Python任务处理框架,专注于异步任务调度、并发执行、任务依赖管理和失败重试等核心场景。其设计目标是简化后台任务处理流程,支持单机/分布式部署(基于Redis/RabbitMQ),适用于定时任务、批量数据处理、异步接口响应等场景。
核心优势:
- 低侵入式API,快速集成现有项目;
- 支持任务优先级、依赖链、超时控制;
- 内置失败重试、死信队列机制;
- 兼容多后端存储(内存/Redis/RabbitMQ);
- 支持任务状态监控和结果回调。
二、安装指南
1. 基础安装(仅本地内存队列)
pip install aetosky-job-processing
2. 完整安装(支持Redis/RabbitMQ分布式)
# 包含Redis和RabbitMQ依赖
pip install aetosky-job-processing[full]
3. 验证安装
import aetosky_job_processing as ajp
print(ajp.__version__) # 输出版本号(如0.3.2),验证安装成功
三、核心语法与参数说明
1. 核心组件
| 组件 | 作用 | 常用类/函数 |
|---|---|---|
| 任务定义器 | 声明任务逻辑、参数约束、执行配置 | @ajp.job() 装饰器 |
| 任务队列 | 存储待执行任务,支持优先级排序 | ajp.Queue()、ajp.RedisQueue() |
| 任务执行器 | 消费队列任务,支持并发/异步执行 | ajp.Worker()、ajp.AsyncWorker() |
| 调度器 | 支持定时/延迟任务调度 | ajp.Scheduler() |
| 结果处理器 | 处理任务执行结果/失败回调 | ajp.ResultHandler() |
2. 任务定义装饰器 @ajp.job() 参数
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
queue_name | str | “default” | 任务所属队列名称 |
priority | int | 5 | 优先级(1-10,1最高) |
retry_count | int | 0 | 失败重试次数 |
retry_delay | int/float | 1.0 | 重试间隔(秒) |
timeout | int/float | None | 任务超时时间(秒,None表示无限制) |
dependencies | List[str] | [] | 依赖的任务ID列表(需先执行依赖任务) |
result_ttl | int | 3600 | 结果缓存时间(秒) |
on_success | Callable | None | 成功回调函数(参数:任务结果) |
on_failure | Callable | None | 失败回调函数(参数:异常信息) |
3. 队列 ajp.Queue() 参数
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name | str | “default” | 队列名称 |
backend | str | “memory” | 存储后端(“memory”/“redis”/“rabbitmq”) |
redis_url | str | None | Redis连接URL(backend="redis"时必填) |
rabbitmq_url | str | None | RabbitMQ连接URL(backend="rabbitmq"时必填) |
max_size | int | None | 队列最大容量(None表示无限制) |
4. 执行器 ajp.Worker() 参数
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
queues | List[Queue] | [] | 监听的队列列表 |
workers | int | 4 | 并发工作进程数 |
daemon | bool | True | 是否以守护进程运行 |
log_level | str | “INFO” | 日志级别(“DEBUG”/“INFO”/“WARN”/“ERROR”) |
shutdown_timeout | int | 10 | 关闭超时时间(秒) |
5. 调度器 ajp.Scheduler() 参数
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
queue | Queue | None | 调度任务的目标队列 |
interval | int/float | 60 | 调度检查间隔(秒) |
daemon | bool | True | 是否以守护进程运行 |
四、8个实际应用案例
案例1:基础同步任务(本地内存队列)
场景:简单的同步任务执行,适用于单机轻量场景。
import aetosky_job_processing as ajp
# 1. 定义任务(使用默认队列)
@ajp.job()
def add(a, b):
"""简单加法任务"""
return a + b
# 2. 创建队列和执行器
queue = ajp.Queue(name="default", backend="memory")
worker = ajp.Worker(queues=[queue], workers=2)
# 3. 启动执行器(非阻塞,守护进程模式)
worker.start()
# 4. 提交任务
job_id = add.delay(3, 5) # 异步提交,返回任务ID
print(f"提交任务,ID: {job_id}")
# 5. 获取任务结果(阻塞直到任务完成)
result = ajp.get_job_result(job_id, timeout=10)
print(f"任务结果: 3 + 5 = {result}")
# 6. 停止执行器
worker.stop()
案例2:带重试和超时的异步任务
场景:网络请求类任务(可能失败),需要重试和超时控制。
import aetosky_job_processing as ajp
import requests
# 1. 定义带重试和超时的任务
@ajp.job(
retry_count=3, # 失败重试3次
retry_delay=2, # 重试间隔2秒
timeout=5, # 任务超时5秒
on_failure=lambda e: print(f"任务失败: {str(e)}") # 失败回调
)
def fetch_url(url):
"""请求URL内容"""
response = requests.get(url, timeout=3)
response.raise_for_status() # 状态码非200抛出异常
return response.text[:100] # 返回前100个字符
# 2. 创建Redis队列(支持分布式)
queue = ajp.Queue(
name="http_queue",
backend="redis",
redis_url="redis://localhost:6379/0" # Redis连接地址
)
worker = ajp.Worker(queues=[queue], workers=3)
worker.start()
# 3. 提交任务(模拟无效URL触发重试)
job_id1 = fetch_url.delay("https://www.baidu.com") # 有效URL
job_id2 = fetch_url.delay("https://invalid-url-12345.com") # 无效URL
# 4. 获取结果
try:
result1 = ajp.get_job_result(job_id1, timeout=10)
print(f"百度首页前100字符: {result1}")
except TimeoutError:
print("任务1超时")
try:
result2 = ajp.get_job_result(job_id2, timeout=10)
except Exception as e:
print(f"任务2最终失败: {str(e)}")
worker.stop()
案例3:任务依赖链(先执行前置任务)
场景:数据处理流程,需先完成数据下载,再进行解析。
import aetosky_job_processing as ajp
import json
# 1. 前置任务:下载数据
@ajp.job(queue_name="data_queue")
def download_data(api_url):
import requests
response = requests.get(api_url)
return response.json() # 返回原始数据
# 2. 后置任务:依赖下载任务,解析数据
@ajp.job(
queue_name="parse_queue",
dependencies=["download_job_id"], # 依赖前置任务ID
on_success=lambda res: print(f"解析完成,结果: {res}")
)
def parse_data(raw_data):
# 解析逻辑:提取关键字段
return {
"total": len(raw_data),
"first_item": raw_data[0] if raw_data else None
}
# 3. 创建队列和执行器(监听两个队列)
data_queue = ajp.Queue(name="data_queue", backend="redis", redis_url="redis://localhost:6379/0")
parse_queue = ajp.Queue(name="parse_queue", backend="redis", redis_url="redis://localhost:6379/0")
worker = ajp.Worker(queues=[data_queue, parse_queue], workers=4)
worker.start()
# 4. 提交任务(先提交前置任务,再提交依赖任务)
download_job_id = download_data.delay("https://jsonplaceholder.typicode.com/todos")
print(f"下载任务ID: {download_job_id}")
# 提交后置任务时,指定依赖的任务ID
parse_job_id = parse_data.delay(
raw_data=ajp.DependsOn(download_job_id) # 表示依赖download_job_id的结果
)
print(f"解析任务ID: {parse_job_id}")
# 5. 等待结果
parse_result = ajp.get_job_result(parse_job_id, timeout=20)
print(f"最终解析结果: {parse_result}")
worker.stop()
案例4:定时任务(周期性执行)
场景:定时备份数据库(每天凌晨2点执行)。
import aetosky_job_processing as ajp
from datetime import datetime
# 1. 定义定时任务
@ajp.job(queue_name="backup_queue")
def backup_database(db_name):
"""模拟数据库备份"""
backup_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"[{backup_time}] 备份数据库 {db_name} 成功")
return f"backup_{db_name}_{backup_time}.sql"
# 2. 创建队列和调度器
queue = ajp.Queue(name="backup_queue", backend="redis", redis_url="redis://localhost:6379/0")
worker = ajp.Worker(queues=[queue], workers=1)
scheduler = ajp.Scheduler(queue=queue, interval=60) # 每分钟检查一次调度规则
# 3. 添加定时任务(每天凌晨2点执行)
# 使用cron表达式:分 时 日 月 周(*表示任意)
scheduler.add_cron_job(
job_func=backup_database,
cron_expr="0 2 * * *", # 每天2:00执行
args=("mydb",) # 任务参数
)
# 4. 启动执行器和调度器
worker.start()
scheduler.start()
# 5. 保持主线程运行(实际使用中可部署为服务)
try:
while True:
import time
time.sleep(3600)
except KeyboardInterrupt:
scheduler.stop()
worker.stop()
案例5:高优先级任务插队
场景:普通任务和紧急任务分离,紧急任务优先执行。
import aetosky_job_processing as ajp
import time
# 1. 定义普通任务(优先级5,默认)
@ajp.job(queue_name="normal_queue", priority=5)
def process_order(order_id):
"""处理普通订单"""
time.sleep(2) # 模拟处理耗时
return f"普通订单 {order_id} 处理完成"
# 2. 定义紧急任务(优先级1,最高)
@ajp.job(queue_name="urgent_queue", priority=1)
def process_urgent_order(order_id):
"""处理紧急订单"""
time.sleep(1)
return f"紧急订单 {order_id} 处理完成"
# 3. 创建队列(优先级队列会自动排序)
normal_queue = ajp.Queue(name="normal_queue", backend="memory")
urgent_queue = ajp.Queue(name="urgent_queue", backend="memory")
# 4. 执行器监听两个队列(优先级高的任务先被消费)
worker = ajp.Worker(queues=[urgent_queue, normal_queue], workers=2)
worker.start()
# 5. 提交任务(先提交普通订单,再提交紧急订单)
normal_job1 = process_order.delay("ORD-1001")
normal_job2 = process_order.delay("ORD-1002")
urgent_job = process_urgent_order.delay("ORD-9999") # 紧急任务插队
# 6. 获取结果(验证紧急任务先完成)
print(ajp.get_job_result(urgent_job, timeout=5)) # 先输出
print(ajp.get_job_result(normal_job1, timeout=5))
print(ajp.get_job_result(normal_job2, timeout=5))
worker.stop()
案例6:批量任务提交与结果聚合
场景:批量处理用户数据,最终聚合结果。
import aetosky_job_processing as ajp
import random
# 1. 定义单用户处理任务
@ajp.job(queue_name="user_queue", retry_count=2)
def process_user(user_id):
"""模拟处理用户数据:计算积分"""
积分 = random.randint(10, 100) # 模拟业务逻辑
return {"user_id": user_id, "points": 积分}
# 2. 创建队列和执行器
queue = ajp.Queue(name="user_queue", backend="redis", redis_url="redis://localhost:6379/0")
worker = ajp.Worker(queues=[queue], workers=5) # 5个并发进程加速批量处理
worker.start()
# 3. 批量提交任务(100个用户)
user_ids = [f"USER-{i:03d}" for i in range(100)]
job_ids = [process_user.delay(uid) for uid in user_ids]
print(f"提交了 {len(job_ids)} 个用户处理任务")
# 4. 聚合所有任务结果
total_points = 0
user_results = []
for job_id in job_ids:
try:
result = ajp.get_job_result(job_id, timeout=15)
user_results.append(result)
total_points += result["points"]
except Exception as e:
print(f"用户 {job_id} 处理失败: {str(e)}")
# 5. 输出聚合结果
print(f"批量处理完成,共 {len(user_results)} 个用户")
print(f"总积分: {total_points}")
print(f"平均积分: {total_points / len(user_results):.2f}")
worker.stop()
案例7:分布式任务(多Worker共享Redis队列)
场景:多台服务器分布式处理任务,通过Redis共享队列。
服务端1(提交任务)
import aetosky_job_processing as ajp
# 定义任务(无需启动Worker,仅提交)
@ajp.job(queue_name="distributed_queue")
def compute_factorial(n):
"""计算阶乘(CPU密集型任务)"""
result = 1
for i in range(1, n+1):
result *= i
return f"{n}! = {result}"
# 连接Redis队列(与其他Worker共享)
queue = ajp.Queue(
name="distributed_queue",
backend="redis",
redis_url="redis://192.168.1.100:6379/0" # 共享Redis地址
)
# 提交多个CPU密集型任务
for n in [20, 30, 40, 50]:
job_id = compute_factorial.delay(n)
print(f"提交任务: {n}!,ID: {job_id}")
服务端2(Worker节点1)
import aetosky_job_processing as ajp
# 连接相同的Redis队列
queue = ajp.Queue(
name="distributed_queue",
backend="redis",
redis_url="redis://192.168.1.100:6379/0"
)
# 启动4个并发Worker(利用服务器2的CPU)
worker = ajp.Worker(queues=[queue], workers=4)
worker.start()
# 保持运行
try:
while True:
import time
time.sleep(3600)
except KeyboardInterrupt:
worker.stop()
服务端3(Worker节点2)
# 与服务端2代码完全一致,启动后自动消费Redis队列中的任务
# 实现分布式负载均衡
import aetosky_job_processing as ajp
queue = ajp.Queue(
name="distributed_queue",
backend="redis",
redis_url="redis://192.168.1.100:6379/0"
)
worker = ajp.Worker(queues=[queue], workers=4)
worker.start()
try:
while True:
import time
time.sleep(3600)
except KeyboardInterrupt:
worker.stop()
案例8:任务结果回调与死信队列
场景:重要任务失败后,将任务转入死信队列,后续人工处理。
import aetosky_job_processing as ajp
# 1. 定义死信队列(存储最终失败的任务)
dead_letter_queue = ajp.Queue(
name="dead_letter_queue",
backend="redis",
redis_url="redis://localhost:6379/0"
)
# 2. 定义失败回调函数(将任务转入死信队列)
def handle_final_failure(job_id, exception):
"""任务最终失败后的回调:转入死信队列"""
job = ajp.get_job(job_id) # 获取原始任务信息
dead_letter_queue.enqueue(
job_func=job.func,
args=job.args,
kwargs=job.kwargs,
metadata={"failure_reason": str(exception), "job_id": job_id}
)
print(f"任务 {job_id} 转入死信队列,原因: {str(exception)}")
# 3. 定义核心任务(重试3次后转入死信队列)
@ajp.job(
queue_name="core_queue",
retry_count=3,
retry_delay=2,
on_failure=handle_final_failure # 最终失败回调
)
def process_payment(payment_id, amount):
"""模拟支付处理(可能失败)"""
import random
if random.random() < 0.7: # 70%概率失败
raise ValueError(f"支付网关异常,支付ID: {payment_id}")
return f"支付 {amount} 元成功,ID: {payment_id}"
# 4. 创建核心队列和执行器
core_queue = ajp.Queue(
name="core_queue",
backend="redis",
redis_url="redis://localhost:6379/0"
)
worker = ajp.Worker(queues=[core_queue], workers=2)
worker.start()
# 5. 提交10个支付任务
for i in range(10):
process_payment.delay(payment_id=f"PAY-{i:03d}", amount=100+i*10)
# 6. 后续可单独处理死信队列(例如:人工重试)
def process_dead_letter():
"""处理死信队列中的任务"""
while not dead_letter_queue.is_empty():
task = dead_letter_queue.dequeue()
print(f"处理死信任务: {task.metadata}")
# 人工干预逻辑...
# 运行一段时间后检查死信队列
import time
time.sleep(30)
process_dead_letter()
worker.stop()
五、常见错误与解决方案
1. 错误:RedisConnectionError(Redis连接失败)
- 原因:Redis服务未启动、连接URL错误、网络不通。
- 解决方案:
- 检查Redis服务是否运行(
systemctl status redis); - 验证Redis连接URL格式(
redis://[用户名:密码@]主机:端口/数据库); - 关闭防火墙或开放Redis端口(
ufw allow 6379)。
- 检查Redis服务是否运行(
2. 错误:TaskTimeoutError(任务超时)
- 原因:任务执行时间超过
timeout参数设置,或任务逻辑卡住。 - 解决方案:
- 增大
timeout参数(如@ajp.job(timeout=30)); - 优化任务逻辑(拆分长任务、异步化IO操作);
- 检查任务是否存在死循环或阻塞(如未设置超时的网络请求)。
- 增大
3. 错误:JobDependencyError(任务依赖失败)
- 原因:依赖的任务执行失败或超时,导致当前任务无法执行。
- 解决方案:
- 检查依赖任务的日志,修复依赖任务的执行问题;
- 为依赖任务增加重试机制(
retry_count); - 在当前任务中处理依赖失败的情况(如
on_failure回调)。
4. 错误:QueueFullError(队列满)
- 原因:队列
max_size设置过小,任务提交速度超过消费速度。 - 解决方案:
- 增大
max_size参数(或设为None取消限制); - 增加Worker并发数(
workers参数); - 优化任务执行效率,提高消费速度;
- 实现任务限流(避免短时间提交大量任务)。
- 增大
5. 错误:TaskNotFoundError(任务未找到)
- 原因:任务ID错误、结果缓存过期(
result_ttl)、队列后端数据丢失。 - 解决方案:
- 验证任务ID是否正确(提交任务后保存
job_id); - 增大
result_ttl参数(延长结果缓存时间); - 使用可靠的后端(如Redis/RabbitMQ)替代内存队列(避免服务重启后数据丢失)。
- 验证任务ID是否正确(提交任务后保存
6. 错误:ConcurrencyError(并发冲突)
- 原因:多Worker同时操作共享资源(如文件、数据库),导致数据不一致。
- 解决方案:
- 对共享资源加锁(如
threading.Lock、Redis分布式锁); - 使用原子操作(如数据库事务、Redis原子命令);
- 避免多任务同时写入同一文件(采用分片写入或队列串行化)。
- 对共享资源加锁(如
六、使用注意事项
1. 任务函数设计规范
- 任务函数应幂等(多次执行结果一致):避免依赖全局变量、随机数(需固定种子)、时间戳(如必须使用,需传入而非函数内生成);
- 任务参数应可序列化:仅使用字符串、数字、列表、字典等JSON可序列化类型(避免传入对象、函数等不可序列化数据);
- 避免在任务函数中使用动态导入(如
importlib.import_module),可能导致Worker节点导入失败。
2. 队列后端选择
- 本地测试/轻量场景:使用
backend="memory"(无需额外依赖,速度快); - 单机生产场景:使用
backend="redis"(数据持久化,支持重启后恢复任务); - 分布式场景:使用
backend="redis"或backend="rabbitmq"(支持多Worker共享队列); - 高可靠性场景:优先选择RabbitMQ(支持更复杂的队列模式,如死信队列、延迟队列)。
3. 并发控制
- 根据CPU核心数设置Worker并发数(推荐
workers = CPU核心数 * 2 + 1); - IO密集型任务(如网络请求、数据库操作):可适当增加并发数;
- CPU密集型任务(如计算、数据处理):并发数不宜超过CPU核心数(避免上下文切换开销)。
4. 日志与监控
- 开启DEBUG日志(
log_level="DEBUG")排查任务执行问题; - 定期监控队列长度(
queue.size()),避免任务堆积; - 记录任务执行状态(成功/失败次数、执行时间),便于性能优化;
- 对关键任务实现告警机制(如失败次数超过阈值时发送邮件/短信)。
5. 分布式部署注意事项
- 所有Worker节点必须使用相同的队列后端(Redis/RabbitMQ)和配置;
- 任务函数的代码在所有Worker节点上必须一致(避免版本不一致导致执行失败);
- 共享资源(如数据库、文件存储)需支持分布式访问(避免单机存储瓶颈);
- 配置Redis/RabbitMQ的持久化机制(避免服务重启后任务丢失)。
6. 资源释放
- 任务执行完成后,及时释放资源(如数据库连接、文件句柄、网络连接);
- 使用
try...finally或上下文管理器(with语句)确保资源释放; - 避免在任务函数中创建长期存在的对象(如全局数据库连接池,应在Worker启动时初始化)。
七、总结
aetosky-job-processing 是一个灵活、轻量的任务处理框架,支持同步/异步、单机/分布式、定时/依赖任务等多种场景。通过简单的装饰器和API,可快速集成到Python项目中,解决后台任务处理的核心问题。
使用时需注意:合理选择队列后端、设计幂等任务函数、控制并发数、做好日志监控,避免常见的连接、超时、依赖问题。8个实际案例覆盖了大部分核心场景,可根据业务需求灵活调整参数和扩展功能。
《AI提示工程必知必会》为读者提供了丰富的AI提示工程知识与实战技能。《AI提示工程必知必会》主要内容包括各类提示词的应用,如问答式、指令式、状态类、建议式、安全类和感谢类提示词,以及如何通过实战演练掌握提示词的使用技巧;使用提示词进行文本摘要、改写重述、语法纠错、机器翻译等语言处理任务,以及在数据挖掘、程序开发等领域的应用;AI在绘画创作上的应用,百度文心一言和阿里通义大模型这两大智能平台的特性与功能,以及市场调研中提示词的实战应用。通过阅读《AI提示工程必知必会》,读者可掌握如何有效利用AI提示工程提升工作效率,创新工作流程,并在职场中脱颖而出。

487

被折叠的 条评论
为什么被折叠?



