jettask 0.2.20__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.
Files changed (110) hide show
  1. jettask/__init__.py +4 -0
  2. jettask/cli.py +12 -8
  3. jettask/config/lua_scripts.py +37 -0
  4. jettask/config/nacos_config.py +1 -1
  5. jettask/core/app.py +313 -340
  6. jettask/core/container.py +4 -4
  7. jettask/{persistence → core}/namespace.py +93 -27
  8. jettask/core/task.py +16 -9
  9. jettask/core/unified_manager_base.py +136 -26
  10. jettask/db/__init__.py +67 -0
  11. jettask/db/base.py +137 -0
  12. jettask/{utils/db_connector.py → db/connector.py} +130 -26
  13. jettask/db/models/__init__.py +16 -0
  14. jettask/db/models/scheduled_task.py +196 -0
  15. jettask/db/models/task.py +77 -0
  16. jettask/db/models/task_run.py +85 -0
  17. jettask/executor/__init__.py +0 -15
  18. jettask/executor/core.py +76 -31
  19. jettask/executor/process_entry.py +29 -114
  20. jettask/executor/task_executor.py +4 -0
  21. jettask/messaging/event_pool.py +928 -685
  22. jettask/messaging/scanner.py +30 -0
  23. jettask/persistence/__init__.py +28 -103
  24. jettask/persistence/buffer.py +170 -0
  25. jettask/persistence/consumer.py +330 -249
  26. jettask/persistence/manager.py +304 -0
  27. jettask/persistence/persistence.py +391 -0
  28. jettask/scheduler/__init__.py +15 -3
  29. jettask/scheduler/{task_crud.py → database.py} +61 -57
  30. jettask/scheduler/loader.py +2 -2
  31. jettask/scheduler/{scheduler_coordinator.py → manager.py} +23 -6
  32. jettask/scheduler/models.py +14 -10
  33. jettask/scheduler/schedule.py +166 -0
  34. jettask/scheduler/scheduler.py +12 -11
  35. jettask/schemas/__init__.py +50 -1
  36. jettask/schemas/backlog.py +43 -6
  37. jettask/schemas/namespace.py +70 -19
  38. jettask/schemas/queue.py +19 -3
  39. jettask/schemas/responses.py +493 -0
  40. jettask/task/__init__.py +0 -2
  41. jettask/task/router.py +3 -0
  42. jettask/test_connection_monitor.py +1 -1
  43. jettask/utils/__init__.py +7 -5
  44. jettask/utils/db_init.py +8 -4
  45. jettask/utils/namespace_dep.py +167 -0
  46. jettask/utils/queue_matcher.py +186 -0
  47. jettask/utils/rate_limit/concurrency_limiter.py +7 -1
  48. jettask/utils/stream_backlog.py +1 -1
  49. jettask/webui/__init__.py +0 -1
  50. jettask/webui/api/__init__.py +4 -4
  51. jettask/webui/api/alerts.py +806 -71
  52. jettask/webui/api/example_refactored.py +400 -0
  53. jettask/webui/api/namespaces.py +390 -45
  54. jettask/webui/api/overview.py +300 -54
  55. jettask/webui/api/queues.py +971 -267
  56. jettask/webui/api/scheduled.py +1249 -56
  57. jettask/webui/api/settings.py +129 -7
  58. jettask/webui/api/workers.py +442 -0
  59. jettask/webui/app.py +46 -2329
  60. jettask/webui/middleware/__init__.py +6 -0
  61. jettask/webui/middleware/namespace_middleware.py +135 -0
  62. jettask/webui/services/__init__.py +146 -0
  63. jettask/webui/services/heartbeat_service.py +251 -0
  64. jettask/webui/services/overview_service.py +60 -51
  65. jettask/webui/services/queue_monitor_service.py +426 -0
  66. jettask/webui/services/redis_monitor_service.py +87 -0
  67. jettask/webui/services/settings_service.py +174 -111
  68. jettask/webui/services/task_monitor_service.py +222 -0
  69. jettask/webui/services/timeline_pg_service.py +452 -0
  70. jettask/webui/services/timeline_service.py +189 -0
  71. jettask/webui/services/worker_monitor_service.py +467 -0
  72. jettask/webui/utils/__init__.py +11 -0
  73. jettask/webui/utils/time_utils.py +122 -0
  74. jettask/worker/lifecycle.py +8 -2
  75. {jettask-0.2.20.dist-info → jettask-0.2.24.dist-info}/METADATA +1 -1
  76. jettask-0.2.24.dist-info/RECORD +142 -0
  77. jettask/executor/executor.py +0 -338
  78. jettask/persistence/backlog_monitor.py +0 -567
  79. jettask/persistence/base.py +0 -2334
  80. jettask/persistence/db_manager.py +0 -516
  81. jettask/persistence/maintenance.py +0 -81
  82. jettask/persistence/message_consumer.py +0 -259
  83. jettask/persistence/models.py +0 -49
  84. jettask/persistence/offline_recovery.py +0 -196
  85. jettask/persistence/queue_discovery.py +0 -215
  86. jettask/persistence/task_persistence.py +0 -218
  87. jettask/persistence/task_updater.py +0 -583
  88. jettask/scheduler/add_execution_count.sql +0 -11
  89. jettask/scheduler/add_priority_field.sql +0 -26
  90. jettask/scheduler/add_scheduler_id.sql +0 -25
  91. jettask/scheduler/add_scheduler_id_index.sql +0 -10
  92. jettask/scheduler/make_scheduler_id_required.sql +0 -28
  93. jettask/scheduler/migrate_interval_seconds.sql +0 -9
  94. jettask/scheduler/performance_optimization.sql +0 -45
  95. jettask/scheduler/run_scheduler.py +0 -186
  96. jettask/scheduler/schema.sql +0 -84
  97. jettask/task/task_executor.py +0 -318
  98. jettask/webui/api/analytics.py +0 -323
  99. jettask/webui/config.py +0 -90
  100. jettask/webui/models/__init__.py +0 -3
  101. jettask/webui/models/namespace.py +0 -63
  102. jettask/webui/namespace_manager/__init__.py +0 -10
  103. jettask/webui/namespace_manager/multi.py +0 -593
  104. jettask/webui/namespace_manager/unified.py +0 -193
  105. jettask/webui/run.py +0 -46
  106. jettask-0.2.20.dist-info/RECORD +0 -145
  107. {jettask-0.2.20.dist-info → jettask-0.2.24.dist-info}/WHEEL +0 -0
  108. {jettask-0.2.20.dist-info → jettask-0.2.24.dist-info}/entry_points.txt +0 -0
  109. {jettask-0.2.20.dist-info → jettask-0.2.24.dist-info}/licenses/LICENSE +0 -0
  110. {jettask-0.2.20.dist-info → jettask-0.2.24.dist-info}/top_level.txt +0 -0
