jettask 0.2.19__py3-none-any.whl → 0.2.23__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 (165) hide show
  1. jettask/__init__.py +12 -3
  2. jettask/cli.py +314 -228
  3. jettask/config/__init__.py +9 -1
  4. jettask/config/config.py +245 -0
  5. jettask/config/env_loader.py +381 -0
  6. jettask/config/lua_scripts.py +158 -0
  7. jettask/config/nacos_config.py +132 -5
  8. jettask/core/__init__.py +1 -1
  9. jettask/core/app.py +1573 -666
  10. jettask/core/app_importer.py +33 -16
  11. jettask/core/container.py +532 -0
  12. jettask/core/task.py +1 -4
  13. jettask/core/unified_manager_base.py +2 -2
  14. jettask/executor/__init__.py +38 -0
  15. jettask/executor/core.py +625 -0
  16. jettask/executor/executor.py +338 -0
  17. jettask/executor/orchestrator.py +290 -0
  18. jettask/executor/process_entry.py +638 -0
  19. jettask/executor/task_executor.py +317 -0
  20. jettask/messaging/__init__.py +68 -0
  21. jettask/messaging/event_pool.py +2188 -0
  22. jettask/messaging/reader.py +519 -0
  23. jettask/messaging/registry.py +266 -0
  24. jettask/messaging/scanner.py +369 -0
  25. jettask/messaging/sender.py +312 -0
  26. jettask/persistence/__init__.py +118 -0
  27. jettask/persistence/backlog_monitor.py +567 -0
  28. jettask/{backend/data_access.py → persistence/base.py} +58 -57
  29. jettask/persistence/consumer.py +315 -0
  30. jettask/{core → persistence}/db_manager.py +23 -22
  31. jettask/persistence/maintenance.py +81 -0
  32. jettask/persistence/message_consumer.py +259 -0
  33. jettask/{backend/namespace_data_access.py → persistence/namespace.py} +66 -98
  34. jettask/persistence/offline_recovery.py +196 -0
  35. jettask/persistence/queue_discovery.py +215 -0
  36. jettask/persistence/task_persistence.py +218 -0
  37. jettask/persistence/task_updater.py +583 -0
  38. jettask/scheduler/__init__.py +2 -2
  39. jettask/scheduler/loader.py +6 -5
  40. jettask/scheduler/run_scheduler.py +1 -1
  41. jettask/scheduler/scheduler.py +7 -7
  42. jettask/scheduler/{unified_scheduler_manager.py → scheduler_coordinator.py} +18 -13
  43. jettask/task/__init__.py +16 -0
  44. jettask/{router.py → task/router.py} +26 -8
  45. jettask/task/task_center/__init__.py +9 -0
  46. jettask/task/task_executor.py +318 -0
  47. jettask/task/task_registry.py +291 -0
  48. jettask/test_connection_monitor.py +73 -0
  49. jettask/utils/__init__.py +31 -1
  50. jettask/{monitor/run_backlog_collector.py → utils/backlog_collector.py} +1 -1
  51. jettask/utils/db_connector.py +1629 -0
  52. jettask/{db_init.py → utils/db_init.py} +1 -1
  53. jettask/utils/rate_limit/__init__.py +30 -0
  54. jettask/utils/rate_limit/concurrency_limiter.py +665 -0
  55. jettask/utils/rate_limit/config.py +145 -0
  56. jettask/utils/rate_limit/limiter.py +41 -0
  57. jettask/utils/rate_limit/manager.py +269 -0
  58. jettask/utils/rate_limit/qps_limiter.py +154 -0
  59. jettask/utils/rate_limit/task_limiter.py +384 -0
  60. jettask/utils/serializer.py +3 -0
  61. jettask/{monitor/stream_backlog_monitor.py → utils/stream_backlog.py} +14 -6
  62. jettask/utils/time_sync.py +173 -0
  63. jettask/webui/__init__.py +27 -0
  64. jettask/{api/v1 → webui/api}/alerts.py +1 -1
  65. jettask/{api/v1 → webui/api}/analytics.py +2 -2
  66. jettask/{api/v1 → webui/api}/namespaces.py +1 -1
  67. jettask/{api/v1 → webui/api}/overview.py +1 -1
  68. jettask/{api/v1 → webui/api}/queues.py +3 -3
  69. jettask/{api/v1 → webui/api}/scheduled.py +1 -1
  70. jettask/{api/v1 → webui/api}/settings.py +1 -1
  71. jettask/{api.py → webui/app.py} +253 -145
  72. jettask/webui/namespace_manager/__init__.py +10 -0
  73. jettask/{multi_namespace_consumer.py → webui/namespace_manager/multi.py} +69 -22
  74. jettask/{unified_consumer_manager.py → webui/namespace_manager/unified.py} +1 -1
  75. jettask/{run.py → webui/run.py} +2 -2
  76. jettask/{services → webui/services}/__init__.py +1 -3
  77. jettask/{services → webui/services}/overview_service.py +34 -16
  78. jettask/{services → webui/services}/queue_service.py +1 -1
  79. jettask/{backend → webui/services}/queue_stats_v2.py +1 -1
  80. jettask/{services → webui/services}/settings_service.py +1 -1
  81. jettask/worker/__init__.py +53 -0
  82. jettask/worker/lifecycle.py +1507 -0
  83. jettask/worker/manager.py +583 -0
  84. jettask/{core/offline_worker_recovery.py → worker/recovery.py} +268 -175
  85. {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/METADATA +2 -71
  86. jettask-0.2.23.dist-info/RECORD +145 -0
  87. jettask/__main__.py +0 -140
  88. jettask/api/__init__.py +0 -103
  89. jettask/backend/__init__.py +0 -1
  90. jettask/backend/api/__init__.py +0 -3
  91. jettask/backend/api/v1/__init__.py +0 -17
  92. jettask/backend/api/v1/monitoring.py +0 -431
  93. jettask/backend/api/v1/namespaces.py +0 -504
  94. jettask/backend/api/v1/queues.py +0 -342
  95. jettask/backend/api/v1/tasks.py +0 -367
  96. jettask/backend/core/__init__.py +0 -3
  97. jettask/backend/core/cache.py +0 -221
  98. jettask/backend/core/database.py +0 -200
  99. jettask/backend/core/exceptions.py +0 -102
  100. jettask/backend/dependencies.py +0 -261
  101. jettask/backend/init_meta_db.py +0 -158
  102. jettask/backend/main.py +0 -1426
  103. jettask/backend/main_unified.py +0 -78
  104. jettask/backend/main_v2.py +0 -394
  105. jettask/backend/models/__init__.py +0 -3
  106. jettask/backend/models/requests.py +0 -236
  107. jettask/backend/models/responses.py +0 -230
  108. jettask/backend/namespace_api_old.py +0 -267
  109. jettask/backend/services/__init__.py +0 -3
  110. jettask/backend/start.py +0 -42
  111. jettask/backend/unified_api_router.py +0 -1541
  112. jettask/cleanup_deprecated_tables.sql +0 -16
  113. jettask/core/consumer_manager.py +0 -1695
  114. jettask/core/delay_scanner.py +0 -256
  115. jettask/core/event_pool.py +0 -1700
  116. jettask/core/heartbeat_process.py +0 -222
  117. jettask/core/task_batch.py +0 -153
  118. jettask/core/worker_scanner.py +0 -271
  119. jettask/executors/__init__.py +0 -5
  120. jettask/executors/asyncio.py +0 -876
  121. jettask/executors/base.py +0 -30
  122. jettask/executors/common.py +0 -148
  123. jettask/executors/multi_asyncio.py +0 -309
  124. jettask/gradio_app.py +0 -570
  125. jettask/integrated_gradio_app.py +0 -1088
  126. jettask/main.py +0 -0
  127. jettask/monitoring/__init__.py +0 -3
  128. jettask/pg_consumer.py +0 -1896
  129. jettask/run_monitor.py +0 -22
  130. jettask/run_webui.py +0 -148
  131. jettask/scheduler/multi_namespace_scheduler.py +0 -294
  132. jettask/scheduler/unified_manager.py +0 -450
  133. jettask/task_center_client.py +0 -150
  134. jettask/utils/serializer_optimized.py +0 -33
  135. jettask/webui_exceptions.py +0 -67
  136. jettask-0.2.19.dist-info/RECORD +0 -150
  137. /jettask/{constants.py → config/constants.py} +0 -0
  138. /jettask/{backend/config.py → config/task_center.py} +0 -0
  139. /jettask/{pg_consumer → messaging/pg_consumer}/pg_consumer_v2.py +0 -0
  140. /jettask/{pg_consumer → messaging/pg_consumer}/sql/add_execution_time_field.sql +0 -0
  141. /jettask/{pg_consumer → messaging/pg_consumer}/sql/create_new_tables.sql +0 -0
  142. /jettask/{pg_consumer → messaging/pg_consumer}/sql/create_tables_v3.sql +0 -0
  143. /jettask/{pg_consumer → messaging/pg_consumer}/sql/migrate_to_new_structure.sql +0 -0
  144. /jettask/{pg_consumer → messaging/pg_consumer}/sql/modify_time_fields.sql +0 -0
  145. /jettask/{pg_consumer → messaging/pg_consumer}/sql_utils.py +0 -0
  146. /jettask/{models.py → persistence/models.py} +0 -0
  147. /jettask/scheduler/{manager.py → task_crud.py} +0 -0
  148. /jettask/{schema.sql → schemas/schema.sql} +0 -0
  149. /jettask/{task_center.py → task/task_center/client.py} +0 -0
  150. /jettask/{monitoring → utils}/file_watcher.py +0 -0
  151. /jettask/{services/redis_monitor_service.py → utils/redis_monitor.py} +0 -0
  152. /jettask/{api/v1 → webui/api}/__init__.py +0 -0
  153. /jettask/{webui_config.py → webui/config.py} +0 -0
  154. /jettask/{webui_models → webui/models}/__init__.py +0 -0
  155. /jettask/{webui_models → webui/models}/namespace.py +0 -0
  156. /jettask/{services → webui/services}/alert_service.py +0 -0
  157. /jettask/{services → webui/services}/analytics_service.py +0 -0
  158. /jettask/{services → webui/services}/scheduled_task_service.py +0 -0
  159. /jettask/{services → webui/services}/task_service.py +0 -0
  160. /jettask/{webui_sql → webui/sql}/batch_upsert_functions.sql +0 -0
  161. /jettask/{webui_sql → webui/sql}/verify_database.sql +0 -0
  162. {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/WHEEL +0 -0
  163. {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/entry_points.txt +0 -0
  164. {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/licenses/LICENSE +0 -0
  165. {jettask-0.2.19.dist-info → jettask-0.2.23.dist-info}/top_level.txt +0 -0
@@ -10,8 +10,13 @@ from datetime import datetime, timedelta, timezone
10
10
  from typing import Dict, List, Optional, Tuple
11
11
  import redis.asyncio as redis
12
12
  from sqlalchemy import text, bindparam
13
- from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
14
- from sqlalchemy.orm import sessionmaker
13
+ from sqlalchemy.ext.asyncio import AsyncSession
14
+
15
+ # 导入统一的数据库连接工具
16
+ from ..utils.db_connector import (
17
+ get_dual_mode_async_redis_client,
18
+ get_pg_engine_and_factory
19
+ )
15
20
 
16
21
  # 设置日志
17
22
  logger = logging.getLogger(__name__)
@@ -62,67 +67,60 @@ class PostgreSQLConfig:
62
67
 
63
68
 
64
69
  class JetTaskDataAccess:
65
- """JetTask数据访问类"""
66
-
70
+ """JetTask数据访问类(使用统一的数据库连接工具)"""
71
+
67
72
  def __init__(self):
68
73
  self.redis_config = RedisConfig.from_env()
69
74
  self.pg_config = PostgreSQLConfig.from_env()
70
75
  # Redis前缀可以从环境变量配置,默认为 "jettask"
71
76
  self.redis_prefix = os.environ.get('JETTASK_REDIS_PREFIX', 'jettask')
77
+
78
+ # 使用全局单例连接池
79
+ self._text_redis_client: Optional[redis.Redis] = None
80
+ self._binary_redis_client: Optional[redis.Redis] = None
81
+
82
+ # PostgreSQL 相关
72
83
  self.async_engine = None
73
84
  self.AsyncSessionLocal = None
74
- self._redis_pool = None
75
- self._binary_redis_pool = None # 用于处理包含二进制数据的Stream
76
85
 
77
86
  async def initialize(self):
78
- """初始化数据库连接"""
87
+ """初始化数据库连接(使用全局单例)"""
79
88
  try:
80
- # 初始化PostgreSQL引擎
81
- dsn = self.pg_config.dsn
82
- if dsn.startswith('postgresql://'):
83
- dsn = dsn.replace('postgresql://', 'postgresql+psycopg://', 1)
84
-
85
- self.async_engine = create_async_engine(
86
- dsn,
89
+ # 构建 Redis URL
90
+ redis_url = f"redis://"
91
+ if self.redis_config.password:
92
+ redis_url += f":{self.redis_config.password}@"
93
+ redis_url += f"{self.redis_config.host}:{self.redis_config.port}/{self.redis_config.db}"
94
+
95
+ # 构建 PostgreSQL 配置
96
+ pg_config = {
97
+ 'host': self.pg_config.host,
98
+ 'port': self.pg_config.port,
99
+ 'user': self.pg_config.user,
100
+ 'password': self.pg_config.password,
101
+ 'database': self.pg_config.database
102
+ }
103
+
104
+ # 初始化 PostgreSQL 连接(使用全局单例)
105
+ self.async_engine, self.AsyncSessionLocal = get_pg_engine_and_factory(
106
+ config=pg_config,
87
107
  pool_size=10,
88
108
  max_overflow=5,
89
109
  pool_pre_ping=True,
90
110
  echo=False
91
111
  )
92
-
93
- self.AsyncSessionLocal = sessionmaker(
94
- bind=self.async_engine,
95
- class_=AsyncSession,
96
- expire_on_commit=False
97
- )
98
-
99
- # 初始化Redis连接池(用于普通操作)
100
- self._redis_pool = redis.ConnectionPool(
101
- host=self.redis_config.host,
102
- port=self.redis_config.port,
103
- db=self.redis_config.db,
104
- password=self.redis_config.password,
105
- encoding='utf-8',
106
- decode_responses=True,
107
- socket_keepalive=True,
108
- socket_connect_timeout=5,
109
- retry_on_timeout=True
110
- )
111
-
112
- # 初始化二进制Redis连接池(用于Stream操作)
113
- self._binary_redis_pool = redis.ConnectionPool(
114
- host=self.redis_config.host,
115
- port=self.redis_config.port,
116
- db=self.redis_config.db,
117
- password=self.redis_config.password,
118
- decode_responses=False, # 不解码,因为Stream包含msgpack二进制数据
112
+
113
+ # 初始化 Redis 连接(使用全局单例,双模式)
114
+ self._text_redis_client, self._binary_redis_client = get_dual_mode_async_redis_client(
115
+ redis_url=redis_url,
116
+ max_connections=50,
119
117
  socket_keepalive=True,
120
118
  socket_connect_timeout=5,
121
119
  retry_on_timeout=True
122
120
  )
123
-
121
+
124
122
  logger.info("数据库连接初始化成功")
125
-
123
+
126
124
  except Exception as e:
127
125
  logger.error(f"数据库连接初始化失败: {e}")
128
126
  raise
@@ -132,21 +130,24 @@ class JetTaskDataAccess:
132
130
  return self.AsyncSessionLocal()
133
131
 
134
132
  async def close(self):
135
- """关闭数据库连接"""
136
- if self.async_engine:
137
- await self.async_engine.dispose()
138
- if self._redis_pool:
139
- await self._redis_pool.disconnect()
140
- if self._binary_redis_pool:
141
- await self._binary_redis_pool.disconnect()
142
-
133
+ """关闭数据库连接(由于使用全局单例,这里只重置状态)"""
134
+ # 注意:连接池由全局单例管理,这里只清理引用
135
+ self._text_redis_client = None
136
+ self._binary_redis_client = None
137
+ self.async_engine = None
138
+ self.AsyncSessionLocal = None
139
+
143
140
  async def get_redis_client(self):
144
- """获取Redis客户端"""
145
- return redis.Redis(connection_pool=self._redis_pool)
146
-
141
+ """获取 Redis 客户端(使用全局单例)"""
142
+ if not self._text_redis_client:
143
+ raise RuntimeError("Redis client not initialized")
144
+ return self._text_redis_client
145
+
147
146
  async def get_binary_redis_client(self):
148
- """获取二进制Redis客户端(用于Stream操作)"""
149
- return redis.Redis(connection_pool=self._binary_redis_pool)
147
+ """获取二进制 Redis 客户端(用于Stream操作,使用全局单例)"""
148
+ if not self._binary_redis_client:
149
+ raise RuntimeError("Binary Redis client not initialized")
150
+ return self._binary_redis_client
150
151
 
151
152
  async def fetch_queues_data(self) -> List[Dict]:
152
153
  """获取队列数据(基于Redis Stream)"""
@@ -2046,7 +2047,7 @@ class JetTaskDataAccess:
2046
2047
  async def _sync_task_to_redis(self, task_id: str, enabled: bool):
2047
2048
  """同步任务状态到 Redis"""
2048
2049
  try:
2049
- if not self._redis_pool:
2050
+ if not self._redis_connector:
2050
2051
  logger.debug("Redis not configured, skipping sync")
2051
2052
  return
2052
2053
 
@@ -0,0 +1,315 @@
1
+ """PostgreSQL消费者主模块
2
+
3
+ 协调各个子模块,提供统一的消费者接口。
4
+ """
5
+
6
+ import asyncio
7
+ import logging
8
+ import os
9
+ import socket
10
+ import traceback
11
+ from typing import Optional
12
+
13
+ import redis.asyncio as redis
14
+ from redis.asyncio import Redis
15
+ from sqlalchemy.ext.asyncio import create_async_engine
16
+ from sqlalchemy.orm import sessionmaker
17
+ from sqlalchemy.ext.asyncio import AsyncSession
18
+ from sqlalchemy import text
19
+
20
+ from jettask.webui.config import PostgreSQLConfig, RedisConfig
21
+ from jettask.worker.manager import ConsumerManager
22
+
23
+ from .backlog_monitor import BacklogMonitor
24
+ from .task_updater import TaskUpdater
25
+ from .offline_recovery import OfflineRecoveryHandler
26
+ from .task_persistence import TaskPersistence
27
+ from .queue_discovery import QueueDiscovery
28
+ from .message_consumer import MessageConsumer
29
+ from .maintenance import DatabaseMaintenance
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ class PostgreSQLConsumer:
35
+ """PostgreSQL消费者,从Redis队列消费任务并持久化到PostgreSQL
36
+
37
+ 支持多租户(命名空间)隔离
38
+
39
+ 架构说明:
40
+ - BacklogMonitor: 监控Stream积压情况
41
+ - TaskUpdater: 更新任务状态(从TASK_CHANGES流)
42
+ - OfflineRecoveryHandler: 恢复离线worker的消息
43
+ - TaskPersistence: 解析并持久化任务数据
44
+ - QueueDiscovery: 发现和管理队列
45
+ - MessageConsumer: 消费队列消息
46
+ - DatabaseMaintenance: 数据库维护任务
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ pg_config: PostgreSQLConfig,
52
+ redis_config: RedisConfig,
53
+ prefix: str = "jettask",
54
+ node_id: str = None,
55
+ # consumer_strategy 参数已移除,现在只使用 HEARTBEAT 策略
56
+ namespace_id: str = None,
57
+ namespace_name: str = None,
58
+ enable_backlog_monitor: bool = True,
59
+ backlog_monitor_interval: int = 1
60
+ ):
61
+ """初始化PostgreSQL消费者
62
+
63
+ Args:
64
+ pg_config: PostgreSQL配置
65
+ redis_config: Redis配置
66
+ prefix: Redis键前缀
67
+ node_id: 节点ID
68
+ consumer_strategy: 消费者策略
69
+ namespace_id: 命名空间ID
70
+ namespace_name: 命名空间名称
71
+ enable_backlog_monitor: 是否启用积压监控
72
+ backlog_monitor_interval: 积压监控间隔(秒)
73
+ """
74
+ self.pg_config = pg_config
75
+ self.redis_config = redis_config
76
+ self.prefix = prefix
77
+
78
+ # 命名空间支持
79
+ self.namespace_id = namespace_id
80
+ self.namespace_name = namespace_name or "default"
81
+
82
+ # 节点标识
83
+ hostname = socket.gethostname()
84
+ self.node_id = node_id or f"{hostname}_{os.getpid()}"
85
+
86
+ # 消费者配置
87
+ # consumer_strategy 已移除,现在只使用 HEARTBEAT 策略
88
+ self.consumer_group = f"{prefix}_pg_consumer"
89
+
90
+ # Redis和数据库连接(将在start时初始化)
91
+ self.redis_client: Optional[Redis] = None
92
+ self.async_engine = None
93
+ self.AsyncSessionLocal = None
94
+
95
+ # ConsumerManager(将在start时初始化)
96
+ self.consumer_manager = None
97
+ self.consumer_id = None
98
+
99
+ # 各个子模块(将在start时初始化)
100
+ self.backlog_monitor: Optional[BacklogMonitor] = None
101
+ self.task_updater: Optional[TaskUpdater] = None
102
+ self.offline_recovery: Optional[OfflineRecoveryHandler] = None
103
+ self.task_persistence: Optional[TaskPersistence] = None
104
+ self.queue_discovery: Optional[QueueDiscovery] = None
105
+ self.message_consumer: Optional[MessageConsumer] = None
106
+ self.database_maintenance: Optional[DatabaseMaintenance] = None
107
+
108
+ # 积压监控配置
109
+ self.enable_backlog_monitor = enable_backlog_monitor
110
+ self.backlog_monitor_interval = backlog_monitor_interval
111
+
112
+ self._running = False
113
+
114
+ async def start(self):
115
+ """启动消费者"""
116
+ logger.info(f"Starting PostgreSQL consumer (modular) on node: {self.node_id}")
117
+
118
+ # 1. 连接Redis(使用全局客户端实例)
119
+ from jettask.utils.db_connector import get_async_redis_client, get_sync_redis_client
120
+
121
+ # 构建 Redis URL
122
+ redis_url = f"redis://"
123
+ if self.redis_config.password:
124
+ redis_url += f":{self.redis_config.password}@"
125
+ redis_url += f"{self.redis_config.host}:{self.redis_config.port}/{self.redis_config.db}"
126
+
127
+ self.redis_client = get_async_redis_client(
128
+ redis_url=redis_url,
129
+ decode_responses=False # 保持二进制模式
130
+ )
131
+
132
+ # 2. 初始化ConsumerManager(需要同步的Redis客户端)
133
+ sync_redis_client = get_sync_redis_client(
134
+ redis_url=redis_url,
135
+ decode_responses=True
136
+ )
137
+
138
+ # 配置ConsumerManager
139
+ initial_queues = ['TASK_CHANGES'] # TASK_CHANGES是固定的
140
+ consumer_config = {
141
+ 'redis_prefix': self.prefix,
142
+ 'queues': initial_queues,
143
+ 'worker_prefix': 'PG_CONSUMER', # 使用不同的前缀,与task worker区分开
144
+ }
145
+
146
+ self.consumer_manager = ConsumerManager(
147
+ redis_client=sync_redis_client,
148
+ # strategy 参数已移除,现在只使用 HEARTBEAT 策略
149
+ config=consumer_config
150
+ )
151
+
152
+ # 获取稳定的consumer_id
153
+ self.consumer_id = self.consumer_manager.get_consumer_name('TASK_CHANGES')
154
+ logger.debug(f"Using consumer_id: {self.consumer_id} with strategy: HEARTBEAT")
155
+
156
+ # 3. 创建SQLAlchemy异步引擎
157
+ if self.pg_config.dsn.startswith('postgresql://'):
158
+ dsn = self.pg_config.dsn.replace('postgresql://', 'postgresql+asyncpg://', 1)
159
+ else:
160
+ dsn = self.pg_config.dsn
161
+
162
+ self.async_engine = create_async_engine(
163
+ dsn,
164
+ pool_size=50,
165
+ max_overflow=20,
166
+ pool_pre_ping=True,
167
+ pool_recycle=300,
168
+ echo=False
169
+ )
170
+
171
+ # 预热连接池
172
+ logger.debug("Pre-warming database connection pool...")
173
+ async with self.async_engine.begin() as conn:
174
+ await conn.execute(text("SELECT 1"))
175
+
176
+ # 创建异步会话工厂
177
+ self.AsyncSessionLocal = sessionmaker(
178
+ self.async_engine,
179
+ class_=AsyncSession,
180
+ expire_on_commit=False
181
+ )
182
+
183
+ # 4. 初始化各个子模块
184
+ # 任务持久化处理器
185
+ self.task_persistence = TaskPersistence(
186
+ async_session_local=self.AsyncSessionLocal,
187
+ namespace_id=self.namespace_id,
188
+ namespace_name=self.namespace_name
189
+ )
190
+
191
+ # 队列发现器
192
+ self.queue_discovery = QueueDiscovery(
193
+ redis_client=self.redis_client,
194
+ redis_prefix=self.prefix,
195
+ consumer_group=self.consumer_group,
196
+ consumer_manager=self.consumer_manager
197
+ )
198
+
199
+ # 先进行一次队列发现,确保ConsumerManager有正确的队列列表
200
+ await self.queue_discovery.initial_queue_discovery()
201
+
202
+ # 消息消费器
203
+ self.message_consumer = MessageConsumer(
204
+ redis_client=self.redis_client,
205
+ redis_prefix=self.prefix,
206
+ consumer_group=self.consumer_group,
207
+ consumer_id=self.consumer_id,
208
+ task_persistence=self.task_persistence,
209
+ queue_discovery=self.queue_discovery
210
+ )
211
+
212
+ # 任务状态更新器
213
+ self.task_updater = TaskUpdater(
214
+ redis_client=self.redis_client,
215
+ async_session_local=self.AsyncSessionLocal,
216
+ redis_prefix=self.prefix,
217
+ consumer_id=self.consumer_id
218
+ )
219
+
220
+ # 离线恢复处理器
221
+ self.offline_recovery = OfflineRecoveryHandler(
222
+ redis_client=self.redis_client,
223
+ redis_prefix=self.prefix,
224
+ consumer_id=self.consumer_id,
225
+ task_updater=self.task_updater
226
+ )
227
+ # 延迟初始化(需要consumer_manager)
228
+ self.offline_recovery.set_consumer_manager(self.consumer_manager)
229
+
230
+ # 数据库维护
231
+ self.database_maintenance = DatabaseMaintenance(
232
+ async_session_local=self.AsyncSessionLocal
233
+ )
234
+
235
+ # 积压监控器
236
+ self.backlog_monitor = BacklogMonitor(
237
+ redis_client=self.redis_client,
238
+ async_session_local=self.AsyncSessionLocal,
239
+ redis_prefix=self.prefix,
240
+ namespace_name=self.namespace_name,
241
+ node_id=self.node_id,
242
+ enable_monitor=self.enable_backlog_monitor,
243
+ monitor_interval=self.backlog_monitor_interval
244
+ )
245
+
246
+ # 5. 启动所有子模块
247
+ self._running = True
248
+
249
+ # 启动队列发现
250
+ await self.queue_discovery.start_discovery()
251
+
252
+ # 启动消息消费
253
+ await self.message_consumer.start()
254
+
255
+ # 启动任务状态更新
256
+ await self.task_updater.start()
257
+
258
+ # 启动离线恢复
259
+ await self.offline_recovery.start()
260
+
261
+ # 启动数据库维护
262
+ await self.database_maintenance.start()
263
+
264
+ # 启动积压监控
265
+ if self.enable_backlog_monitor:
266
+ await self.backlog_monitor.start()
267
+ logger.info(f"Stream backlog monitor enabled with {self.backlog_monitor_interval}s interval")
268
+
269
+ # 如果使用HEARTBEAT策略,ConsumerManager会自动管理心跳
270
+ if self.consumer_manager:
271
+ logger.debug("Heartbeat is managed by ConsumerManager")
272
+
273
+ logger.debug("PostgreSQL consumer started successfully")
274
+
275
+ async def stop(self):
276
+ """停止消费者"""
277
+ logger.debug("Stopping PostgreSQL consumer...")
278
+ self._running = False
279
+
280
+ # 停止所有子模块
281
+ if self.backlog_monitor:
282
+ await self.backlog_monitor.stop()
283
+
284
+ if self.database_maintenance:
285
+ await self.database_maintenance.stop()
286
+
287
+ if self.offline_recovery:
288
+ await self.offline_recovery.stop()
289
+
290
+ if self.task_updater:
291
+ await self.task_updater.stop()
292
+
293
+ if self.message_consumer:
294
+ await self.message_consumer.stop()
295
+
296
+ if self.queue_discovery:
297
+ await self.queue_discovery.stop_discovery()
298
+
299
+ # 清理ConsumerManager
300
+ if self.consumer_manager:
301
+ try:
302
+ self.consumer_manager.cleanup()
303
+ logger.debug(f"Cleaned up ConsumerManager for consumer: {self.consumer_id}")
304
+ except Exception as e:
305
+ logger.error(f"Error cleaning up ConsumerManager: {e}")
306
+ logger.error(traceback.format_exc())
307
+
308
+ # 关闭连接
309
+ if self.redis_client:
310
+ await self.redis_client.close()
311
+
312
+ if self.async_engine:
313
+ await self.async_engine.dispose()
314
+
315
+ logger.debug("PostgreSQL consumer stopped")
@@ -55,8 +55,8 @@ class ConnectionPool:
55
55
  self.config = config
56
56
 
57
57
  # Redis连接池
58
- self._redis_pool: Optional[redis.ConnectionPool] = None
59
- self._binary_redis_pool: Optional[redis.ConnectionPool] = None
58
+ self._redis_client: Optional[redis.Redis] = None
59
+ self._binary_redis_client: Optional[redis.Redis] = None
60
60
 
61
61
  # PostgreSQL连接池
62
62
  self._pg_pool: Optional[asyncpg.Pool] = None
@@ -92,25 +92,26 @@ class ConnectionPool:
92
92
  raise
93
93
 
94
94
  async def _init_redis(self):
95
- """初始化Redis连接池"""
95
+ """初始化Redis客户端(使用全局客户端实例)"""
96
+ from jettask.utils.db_connector import get_async_redis_client
97
+
96
98
  url = self.config.redis_url
97
-
98
- # 文本连接池(decode_responses=True)
99
- self._redis_pool = redis.ConnectionPool.from_url(
100
- url,
99
+
100
+ # 文本客户端(decode_responses=True)
101
+ self._redis_client = get_async_redis_client(
102
+ redis_url=url,
101
103
  max_connections=20,
102
- decode_responses=True,
103
- encoding='utf-8'
104
+ decode_responses=True
104
105
  )
105
-
106
- # 二进制连接池(decode_responses=False)
107
- self._binary_redis_pool = redis.ConnectionPool.from_url(
108
- url,
106
+
107
+ # 二进制客户端(decode_responses=False)
108
+ self._binary_redis_client = get_async_redis_client(
109
+ redis_url=url,
109
110
  max_connections=20,
110
111
  decode_responses=False
111
112
  )
112
-
113
- logger.debug(f"Redis连接池已创建: {self.namespace}")
113
+
114
+ logger.debug(f"Redis客户端已就绪: {self.namespace}")
114
115
 
115
116
  async def _init_postgres(self):
116
117
  """初始化PostgreSQL连接池"""
@@ -160,8 +161,8 @@ class ConnectionPool:
160
161
  if not self.config.has_redis():
161
162
  raise ValueError(f"命名空间 '{self.namespace}' 未配置Redis")
162
163
 
163
- pool = self._redis_pool if decode else self._binary_redis_pool
164
- return redis.Redis(connection_pool=pool)
164
+ client = self._redis_client if decode else self._binary_redis_client
165
+ return client
165
166
 
166
167
  @asynccontextmanager
167
168
  async def get_pg_connection(self) -> AsyncGenerator[asyncpg.Connection, None]:
@@ -197,11 +198,11 @@ class ConnectionPool:
197
198
  async def close(self):
198
199
  """关闭所有连接"""
199
200
  try:
200
- if self._redis_pool:
201
- await self._redis_pool.aclose()
202
-
203
- if self._binary_redis_pool:
204
- await self._binary_redis_pool.aclose()
201
+ if self._redis_client:
202
+ await self._redis_client.aclose()
203
+
204
+ if self._binary_redis_client:
205
+ await self._binary_redis_client.aclose()
205
206
 
206
207
  if self._pg_pool:
207
208
  await self._pg_pool.close()
@@ -0,0 +1,81 @@
1
+ """数据库维护模块
2
+
3
+ 负责定期执行数据库维护任务,如ANALYZE等。
4
+ """
5
+
6
+ import asyncio
7
+ import logging
8
+ import time
9
+ import traceback
10
+
11
+ from sqlalchemy import text
12
+ from sqlalchemy.orm import sessionmaker
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class DatabaseMaintenance:
18
+ """数据库维护处理器
19
+
20
+ 职责:
21
+ - 定期执行ANALYZE命令更新统计信息
22
+ - 优化查询性能
23
+ """
24
+
25
+ def __init__(self, async_session_local: sessionmaker):
26
+ """初始化数据库维护处理器
27
+
28
+ Args:
29
+ async_session_local: SQLAlchemy会话工厂
30
+ """
31
+ self.AsyncSessionLocal = async_session_local
32
+
33
+ # 维护配置
34
+ self.analyze_interval = 7200 # 每2小时执行一次ANALYZE
35
+ self.last_analyze_time = 0
36
+
37
+ self._running = False
38
+ self._maintenance_task = None
39
+
40
+ async def start(self):
41
+ """启动维护任务"""
42
+ self._running = True
43
+ self._maintenance_task = asyncio.create_task(self._maintenance_loop())
44
+ logger.debug("DatabaseMaintenance started")
45
+
46
+ async def stop(self):
47
+ """停止维护任务"""
48
+ self._running = False
49
+
50
+ if self._maintenance_task:
51
+ self._maintenance_task.cancel()
52
+ try:
53
+ await self._maintenance_task
54
+ except asyncio.CancelledError:
55
+ pass
56
+
57
+ logger.debug("DatabaseMaintenance stopped")
58
+
59
+ async def _maintenance_loop(self):
60
+ """维护循环"""
61
+ while self._running:
62
+ try:
63
+ current_time = time.time()
64
+
65
+ # 执行ANALYZE
66
+ if current_time - self.last_analyze_time > self.analyze_interval:
67
+ async with self.AsyncSessionLocal() as session:
68
+ logger.debug("Running ANALYZE on tasks and task_runs tables...")
69
+ await session.execute(text("ANALYZE tasks"))
70
+ await session.execute(text("ANALYZE task_runs"))
71
+ await session.commit()
72
+ logger.debug("ANALYZE completed successfully for both tables")
73
+ self.last_analyze_time = current_time
74
+
75
+ # 每5分钟检查一次
76
+ await asyncio.sleep(300)
77
+
78
+ except Exception as e:
79
+ logger.error(f"Error in database maintenance: {e}")
80
+ logger.error(traceback.format_exc())
81
+ await asyncio.sleep(60)