一个通用的异步任务处理插件,支持RabbitMQ队列监听、任务重试、进度通知等功能
Project description
grid 异步任务处理插件
一个通用的异步任务处理插件,支持RabbitMQ队列监听、任务重试、进度通知、回调等功能。专为Kubernetes + KEDA自动扩缩容环境设计。
功能特性
- 🚀 RabbitMQ队列监听: 支持从RabbitMQ队列获取任务,每次处理一个任务
- 🔄 智能重试机制: 支持任务执行失败时的重试,可配置重试次数和策略
- 📊 进度通知: 支持任务执行过程中的进度通知
- 📞 多种回调方式: 支持HTTP和RabbitMQ两种回调通知方式
- 💾 任务持久化: 基于MySQL的任务状态持久化存储
- 📝 详细日志: 完整的任务执行日志记录
- 🔔 飞书通知: 任务失败时的飞书webhook通知
- 🏗️ 高可用设计: 支持网络中断重连、数据库连接池等
- ⏰ 空闲超时: 支持空闲超时自动退出,优化资源使用
- ⚙️ 环境配置: 通过环境变量或.env文件进行配置
安装
pip install git+https://gitee.com/shenzhen-grid/grid-async-task-plugin.git
快速开始
1. 配置环境变量
创建 .env 文件或设置环境变量:
# ====================================
# Grid异步任务处理插件配置 (必填)
# ====================================
# RabbitMQ AMQP连接URL
GRID_ASYNC_TASK_AMQP_URL=amqp://guest:guest@localhost:5672/
# 任务队列名称
GRID_ASYNC_TASK_QUEUE=task_queue
# MySQL连接URL
GRID_ASYNC_TASK_MYSQL_URL=mysql+pymysql://root:password@localhost:3306/task_db?charset=utf8mb4
# ====================================
# 可选配置项
# ====================================
# 数据库表配置(避免表名冲突)
GRID_ASYNC_TASK_TABLE_PREFIX=grid_async_task_
GRID_ASYNC_TASK_TABLE_NAME=tasks
# 任务重试配置
GRID_ASYNC_TASK_MAX_RETRY_COUNT=3
GRID_ASYNC_TASK_RETRY_DELAY=60
# 空闲超时配置(秒),0表示永不退出
GRID_ASYNC_TASK_IDLE_TIMEOUT=0
# 日志配置
GRID_ASYNC_TASK_LOG_LEVEL=INFO
GRID_ASYNC_TASK_LOG_DIR=logs
# 飞书通知(可选,任务失败时发送通知)
GRID_ASYNC_TASK_FEISHU_WEBHOOK_URL=https://open.feishu.cn/open-apis/bot/v2/hook/xxx
配置说明:
- 📋 参考项目目录下的
env_example.txt文件查看完整配置示例 - 🔒 敏感信息(密码、密钥等)请妥善保管,不要提交到代码仓库
2. 创建任务处理器
from grid_async_task import TaskHandler, TaskProcessor
class MyTaskHandler(TaskHandler):
def execute(self, task_data):
"""执行具体的任务逻辑"""
# 报告进度
self.report_progress(20, "开始处理...")
# 执行任务逻辑
result = self.process_data(task_data)
# 报告进度
self.report_progress(100, "处理完成")
return result
def process_data(self, data):
# 你的具体业务逻辑
return {"status": "success", "result": data}
# 启动任务处理器
processor = TaskProcessor(handler=MyTaskHandler())
processor.start()
3. 任务生产者示例
from grid_async_task import TaskProducer
# 基本使用 - 使用配置文件中的默认队列
producer = TaskProducer()
producer.send_task({
"task_id": "unique_task_id",
"task_type": "data_processing",
"data": {"input": "test_data"},
"callback_url": "http://your-api.com/callback",
"retry_count": 3
})
4. 动态队列功能(NEW!)
TaskProducer 现在支持动态传入队列名称,让您可以将不同类型的任务发送到不同的队列中,实现更灵活的任务调度。
基本用法
from grid_async_task import TaskProducer
# 方式1:指定默认队列
producer = TaskProducer(default_queue="my_custom_queue")
task_id = producer.send_task(task_data) # 发送到 my_custom_queue
# 方式2:动态指定队列
producer = TaskProducer()
task_id = producer.send_task(task_data, queue_name="high_priority_queue")
# 方式3:使用便捷方法
task_id = producer.send_task_to_queue(task_data, "gpu_queue")
批量发送任务
# 批量发送到指定队列
batch_tasks = [
{"task_data": {"task_type": "validation", "data": {"file": f"file_{i}"}}
for i in range(10)
]
task_ids = producer.batch_send_tasks(batch_tasks, queue_name="validation_queue")
# 批量发送到默认队列
task_ids = producer.batch_send_tasks(batch_tasks)
队列管理
# 检查队列是否已声明
if not producer.is_queue_declared("new_queue"):
producer.send_task(task_data, queue_name="new_queue") # 自动声明队列
# 查看已声明的队列
declared_queues = producer.get_declared_queues()
print(f"已声明的队列: {declared_queues}")
实际应用场景
# 按优先级分发任务
high_priority_task = {
"task_type": "urgent_analysis",
"data": {"priority": "high"}
}
producer.send_task(high_priority_task, queue_name="high_priority_queue")
low_priority_task = {
"task_type": "batch_processing",
"data": {"priority": "low"}
}
producer.send_task(low_priority_task, queue_name="low_priority_queue")
# 按资源类型分发任务
gpu_task = {"task_type": "ml_training", "data": {"model": "cnn"}}
producer.send_task(gpu_task, queue_name="gpu_queue")
cpu_task = {"task_type": "data_processing", "data": {"format": "csv"}}
producer.send_task(cpu_task, queue_name="cpu_queue")
协议说明
任务消息协议
RabbitMQ 消息格式
发送到队列的消息必须是 JSON 格式,包含以下字段:
{
"task_id": "string", // 必需:任务唯一标识符
"task_type": "string", // 可选:任务类型,默认 "default"
"data": {}, // 可选:任务具体数据,任意 JSON 对象
"max_retry_count": 3, // 可选:最大重试次数,默认 0
"callback_url": "string", // 可选:HTTP 回调地址
"callback_type": "http|rabbitmq", // 可选:回调类型,默认 "http"
"callback_data": {} // 可选:回调附加数据
}
字段说明
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
task_id |
string | ✓ | 任务唯一标识符,建议使用UUID |
task_type |
string | ✗ | 任务类型标识,用于业务分类 |
data |
object | ✗ | 任务执行所需的具体数据 |
max_retry_count |
int | ✗ | 最大重试次数,0表示不重试 |
callback_url |
string | ✗ | 任务完成后的HTTP回调地址 |
callback_type |
string | ✗ | 回调方式:http 或 rabbitmq |
callback_data |
object | ✗ | 回调时携带的额外数据 |
任务状态定义
| 状态 | 说明 |
|---|---|
pending |
等待处理 |
running |
执行中 |
success |
执行成功 |
failed |
执行失败 |
retrying |
重试中 |
回调协议
HTTP 回调
当任务完成时,系统会向 callback_url 发送 POST 请求:
{
"task_id": "string", // 任务ID
"status": "success|failed", // 任务状态
"result": {}, // 任务结果数据
"timestamp": "string", // 完成时间 (ISO 8601)
"callback_data": {} // 任务提交时的回调数据
}
HTTP 回调要求:
- 请求方法:
POST - Content-Type:
application/json - 超时时间:30秒
- 重试次数:3次
- 成功判断:返回状态码
200
RabbitMQ 回调
当 callback_type 为 rabbitmq 时,回调消息会发送到指定队列:
{
"task_id": "string",
"status": "success|failed",
"result": {},
"timestamp": "string",
"callback_data": {
"queue_name": "callback_queue" // 可选:指定队列名,默认 "callback_queue"
}
}
进度通知协议
任务执行过程中的进度会更新到数据库,可通过查询API获取:
{
"task_id": "string",
"progress": 75, // 进度百分比 (0-100)
"progress_message": "处理中...", // 进度描述
"status": "running",
"updated_at": "2024-01-15T10:30:00Z"
}
多语言接入示例
Python
import json
import pika
import uuid
# 连接RabbitMQ
connection = pika.BlockingConnection(
pika.ConnectionParameters('localhost')
)
channel = connection.channel()
# 发送任务
task = {
"task_id": str(uuid.uuid4()),
"task_type": "data_processing",
"data": {"input": "test_data"},
"callback_url": "http://your-api.com/callback",
"max_retry_count": 3
}
channel.basic_publish(
exchange='',
routing_key='task_queue',
body=json.dumps(task),
properties=pika.BasicProperties(delivery_mode=2)
)
Node.js
const amqp = require('amqplib');
const { v4: uuidv4 } = require('uuid');
async function sendTask() {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
const task = {
task_id: uuidv4(),
task_type: 'data_processing',
data: { input: 'test_data' },
callback_url: 'http://your-api.com/callback',
max_retry_count: 3
};
await channel.sendToQueue(
'task_queue',
Buffer.from(JSON.stringify(task)),
{ persistent: true }
);
}
Java (Spring Boot)
@Service
public class TaskProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public String sendTask(String taskType, Object data, String callbackUrl) {
String taskId = UUID.randomUUID().toString();
Map<String, Object> task = new HashMap<>();
task.put("task_id", taskId);
task.put("task_type", taskType);
task.put("data", data);
task.put("callback_url", callbackUrl);
task.put("max_retry_count", 3);
rabbitTemplate.convertAndSend("task_queue", task);
return taskId;
}
}
Go
package main
import (
"encoding/json"
"github.com/google/uuid"
"github.com/streadway/amqp"
)
type Task struct {
TaskID string `json:"task_id"`
TaskType string `json:"task_type"`
Data interface{} `json:"data"`
CallbackURL string `json:"callback_url"`
MaxRetryCount int `json:"max_retry_count"`
}
func sendTask() error {
conn, err := amqp.Dial("amqp://localhost")
if err != nil {
return err
}
defer conn.Close()
ch, err := conn.Channel()
if err != nil {
return err
}
defer ch.Close()
task := Task{
TaskID: uuid.New().String(),
TaskType: "data_processing",
Data: map[string]string{"input": "test_data"},
CallbackURL: "http://your-api.com/callback",
MaxRetryCount: 3,
}
body, _ := json.Marshal(task)
return ch.Publish(
"", // exchange
"task_queue", // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
DeliveryMode: amqp.Persistent,
ContentType: "application/json",
Body: body,
})
}
PHP
<?php
require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
function sendTask() {
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$task = [
'task_id' => uniqid(),
'task_type' => 'data_processing',
'data' => ['input' => 'test_data'],
'callback_url' => 'http://your-api.com/callback',
'max_retry_count' => 3
];
$msg = new AMQPMessage(
json_encode($task),
['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]
);
$channel->basic_publish($msg, '', 'task_queue');
$channel->close();
$connection->close();
}
?>
错误处理
常见错误码
| 错误类型 | 说明 | 处理建议 |
|---|---|---|
ValidationError |
任务数据验证失败 | 检查数据格式和必需字段 |
ConnectionError |
数据库连接失败 | 检查数据库配置和网络 |
TimeoutError |
任务执行超时 | 增加超时时间或优化任务逻辑 |
RetryExhausted |
重试次数耗尽 | 检查任务逻辑或增加重试次数 |
错误回调格式
{
"task_id": "string",
"status": "failed",
"result": {
"error": "错误描述",
"error_type": "ValidationError"
},
"timestamp": "2024-01-15T10:30:00Z",
"callback_data": {}
}
注意: 为了安全考虑,详细的错误堆栈信息(traceback)不会在回调通知中返回,只会在飞书webhook通知中包含,便于开发人员调试。
详细配置
环境变量配置
| 变量名 | 描述 | 默认值 |
|---|---|---|
RABBITMQ_HOST |
RabbitMQ主机 | localhost |
RABBITMQ_PORT |
RabbitMQ端口 | 5672 |
RABBITMQ_USERNAME |
RabbitMQ用户名 | guest |
RABBITMQ_PASSWORD |
RabbitMQ密码 | guest |
RABBITMQ_VHOST |
RabbitMQ虚拟主机 | / |
RABBITMQ_QUEUE |
任务队列名称 | task_queue |
MYSQL_HOST |
MySQL主机 | localhost |
MYSQL_PORT |
MySQL端口 | 3306 |
MYSQL_DATABASE |
数据库名 | task_db |
MYSQL_USERNAME |
数据库用户名 | root |
MYSQL_PASSWORD |
数据库密码 | password |
FEISHU_WEBHOOK_URL |
飞书webhook地址 | - |
LOG_LEVEL |
日志级别 | INFO |
LOG_DIR |
日志目录 | logs |
任务表结构
插件会自动创建以下任务表:
CREATE TABLE IF NOT EXISTS async_tasks (
id VARCHAR(255) PRIMARY KEY,
task_type VARCHAR(100) NOT NULL,
status ENUM('pending', 'running', 'success', 'failed', 'retrying') DEFAULT 'pending',
data JSON,
result JSON,
error_message TEXT,
progress INT DEFAULT 0,
retry_count INT DEFAULT 0,
max_retry_count INT DEFAULT 0,
callback_url VARCHAR(500),
callback_type ENUM('http', 'rabbitmq') DEFAULT 'http',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
started_at TIMESTAMP NULL,
completed_at TIMESTAMP NULL
);
API参考
TaskProducer
任务生产者用于发送任务到RabbitMQ队列:
class TaskProducer:
def __init__(self, default_queue: Optional[str] = None):
"""
初始化任务生产者
Args:
default_queue: 默认队列名称,如果不提供则使用配置文件中的队列名称
"""
def send_task(self, task_data: Dict[str, Any], task_id: Optional[str] = None,
queue_name: Optional[str] = None) -> str:
"""
发送任务到指定队列
Args:
task_data: 任务数据
task_id: 任务ID,如果不提供则自动生成
queue_name: 队列名称,如果不提供则使用默认队列
Returns:
任务ID
"""
def send_task_to_queue(self, task_data: Dict[str, Any], queue_name: str,
task_id: Optional[str] = None) -> str:
"""发送任务到指定队列的便捷方法"""
def batch_send_tasks(self, tasks: List[Dict[str, Any]],
queue_name: Optional[str] = None) -> List[str]:
"""批量发送任务到指定队列"""
def get_declared_queues(self) -> set:
"""获取已声明的队列列表"""
def is_queue_declared(self, queue_name: str) -> bool:
"""检查队列是否已声明"""
def close(self):
"""关闭连接"""
TaskHandler
自定义任务处理器需要继承 TaskHandler 类:
class TaskHandler:
def execute(self, task_data):
"""执行任务的核心方法,子类必须实现"""
raise NotImplementedError
def report_progress(self, progress, message=""):
"""报告任务进度"""
pass
def get_task_id(self):
"""获取当前任务ID"""
return self.task_id
TaskProcessor
任务处理器的主要配置选项:
processor = TaskProcessor(
handler=MyTaskHandler(),
max_retry_count=3, # 最大重试次数
retry_delay=60, # 重试延迟(秒)
progress_interval=10, # 进度报告间隔(秒)
enable_auto_ack=True, # 自动确认消息
prefetch_count=1 # 预取消息数量
)
Docker部署
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "-m", "grid_async_task.cli"]
Kubernetes + KEDA配置
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: task-processor-scaler
spec:
scaleTargetRef:
name: task-processor
minReplicaCount: 0
maxReplicaCount: 10
triggers:
- type: rabbitmq
metadata:
host: amqp://user:password@rabbitmq:5672/
queueName: task_queue
queueLength: "1"
开发
本地开发环境
# 克隆项目
git clone https://github.com/grid/grid-async-task-plugin.git
cd grid-async-task-plugin
# 安装依赖
pip install -r requirements.txt
# 运行测试
python -m pytest tests/
# 构建包
python setup.py sdist bdist_wheel
🔧 故障排除
配置错误问题
如果看到以下错误信息:
Grid异步任务插件配置错误:缺少必需的环境变量配置
缺少的配置项:amqp_url, rabbitmq_queue, mysql_url
解决方法:
- 检查环境变量是否正确设置(注意
GRID_ASYNC_TASK_前缀) - 参考项目目录下的
env_example.txt文件配置示例 - 确认配置文件路径和格式正确
常见配置示例
# 设置环境变量方式1:导出环境变量
export GRID_ASYNC_TASK_AMQP_URL="amqp://guest:guest@localhost:5672/"
export GRID_ASYNC_TASK_QUEUE="task_queue"
export GRID_ASYNC_TASK_MYSQL_URL="mysql+pymysql://root:password@localhost:3306/task_db?charset=utf8mb4"
# 设置环境变量方式2:.env文件
echo 'GRID_ASYNC_TASK_AMQP_URL=amqp://guest:guest@localhost:5672/' > .env
echo 'GRID_ASYNC_TASK_QUEUE=task_queue' >> .env
echo 'GRID_ASYNC_TASK_MYSQL_URL=mysql+pymysql://root:password@localhost:3306/task_db?charset=utf8mb4' >> .env
连接问题排查
-
RabbitMQ连接失败
- 检查 AMQP URL 格式是否正确
- 确认 RabbitMQ 服务运行状态
- 验证用户名密码和虚拟主机配置
-
MySQL连接失败
- 检查 MySQL URL 格式和连接参数
- 确认数据库服务运行状态
- 验证数据库用户权限
-
日志查看
- 默认日志保存在
logs/目录 - 调整
GRID_ASYNC_TASK_LOG_LEVEL=DEBUG获取详细日志
- 默认日志保存在
技术支持
- 📖 查看项目 Wiki 获取更多帮助
- 🐛 遇到 Bug?请在 GitHub Issues 中报告
- 💬 讨论和建议请使用 GitHub Discussions
许可证
MIT License
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file grid_async_task-1.0.3.tar.gz.
File metadata
- Download URL: grid_async_task-1.0.3.tar.gz
- Upload date:
- Size: 33.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.10.18
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
885460d2e3636a2b78a45666d7a3c47e24a6728554bde604a08eb9ff3cc9a824
|
|
| MD5 |
5848f6c248ce15c9907ccf71c7e9a199
|
|
| BLAKE2b-256 |
9cf7e503b2e8ea7f4287f1db3df793f854c8a62c0913b37fbe212f7a4d9ee6ce
|
File details
Details for the file grid_async_task-1.0.3-py3-none-any.whl.
File metadata
- Download URL: grid_async_task-1.0.3-py3-none-any.whl
- Upload date:
- Size: 31.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.10.18
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a848db640156c8ac47fc272e1c69ae03d06c5da49a4c33c1b2c02be69ea8f22d
|
|
| MD5 |
1c0c03d7dc9184d62a490e4df0b01e57
|
|
| BLAKE2b-256 |
6c0a58f449a0f889140f3ddb19d96bcd698cb898bdc1fb0b42eb9a8bd1f88cfa
|