Airflow 在气流中创建动态池
我有一个DAG,它创建一个集群,启动计算任务,完成后,拆掉集群。我想将这个集群上执行的计算任务的并发性限制为固定数量。因此,从逻辑上讲,我需要一个由任务创建的集群专用的池。我不希望干扰其他DAG或同一DAG的不同运行 我认为我可以通过在集群创建后从任务动态创建一个池来解决这个问题,并在计算任务完成后将其删除。我想我可以模板化计算任务的Airflow 在气流中创建动态池,airflow,Airflow,我有一个DAG,它创建一个集群,启动计算任务,完成后,拆掉集群。我想将这个集群上执行的计算任务的并发性限制为固定数量。因此,从逻辑上讲,我需要一个由任务创建的集群专用的池。我不希望干扰其他DAG或同一DAG的不同运行 我认为我可以通过在集群创建后从任务动态创建一个池来解决这个问题,并在计算任务完成后将其删除。我想我可以模板化计算任务的池参数,使它们使用这个动态创建的集群 # execute registers a pool and returns with the pool name creat
池
参数,使它们使用这个动态创建的集群
# execute registers a pool and returns with the pool name
create_pool = CreatePoolOperator(
slots=4,
task_id='create_pool',
dag=self
)
# the pool parameter is templated
computation = ComputeOperator(
task_id=compute_subtask_name,
pool="{{ ti.xcom_pull(task_ids='create_pool') }}",
dag=self
)
create_pool >> computation
但这样,计算任务将永远不会被触发。因此,我认为池参数在模板化之前保存在任务实例中。
我想听听您对如何实现所需行为的想法。与其尝试使用动态池,不如看看
aiffair.models.DAG
上的concurrency
属性是否能奏效。这将限制流程当前运行中正在运行的任务的数量。这个答案可能会加剧一些问题,但这是一个可能的途径,因此值得记录。使Airflow比其竞争对手更强大的核心功能是,所有内容都是使用代码定义的。在一天结束时,如果气流没有为我们提供一个特性,我们总是可以使用Python自己创建该特性
您希望能够在DAG中汇集任务,但仅限于该特定DAG运行。因此,尝试在任务上创建一个自定义池。我脑子里有一些伪代码
List<String> tasksPoolQueue = new ArrayList<String>();
def taskOnesFunction()
while true:
if tasksPoolQueue.get(0) == "taskOnesTurn":
print("Do some work it's your turn")
# Delete this run from the list and shift the list over to the left one index
# So that the next value is now the first value in the list
tasksPoolQueue.delete(0)
return 0
else:
sleep(10 seconds)
def taskTwosFunction()
while true:
if tasksPoolQueue.get(0) == "taskTwosTurn":
print("Do some work it's your turn")
# Delete this run from the list and shift the list over to the left one index
# So that the next value is now the first value in the list
tasksPoolQueue.delete(0)
return 0
else:
sleep(10 seconds)
def createLogicalOrderingOfTaskPoolQueue():
if foobar == true:
tasksPoolQueue[0] = "taskOnesTurn"
tasksPoolQueue[1] = "taskTwosTurn"
else:
tasksPoolQueue[0] = "taskTwosTurn"
tasksPoolQueue[1] = "taskOnesTurn"
return 0
determine_pool_queue_ordering = PythonOperator(
task_id='determine_pool_queue_ordering',
retries=0,
dag=dag,
provide_context=True,
python_callable=createLogicalOrderingOfTaskPoolQueue,
op_args=[])
task1 = PythonOperator(
task_id='task1',
retries=0,
dag=dag,
provide_context=True,
python_callable=taskOnesFunction,
op_args=[])
task2= PythonOperator(
task_id='task2',
retries=0,
dag=dag,
provide_context=True,
python_callable=taskTwosFunction,
op_args=[])
determine_pool_queue_ordering.set_downstream(task1)
determine_pool_queue_ordering.set_downstream(task2)
List tasksPoolQueue=new ArrayList();
def taskOnesFunction()
尽管如此:
如果tasksPoolQueue.get(0)=“taskOnesTurn”:
打印(“做些工作,轮到你了”)
#从列表中删除此运行,并将列表移到左侧索引
#因此,下一个值现在是列表中的第一个值
tasksPoolQueue.delete(0)
返回0
其他:
睡眠(10秒)
def taskTwosFunction()
尽管如此:
如果tasksPoolQueue.get(0)=“taskTwosTurn”:
打印(“做些工作,轮到你了”)
#从列表中删除此运行,并将列表移到左侧索引
#因此,下一个值现在是列表中的第一个值
tasksPoolQueue.delete(0)
返回0
其他:
睡眠(10秒)
def createLogicalOrderingOfTaskPoolQueue():
如果foobar==true:
tasksPoolQueue[0]=“taskOnesTurn”
tasksPoolQueue[1]=“taskTwosTurn”
其他:
tasksPoolQueue[0]=“taskTwosTurn”
tasksPoolQueue[1]=“taskOnesTurn”
返回0
确定池队列排序=PythonOperator(
任务\u id='determinate\u pool\u queue\u ordering',
重试次数=0,
dag=dag,
提供上下文=True,
python_callable=createLogicalOrderingOfTaskPoolQueue,
op_args=[])
task1=蟒蛇算子(
task_id='task1',
重试次数=0,
dag=dag,
提供上下文=True,
python_callable=taskOnesFunction,
op_args=[])
task2=蟒蛇算子(
任务_id='task2',
重试次数=0,
dag=dag,
提供上下文=True,
python_callable=taskTwosFunction,
op_args=[])
确定池队列顺序。设置下游(任务1)
确定池队列顺序。设置下游(任务2)
希望大家都能理解我的伪代码。我不知道创建自定义池的最佳方法是什么,它不会引入“竞争条件”,所以这个列表队列想法是我第一眼想到的。但这里的要点是task1和task2将同时运行,但在它们的函数中,我可以使它在通过if语句之前不会执行任何有意义的操作,因为if语句阻止它运行真正的代码
第一个任务将使用列表动态设置哪些任务首先运行以及以什么顺序运行。然后让所有需要在此自定义池中的函数引用该列表。由于我们的if语句只有在taskName位于列表的第一位时才等于true,因此本质上意味着一次只能运行一个任务。列表中的第一个任务将在处理完需要执行的任何操作后从列表中删除自己。然后,其他任务将在等待其任务名称位于列表的第一位时休眠
因此,只需创建一些类似于我的自定义逻辑。这里有一个操作符,如果池不存在,它将创建一个池
from airflow.api.common.experimental.pool import get_pool, create_pool
from airflow.exceptions import PoolNotFound
from airflow.models import BaseOperator
from airflow.utils import apply_defaults
class CreatePoolOperator(BaseOperator):
# its pool blue, get it?
ui_color = '#b8e9ee'
@apply_defaults
def __init__(
self,
name,
slots,
description='',
*args, **kwargs):
super(CreatePoolOperator, self).__init__(*args, **kwargs)
self.description = description
self.slots = slots
self.name = name
def execute(self, context):
try:
pool = get_pool(name=self.name)
if pool:
self.log(f'Pool exists: {pool}')
return
except PoolNotFound:
# create the pool
pool = create_pool(name=self.name, slots=self.slots, description=self.description)
self.log(f'Created pool: {pool}')
删除池也可以采用类似的方式。我已经知道该配置选项。但是我需要控制一组特定任务的并发性,所以这不是我想要的。但是我意识到我们一次只需要运行一次相同的DAG,所以我将
max\u active\u runs
设置为1,并为任务可以使用的DAG插入一个池。尽管如此,这并不是一个最佳的解决方案……@joeb,@midparse好的,至少我不必动态创建池,所以您认为这样做可行吗?(仅在部署DAG时完成)。此外,你能想出一个可行的解决方案吗?是的,这将适用于全球池。但是,在这种情况下,使用某种init脚本(例如来自bash)来设置池会更干净。这样你就不会不必要地对数据库运行查询了。我已经为你提供了一个定制的解决方案。请用专业的战斗准备代码来调整伪代码,这不会导致“种族状况”:(我当时正在匈牙利布达佩斯参观我的办公室!!!也许我遇到你了,哈哈。我想我们没见过面。希望你玩得开心!:)我在下面发布了一个答案,为什么在创建池之前你不能知道池名?这似乎可以解决您的问题。为什么在创建池之前您不能知道池名?这似乎可以解决你的问题:因为我希望每次跑步都有一个新的游泳池。这需要至少将DagRun的id传递给name,这需要templatin