fast-mq-task 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fast-mq-task-1.0.0/MANIFEST.in +0 -0
- fast-mq-task-1.0.0/PKG-INFO +16 -0
- fast-mq-task-1.0.0/README.rst +5 -0
- fast-mq-task-1.0.0/fast_mq_task/__init__.py +0 -0
- fast-mq-task-1.0.0/fast_mq_task/client.py +88 -0
- fast-mq-task-1.0.0/fast_mq_task/consumer.py +98 -0
- fast-mq-task-1.0.0/fast_mq_task/decorators.py +32 -0
- fast-mq-task-1.0.0/fast_mq_task/keys.py +19 -0
- fast-mq-task-1.0.0/fast_mq_task/models.py +57 -0
- fast-mq-task-1.0.0/fast_mq_task/producer.py +28 -0
- fast-mq-task-1.0.0/fast_mq_task.egg-info/PKG-INFO +16 -0
- fast-mq-task-1.0.0/fast_mq_task.egg-info/SOURCES.txt +14 -0
- fast-mq-task-1.0.0/fast_mq_task.egg-info/dependency_links.txt +1 -0
- fast-mq-task-1.0.0/fast_mq_task.egg-info/top_level.txt +1 -0
- fast-mq-task-1.0.0/setup.cfg +4 -0
- fast-mq-task-1.0.0/setup.py +21 -0
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: fast-mq-task
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: fast rabbitmq task for zsodata
|
|
5
|
+
Home-page: http://www.zsodata.com
|
|
6
|
+
Author: zsodata
|
|
7
|
+
Author-email: team@zso.io
|
|
8
|
+
License: BSD License
|
|
9
|
+
Platform: all
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
|
+
|
|
12
|
+
pip uninstall fast-mq-task
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
pip install --upgrade fast-mq-task -i https://pypi.python.org/simple
|
|
16
|
+
pip show fast-mq-task
|
|
File without changes
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Dict, Union
|
|
4
|
+
from aio_pika import connect, Exchange, ExchangeType, Channel, Queue
|
|
5
|
+
from aio_pika.abc import AbstractExchange, AbstractQueue
|
|
6
|
+
|
|
7
|
+
from .keys import get_default_exchange_name, get_default_routing_key, get_default_queue_name
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# --------------------------
|
|
11
|
+
# 客户端基础模块 (client.py)
|
|
12
|
+
# --------------------------
|
|
13
|
+
class RabbitMQClient:
|
|
14
|
+
def __init__(self, amqp_url: str):
|
|
15
|
+
self.amqp_url = amqp_url
|
|
16
|
+
self.connection = None
|
|
17
|
+
self.channels: Dict[str, Channel] = {}
|
|
18
|
+
self.exchanges: Dict[str, Union[Exchange, AbstractExchange]] = {}
|
|
19
|
+
self.queues: Dict[str, Union[Queue, AbstractQueue]] = {}
|
|
20
|
+
|
|
21
|
+
async def connect(self):
|
|
22
|
+
self.connection = await connect(self.amqp_url)
|
|
23
|
+
return self
|
|
24
|
+
|
|
25
|
+
async def close(self):
|
|
26
|
+
for channel in self.channels.values():
|
|
27
|
+
await channel.close()
|
|
28
|
+
await self.connection.close()
|
|
29
|
+
|
|
30
|
+
async def get_channel(self, task_type: str, prefetch: int = 4) -> Channel:
|
|
31
|
+
task_type = task_type or 'default'
|
|
32
|
+
if task_type not in self.channels:
|
|
33
|
+
channel = await self.connection.channel()
|
|
34
|
+
await channel.set_qos(
|
|
35
|
+
prefetch_count=prefetch
|
|
36
|
+
)
|
|
37
|
+
logging.info(f"信道定义: task_type={task_type}, prefetch={prefetch}")
|
|
38
|
+
self.channels[task_type] = channel
|
|
39
|
+
|
|
40
|
+
return self.channels[task_type]
|
|
41
|
+
|
|
42
|
+
async def get_exchange(self, channel: Channel, exchange_name: str = None, exchange_type: str = "topic") -> Exchange:
|
|
43
|
+
# 统一交换器类型为小写
|
|
44
|
+
exchange_type = exchange_type.lower()
|
|
45
|
+
|
|
46
|
+
# 添加交换器类型校验
|
|
47
|
+
if exchange_type not in ["direct", "topic", "fanout", "headers"]:
|
|
48
|
+
raise ValueError(f"Invalid exchange type: {exchange_type}")
|
|
49
|
+
if not exchange_name:
|
|
50
|
+
exchange_name = get_default_exchange_name()
|
|
51
|
+
|
|
52
|
+
if exchange_name not in self.exchanges:
|
|
53
|
+
self.exchanges[exchange_name] = await channel.declare_exchange(
|
|
54
|
+
name=exchange_name,
|
|
55
|
+
type=exchange_type,
|
|
56
|
+
durable=True, # 持久化交换器
|
|
57
|
+
auto_delete=False,
|
|
58
|
+
arguments={
|
|
59
|
+
"x-queue-type": "quorum" # 使用高可用队列类型
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
logging.info(f"交换机定义: exchange={exchange_name}, type={exchange_type}")
|
|
63
|
+
|
|
64
|
+
return self.exchanges[exchange_name]
|
|
65
|
+
|
|
66
|
+
async def ensure_declare(self, task_type: str, prefetch: int = 10):
|
|
67
|
+
# 创建专属channel避免相互影响
|
|
68
|
+
channel = await self.get_channel(task_type, prefetch)
|
|
69
|
+
|
|
70
|
+
exchange_key = get_default_exchange_name()
|
|
71
|
+
exchange_type = ExchangeType.TOPIC
|
|
72
|
+
routing_key = get_default_routing_key(task_type)
|
|
73
|
+
queue_key = get_default_queue_name(task_type)
|
|
74
|
+
|
|
75
|
+
# 声明交换器和队列
|
|
76
|
+
exchange = await self.get_exchange(channel=channel, exchange_name=exchange_key, exchange_type=exchange_type)
|
|
77
|
+
if queue_key not in self.queues:
|
|
78
|
+
queue = await channel.declare_queue(
|
|
79
|
+
name=queue_key,
|
|
80
|
+
durable=True,
|
|
81
|
+
arguments={
|
|
82
|
+
'x-max-priority': 10 # 支持优先级队列
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
await queue.bind(exchange, routing_key)
|
|
86
|
+
self.queues[queue_key] = queue
|
|
87
|
+
|
|
88
|
+
return self.queues[queue_key], exchange
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import asyncio
|
|
3
|
+
import inspect
|
|
4
|
+
import logging
|
|
5
|
+
import traceback
|
|
6
|
+
from functools import partial
|
|
7
|
+
from typing import Callable, Dict
|
|
8
|
+
from aio_pika import IncomingMessage
|
|
9
|
+
from .client import RabbitMQClient
|
|
10
|
+
from .decorators import TaskRegistry
|
|
11
|
+
from .models import TaskMessage, RabbitMeta
|
|
12
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# --------------------------
|
|
16
|
+
# 消费者模块 (consumer.py)
|
|
17
|
+
# --------------------------
|
|
18
|
+
class TaskConsumer(RabbitMQClient):
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
amqp_url: str,
|
|
22
|
+
task_config: Dict[str, Dict] = None, # 新增任务专属配置
|
|
23
|
+
default_prefetch: int = 10
|
|
24
|
+
):
|
|
25
|
+
super().__init__(amqp_url)
|
|
26
|
+
self.task_config = task_config or {}
|
|
27
|
+
self.default_prefetch = default_prefetch
|
|
28
|
+
self._running = False
|
|
29
|
+
self._executors: Dict[str, ThreadPoolExecutor] = {} # 任务专属线程池
|
|
30
|
+
self._semaphores: Dict[str, asyncio.Semaphore] = {} # 异步信号量控制
|
|
31
|
+
|
|
32
|
+
async def _init_resources(self):
|
|
33
|
+
"""初始化每个任务的执行资源"""
|
|
34
|
+
for idx, task_type in enumerate(TaskRegistry._handlers):
|
|
35
|
+
# 创建独立线程池
|
|
36
|
+
config = self.task_config.get(task_type, {})
|
|
37
|
+
max_workers = config.get('max_workers', 4)
|
|
38
|
+
self._executors[task_type] = ThreadPoolExecutor(
|
|
39
|
+
max_workers=max_workers,
|
|
40
|
+
thread_name_prefix=f"Worker-{task_type}-"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# 创建异步信号量
|
|
44
|
+
prefetch = config.get('prefetch', self.default_prefetch)
|
|
45
|
+
self._semaphores[task_type] = asyncio.Semaphore(prefetch)
|
|
46
|
+
logger.info(f'消费者初始化[{idx}]: task_type={task_type}, prefetch={prefetch}, max_workers={max_workers}')
|
|
47
|
+
|
|
48
|
+
async def _process_message(self, rabbit_meta: RabbitMeta, msg: IncomingMessage):
|
|
49
|
+
"""处理消息的优化版本"""
|
|
50
|
+
async with msg.process():
|
|
51
|
+
try:
|
|
52
|
+
handler: Callable = rabbit_meta.handler_func
|
|
53
|
+
# 获取任务配置
|
|
54
|
+
task_type = rabbit_meta.task_type
|
|
55
|
+
semaphore = self._semaphores[task_type]
|
|
56
|
+
executor = self._executors[task_type]
|
|
57
|
+
|
|
58
|
+
# 使用专属信号量控制并发
|
|
59
|
+
async with semaphore:
|
|
60
|
+
task = TaskMessage.parse_raw(msg.body)
|
|
61
|
+
|
|
62
|
+
if inspect.iscoroutinefunction(handler):
|
|
63
|
+
# 异步任务直接await
|
|
64
|
+
await handler(task)
|
|
65
|
+
else:
|
|
66
|
+
# 同步任务放入专属线程池
|
|
67
|
+
loop = asyncio.get_event_loop()
|
|
68
|
+
await loop.run_in_executor(
|
|
69
|
+
executor,
|
|
70
|
+
partial(handler, task)
|
|
71
|
+
)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logging.error(f"处理失败: {traceback.format_exc()}")
|
|
74
|
+
|
|
75
|
+
async def _setup_queues(self):
|
|
76
|
+
"""队列初始化优化"""
|
|
77
|
+
for rabbit_meta in TaskRegistry._handlers.values():
|
|
78
|
+
queue, _ = await self.ensure_declare(task_type=rabbit_meta.task_type, prefetch=rabbit_meta.prefetch or self.default_prefetch)
|
|
79
|
+
|
|
80
|
+
# 为每个队列创建独立消费者
|
|
81
|
+
await queue.consume(
|
|
82
|
+
partial(self._process_message, rabbit_meta)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
async def start(self):
|
|
86
|
+
"""启动消费者"""
|
|
87
|
+
await self._init_resources()
|
|
88
|
+
await self._setup_queues()
|
|
89
|
+
self._running = True
|
|
90
|
+
while self._running:
|
|
91
|
+
await asyncio.sleep(1)
|
|
92
|
+
|
|
93
|
+
async def stop(self):
|
|
94
|
+
"""优雅关闭"""
|
|
95
|
+
self._running = False
|
|
96
|
+
for executor in self._executors.values():
|
|
97
|
+
executor.shutdown(wait=True)
|
|
98
|
+
await super().close()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Callable, Dict
|
|
4
|
+
from .models import RabbitMeta
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# --------------------------
|
|
8
|
+
# 装饰器模块 (decorators.py)
|
|
9
|
+
# --------------------------
|
|
10
|
+
class TaskRegistry:
|
|
11
|
+
_handlers: Dict[str, RabbitMeta] = {}
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def register(cls, task_type: str) -> Callable:
|
|
15
|
+
# 新增参数校验
|
|
16
|
+
if not task_type:
|
|
17
|
+
raise ValueError("`task_type`不能全部为空")
|
|
18
|
+
|
|
19
|
+
def decorator(func: Callable) -> Callable:
|
|
20
|
+
meta = RabbitMeta(
|
|
21
|
+
task_type=task_type,
|
|
22
|
+
handler_func=func
|
|
23
|
+
)
|
|
24
|
+
cls._handlers[task_type] = meta
|
|
25
|
+
logging.info(f"处理器注册: task_type={task_type}, exchange={meta.queue_key}, func={func}")
|
|
26
|
+
|
|
27
|
+
return func
|
|
28
|
+
|
|
29
|
+
return decorator
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
task_handler = TaskRegistry.register
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_default_exchange_name():
|
|
5
|
+
return f"{os.getenv('RABBIT_EXCHANGE_PREFIX') or 'fast'}.{os.getenv('APP_CODE') or 'default'}"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_default_routing_key(task_type: str):
|
|
9
|
+
if not task_type:
|
|
10
|
+
raise ValueError(f'task_type must not none: task_type={task_type}')
|
|
11
|
+
|
|
12
|
+
return f"{os.getenv('RABBIT_ROUTING_KEY_PREFIX') or 'fast'}.{os.getenv('APP_CODE') or 'default'}.{task_type}"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_default_queue_name(task_type: str):
|
|
16
|
+
if not task_type:
|
|
17
|
+
raise ValueError(f'task_type must not none: task_type={task_type}')
|
|
18
|
+
|
|
19
|
+
return f"{os.getenv('RABBIT_QUEUE_PREFIX') or 'fast'}.{os.getenv('APP_CODE') or 'default'}.{task_type}"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import time
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import Any, Dict, Callable
|
|
5
|
+
from aio_pika import ExchangeType
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from . import keys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# --------------------------
|
|
12
|
+
# 模型类模块 (models.py)
|
|
13
|
+
# --------------------------
|
|
14
|
+
class RabbitMeta(BaseModel):
|
|
15
|
+
"""
|
|
16
|
+
消息发布元数据
|
|
17
|
+
"""
|
|
18
|
+
# 交换机键
|
|
19
|
+
task_type: str = None
|
|
20
|
+
# 交换机键
|
|
21
|
+
exchange_key: str = None
|
|
22
|
+
# 交换机类型
|
|
23
|
+
exchange_type: str = None
|
|
24
|
+
# 绑定关系(路由)键
|
|
25
|
+
routing_key: str = None
|
|
26
|
+
# 队列名称
|
|
27
|
+
queue_key: str = None
|
|
28
|
+
# 队列名称
|
|
29
|
+
prefetch: int = None
|
|
30
|
+
# 队列名称
|
|
31
|
+
max_workers: int = None
|
|
32
|
+
|
|
33
|
+
# 处理函数
|
|
34
|
+
handler_func: Callable = None
|
|
35
|
+
|
|
36
|
+
def __init__(self, task_type: str = None, prefetch: int = None, max_workers: int = None, handler_func: Callable = None, **data: Any):
|
|
37
|
+
super().__init__(**data)
|
|
38
|
+
self.task_type = task_type
|
|
39
|
+
|
|
40
|
+
self.prefetch = prefetch
|
|
41
|
+
self.max_workers = max_workers
|
|
42
|
+
self.handler_func = handler_func
|
|
43
|
+
|
|
44
|
+
self.exchange_key = keys.get_default_exchange_name()
|
|
45
|
+
self.exchange_type = ExchangeType.TOPIC
|
|
46
|
+
self.routing_key = keys.get_default_routing_key(task_type)
|
|
47
|
+
self.queue_key = keys.get_default_queue_name(task_type)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TaskMessage(BaseModel):
|
|
51
|
+
task_type: str
|
|
52
|
+
data: Dict[str, Any]
|
|
53
|
+
task_id: str = str(uuid.uuid4())
|
|
54
|
+
created_at: int = int(time.time())
|
|
55
|
+
|
|
56
|
+
def get_routing_key(self):
|
|
57
|
+
return keys.get_default_routing_key(self.task_type)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import time
|
|
3
|
+
from aio_pika import Message, DeliveryMode
|
|
4
|
+
from .client import RabbitMQClient
|
|
5
|
+
from .models import TaskMessage
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# --------------------------
|
|
9
|
+
# 生产者模块 (producer.py)
|
|
10
|
+
# --------------------------
|
|
11
|
+
class TaskProducer(RabbitMQClient):
|
|
12
|
+
async def publish_task(self, task: TaskMessage):
|
|
13
|
+
# 确保队列定义
|
|
14
|
+
_, exchange = await self.ensure_declare(task_type=task.task_type)
|
|
15
|
+
|
|
16
|
+
message = Message(
|
|
17
|
+
body=task.json().encode(),
|
|
18
|
+
content_type="application/json",
|
|
19
|
+
headers={
|
|
20
|
+
"x-task-type": task.task_type,
|
|
21
|
+
"x-created-at": time.time()
|
|
22
|
+
},
|
|
23
|
+
delivery_mode=DeliveryMode.PERSISTENT # 添加消息持久化
|
|
24
|
+
)
|
|
25
|
+
await exchange.publish(
|
|
26
|
+
message=message,
|
|
27
|
+
routing_key=task.get_routing_key()
|
|
28
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: fast-mq-task
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: fast rabbitmq task for zsodata
|
|
5
|
+
Home-page: http://www.zsodata.com
|
|
6
|
+
Author: zsodata
|
|
7
|
+
Author-email: team@zso.io
|
|
8
|
+
License: BSD License
|
|
9
|
+
Platform: all
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
|
+
|
|
12
|
+
pip uninstall fast-mq-task
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
pip install --upgrade fast-mq-task -i https://pypi.python.org/simple
|
|
16
|
+
pip show fast-mq-task
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.rst
|
|
3
|
+
setup.py
|
|
4
|
+
fast_mq_task/__init__.py
|
|
5
|
+
fast_mq_task/client.py
|
|
6
|
+
fast_mq_task/consumer.py
|
|
7
|
+
fast_mq_task/decorators.py
|
|
8
|
+
fast_mq_task/keys.py
|
|
9
|
+
fast_mq_task/models.py
|
|
10
|
+
fast_mq_task/producer.py
|
|
11
|
+
fast_mq_task.egg-info/PKG-INFO
|
|
12
|
+
fast_mq_task.egg-info/SOURCES.txt
|
|
13
|
+
fast_mq_task.egg-info/dependency_links.txt
|
|
14
|
+
fast_mq_task.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fast_mq_task
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from distutils.core import setup
|
|
2
|
+
from setuptools import find_packages
|
|
3
|
+
|
|
4
|
+
with open('README.rst', 'r', encoding="utf-8") as f:
|
|
5
|
+
long_description = f.read()
|
|
6
|
+
|
|
7
|
+
setup(name='fast-mq-task',
|
|
8
|
+
version='1.0.0',
|
|
9
|
+
description='fast rabbitmq task for zsodata',
|
|
10
|
+
long_description=long_description,
|
|
11
|
+
author='zsodata',
|
|
12
|
+
author_email='team@zso.io',
|
|
13
|
+
url='http://www.zsodata.com',
|
|
14
|
+
install_requires=[
|
|
15
|
+
],
|
|
16
|
+
python_requires='>=3.7',
|
|
17
|
+
license='BSD License',
|
|
18
|
+
packages=find_packages(),
|
|
19
|
+
platforms=['all'],
|
|
20
|
+
include_package_data=True
|
|
21
|
+
)
|