@@ -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:
@@ -1,118 +1,43 @@
1
- """PostgreSQL Consumer模块
1
+ """PostgreSQL Consumer 模块 - V4 重构版本
2
2
 
3
- 从Redis队列消费任务并持久化到PostgreSQL数据库。
3
+ 基于 Jettask 通配符队列的 PostgreSQL 消费者实现。
4
+
5
+ 核心特性:
6
+ - 使用通配符队列 (*) 自动发现和监听所有队列
7
+ - 独立的 TASK_CHANGES 队列处理状态更新
8
+ - 批量 INSERT 和 UPDATE 提升性能
9
+ - 支持多命名空间管理
4
10
 
5
11
  模块结构:
6
- - consumer.py: 主消费者类(协调器)
7
- - backlog_monitor.py: Stream积压监控
8
- - task_updater.py: 任务状态更新
9
- - offline_recovery.py: 离线Worker恢复
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.services.pg_consumer import PostgreSQLConsumer, run_pg_consumer, main
18
+ from jettask.persistence import PostgreSQLConsumer, UnifiedConsumerManager
17
19
 
18
- # 使用Consumer
19
- consumer = PostgreSQLConsumer(pg_config, redis_config)
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
- await run_pg_consumer(pg_config, redis_config)
24
-
25
- # 或使用main函数(从环境变量读取配置)
26
- main()
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
- 'run_pg_consumer',
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
+ }