Pyodps批量处理作业

1.背景

        由于业务原因,工作中一直使用的是专有云,且目前不支持OpenAPI,因此作业只能使用手工创建,这在有大量表对象需要创建作业时造成了明显的时间和人力的耗费。

        针对这种问题,首先想到了编写资源,再通过一个Pyodps作业调用资源处理批量的表对象。但是必须解决两个问题:1.各个子实例需要实现独立并行;2.需要不影响子实例的运维。即某个子实例报错需要反馈出来且不影响其他子实例的正常运行

2.思路

        总体思路:通过一个Pyodps作业,并行地去调起表操作实例。

        1.由于Pyodps提供了两种执行SQL语句的方法,execute_sql与run_sql,前者会阻塞调起SQL实例,而后者是不会阻塞的,可实现并行,这就为第一个问题提供了解决方案。值的注意的是run_sql这个方法默认的是只能执行一句SQL(即一个分号前的语句),如果想执行多个SQL过程,需要加一个设置参数"odps.sql.submit.mode":"script",即

o.run_sql(sql,hints={"odps.sql.submit.mode":"script"})

        2.为了保证同一个Pyodps作业可以对所有子实例起到运维监测的作用,就需要在作业里有两个全局列表变量,一个用来记录下每一个子实例以及其所操作的对象名称(一般是表名),另一个用来记录失败的实例表名。即在所有run_sql实例都提交云上并行处理后,遍历这个实例列表,对每个子实例进行状态的追踪,如果子实例运行结束且它运行失败了(成功不做处理),那么就将这个实例所操作的对象名记录在失败的清单里,然后增加偏移量,继续追踪下一个子实例。

        每个实例即instance,都有status.name属性显示是否运行完成(运行完成不代表成功或失败),'TERMINATED'表示instance完成。检验是否成功则调用instance的is_successful方法,若instance.is_successful()返回True即代表成功,False则代表失败。

        3.在遍历完所有子实例(即子实例全部'TERMINATED')后,检查失败清单,如果失败清单为空则成功结束。如果失败清单不为空,则打印出失败清单,并且抛出错误(这样此作业会报错,可以通过运维中心查看日志定位失败清单)。

3.示例

        简单起见,这里举一个表批量同步落地的示例,将其他空间的表同步到本空间(表名与表结构不变)。

1.编写资源(也可以直接放在作业里)

创建名为utils_data_sync.py的py资源,加入以下代码并提交。

# -*- coding: utf-8 -*-

#批量数据落地
def exec_merge_sql_batch (o,sourceproject,sourcetable):
    #检查源空间是否存在此表
    if not o.exist_table(sourcetable, sourceproject):
        return None
    #获得源表
    table = o.get_table(sourcetable, sourceproject)
    source_columns = ''
    #获得源表字段以拼接插表语句
    for col in table.schema.columns:
        source_columns += '    ,' + col.name + '\n'
    #检查是否存在插入目标表,若不存在则创建
    if not o.exist_table(sourcetable):
    #若对建表的结构有处理需求也可以单起一个建表方法,返回建表语句,也可用gen_create_table_sql
    #但是需要注意,如果是执行建表语句应该使用execute_sql保证阻塞
        o.create_table(sourcetable,table.schema,if_not_exists=True)
        print('\n========建表sql========\n' + sourcetable)
    #插表语句的最终形成
    dm_sql = '''
    insert overwrite table %s
    select %s from %s
    ''' %(sourcetable,source_columns[5:],sourceproject + '.' + sourcetable)
    instance=o.run_sql(dm_sql)
    print('\n========合并sql========\n' + sourcetable)
    #注意,一定要返回实例(元组绑定实例和表名)
    return (instance,sourcetable)

2.Pyodps作业

# -*- coding: utf-8 -*-
##@resource_reference{"utils_data_sync.py"}
import sys
import os
import time
sys.path.append(os.path.dirname(os.path.abspath('utils_data_sync.py'))) #将资源引入工作空间
reload(sys)
sys.setdefaultencoding('utf8')
import utils_data_sync as uds

#批量表清单(自行填写)
tables=['TABLE_A',
'TABLE_B',
'TABLE_C']

# 数据源空间名(自行填写)
sourceproject = 'XXXXXXX'

# 实例容器
instances=[]
fail_table_list=[]
all_cnt=0
complete_cnt=0

#批量调起并行拉表实例
for table in tables:
    ret=uds.exec_merge_sql_batch(o,sourceproject,table)
    if not ret:
        fail_table_list.append(table)
    else:
        instances.append(ret)
        all_cnt+=1

#实例状态追踪
while complete_cnt<all_cnt:
    if instances[complete_cnt][0].status.name=='TERMINATED':
        if not instances[complete_cnt][0].is_successful():
            fail_table_list.append(instances[complete_cnt][1])
        complete_cnt+=1
    else:
        time.sleep(10)
    print('已完成实例---总实例')
    print('{:.0f}---{:.0f}'.format(complete_cnt,all_cnt))


try:
    if not fail_table_list:
        print('========全部实例运行成功========')
    else:
        print('========存在实例出现异常========')
        print(fail_table_list)
        raise Exception("请查看打印出的异常实例!")
except RuntimeError as e:
    print(repr(e))

3.结果展示

        将上述Pyodps作业上调度,然后如果自然调度报错,可在运维空间查看,如下:

        (有需要的也可使用logging和traceback等做记录)

4.总结

        这都是在没有OpenAPI情况下的权宜之计。。。

5.更新

        其实可以跟进一步地打印出报错的实例log,即把错误实例直接放进报错列表,抛错时通过实例的get_logview_address方法打印log地址备查。这样就不需要单独再检查报错实例的问题了。

#实例状态追踪
while complete_cnt<all_cnt:
    if instances[complete_cnt][0].status.name=='TERMINATED':
        if not instances[complete_cnt][0].is_successful():
            fail_table_list.append(instances[complete_cnt])
        complete_cnt+=1
    else:
        time.sleep(10)
    print('已完成实例---总实例')
    print('{:.0f}---{:.0f}'.format(complete_cnt,all_cnt))


try:
    if not fail_table_list:
        print('========全部实例运行成功========')
    else:
        print('========存在实例出现异常========')
        for instance in fail_table_list:
            print('异常表'+instance[1])
            if instance[0]=='数源无此数据':print('异常log:数源无此数据')
	#如果是专有云,那么打印log需根据空间的实际网络地址替换
            else:print('异常log:'+instance[0].get_logview_address().replace('http://logview.odps.aliyun.com','专有云地址'))
        raise Exception("请查看打印出的异常实例!")
except RuntimeError as e:
    print(repr(e))

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值