-
Notifications
You must be signed in to change notification settings - Fork 22
feat: 流程任务支持并发限制 --story=134711116 #749
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -440,6 +440,23 @@ def validate(cls, value: str): | |
| return True | ||
|
|
||
|
|
||
| class ConcurrencyControlConfig(BaseSpaceConfig): | ||
| name = "concurrency_control" | ||
| desc = _("流程并发控制") | ||
| default_value = 0 | ||
| LEAST_NUMBER = 1 | ||
| control = True | ||
|
|
||
| @classmethod | ||
| def validate(cls, value: str): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if int(value) < cls.LEAST_NUMBER: | ||
| raise ValidationError( | ||
| f"[validate concurrency control error]: concurrency control only support {cls.LEAST_NUMBER}" | ||
| ) | ||
|
|
||
| return True | ||
|
|
||
|
|
||
| # 定义 SCHEMA_V1 对应的模型 | ||
| class SchemaV1Model(BaseModel): | ||
| meta_apis: str | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,7 +33,12 @@ | |
| TaskInstance, | ||
| TimeoutNodeConfig, | ||
| ) | ||
| from bkflow.task.utils import ATOM_FAILED, TASK_FINISHED, redis_inst_check | ||
| from bkflow.task.utils import ( | ||
| ATOM_FAILED, | ||
| TASK_FINISHED, | ||
| redis_inst_check, | ||
| update_running_task, | ||
| ) | ||
|
|
||
| logger = logging.getLogger("root") | ||
|
|
||
|
|
@@ -84,6 +89,16 @@ def _node_timeout_info_update(redis_inst, to_state, node_id, version): | |
| redis_inst.zrem(settings.EXECUTING_NODE_POOL, key) | ||
|
|
||
|
|
||
| def _remove_task_from_running_set(root_id): | ||
| """根据 pipeline_instance_id (root_id) 查出任务实例,并从 Redis 运行中任务集合中移除""" | ||
| try: | ||
| task_instance = TaskInstance.objects.filter(instance_id=root_id).only("id", "template_id").first() | ||
| if task_instance and task_instance.template_id is not None: | ||
| update_running_task(task_instance.template_id, task_instance.id, action="remove") | ||
| except Exception as e: | ||
| logger.exception(f"[_remove_task_from_running_set] root_id={root_id} error: {e}") | ||
|
|
||
|
|
||
| @receiver(post_set_state) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| def bamboo_engine_eri_post_set_state_handler(sender, node_id, to_state, version, root_id, parent_id, loop, **kwargs): | ||
| if to_state == bamboo_engine_states.FAILED: | ||
|
|
@@ -100,12 +115,14 @@ def bamboo_engine_eri_post_set_state_handler(sender, node_id, to_state, version, | |
| queue=f"task_common_{settings.BKFLOW_MODULE.code}", | ||
| routing_key=f"task_common_{settings.BKFLOW_MODULE.code}", | ||
| ) | ||
| _remove_task_from_running_set(root_id) | ||
| elif to_state == bamboo_engine_states.REVOKED and node_id == root_id: | ||
| try: | ||
| TaskInstance.objects.set_revoked(root_id) | ||
| except Exception as e: | ||
| logger.exception(f"TaskInstance set revoked error: {e}") | ||
| _check_and_callback(root_id, task_success=False) | ||
| _remove_task_from_running_set(root_id) | ||
| elif to_state == bamboo_engine_states.FINISHED and node_id == root_id: | ||
| try: | ||
| TaskInstance.objects.set_finished(root_id) | ||
|
|
@@ -121,7 +138,7 @@ def bamboo_engine_eri_post_set_state_handler(sender, node_id, to_state, version, | |
| routing_key=f"task_common_{settings.BKFLOW_MODULE.code}", | ||
| ) | ||
| _check_and_callback(root_id, task_success=True) | ||
|
|
||
| _remove_task_from_running_set(root_id) | ||
| try: | ||
| _node_timeout_info_update(settings.redis_inst, to_state, node_id, version) | ||
| except Exception as e: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,7 @@ | |
| from pipeline.engine.utils import calculate_elapsed_time | ||
| from redis.client import Redis | ||
|
|
||
| from bkflow.contrib.api.collections.interface import InterfaceModuleClient | ||
| from bkflow.utils.dates import format_datetime | ||
| from bkflow.utils.message import send_message | ||
|
|
||
|
|
@@ -166,3 +167,79 @@ def extract_extra_info(constants, keys=None): | |
| for key in list(constants.keys()) if not keys else keys: | ||
| extra_info.update({key: {"name": constants[key]["name"], "value": constants[key]["value"]}}) | ||
| return json.dumps(extra_info, ensure_ascii=False) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
|
|
||
| def get_running_tasks_redis_key(template_id) -> str: | ||
| return f"bkflow_running_tasks_template_{template_id}" | ||
|
|
||
|
|
||
| @redis_inst_check | ||
| def get_running_task_count(template_id) -> int: | ||
| """获取指定模板当前运行中的任务数量。Redis 不可用时返回 0,不阻塞流程。""" | ||
| if template_id is None: | ||
| return 0 | ||
| try: | ||
| return int(settings.redis_inst.scard(get_running_tasks_redis_key(template_id)) or 0) | ||
| except Exception as e: | ||
| logger.exception(f"[get_running_task_count] template_id={template_id} error: {e}") | ||
| return 0 | ||
|
|
||
|
|
||
| @redis_inst_check | ||
| def update_running_task(template_id, task_id, action: str) -> bool: | ||
| """更新模板运行中任务集合。 | ||
|
|
||
| :param template_id: 模板 ID | ||
| :param task_id: 任务 ID | ||
| :param action: 操作类型,"add":加入运行集合;"remove":从运行集合移除 | ||
| :return: 操作是否成功 | ||
| """ | ||
| if template_id is None: | ||
| return False | ||
| if action not in ("add", "remove"): | ||
| logger.error(f"[update_running_task] invalid action: {action}") | ||
| return False | ||
| try: | ||
| key = get_running_tasks_redis_key(template_id) | ||
| if action == "add": | ||
| settings.redis_inst.sadd(key, str(task_id)) | ||
| else: | ||
| settings.redis_inst.srem(key, str(task_id)) | ||
| return True | ||
| except Exception as e: | ||
| logger.exception( | ||
| f"[update_running_task] action={action} template_id={template_id} task_id={task_id} error: {e}" | ||
| ) | ||
| return False | ||
|
|
||
|
|
||
| def check_template_concurrency(space_id, template_id): | ||
| """检查模板当前运行中任务数是否达到空间配置的最大并发上限。 | ||
|
|
||
| :param space_id: 空间 ID | ||
| :param template_id: 模板 ID | ||
| :return: (is_allowed, message) | ||
| is_allowed: True 表示允许执行;False 表示已达上限。 | ||
| message: 当 is_allowed 为 False 时为提示信息。 | ||
| """ | ||
| if template_id is None: | ||
| return True, "" | ||
| try: | ||
| interface_client = InterfaceModuleClient() | ||
| space_infos_result = interface_client.get_space_infos( | ||
| {"space_id": space_id, "config_names": "concurrency_control"} | ||
| ) | ||
| space_configs = space_infos_result.get("data", {}).get("configs", {}) | ||
| max_running = int(space_configs.get("concurrency_control", 0)) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⚡ 竞态条件: |
||
| except Exception as e: | ||
| logger.exception(f"[check_template_concurrency] get space concurrency_control error: {e}") | ||
| return True, "" | ||
|
|
||
| if not max_running: | ||
| return True, "" | ||
|
|
||
| running_count = get_running_task_count(template_id) | ||
| if running_count >= max_running: | ||
| msg = f"当前模板(template_id={template_id})正在运行的任务数已达上限({max_running})," f"当前运行中数量为{running_count},请稍后再试" | ||
| return False, msg | ||
| return True, "" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✨ 其他 TEXT 类型配置的
default_value均为字符串(如"1h"、"true"),此处用整数0不一致,建议改为"0"。