jettask 0.2.1__py3-none-any.whl → 0.2.4__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/constants.py +213 -0
- jettask/core/app.py +525 -205
- jettask/core/cli.py +193 -185
- jettask/core/consumer_manager.py +126 -34
- jettask/core/context.py +3 -0
- jettask/core/enums.py +137 -0
- jettask/core/event_pool.py +501 -168
- jettask/core/message.py +147 -0
- jettask/core/offline_worker_recovery.py +181 -114
- jettask/core/task.py +10 -174
- jettask/core/task_batch.py +153 -0
- jettask/core/unified_manager_base.py +243 -0
- jettask/core/worker_scanner.py +54 -54
- jettask/executors/asyncio.py +184 -64
- jettask/webui/backend/config.py +51 -0
- jettask/webui/backend/data_access.py +2083 -92
- jettask/webui/backend/data_api.py +3294 -0
- jettask/webui/backend/dependencies.py +261 -0
- jettask/webui/backend/init_meta_db.py +158 -0
- jettask/webui/backend/main.py +1358 -69
- jettask/webui/backend/main_unified.py +78 -0
- jettask/webui/backend/main_v2.py +394 -0
- jettask/webui/backend/namespace_api.py +295 -0
- jettask/webui/backend/namespace_api_old.py +294 -0
- jettask/webui/backend/namespace_data_access.py +611 -0
- jettask/webui/backend/queue_backlog_api.py +727 -0
- jettask/webui/backend/queue_stats_v2.py +521 -0
- jettask/webui/backend/redis_monitor_api.py +476 -0
- jettask/webui/backend/unified_api_router.py +1601 -0
- jettask/webui/db_init.py +204 -32
- jettask/webui/frontend/package-lock.json +492 -1
- jettask/webui/frontend/package.json +4 -1
- jettask/webui/frontend/src/App.css +105 -7
- jettask/webui/frontend/src/App.jsx +49 -20
- jettask/webui/frontend/src/components/NamespaceSelector.jsx +166 -0
- jettask/webui/frontend/src/components/QueueBacklogChart.jsx +298 -0
- jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +638 -0
- jettask/webui/frontend/src/components/QueueDetailsTable.css +65 -0
- jettask/webui/frontend/src/components/QueueDetailsTable.jsx +487 -0
- jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +465 -0
- jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +423 -0
- jettask/webui/frontend/src/components/TaskFilter.jsx +425 -0
- jettask/webui/frontend/src/components/TimeRangeSelector.css +21 -0
- jettask/webui/frontend/src/components/TimeRangeSelector.jsx +160 -0
- jettask/webui/frontend/src/components/layout/AppLayout.css +95 -0
- jettask/webui/frontend/src/components/layout/AppLayout.jsx +49 -0
- jettask/webui/frontend/src/components/layout/Header.css +34 -10
- jettask/webui/frontend/src/components/layout/Header.jsx +31 -23
- jettask/webui/frontend/src/components/layout/SideMenu.css +137 -0
- jettask/webui/frontend/src/components/layout/SideMenu.jsx +209 -0
- jettask/webui/frontend/src/components/layout/TabsNav.css +244 -0
- jettask/webui/frontend/src/components/layout/TabsNav.jsx +206 -0
- jettask/webui/frontend/src/components/layout/UserInfo.css +197 -0
- jettask/webui/frontend/src/components/layout/UserInfo.jsx +197 -0
- jettask/webui/frontend/src/contexts/NamespaceContext.jsx +72 -0
- jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +245 -0
- jettask/webui/frontend/src/main.jsx +1 -0
- jettask/webui/frontend/src/pages/Alerts.jsx +684 -0
- jettask/webui/frontend/src/pages/Dashboard.jsx +1330 -0
- jettask/webui/frontend/src/pages/QueueDetail.jsx +1109 -10
- jettask/webui/frontend/src/pages/QueueMonitor.jsx +236 -115
- jettask/webui/frontend/src/pages/Queues.jsx +5 -1
- jettask/webui/frontend/src/pages/ScheduledTasks.jsx +809 -0
- jettask/webui/frontend/src/pages/Settings.jsx +800 -0
- jettask/webui/frontend/src/services/api.js +7 -5
- jettask/webui/frontend/src/utils/suppressWarnings.js +22 -0
- jettask/webui/frontend/src/utils/userPreferences.js +154 -0
- jettask/webui/multi_namespace_consumer.py +543 -0
- jettask/webui/pg_consumer.py +983 -246
- jettask/webui/static/dist/assets/index-7129cfe1.css +1 -0
- jettask/webui/static/dist/assets/index-8d1935cc.js +774 -0
- jettask/webui/static/dist/index.html +2 -2
- jettask/webui/task_center.py +216 -0
- jettask/webui/task_center_client.py +150 -0
- jettask/webui/unified_consumer_manager.py +193 -0
- {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/METADATA +1 -1
- jettask-0.2.4.dist-info/RECORD +134 -0
- jettask/webui/pg_consumer_slow.py +0 -1099
- jettask/webui/pg_consumer_test.py +0 -678
- jettask/webui/static/dist/assets/index-823408e8.css +0 -1
- jettask/webui/static/dist/assets/index-9968b0b8.js +0 -543
- jettask/webui/test_pg_consumer_recovery.py +0 -547
- jettask/webui/test_recovery_simple.py +0 -492
- jettask/webui/test_self_recovery.py +0 -467
- jettask-0.2.1.dist-info/RECORD +0 -91
- {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/WHEEL +0 -0
- {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/entry_points.txt +0 -0
- {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/licenses/LICENSE +0 -0
- {jettask-0.2.1.dist-info → jettask-0.2.4.dist-info}/top_level.txt +0 -0
jettask/core/app.py
CHANGED
@@ -107,7 +107,7 @@ def get_async_redis_pool(redis_url: str, max_connections: int = 200):
|
|
107
107
|
return _async_redis_pools[redis_url]
|
108
108
|
|
109
109
|
def get_binary_redis_pool(redis_url: str, max_connections: int = 200):
|
110
|
-
"""获取或创建用于二进制数据的Redis
|
110
|
+
"""获取或创建用于二进制数据的Redis连接池(Stream操作需要)"""
|
111
111
|
if redis_url not in _binary_redis_pools:
|
112
112
|
# 构建socket keepalive选项,仅在Linux上使用
|
113
113
|
socket_keepalive_options = {}
|
@@ -120,7 +120,7 @@ def get_binary_redis_pool(redis_url: str, max_connections: int = 200):
|
|
120
120
|
|
121
121
|
_binary_redis_pools[redis_url] = redis.ConnectionPool.from_url(
|
122
122
|
redis_url,
|
123
|
-
decode_responses=False, #
|
123
|
+
decode_responses=False, # 不解码,因为Stream数据是msgpack二进制
|
124
124
|
max_connections=max_connections,
|
125
125
|
retry_on_timeout=True,
|
126
126
|
retry_on_error=[ConnectionError, TimeoutError],
|
@@ -134,7 +134,7 @@ def get_binary_redis_pool(redis_url: str, max_connections: int = 200):
|
|
134
134
|
return _binary_redis_pools[redis_url]
|
135
135
|
|
136
136
|
def get_async_binary_redis_pool(redis_url: str, max_connections: int = 200):
|
137
|
-
"""获取或创建用于二进制数据的异步Redis
|
137
|
+
"""获取或创建用于二进制数据的异步Redis连接池(Stream操作需要)"""
|
138
138
|
if redis_url not in _async_binary_redis_pools:
|
139
139
|
# 构建socket keepalive选项,仅在Linux上使用
|
140
140
|
socket_keepalive_options = {}
|
@@ -147,7 +147,7 @@ def get_async_binary_redis_pool(redis_url: str, max_connections: int = 200):
|
|
147
147
|
|
148
148
|
_async_binary_redis_pools[redis_url] = aioredis.ConnectionPool.from_url(
|
149
149
|
redis_url,
|
150
|
-
decode_responses=False, #
|
150
|
+
decode_responses=False, # 不解码,因为Stream数据是msgpack二进制
|
151
151
|
max_connections=max_connections,
|
152
152
|
retry_on_timeout=True,
|
153
153
|
retry_on_error=[ConnectionError, TimeoutError],
|
@@ -183,8 +183,15 @@ class Jettask(object):
|
|
183
183
|
local delay_seconds = tonumber(ARGV[i+3])
|
184
184
|
local queue = ARGV[i+4]
|
185
185
|
|
186
|
-
--
|
187
|
-
local
|
186
|
+
-- 使用Hash存储所有队列的offset
|
187
|
+
local offsets_hash = prefix .. ':QUEUE_OFFSETS'
|
188
|
+
-- 使用HINCRBY原子递增offset
|
189
|
+
local offset = redis.call('HINCRBY', offsets_hash, queue, 1)
|
190
|
+
|
191
|
+
-- 1. 添加消息到Stream(包含offset字段)
|
192
|
+
local stream_id = redis.call('XADD', stream_key, '*',
|
193
|
+
'data', stream_data,
|
194
|
+
'offset', offset)
|
188
195
|
|
189
196
|
-- 2. 添加到延迟队列ZSET
|
190
197
|
local delayed_queue_key = prefix .. ':DELAYED_QUEUE:' .. queue
|
@@ -216,8 +223,17 @@ class Jettask(object):
|
|
216
223
|
local stream_key = ARGV[i]
|
217
224
|
local stream_data = ARGV[i+1]
|
218
225
|
|
219
|
-
--
|
220
|
-
local
|
226
|
+
-- 从stream_key中提取队列名(格式: prefix:STREAM:queue_name)
|
227
|
+
local queue_name = string.match(stream_key, prefix .. ':STREAM:(.*)')
|
228
|
+
|
229
|
+
-- 获取并递增offset
|
230
|
+
local offset_key = prefix .. ':STREAM:' .. queue_name .. ':next_offset'
|
231
|
+
local offset = redis.call('INCR', offset_key)
|
232
|
+
|
233
|
+
-- 1. 添加消息到Stream(包含offset字段)
|
234
|
+
local stream_id = redis.call('XADD', stream_key, '*',
|
235
|
+
'data', stream_data,
|
236
|
+
'offset', offset)
|
221
237
|
|
222
238
|
-- 2. 设置任务状态Hash(只存储status)
|
223
239
|
local task_key = prefix .. ':TASK:' .. stream_id
|
@@ -235,10 +251,19 @@ class Jettask(object):
|
|
235
251
|
|
236
252
|
def __init__(self, redis_url: str = None, include: list = None, max_connections: int = 200,
|
237
253
|
consumer_strategy: str = None, consumer_config: dict = None, tasks=None,
|
238
|
-
redis_prefix: str = None, scheduler_config: dict = None, pg_url: str = None
|
254
|
+
redis_prefix: str = None, scheduler_config: dict = None, pg_url: str = None,
|
255
|
+
task_center=None) -> None:
|
239
256
|
self._tasks = tasks or {}
|
257
|
+
self._queue_tasks = {} # 记录每个队列对应的任务列表
|
240
258
|
self.asyncio = False
|
241
259
|
self.include = include or []
|
260
|
+
|
261
|
+
# 任务中心相关属性
|
262
|
+
self.task_center = None # 将通过mount_task_center方法挂载或初始化时指定
|
263
|
+
self._task_center_config = None
|
264
|
+
self._original_redis_url = redis_url
|
265
|
+
self._original_pg_url = pg_url
|
266
|
+
|
242
267
|
self.redis_url = redis_url
|
243
268
|
self.pg_url = pg_url # 存储PostgreSQL URL
|
244
269
|
self.max_connections = max_connections
|
@@ -249,6 +274,10 @@ class Jettask(object):
|
|
249
274
|
# Redis prefix configuration
|
250
275
|
self.redis_prefix = redis_prefix or "jettask"
|
251
276
|
|
277
|
+
# 如果初始化时提供了task_center,直接挂载
|
278
|
+
if task_center:
|
279
|
+
self.mount_task_center(task_center)
|
280
|
+
|
252
281
|
# Update prefixes with the configured prefix using colon namespace
|
253
282
|
self.STATUS_PREFIX = f"{self.redis_prefix}:STATUS:"
|
254
283
|
self.RESULT_PREFIX = f"{self.redis_prefix}:RESULT:"
|
@@ -270,6 +299,236 @@ class Jettask(object):
|
|
270
299
|
self._worker_started = False
|
271
300
|
self._handlers_registered = False
|
272
301
|
|
302
|
+
async def _load_config_from_task_center_async(self):
|
303
|
+
"""异步加载任务中心配置"""
|
304
|
+
try:
|
305
|
+
if not self.task_center:
|
306
|
+
return False
|
307
|
+
|
308
|
+
# 连接并获取配置
|
309
|
+
connected = await self.task_center.connect()
|
310
|
+
if not connected:
|
311
|
+
return False
|
312
|
+
|
313
|
+
config = {
|
314
|
+
'redis_config': self.task_center.redis_config,
|
315
|
+
'pg_config': self.task_center.pg_config,
|
316
|
+
'namespace_name': self.task_center.namespace_name,
|
317
|
+
'version': self.task_center.version
|
318
|
+
}
|
319
|
+
|
320
|
+
if config['redis_config']:
|
321
|
+
# 任务中心配置优先级高于手动配置
|
322
|
+
redis_config = config.get('redis_config', {})
|
323
|
+
pg_config = config.get('pg_config', {})
|
324
|
+
# 构建Redis URL
|
325
|
+
if redis_config:
|
326
|
+
redis_host = redis_config.get('host', 'localhost')
|
327
|
+
redis_port = redis_config.get('port', 6379)
|
328
|
+
redis_password = redis_config.get('password')
|
329
|
+
redis_db = redis_config.get('db', 0)
|
330
|
+
|
331
|
+
if redis_password:
|
332
|
+
self.redis_url = f"redis://:{redis_password}@{redis_host}:{redis_port}/{redis_db}"
|
333
|
+
else:
|
334
|
+
self.redis_url = f"redis://{redis_host}:{redis_port}/{redis_db}"
|
335
|
+
|
336
|
+
logger.info(f"从任务中心加载Redis配置: {redis_host}:{redis_port}/{redis_db}")
|
337
|
+
|
338
|
+
# 构建PostgreSQL URL
|
339
|
+
if pg_config:
|
340
|
+
pg_host = pg_config.get('host', 'localhost')
|
341
|
+
pg_port = pg_config.get('port', 5432)
|
342
|
+
pg_user = pg_config.get('user', 'postgres')
|
343
|
+
pg_password = pg_config.get('password', '')
|
344
|
+
pg_database = pg_config.get('database', 'jettask')
|
345
|
+
|
346
|
+
self.pg_url = f"postgresql://{pg_user}:{pg_password}@{pg_host}:{pg_port}/{pg_database}"
|
347
|
+
logger.info(f"从任务中心加载PostgreSQL配置: {pg_host}:{pg_port}/{pg_database}")
|
348
|
+
|
349
|
+
# 保存配置供后续使用
|
350
|
+
self._task_center_config = config
|
351
|
+
|
352
|
+
# 更新Redis前缀为命名空间名称
|
353
|
+
if self.task_center and self.task_center.redis_prefix != "jettask":
|
354
|
+
self.redis_prefix = self.task_center.redis_prefix
|
355
|
+
# 更新相关前缀
|
356
|
+
self.STATUS_PREFIX = f"{self.redis_prefix}:STATUS:"
|
357
|
+
self.RESULT_PREFIX = f"{self.redis_prefix}:RESULT:"
|
358
|
+
|
359
|
+
# 清理已缓存的Redis连接,强制重新创建
|
360
|
+
if hasattr(self, '_redis'):
|
361
|
+
delattr(self, '_redis')
|
362
|
+
if hasattr(self, '_async_redis'):
|
363
|
+
delattr(self, '_async_redis')
|
364
|
+
if hasattr(self, '_ep'):
|
365
|
+
delattr(self, '_ep')
|
366
|
+
|
367
|
+
return True
|
368
|
+
except Exception as e:
|
369
|
+
logger.warning(f"从任务中心加载配置失败: {e}")
|
370
|
+
return False
|
371
|
+
|
372
|
+
def _load_config_from_task_center(self):
|
373
|
+
"""从任务中心加载配置"""
|
374
|
+
try:
|
375
|
+
import asyncio
|
376
|
+
# 检查是否已经在事件循环中
|
377
|
+
try:
|
378
|
+
loop = asyncio.get_running_loop()
|
379
|
+
# 已在事件循环中,无法同步加载
|
380
|
+
return False
|
381
|
+
except RuntimeError:
|
382
|
+
# 不在事件循环中,可以创建新的
|
383
|
+
loop = asyncio.new_event_loop()
|
384
|
+
if self.task_center:
|
385
|
+
# 如果已经初始化,直接获取配置
|
386
|
+
if self.task_center._initialized:
|
387
|
+
config = self.task_center._config
|
388
|
+
else:
|
389
|
+
# 使用异步模式连接
|
390
|
+
success = loop.run_until_complete(self.task_center.connect(asyncio=True))
|
391
|
+
if success:
|
392
|
+
config = self.task_center._config
|
393
|
+
else:
|
394
|
+
config = None
|
395
|
+
else:
|
396
|
+
config = None
|
397
|
+
loop.close()
|
398
|
+
|
399
|
+
if config:
|
400
|
+
# 任务中心配置优先级高于手动配置
|
401
|
+
redis_config = config.get('redis_config', {})
|
402
|
+
pg_config = config.get('pg_config', {})
|
403
|
+
# 构建Redis URL
|
404
|
+
if redis_config:
|
405
|
+
redis_host = redis_config.get('host', 'localhost')
|
406
|
+
redis_port = redis_config.get('port', 6379)
|
407
|
+
redis_password = redis_config.get('password')
|
408
|
+
redis_db = redis_config.get('db', 0)
|
409
|
+
|
410
|
+
if redis_password:
|
411
|
+
self.redis_url = f"redis://:{redis_password}@{redis_host}:{redis_port}/{redis_db}"
|
412
|
+
else:
|
413
|
+
self.redis_url = f"redis://{redis_host}:{redis_port}/{redis_db}"
|
414
|
+
|
415
|
+
logger.info(f"从任务中心加载Redis配置: {redis_host}:{redis_port}/{redis_db}")
|
416
|
+
|
417
|
+
# 构建PostgreSQL URL
|
418
|
+
if pg_config:
|
419
|
+
pg_host = pg_config.get('host', 'localhost')
|
420
|
+
pg_port = pg_config.get('port', 5432)
|
421
|
+
pg_user = pg_config.get('user', 'postgres')
|
422
|
+
pg_password = pg_config.get('password', '')
|
423
|
+
pg_database = pg_config.get('database', 'jettask')
|
424
|
+
|
425
|
+
self.pg_url = f"postgresql://{pg_user}:{pg_password}@{pg_host}:{pg_port}/{pg_database}"
|
426
|
+
logger.info(f"从任务中心加载PostgreSQL配置: {pg_host}:{pg_port}/{pg_database}")
|
427
|
+
|
428
|
+
# 保存配置供后续使用
|
429
|
+
self._task_center_config = config
|
430
|
+
|
431
|
+
# 更新Redis前缀为命名空间名称
|
432
|
+
if self.task_center and self.task_center.redis_prefix != "jettask":
|
433
|
+
self.redis_prefix = self.task_center.redis_prefix
|
434
|
+
# 更新相关前缀
|
435
|
+
self.STATUS_PREFIX = f"{self.redis_prefix}:STATUS:"
|
436
|
+
self.RESULT_PREFIX = f"{self.redis_prefix}:RESULT:"
|
437
|
+
|
438
|
+
# 清理已缓存的Redis连接,强制重新创建
|
439
|
+
if hasattr(self, '_redis'):
|
440
|
+
delattr(self, '_redis')
|
441
|
+
if hasattr(self, '_async_redis'):
|
442
|
+
delattr(self, '_async_redis')
|
443
|
+
if hasattr(self, '_ep'):
|
444
|
+
delattr(self, '_ep')
|
445
|
+
|
446
|
+
except Exception as e:
|
447
|
+
import traceback
|
448
|
+
traceback.print_exc()
|
449
|
+
logger.warning(f"从任务中心加载配置失败,使用手动配置: {e}")
|
450
|
+
# 恢复原始配置
|
451
|
+
self.redis_url = self._original_redis_url
|
452
|
+
self.pg_url = self._original_pg_url
|
453
|
+
|
454
|
+
def mount_task_center(self, task_center):
|
455
|
+
"""
|
456
|
+
挂载任务中心到Jettask应用
|
457
|
+
|
458
|
+
如果task_center已经连接,会自动应用配置到当前app。
|
459
|
+
|
460
|
+
Args:
|
461
|
+
task_center: TaskCenter实例
|
462
|
+
|
463
|
+
使用示例:
|
464
|
+
from jettask.webui.task_center import TaskCenter
|
465
|
+
|
466
|
+
# 创建任务中心客户端(可复用)
|
467
|
+
task_center = TaskCenter("http://localhost:8001/api/namespaces/demo")
|
468
|
+
await task_center.connect() # 只需连接一次
|
469
|
+
|
470
|
+
# 创建多个app实例,共享同一个task_center
|
471
|
+
app1 = Jettask()
|
472
|
+
app1.mount_task_center(task_center) # 自动应用配置
|
473
|
+
|
474
|
+
app2 = Jettask()
|
475
|
+
app2.mount_task_center(task_center) # 复用配置
|
476
|
+
"""
|
477
|
+
self.task_center = task_center
|
478
|
+
|
479
|
+
# 如果任务中心已连接,立即应用所有配置
|
480
|
+
if task_center and task_center._initialized:
|
481
|
+
# 应用Redis配置
|
482
|
+
if task_center.redis_config:
|
483
|
+
redis_url = task_center.get_redis_url()
|
484
|
+
if redis_url:
|
485
|
+
self.redis_url = redis_url
|
486
|
+
|
487
|
+
# 应用PostgreSQL配置
|
488
|
+
if task_center.pg_config:
|
489
|
+
pg_url = task_center.get_pg_url()
|
490
|
+
if pg_url:
|
491
|
+
self.pg_url = pg_url
|
492
|
+
|
493
|
+
# 更新Redis前缀
|
494
|
+
self.redis_prefix = task_center.redis_prefix
|
495
|
+
# 更新相关前缀
|
496
|
+
self.STATUS_PREFIX = f"{self.redis_prefix}:STATUS:"
|
497
|
+
self.RESULT_PREFIX = f"{self.redis_prefix}:RESULT:"
|
498
|
+
self.QUEUE_PREFIX = f"{self.redis_prefix}:QUEUE:"
|
499
|
+
self.DELAYED_QUEUE_PREFIX = f"{self.redis_prefix}:DELAYED_QUEUE:"
|
500
|
+
self.STREAM_PREFIX = f"{self.redis_prefix}:STREAM:"
|
501
|
+
self.TASK_PREFIX = f"{self.redis_prefix}:TASK:"
|
502
|
+
self.SCHEDULER_PREFIX = f"{self.redis_prefix}:SCHEDULED:"
|
503
|
+
self.LOCK_PREFIX = f"{self.redis_prefix}:LOCK:"
|
504
|
+
|
505
|
+
# 标记配置已加载
|
506
|
+
self._task_center_config = {
|
507
|
+
'redis_config': task_center.redis_config,
|
508
|
+
'pg_config': task_center.pg_config,
|
509
|
+
'namespace_name': task_center.namespace_name,
|
510
|
+
'version': task_center.version
|
511
|
+
}
|
512
|
+
|
513
|
+
async def init_from_task_center(self):
|
514
|
+
"""
|
515
|
+
从任务中心初始化配置(异步方法)
|
516
|
+
|
517
|
+
在异步环境中发送任务前调用此方法,确保配置已加载
|
518
|
+
|
519
|
+
使用示例:
|
520
|
+
from jettask.webui.task_center import TaskCenter
|
521
|
+
|
522
|
+
task_center = TaskCenter("http://localhost:8001/api/namespaces/demo")
|
523
|
+
app = Jettask()
|
524
|
+
app.mount_task_center(task_center)
|
525
|
+
await app.init_from_task_center() # 会自动连接task_center
|
526
|
+
await task.apply_async(...)
|
527
|
+
"""
|
528
|
+
if self.task_center and self.task_center.is_enabled and not self._task_center_config:
|
529
|
+
return await self._load_config_from_task_center_async()
|
530
|
+
return True
|
531
|
+
|
273
532
|
def _setup_cleanup_handlers(self):
|
274
533
|
"""设置清理处理器"""
|
275
534
|
# 避免重复注册
|
@@ -343,6 +602,10 @@ class Jettask(object):
|
|
343
602
|
if hasattr(self, name):
|
344
603
|
return getattr(self, name)
|
345
604
|
|
605
|
+
# 如果配置了任务中心且还未加载配置,先加载配置
|
606
|
+
if self.task_center and self.task_center.is_enabled and not self._task_center_config:
|
607
|
+
self._load_config_from_task_center()
|
608
|
+
|
346
609
|
pool = get_async_redis_pool(self.redis_url, self.max_connections)
|
347
610
|
async_redis = aioredis.StrictRedis(connection_pool=pool)
|
348
611
|
setattr(self, name, async_redis)
|
@@ -354,7 +617,11 @@ class Jettask(object):
|
|
354
617
|
name = "_redis"
|
355
618
|
if hasattr(self, name):
|
356
619
|
return getattr(self, name)
|
357
|
-
|
620
|
+
|
621
|
+
# 如果配置了任务中心且还未加载配置,先加载配置
|
622
|
+
if self.task_center and self.task_center.is_enabled and not self._task_center_config:
|
623
|
+
self._load_config_from_task_center()
|
624
|
+
|
358
625
|
pool = get_redis_pool(self.redis_url, self.max_connections)
|
359
626
|
redis_cli = redis.StrictRedis(connection_pool=pool)
|
360
627
|
setattr(self, name, redis_cli)
|
@@ -416,6 +683,9 @@ class Jettask(object):
|
|
416
683
|
) -> Task:
|
417
684
|
name = name or gen_task_name(fun.__name__, fun.__module__)
|
418
685
|
base = base or Task
|
686
|
+
|
687
|
+
# 不再限制队列模式,因为每个task都有独立的consumer group
|
688
|
+
|
419
689
|
if name not in self._tasks:
|
420
690
|
run = staticmethod(fun)
|
421
691
|
task: Task = type(
|
@@ -441,6 +711,12 @@ class Jettask(object):
|
|
441
711
|
with contextlib.suppress(AttributeError):
|
442
712
|
task.__qualname__ = fun.__qualname__
|
443
713
|
self._tasks[task.name] = task
|
714
|
+
|
715
|
+
# 记录队列和任务的映射(用于查找)
|
716
|
+
if queue:
|
717
|
+
if queue not in self._queue_tasks:
|
718
|
+
self._queue_tasks[queue] = []
|
719
|
+
self._queue_tasks[queue].append(name)
|
444
720
|
else:
|
445
721
|
task = self._tasks[name]
|
446
722
|
return task
|
@@ -477,56 +753,196 @@ class Jettask(object):
|
|
477
753
|
|
478
754
|
return _create_task_cls
|
479
755
|
|
480
|
-
def
|
481
|
-
self,
|
482
|
-
queue: str,
|
483
|
-
message: dict,
|
484
|
-
target_tasks: list = None,
|
485
|
-
asyncio_mode: bool = False
|
486
|
-
):
|
756
|
+
async def send_tasks(self, messages: list):
|
487
757
|
"""
|
488
|
-
|
758
|
+
统一的任务发送接口 - 只有这一个发送方法
|
489
759
|
|
490
760
|
Args:
|
491
|
-
|
492
|
-
|
493
|
-
target_tasks: 目标任务列表(None表示所有监听该队列的任务)
|
494
|
-
asyncio_mode: 是否使用异步模式
|
495
|
-
|
761
|
+
messages: TaskMessage对象列表(或字典列表)
|
762
|
+
|
496
763
|
Returns:
|
497
|
-
|
498
|
-
|
499
|
-
使用示例:
|
500
|
-
# 同步模式
|
501
|
-
app.publish_broadcast(
|
502
|
-
queue="events",
|
503
|
-
message={"type": "customer_registered", "data": {...}}
|
504
|
-
)
|
764
|
+
List[str]: 任务ID列表
|
505
765
|
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
766
|
+
使用示例:
|
767
|
+
from jettask.core.message import TaskMessage
|
768
|
+
|
769
|
+
# 发送单个任务(也是用列表)
|
770
|
+
msg = TaskMessage(
|
771
|
+
queue="order_processing",
|
772
|
+
args=(12345,),
|
773
|
+
kwargs={"customer_id": "C001", "amount": 99.99}
|
512
774
|
)
|
775
|
+
task_ids = await app.send_tasks([msg])
|
776
|
+
|
777
|
+
# 批量发送
|
778
|
+
messages = [
|
779
|
+
TaskMessage(queue="email", kwargs={"to": "user1@example.com"}),
|
780
|
+
TaskMessage(queue="email", kwargs={"to": "user2@example.com"}),
|
781
|
+
TaskMessage(queue="sms", kwargs={"phone": "123456789"}),
|
782
|
+
]
|
783
|
+
task_ids = await app.send_tasks(messages)
|
784
|
+
|
785
|
+
# 跨项目发送(不需要task定义)
|
786
|
+
messages = [
|
787
|
+
TaskMessage(queue="remote_queue", kwargs={"data": "value"})
|
788
|
+
]
|
789
|
+
task_ids = await app.send_tasks(messages)
|
513
790
|
"""
|
514
|
-
|
791
|
+
if not messages:
|
792
|
+
return []
|
793
|
+
|
794
|
+
# 导入TaskMessage
|
795
|
+
from .message import TaskMessage
|
796
|
+
|
797
|
+
results = []
|
798
|
+
|
799
|
+
# 按队列分组消息,以便批量处理
|
800
|
+
queue_messages = {}
|
801
|
+
for msg in messages:
|
802
|
+
# 支持TaskMessage对象或字典
|
803
|
+
if isinstance(msg, dict):
|
804
|
+
msg = TaskMessage.from_dict(msg)
|
805
|
+
elif not isinstance(msg, TaskMessage):
|
806
|
+
raise ValueError(f"Invalid message type: {type(msg)}. Expected TaskMessage or dict")
|
807
|
+
|
808
|
+
# 验证消息
|
809
|
+
msg.validate()
|
810
|
+
|
811
|
+
# 确定实际的队列名(考虑优先级)
|
812
|
+
actual_queue = msg.queue
|
813
|
+
if msg.priority is not None:
|
814
|
+
# 将优先级拼接到队列名后面
|
815
|
+
actual_queue = f"{msg.queue}:{msg.priority}"
|
816
|
+
# 更新消息体中的queue字段,确保与实际发送的stream key一致
|
817
|
+
msg.queue = actual_queue
|
818
|
+
|
819
|
+
# 按队列分组
|
820
|
+
if actual_queue not in queue_messages:
|
821
|
+
queue_messages[actual_queue] = []
|
822
|
+
queue_messages[actual_queue].append(msg)
|
823
|
+
|
824
|
+
# 处理每个队列的消息
|
825
|
+
for queue, queue_msgs in queue_messages.items():
|
826
|
+
# 统一使用批量发送,无论是否广播模式
|
827
|
+
# 广播/单播由消费端的consumer group name决定
|
828
|
+
batch_results = await self._send_batch_messages(queue, queue_msgs)
|
829
|
+
results.extend(batch_results)
|
830
|
+
|
831
|
+
return results
|
832
|
+
|
833
|
+
async def _send_batch_messages(self, queue: str, messages: list) -> list:
|
834
|
+
"""批量发送single模式消息(内部方法)"""
|
515
835
|
from ..utils.serializer import dumps_str
|
516
836
|
|
517
|
-
#
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
837
|
+
# 分离普通任务和延迟任务
|
838
|
+
normal_messages = []
|
839
|
+
delayed_messages = []
|
840
|
+
|
841
|
+
for msg in messages:
|
842
|
+
msg_dict = msg.to_dict()
|
843
|
+
|
844
|
+
# 处理延迟任务
|
845
|
+
if msg.delay and msg.delay > 0:
|
846
|
+
# 添加延迟执行标记
|
847
|
+
current_time = time.time()
|
848
|
+
msg_dict['execute_at'] = current_time + msg.delay
|
849
|
+
msg_dict['is_delayed'] = 1
|
850
|
+
delayed_messages.append((msg_dict, msg.delay))
|
851
|
+
else:
|
852
|
+
normal_messages.append(msg_dict)
|
853
|
+
|
854
|
+
results = []
|
855
|
+
|
856
|
+
# 发送普通任务(统一使用批量发送)
|
857
|
+
if normal_messages:
|
858
|
+
batch_results = await self.ep._batch_send_event(
|
859
|
+
self.ep.get_prefixed_queue_name(queue),
|
860
|
+
[{'data': dumps_str(msg)} for msg in normal_messages],
|
861
|
+
self.ep.get_redis_client(asyncio=True, binary=True).pipeline()
|
862
|
+
)
|
863
|
+
results.extend(batch_results)
|
864
|
+
|
865
|
+
# 发送延迟任务(需要同时添加到DELAYED_QUEUE)
|
866
|
+
if delayed_messages:
|
867
|
+
delayed_results = await self._send_delayed_tasks(queue, delayed_messages)
|
868
|
+
results.extend(delayed_results)
|
869
|
+
|
870
|
+
return results
|
871
|
+
|
872
|
+
async def _send_delayed_tasks(self, queue: str, delayed_messages: list) -> list:
|
873
|
+
"""发送延迟任务到Stream并添加到延迟队列"""
|
874
|
+
from ..utils.serializer import dumps_str
|
875
|
+
|
876
|
+
# 使用Lua脚本原子性地处理延迟任务
|
877
|
+
lua_script = """
|
878
|
+
local prefix = ARGV[1]
|
879
|
+
local results = {}
|
880
|
+
|
881
|
+
-- 从ARGV[2]开始,每4个参数为一组任务信息
|
882
|
+
-- [stream_key, stream_data, execute_at, queue]
|
883
|
+
for i = 2, #ARGV, 4 do
|
884
|
+
local stream_key = ARGV[i]
|
885
|
+
local stream_data = ARGV[i+1]
|
886
|
+
local execute_at = tonumber(ARGV[i+2])
|
887
|
+
local queue_name = ARGV[i+3]
|
888
|
+
|
889
|
+
-- 使用Hash存储所有队列的offset
|
890
|
+
local offsets_hash = prefix .. ':QUEUE_OFFSETS'
|
891
|
+
|
892
|
+
-- 从stream_key中提取队列名
|
893
|
+
local queue_name = string.gsub(stream_key, '^' .. prefix .. ':QUEUE:', '')
|
894
|
+
|
895
|
+
-- 使用HINCRBY原子递增offset
|
896
|
+
local current_offset = redis.call('HINCRBY', offsets_hash, queue_name, 1)
|
897
|
+
|
898
|
+
-- 1. 添加消息到Stream(包含offset字段)
|
899
|
+
local stream_id = redis.call('XADD', stream_key, '*',
|
900
|
+
'data', stream_data,
|
901
|
+
'offset', current_offset)
|
902
|
+
|
903
|
+
-- 2. 添加到延迟队列ZSET
|
904
|
+
local delayed_queue_key = prefix .. ':DELAYED_QUEUE:' .. queue_name
|
905
|
+
redis.call('ZADD', delayed_queue_key, execute_at, stream_id)
|
906
|
+
|
907
|
+
-- 3. 设置任务状态Hash
|
908
|
+
local task_key = prefix .. ':TASK:' .. stream_id
|
909
|
+
redis.call('HSET', task_key, 'status', 'delayed')
|
910
|
+
redis.call('EXPIRE', task_key, 3600)
|
911
|
+
|
912
|
+
-- 保存stream_id到结果
|
913
|
+
table.insert(results, stream_id)
|
914
|
+
end
|
915
|
+
|
916
|
+
return results
|
917
|
+
"""
|
918
|
+
|
919
|
+
# 准备Lua脚本参数
|
920
|
+
lua_args = [self.redis_prefix]
|
921
|
+
prefixed_queue = self.ep.get_prefixed_queue_name(queue)
|
922
|
+
|
923
|
+
for msg_dict, delay_seconds in delayed_messages:
|
924
|
+
stream_data = dumps_str(msg_dict)
|
925
|
+
execute_at = msg_dict['execute_at']
|
926
|
+
|
927
|
+
lua_args.extend([
|
928
|
+
prefixed_queue,
|
929
|
+
stream_data,
|
930
|
+
str(execute_at),
|
931
|
+
queue
|
932
|
+
])
|
933
|
+
|
934
|
+
# 执行Lua脚本
|
935
|
+
client = self.ep.get_redis_client(asyncio=True, binary=True)
|
936
|
+
|
937
|
+
# 注册Lua脚本
|
938
|
+
if not hasattr(self, '_delayed_task_script'):
|
939
|
+
self._delayed_task_script = client.register_script(lua_script)
|
940
|
+
|
941
|
+
# 执行脚本
|
942
|
+
results = await self._delayed_task_script(keys=[], args=lua_args)
|
943
|
+
|
944
|
+
# 解码结果
|
945
|
+
return [r.decode('utf-8') if isinstance(r, bytes) else r for r in results]
|
530
946
|
|
531
947
|
def register_router(self, router, prefix: str = None):
|
532
948
|
"""
|
@@ -662,12 +1078,16 @@ class Jettask(object):
|
|
662
1078
|
executor = AsyncioExecutor(async_event_queue, self, concurrency)
|
663
1079
|
await executor.loop()
|
664
1080
|
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
1081
|
+
try:
|
1082
|
+
loop = asyncio.get_event_loop()
|
1083
|
+
if loop.is_running():
|
1084
|
+
# 如果事件循环已经在运行,创建一个新的
|
1085
|
+
loop = asyncio.new_event_loop()
|
1086
|
+
asyncio.set_event_loop(loop)
|
1087
|
+
except RuntimeError:
|
1088
|
+
# 如果当前线程没有事件循环,创建一个新的
|
1089
|
+
loop = asyncio.new_event_loop()
|
1090
|
+
asyncio.set_event_loop(loop)
|
671
1091
|
|
672
1092
|
try:
|
673
1093
|
loop.run_until_complete(run_asyncio_executor())
|
@@ -718,6 +1138,10 @@ class Jettask(object):
|
|
718
1138
|
# 标记worker已启动
|
719
1139
|
self._worker_started = True
|
720
1140
|
|
1141
|
+
# 如果配置了任务中心,从任务中心获取配置
|
1142
|
+
if self.task_center and self.task_center.is_enabled:
|
1143
|
+
self._load_config_from_task_center()
|
1144
|
+
|
721
1145
|
# 注册清理处理器(只在启动worker时注册)
|
722
1146
|
self._setup_cleanup_handlers()
|
723
1147
|
|
@@ -754,147 +1178,6 @@ class Jettask(object):
|
|
754
1178
|
logger.warning("Process did not terminate, killing...")
|
755
1179
|
self.process.kill()
|
756
1180
|
|
757
|
-
def bulk_write(self, tasks: list, asyncio: bool = None):
|
758
|
-
"""
|
759
|
-
统一的批量写入方法,支持同步和异步模式,以及延迟任务
|
760
|
-
|
761
|
-
Args:
|
762
|
-
tasks: 任务列表
|
763
|
-
asyncio: 是否使用异步模式。如果为None,自动检测
|
764
|
-
|
765
|
-
Returns:
|
766
|
-
同步模式: 返回event_ids列表
|
767
|
-
异步模式: 如果在异步环境中调用,返回协程对象
|
768
|
-
"""
|
769
|
-
if not tasks:
|
770
|
-
raise ValueError("tasks 参数不能为空!")
|
771
|
-
|
772
|
-
# 自动检测异步模式
|
773
|
-
if asyncio is None:
|
774
|
-
import asyncio as aio
|
775
|
-
try:
|
776
|
-
loop = aio.get_running_loop()
|
777
|
-
asyncio = True
|
778
|
-
except RuntimeError:
|
779
|
-
asyncio = False
|
780
|
-
|
781
|
-
# 如果需要异步执行
|
782
|
-
if asyncio:
|
783
|
-
return self._bulk_write_async(tasks)
|
784
|
-
|
785
|
-
# 同步执行
|
786
|
-
import asyncio as aio
|
787
|
-
# 在同步模式下,使用 asyncio.run 来运行异步函数
|
788
|
-
return aio.run(self._bulk_write_impl(tasks, asyncio_mode=False))
|
789
|
-
|
790
|
-
async def _bulk_write_impl(self, tasks: list, asyncio_mode: bool):
|
791
|
-
"""批量写入的内部实现,支持同步和异步"""
|
792
|
-
# 获取对应的Redis客户端
|
793
|
-
redis_client = self.get_redis_client(asyncio=asyncio_mode)
|
794
|
-
|
795
|
-
# 分离延迟任务和普通任务
|
796
|
-
delayed_tasks = []
|
797
|
-
normal_tasks = []
|
798
|
-
|
799
|
-
for task in tasks:
|
800
|
-
if 'delay' in task:
|
801
|
-
delayed_tasks.append(task)
|
802
|
-
else:
|
803
|
-
normal_tasks.append(task)
|
804
|
-
|
805
|
-
event_ids = []
|
806
|
-
|
807
|
-
# 处理延迟任务 - 使用Lua脚本原子性处理
|
808
|
-
if delayed_tasks:
|
809
|
-
|
810
|
-
# 准备Lua脚本参数
|
811
|
-
current_time = time.time()
|
812
|
-
lua_args = [self.redis_prefix, str(current_time)]
|
813
|
-
|
814
|
-
for task in delayed_tasks:
|
815
|
-
delay_seconds = task.pop('delay') # 移除delay参数
|
816
|
-
queue = task.get("queue")
|
817
|
-
|
818
|
-
# 添加执行时间到消息中
|
819
|
-
execute_at = current_time + delay_seconds
|
820
|
-
task['execute_at'] = execute_at
|
821
|
-
task['is_delayed'] = 1 # 使用1代替True
|
822
|
-
task['trigger_time'] = current_time # 添加trigger_time
|
823
|
-
|
824
|
-
# 如果queue为None,使用任务名作为队列名
|
825
|
-
task_name = task.get("name", "")
|
826
|
-
actual_queue = queue or task_name
|
827
|
-
task['queue'] = actual_queue
|
828
|
-
|
829
|
-
# 准备Stream数据
|
830
|
-
prefixed_queue = self.ep.get_prefixed_queue_name(actual_queue)
|
831
|
-
from ..utils.serializer import dumps_str
|
832
|
-
stream_data = dumps_str(task)
|
833
|
-
|
834
|
-
# 添加到Lua脚本参数
|
835
|
-
lua_args.extend([
|
836
|
-
prefixed_queue,
|
837
|
-
stream_data,
|
838
|
-
str(execute_at),
|
839
|
-
str(delay_seconds),
|
840
|
-
actual_queue
|
841
|
-
])
|
842
|
-
|
843
|
-
# 注册并执行Lua脚本(使用类常量)
|
844
|
-
script_attr = '_bulk_delayed_script_async' if asyncio_mode else '_bulk_delayed_script_sync'
|
845
|
-
if not hasattr(self, script_attr):
|
846
|
-
setattr(self, script_attr, redis_client.register_script(self._LUA_SCRIPT_DELAYED_TASKS))
|
847
|
-
|
848
|
-
script = getattr(self, script_attr)
|
849
|
-
if asyncio_mode:
|
850
|
-
# 异步执行需要await
|
851
|
-
stream_ids = await script(keys=[], args=lua_args)
|
852
|
-
else:
|
853
|
-
stream_ids = script(keys=[], args=lua_args)
|
854
|
-
|
855
|
-
event_ids.extend(stream_ids)
|
856
|
-
|
857
|
-
# 处理普通任务 - 使用Lua脚本原子性处理
|
858
|
-
if normal_tasks:
|
859
|
-
|
860
|
-
# 准备Lua脚本参数
|
861
|
-
current_time = str(time.time())
|
862
|
-
lua_args = [self.redis_prefix, current_time]
|
863
|
-
|
864
|
-
# 按队列分组并准备参数
|
865
|
-
from ..utils.serializer import dumps_str
|
866
|
-
for task in normal_tasks:
|
867
|
-
queue = task.get("queue")
|
868
|
-
task_name = task.get("name", "")
|
869
|
-
actual_queue = queue or task_name
|
870
|
-
|
871
|
-
# 准备Stream数据
|
872
|
-
prefixed_queue = self.ep.get_prefixed_queue_name(actual_queue)
|
873
|
-
stream_data = dumps_str(task)
|
874
|
-
|
875
|
-
# 添加到Lua脚本参数
|
876
|
-
lua_args.extend([prefixed_queue, stream_data])
|
877
|
-
|
878
|
-
# 注册并执行Lua脚本(使用类常量)
|
879
|
-
script_attr = '_bulk_normal_script_async' if asyncio_mode else '_bulk_normal_script_sync'
|
880
|
-
if not hasattr(self, script_attr):
|
881
|
-
setattr(self, script_attr, redis_client.register_script(self._LUA_SCRIPT_NORMAL_TASKS))
|
882
|
-
|
883
|
-
script = getattr(self, script_attr)
|
884
|
-
if asyncio_mode:
|
885
|
-
# 异步执行需要await
|
886
|
-
stream_ids = await script(keys=[], args=lua_args)
|
887
|
-
else:
|
888
|
-
stream_ids = script(keys=[], args=lua_args)
|
889
|
-
|
890
|
-
event_ids.extend(stream_ids)
|
891
|
-
|
892
|
-
return event_ids
|
893
|
-
|
894
|
-
|
895
|
-
async def _bulk_write_async(self, tasks: list):
|
896
|
-
"""异步批量写入,直接调用统一的实现"""
|
897
|
-
return await self._bulk_write_impl(tasks, asyncio_mode=True)
|
898
1181
|
|
899
1182
|
def get_task_info(self, event_id: str, asyncio: bool = False):
|
900
1183
|
"""获取任务信息(从TASK:hash)"""
|
@@ -1001,7 +1284,7 @@ class Jettask(object):
|
|
1001
1284
|
|
1002
1285
|
def get_result(self, event_id: str, delete: bool = False, asyncio: bool = False,
|
1003
1286
|
delayed_deletion_ex: int = None, wait: bool = False, timeout: int = 300,
|
1004
|
-
poll_interval: float = 0.5
|
1287
|
+
poll_interval: float = 0.5):
|
1005
1288
|
"""获取任务结果(从TASK:hash的result字段)
|
1006
1289
|
|
1007
1290
|
Args:
|
@@ -1040,12 +1323,33 @@ class Jettask(object):
|
|
1040
1323
|
client.expire(key, delayed_deletion_ex)
|
1041
1324
|
return result
|
1042
1325
|
elif delete:
|
1043
|
-
#
|
1326
|
+
# 如果配置了任务中心,不删除消息,等任务中心同步后删除
|
1327
|
+
if self.task_center and self.task_center.is_enabled:
|
1328
|
+
result = client.hget(key, "result")
|
1329
|
+
# 仅标记为待删除,不实际删除
|
1330
|
+
client.hset(key, "__pending_delete", "1")
|
1331
|
+
return result
|
1332
|
+
else:
|
1333
|
+
# 获取结果并删除整个hash
|
1334
|
+
result = client.hget(key, "result")
|
1335
|
+
client.delete(key)
|
1336
|
+
return result
|
1337
|
+
else:
|
1338
|
+
# 先尝试从Redis获取
|
1044
1339
|
result = client.hget(key, "result")
|
1045
|
-
|
1340
|
+
# 如果Redis中没有且配置了任务中心,从任务中心获取
|
1341
|
+
if result is None and self.task_center_client.is_enabled:
|
1342
|
+
import asyncio
|
1343
|
+
loop = asyncio.new_event_loop()
|
1344
|
+
try:
|
1345
|
+
task_data = loop.run_until_complete(
|
1346
|
+
self.task_center_client.get_task_result(event_id)
|
1347
|
+
)
|
1348
|
+
if task_data:
|
1349
|
+
result = task_data.get('result')
|
1350
|
+
finally:
|
1351
|
+
loop.close()
|
1046
1352
|
return result
|
1047
|
-
else:
|
1048
|
-
return client.hget(key, "result")
|
1049
1353
|
|
1050
1354
|
def _get_result_sync_wait(self, event_id: str, delete: bool, delayed_deletion_ex: int,
|
1051
1355
|
timeout: int, poll_interval: float):
|
@@ -1153,14 +1457,12 @@ class Jettask(object):
|
|
1153
1457
|
|
1154
1458
|
# 创建调度器
|
1155
1459
|
scheduler_config = self.scheduler_config.copy()
|
1156
|
-
scheduler_config.setdefault('redis_prefix', f"{self.redis_prefix}:SCHEDULER")
|
1157
1460
|
scheduler_config.setdefault('scan_interval', 0.1)
|
1158
1461
|
scheduler_config.setdefault('batch_size', 100)
|
1159
1462
|
scheduler_config.setdefault('leader_ttl', 10)
|
1160
1463
|
|
1161
1464
|
self.scheduler = TaskScheduler(
|
1162
1465
|
app=self,
|
1163
|
-
redis_url=self.redis_url,
|
1164
1466
|
db_manager=self.scheduler_manager,
|
1165
1467
|
**scheduler_config
|
1166
1468
|
)
|
@@ -1272,12 +1574,21 @@ class Jettask(object):
|
|
1272
1574
|
if not scheduler_id:
|
1273
1575
|
raise ValueError("scheduler_id is required and must be provided")
|
1274
1576
|
|
1577
|
+
# 获取当前命名空间
|
1578
|
+
namespace = 'default'
|
1579
|
+
if self.task_center and hasattr(self.task_center, 'namespace_name'):
|
1580
|
+
namespace = self.task_center.namespace_name
|
1581
|
+
elif self.redis_prefix and self.redis_prefix != 'jettask':
|
1582
|
+
# 如果没有task_center,使用redis_prefix作为命名空间
|
1583
|
+
namespace = self.redis_prefix
|
1584
|
+
|
1275
1585
|
# 创建任务对象
|
1276
1586
|
task = ScheduledTask(
|
1277
1587
|
scheduler_id=scheduler_id,
|
1278
1588
|
task_name=full_task_name, # 使用完整的任务名称(包含模块前缀)
|
1279
1589
|
task_type=TaskType(task_type),
|
1280
1590
|
queue_name=queue_name,
|
1591
|
+
namespace=namespace, # 设置命名空间
|
1281
1592
|
task_args=task_args or [],
|
1282
1593
|
task_kwargs=task_kwargs or {},
|
1283
1594
|
interval_seconds=interval_seconds,
|
@@ -1375,12 +1686,21 @@ class Jettask(object):
|
|
1375
1686
|
if not scheduler_id:
|
1376
1687
|
raise ValueError(f"Task config for '{task_name}' missing required scheduler_id")
|
1377
1688
|
|
1689
|
+
# 获取当前命名空间
|
1690
|
+
namespace = 'default'
|
1691
|
+
if self.task_center and hasattr(self.task_center, 'namespace_name'):
|
1692
|
+
namespace = self.task_center.namespace_name
|
1693
|
+
elif self.redis_prefix and self.redis_prefix != 'jettask':
|
1694
|
+
# 如果没有task_center,使用redis_prefix作为命名空间
|
1695
|
+
namespace = self.redis_prefix
|
1696
|
+
|
1378
1697
|
# 创建任务对象
|
1379
1698
|
task_obj = ScheduledTask(
|
1380
1699
|
scheduler_id=scheduler_id,
|
1381
1700
|
task_name=task_name,
|
1382
1701
|
task_type=TaskType(task_type),
|
1383
1702
|
queue_name=queue_name,
|
1703
|
+
namespace=namespace, # 设置命名空间
|
1384
1704
|
task_args=task_config.get('task_args', []),
|
1385
1705
|
task_kwargs=task_config.get('task_kwargs', {}),
|
1386
1706
|
interval_seconds=task_config.get('interval_seconds'),
|