jettask 0.2.23__py3-none-any.whl → 0.2.24__py3-none-any.whl
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.
- jettask/__init__.py +2 -0
- jettask/cli.py +12 -8
- jettask/config/lua_scripts.py +37 -0
- jettask/config/nacos_config.py +1 -1
- jettask/core/app.py +313 -340
- jettask/core/container.py +4 -4
- jettask/{persistence → core}/namespace.py +93 -27
- jettask/core/task.py +16 -9
- jettask/core/unified_manager_base.py +136 -26
- jettask/db/__init__.py +67 -0
- jettask/db/base.py +137 -0
- jettask/{utils/db_connector.py → db/connector.py} +130 -26
- jettask/db/models/__init__.py +16 -0
- jettask/db/models/scheduled_task.py +196 -0
- jettask/db/models/task.py +77 -0
- jettask/db/models/task_run.py +85 -0
- jettask/executor/__init__.py +0 -15
- jettask/executor/core.py +76 -31
- jettask/executor/process_entry.py +29 -114
- jettask/executor/task_executor.py +4 -0
- jettask/messaging/event_pool.py +928 -685
- jettask/messaging/scanner.py +30 -0
- jettask/persistence/__init__.py +28 -103
- jettask/persistence/buffer.py +170 -0
- jettask/persistence/consumer.py +330 -249
- jettask/persistence/manager.py +304 -0
- jettask/persistence/persistence.py +391 -0
- jettask/scheduler/__init__.py +15 -3
- jettask/scheduler/{task_crud.py → database.py} +61 -57
- jettask/scheduler/loader.py +2 -2
- jettask/scheduler/{scheduler_coordinator.py → manager.py} +23 -6
- jettask/scheduler/models.py +14 -10
- jettask/scheduler/schedule.py +166 -0
- jettask/scheduler/scheduler.py +12 -11
- jettask/schemas/__init__.py +50 -1
- jettask/schemas/backlog.py +43 -6
- jettask/schemas/namespace.py +70 -19
- jettask/schemas/queue.py +19 -3
- jettask/schemas/responses.py +493 -0
- jettask/task/__init__.py +0 -2
- jettask/task/router.py +3 -0
- jettask/test_connection_monitor.py +1 -1
- jettask/utils/__init__.py +7 -5
- jettask/utils/db_init.py +8 -4
- jettask/utils/namespace_dep.py +167 -0
- jettask/utils/queue_matcher.py +186 -0
- jettask/utils/rate_limit/concurrency_limiter.py +7 -1
- jettask/utils/stream_backlog.py +1 -1
- jettask/webui/__init__.py +0 -1
- jettask/webui/api/__init__.py +4 -4
- jettask/webui/api/alerts.py +806 -71
- jettask/webui/api/example_refactored.py +400 -0
- jettask/webui/api/namespaces.py +390 -45
- jettask/webui/api/overview.py +300 -54
- jettask/webui/api/queues.py +971 -267
- jettask/webui/api/scheduled.py +1249 -56
- jettask/webui/api/settings.py +129 -7
- jettask/webui/api/workers.py +442 -0
- jettask/webui/app.py +46 -2329
- jettask/webui/middleware/__init__.py +6 -0
- jettask/webui/middleware/namespace_middleware.py +135 -0
- jettask/webui/services/__init__.py +146 -0
- jettask/webui/services/heartbeat_service.py +251 -0
- jettask/webui/services/overview_service.py +60 -51
- jettask/webui/services/queue_monitor_service.py +426 -0
- jettask/webui/services/redis_monitor_service.py +87 -0
- jettask/webui/services/settings_service.py +174 -111
- jettask/webui/services/task_monitor_service.py +222 -0
- jettask/webui/services/timeline_pg_service.py +452 -0
- jettask/webui/services/timeline_service.py +189 -0
- jettask/webui/services/worker_monitor_service.py +467 -0
- jettask/webui/utils/__init__.py +11 -0
- jettask/webui/utils/time_utils.py +122 -0
- jettask/worker/lifecycle.py +8 -2
- {jettask-0.2.23.dist-info → jettask-0.2.24.dist-info}/METADATA +1 -1
- jettask-0.2.24.dist-info/RECORD +142 -0
- jettask/executor/executor.py +0 -338
- jettask/persistence/backlog_monitor.py +0 -567
- jettask/persistence/base.py +0 -2334
- jettask/persistence/db_manager.py +0 -516
- jettask/persistence/maintenance.py +0 -81
- jettask/persistence/message_consumer.py +0 -259
- jettask/persistence/models.py +0 -49
- jettask/persistence/offline_recovery.py +0 -196
- jettask/persistence/queue_discovery.py +0 -215
- jettask/persistence/task_persistence.py +0 -218
- jettask/persistence/task_updater.py +0 -583
- jettask/scheduler/add_execution_count.sql +0 -11
- jettask/scheduler/add_priority_field.sql +0 -26
- jettask/scheduler/add_scheduler_id.sql +0 -25
- jettask/scheduler/add_scheduler_id_index.sql +0 -10
- jettask/scheduler/make_scheduler_id_required.sql +0 -28
- jettask/scheduler/migrate_interval_seconds.sql +0 -9
- jettask/scheduler/performance_optimization.sql +0 -45
- jettask/scheduler/run_scheduler.py +0 -186
- jettask/scheduler/schema.sql +0 -84
- jettask/task/task_executor.py +0 -318
- jettask/webui/api/analytics.py +0 -323
- jettask/webui/config.py +0 -90
- jettask/webui/models/__init__.py +0 -3
- jettask/webui/models/namespace.py +0 -63
- jettask/webui/namespace_manager/__init__.py +0 -10
- jettask/webui/namespace_manager/multi.py +0 -593
- jettask/webui/namespace_manager/unified.py +0 -193
- jettask/webui/run.py +0 -46
- jettask-0.2.23.dist-info/RECORD +0 -145
- {jettask-0.2.23.dist-info → jettask-0.2.24.dist-info}/WHEEL +0 -0
- {jettask-0.2.23.dist-info → jettask-0.2.24.dist-info}/entry_points.txt +0 -0
- {jettask-0.2.23.dist-info → jettask-0.2.24.dist-info}/licenses/LICENSE +0 -0
- {jettask-0.2.23.dist-info → jettask-0.2.24.dist-info}/top_level.txt +0 -0
jettask/messaging/scanner.py
CHANGED
@@ -122,6 +122,36 @@ class DelayedMessageScanner:
|
|
122
122
|
|
123
123
|
logger.info(f"DelayedMessageScanner started with {len(self._scan_tasks)} scan tasks")
|
124
124
|
|
125
|
+
async def add_queues(self, queues: List[str]):
|
126
|
+
"""
|
127
|
+
动态添加队列到扫描器(用于通配符队列发现)
|
128
|
+
|
129
|
+
Args:
|
130
|
+
queues: 要添加的队列名列表
|
131
|
+
"""
|
132
|
+
if not self._running:
|
133
|
+
logger.warning("Cannot add queues: DelayedMessageScanner is not running")
|
134
|
+
return
|
135
|
+
|
136
|
+
# 记录已经存在的队列任务
|
137
|
+
existing_queues = {task.get_name().replace('_scan_queue_loop_', '')
|
138
|
+
for task in self._scan_tasks if not task.done()}
|
139
|
+
|
140
|
+
# 只为新队列创建扫描任务
|
141
|
+
new_queues = [q for q in queues if q not in existing_queues]
|
142
|
+
|
143
|
+
if not new_queues:
|
144
|
+
logger.debug(f"No new queues to add (already scanning: {existing_queues})")
|
145
|
+
return
|
146
|
+
|
147
|
+
logger.info(f"Adding {len(new_queues)} new queues to DelayedMessageScanner: {new_queues}")
|
148
|
+
|
149
|
+
for queue in new_queues:
|
150
|
+
task = asyncio.create_task(self._scan_queue_loop(queue), name=f'_scan_queue_loop_{queue}')
|
151
|
+
self._scan_tasks.append(task)
|
152
|
+
|
153
|
+
logger.info(f"DelayedMessageScanner now has {len(self._scan_tasks)} scan tasks")
|
154
|
+
|
125
155
|
async def stop(self):
|
126
156
|
"""停止扫描器"""
|
127
157
|
if not self._running:
|
jettask/persistence/__init__.py
CHANGED
@@ -1,118 +1,43 @@
|
|
1
|
-
"""PostgreSQL Consumer模块
|
1
|
+
"""PostgreSQL Consumer 模块 - V4 重构版本
|
2
2
|
|
3
|
-
|
3
|
+
基于 Jettask 通配符队列的 PostgreSQL 消费者实现。
|
4
|
+
|
5
|
+
核心特性:
|
6
|
+
- 使用通配符队列 (*) 自动发现和监听所有队列
|
7
|
+
- 独立的 TASK_CHANGES 队列处理状态更新
|
8
|
+
- 批量 INSERT 和 UPDATE 提升性能
|
9
|
+
- 支持多命名空间管理
|
4
10
|
|
5
11
|
模块结构:
|
6
|
-
- consumer.py:
|
7
|
-
-
|
8
|
-
-
|
9
|
-
-
|
10
|
-
- task_persistence.py: 任务数据持久化
|
11
|
-
- queue_discovery.py: 队列发现
|
12
|
-
- message_consumer.py: 消息消费
|
13
|
-
- maintenance.py: 数据库维护
|
12
|
+
- consumer.py: PostgreSQL 消费者(通配符队列实现)
|
13
|
+
- manager.py: 统一的消费者管理器
|
14
|
+
- buffer.py: 批量缓冲区
|
15
|
+
- persistence.py: 任务持久化逻辑
|
14
16
|
|
15
17
|
使用示例:
|
16
|
-
from jettask.
|
18
|
+
from jettask.persistence import PostgreSQLConsumer, UnifiedConsumerManager
|
17
19
|
|
18
|
-
# 使用Consumer
|
19
|
-
consumer = PostgreSQLConsumer(
|
20
|
+
# 使用 Consumer 类(单命名空间)
|
21
|
+
consumer = PostgreSQLConsumer(
|
22
|
+
pg_config={'url': 'postgresql://...'},
|
23
|
+
redis_config={'url': 'redis://...'},
|
24
|
+
prefix='jettask',
|
25
|
+
namespace_name='default'
|
26
|
+
)
|
20
27
|
await consumer.start()
|
21
28
|
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
29
|
+
# 使用 UnifiedConsumerManager(多命名空间)
|
30
|
+
manager = UnifiedConsumerManager(
|
31
|
+
task_center_url='http://localhost:8001',
|
32
|
+
check_interval=30
|
33
|
+
)
|
34
|
+
await manager.run()
|
27
35
|
"""
|
28
36
|
|
29
|
-
import asyncio
|
30
|
-
import logging
|
31
|
-
import os
|
32
|
-
|
33
|
-
from jettask.webui.config import PostgreSQLConfig, RedisConfig
|
34
|
-
# ConsumerStrategy 已移除,现在只使用 HEARTBEAT 策略
|
35
|
-
|
36
37
|
from .consumer import PostgreSQLConsumer
|
38
|
+
from .manager import UnifiedConsumerManager
|
37
39
|
|
38
|
-
logger = logging.getLogger(__name__)
|
39
|
-
|
40
|
-
# 导出主要类和函数
|
41
40
|
__all__ = [
|
42
41
|
'PostgreSQLConsumer',
|
43
|
-
'
|
44
|
-
'main'
|
42
|
+
'UnifiedConsumerManager',
|
45
43
|
]
|
46
|
-
|
47
|
-
|
48
|
-
async def run_pg_consumer(
|
49
|
-
pg_config: PostgreSQLConfig,
|
50
|
-
redis_config: RedisConfig,
|
51
|
-
# consumer_strategy 参数已移除,现在只使用 HEARTBEAT 策略
|
52
|
-
):
|
53
|
-
"""运行PostgreSQL消费者
|
54
|
-
|
55
|
-
Args:
|
56
|
-
pg_config: PostgreSQL配置
|
57
|
-
redis_config: Redis配置
|
58
|
-
consumer_strategy: 消费者策略
|
59
|
-
"""
|
60
|
-
# 从环境变量读取监控配置
|
61
|
-
enable_backlog_monitor = os.getenv('JETTASK_ENABLE_BACKLOG_MONITOR', 'true').lower() == 'true'
|
62
|
-
backlog_monitor_interval = int(os.getenv('JETTASK_BACKLOG_MONITOR_INTERVAL', '60'))
|
63
|
-
|
64
|
-
logger.info(f"Backlog monitor config: enabled={enable_backlog_monitor}, interval={backlog_monitor_interval}s")
|
65
|
-
|
66
|
-
consumer = PostgreSQLConsumer(
|
67
|
-
pg_config,
|
68
|
-
redis_config,
|
69
|
-
consumer_strategy=consumer_strategy,
|
70
|
-
enable_backlog_monitor=enable_backlog_monitor,
|
71
|
-
backlog_monitor_interval=backlog_monitor_interval
|
72
|
-
)
|
73
|
-
|
74
|
-
try:
|
75
|
-
await consumer.start()
|
76
|
-
while True:
|
77
|
-
await asyncio.sleep(1)
|
78
|
-
|
79
|
-
except KeyboardInterrupt:
|
80
|
-
logger.debug("Received interrupt signal")
|
81
|
-
finally:
|
82
|
-
await consumer.stop()
|
83
|
-
|
84
|
-
|
85
|
-
def main():
|
86
|
-
"""主入口函数(从环境变量读取配置)"""
|
87
|
-
from dotenv import load_dotenv
|
88
|
-
|
89
|
-
load_dotenv()
|
90
|
-
|
91
|
-
logging.basicConfig(
|
92
|
-
level=logging.INFO,
|
93
|
-
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
94
|
-
)
|
95
|
-
|
96
|
-
pg_config = PostgreSQLConfig(
|
97
|
-
host=os.getenv('JETTASK_PG_HOST', 'localhost'),
|
98
|
-
port=int(os.getenv('JETTASK_PG_PORT', '5432')),
|
99
|
-
database=os.getenv('JETTASK_PG_DB', 'jettask'),
|
100
|
-
user=os.getenv('JETTASK_PG_USER', 'jettask'),
|
101
|
-
password=os.getenv('JETTASK_PG_PASSWORD', '123456'),
|
102
|
-
)
|
103
|
-
|
104
|
-
redis_config = RedisConfig(
|
105
|
-
host=os.getenv('REDIS_HOST', 'localhost'),
|
106
|
-
port=int(os.getenv('REDIS_PORT', '6379')),
|
107
|
-
db=int(os.getenv('REDIS_DB', '0')),
|
108
|
-
password=os.getenv('REDIS_PASSWORD'),
|
109
|
-
)
|
110
|
-
|
111
|
-
# 使用 HEARTBEAT 策略(唯一支持的策略)
|
112
|
-
logger.debug("Using consumer strategy: HEARTBEAT")
|
113
|
-
|
114
|
-
asyncio.run(run_pg_consumer(pg_config, redis_config))
|
115
|
-
|
116
|
-
|
117
|
-
if __name__ == '__main__':
|
118
|
-
main()
|
@@ -0,0 +1,170 @@
|
|
1
|
+
"""批量缓冲区管理器
|
2
|
+
|
3
|
+
负责收集任务数据和ACK信息,批量写入数据库并ACK。
|
4
|
+
支持 INSERT 和 UPDATE 两种操作类型。
|
5
|
+
"""
|
6
|
+
|
7
|
+
import time
|
8
|
+
import asyncio
|
9
|
+
import logging
|
10
|
+
from typing import List, Dict, Any
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
class BatchBuffer:
|
16
|
+
"""批量缓冲区管理器
|
17
|
+
|
18
|
+
负责:
|
19
|
+
1. 收集任务数据和ACK信息
|
20
|
+
2. 判断是否应该刷新(批量大小或超时)
|
21
|
+
3. 批量写入数据库并ACK
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(
|
25
|
+
self,
|
26
|
+
max_size: int = 1000,
|
27
|
+
max_delay: float = 5.0,
|
28
|
+
operation_type: str = 'insert' # 'insert' 或 'update'
|
29
|
+
):
|
30
|
+
"""初始化缓冲区
|
31
|
+
|
32
|
+
Args:
|
33
|
+
max_size: 缓冲区最大容量(条数)
|
34
|
+
max_delay: 最大延迟时间(秒)
|
35
|
+
operation_type: 操作类型,'insert' 或 'update'
|
36
|
+
"""
|
37
|
+
self.max_size = max_size
|
38
|
+
self.max_delay = max_delay
|
39
|
+
self.operation_type = operation_type
|
40
|
+
|
41
|
+
# 任务数据缓冲区
|
42
|
+
self.records: List[Dict[str, Any]] = []
|
43
|
+
self.contexts: List[Any] = [] # 保存 TaskContext 用于 ACK
|
44
|
+
|
45
|
+
# 刷新控制
|
46
|
+
self.last_flush_time = time.time()
|
47
|
+
self.flush_lock = asyncio.Lock()
|
48
|
+
|
49
|
+
# 统计信息
|
50
|
+
self.total_flushed = 0
|
51
|
+
self.flush_count = 0
|
52
|
+
|
53
|
+
def add(self, record: dict, context: Any = None):
|
54
|
+
"""添加到缓冲区
|
55
|
+
|
56
|
+
Args:
|
57
|
+
record: 任务数据或更新数据
|
58
|
+
context: TaskContext(用于 ACK)
|
59
|
+
"""
|
60
|
+
self.records.append(record)
|
61
|
+
if context:
|
62
|
+
self.contexts.append(context)
|
63
|
+
|
64
|
+
def should_flush(self) -> bool:
|
65
|
+
"""判断是否应该刷新
|
66
|
+
|
67
|
+
Returns:
|
68
|
+
是否需要刷新
|
69
|
+
"""
|
70
|
+
if not self.records:
|
71
|
+
return False
|
72
|
+
|
73
|
+
# 缓冲区满了
|
74
|
+
if len(self.records) >= self.max_size:
|
75
|
+
logger.info(
|
76
|
+
f"[{self.operation_type.upper()}] 缓冲区已满 "
|
77
|
+
f"({len(self.records)}/{self.max_size}),触发刷新"
|
78
|
+
)
|
79
|
+
return True
|
80
|
+
|
81
|
+
# 超时了
|
82
|
+
elapsed = time.time() - self.last_flush_time
|
83
|
+
if elapsed >= self.max_delay:
|
84
|
+
logger.info(
|
85
|
+
f"[{self.operation_type.upper()}] 缓冲区超时 "
|
86
|
+
f"({elapsed:.1f}s >= {self.max_delay}s),触发刷新"
|
87
|
+
)
|
88
|
+
return True
|
89
|
+
|
90
|
+
return False
|
91
|
+
|
92
|
+
async def flush(self, db_manager):
|
93
|
+
"""刷新缓冲区到数据库
|
94
|
+
|
95
|
+
1. 批量写入数据库
|
96
|
+
2. 批量ACK(如果有context)
|
97
|
+
3. 清空缓冲区
|
98
|
+
|
99
|
+
Args:
|
100
|
+
db_manager: 数据库管理器,需要有 batch_insert_tasks 或 batch_update_tasks 方法
|
101
|
+
"""
|
102
|
+
async with self.flush_lock:
|
103
|
+
if not self.records:
|
104
|
+
return 0
|
105
|
+
|
106
|
+
count = len(self.records)
|
107
|
+
start_time = time.time()
|
108
|
+
|
109
|
+
try:
|
110
|
+
logger.info(f"[{self.operation_type.upper()}] 开始批量刷新 {count} 条记录...")
|
111
|
+
|
112
|
+
# 1. 批量写入数据库
|
113
|
+
if self.operation_type == 'insert':
|
114
|
+
await db_manager.batch_insert_tasks(self.records)
|
115
|
+
logger.info(f" ✓ 批量插入 {count} 条任务记录")
|
116
|
+
else: # update
|
117
|
+
print(f'{self.records=}')
|
118
|
+
await db_manager.batch_update_tasks(self.records)
|
119
|
+
logger.info(f" ✓ 批量更新 {count} 条任务状态")
|
120
|
+
|
121
|
+
# 2. 批量ACK(如果使用 Jettask 的 context)
|
122
|
+
if self.contexts:
|
123
|
+
for ctx in self.contexts:
|
124
|
+
if hasattr(ctx, 'ack'):
|
125
|
+
await ctx.ack()
|
126
|
+
logger.info(f" ✓ 批量确认 {len(self.contexts)} 条消息")
|
127
|
+
|
128
|
+
# 3. 清空缓冲区
|
129
|
+
self.records.clear()
|
130
|
+
self.contexts.clear()
|
131
|
+
self.last_flush_time = time.time()
|
132
|
+
|
133
|
+
# 4. 统计
|
134
|
+
self.total_flushed += count
|
135
|
+
self.flush_count += 1
|
136
|
+
elapsed = time.time() - start_time
|
137
|
+
|
138
|
+
logger.info(
|
139
|
+
f"[{self.operation_type.upper()}] ✓ 批量刷新完成! "
|
140
|
+
f"本次: {count}条, "
|
141
|
+
f"耗时: {elapsed:.3f}s, "
|
142
|
+
f"总计: {self.total_flushed}条 ({self.flush_count}次刷新)"
|
143
|
+
)
|
144
|
+
|
145
|
+
return count
|
146
|
+
|
147
|
+
except Exception as e:
|
148
|
+
logger.error(
|
149
|
+
f"[{self.operation_type.upper()}] ✗ 批量刷新失败: {e}",
|
150
|
+
exc_info=True
|
151
|
+
)
|
152
|
+
# 失败时清空缓冲区,避免无限重试
|
153
|
+
self.records.clear()
|
154
|
+
self.contexts.clear()
|
155
|
+
raise
|
156
|
+
|
157
|
+
def get_stats(self) -> dict:
|
158
|
+
"""获取统计信息
|
159
|
+
|
160
|
+
Returns:
|
161
|
+
统计信息字典
|
162
|
+
"""
|
163
|
+
return {
|
164
|
+
'operation_type': self.operation_type,
|
165
|
+
'current_size': len(self.records),
|
166
|
+
'max_size': self.max_size,
|
167
|
+
'total_flushed': self.total_flushed,
|
168
|
+
'flush_count': self.flush_count,
|
169
|
+
'avg_per_flush': self.total_flushed // self.flush_count if self.flush_count > 0 else 0
|
170
|
+
}
|