这是第一篇博客,内容很简单,主要是记录工作中遇到的关于airflow分布式方面的经验。意义在于学习博客的使用,让自己养成技术博客的习惯。
目录
优劣势分析
分布式部署架构简析
分布式参数
调度、业务代码分离
注意事项
airflow是一个编排、调度和监控workflow的平台,是一个工作任务的调度平台,其内置了比较全面实用的调度功能。下表列出了实际场景下,使用airflow和未使用airflow的对比。
未使用airflow |
使用airflow |
需要自己添加调度代码、调试复杂、功能单一、缺乏整体调度能力 | 框架调度,简单易用,更稳定,功能全面,可以整体调度 |
缺乏图形化能力,给任务的新增、排查等操作带来很多困难。特别是当任务较多,结构复杂的时候 | 内置树状图和流程图,清晰明了的展现任务拓扑结构 |
需要自己添加任务实时监测代码 | 任务实时状态返回网页界面,方便管理和查看 |
任务的各种操作大多需要编码或命令行完成,不够高效 | 常见操作方式转化为图形化界面,高效清晰 |
需要手动分离调度和业务代码 | 调度和业务代码分离,减少耦合性,方便运维和迭代 |
除了以上的优点,工程实践中有一个不足就是分布式部署有点麻烦,容易出错。
如上图所示,airflow主要是通过参数控制调度。具体是,调度机和生产机的dag要放置在相同目录下,并且保持相同的代码。唯一的区别只是,调度机的dag不需要添加生产逻辑。下面给出了调度机的dag示例
#调度机中的dag代码示例
#生产机中的dag要放置到与调度机同样的目录下,并且将执行过程增加到*_function()
import airflow
from airflow.models import DAG
from airflow.operators.python_operator import PythonOperator
default_args = {
'owner': 'xiaoming',
'start_date': airflow.utils.dates.days_ago(1),
'depends_on_past': False,
# 失败发邮件
'email': ['[email protected]'],
'email_on_failure': True,
'email_on_retry': True,
# 重试相关
'retries': 3,
'retry_delay': timedelta(minutes=5),
# 并发限制
'pool': 'data_hadoop_pool',
'priority_weight': 900,
# 按机器名指定运行位置
'queue': '66.66.0.66:8080'
}
dag = DAG(
dag_id='daily',
default_args=default_args, #配置默认参数
schedule_interval='0 13 * * *')
#生产机中,将具体执行过程放置在该函数下
def fetch_data_from_hdfs_function(ds, **kwargs):
pass
#生产机中,将具体执行过程放置在该函数下
def push_data_to_mysql_function(ds, **kwargs):
pass
fetch_data_from_hdfs = PythonOperator(
task_id='fetch_data_from_hdfs',
provide_context=True,
python_callable=fetch_data_from_hdfs_function,
dag=dag)
push_data_to_mysql = PythonOperator(
task_id='push_data_to_mysql',
provide_context=True,
python_callable=push_data_to_mysql_function,
dag=dag)
fetch_data_from_hdfs >> push_data_to_mysql
通过上述简单的部署方式,就可以实现airflow的分布式部署。团队在实践过程中,是使用一台生产机作为调度机,另外机器作为生产机。并且,选择其中一台负载压力不打的生产机作为后备调度机,防止调度机出现问题时,业务崩溃。
从分布式架构图中可以看出,airflow的调度逻辑主要是通过修改参数实现,示例代码如下:
#该task未修改参数,采用默认参数
fetch_data_from_hdfs = PythonOperator(
task_id='fetch_data_from_hdfs',
provide_context=True,
python_callable=fetch_data_from_hdfs_function,
dag=dag)
#该task修改通过指定参数,覆盖默认参数,调整调度行为
push_data_to_mysql = PythonOperator(
task_id='push_data_to_mysql',
queue='77.66.0.66:8080', #通过修改参数,调整调度
pool='data_mysql_pool', #通过修改参数,调整调度
provide_context=True,
python_callable=push_data_to_mysql_function,
dag=dag)
要注意的是,基于Java面向对象思想,会认为修改参数的方式是通过dag.setXxx()。这种方式更符合编程经验,但是airflow简化了此过程,直接可以在Operator中覆盖默认参数,就可以传递给dag进行调用。
上述的dag示例不够完善,因为生产机如果直接在dag中写处理逻辑,会让业务代码与调度代码耦合严重,不利于后期的维护与扩展。因此可以通过下述方式进行业务、调度代码分离。
如上图所示,把业务代码包装成函数,引入生产机dag即可。示例代码如下
import xx.fetch_data_from_hdfs #将包装成函数的业务代码引入
#生产机中,将具体执行过程放置在该函数下
def fetch_data_from_hdfs_function(ds, **kwargs):
if not fetch_data_from_hdfs: #判断业务代码是否执行成功,不成功报错
raise AirflowException('run fail: fetch_data_from_hdfs')
fetch_data_from_hdfs = PythonOperator(
task_id='fetch_data_from_hdfs',
provide_context=True,
python_callable=fetch_data_from_hdfs_function,
dag=dag)
调度机的dag和生产机的dag要保持一致。不一致时,不同的情况造成的后果不一样。比如,生产机参数与调度机不一致,比如指定的pool不存在,程序会正常运行。因为会使用调度机指定的pool。其余的不一致情况可能会造成程序崩溃。