业务背景
花呗有一个生息产品叫做循环, 也就最低还款: 即每月进行最小还款, 剩下的金额产生利息.
用户每个月都可以进行最低还款的办理, 即还不掉的本金永远在里面滚着.
业务方想要知道一个业务指标, 就是用户连续办理了多少个月的循环, 然后针对这部分用户做精细化运营
解决思路
这个问题有两个解法
1. ODPS SQL解法
每月月末跑一个数, 统计本月用户是否使用过循环, 开始月份是X月, X+1月有用过则把连续循环数+1, 没有用过则置零. 该方法的缺点是时效性问题, 即月末才能看到数据, 有些用户在月中就完成循环了.
2. ODPS UDF解法
拉取用户历史以来循环办理日期(中间层有个全量表可以直接取出数据), 通过日期数组直接判定是否连续循环, 该方法效率快, 准确性高, 数据时效性也好, 唯一麻烦的就是需要自己写UDF
后来评估了两个方法的开发量, 最后决定使用UDF实现这个功能, 所以接下来说说UDF的实践过程
D2开发Python3 UDF的流程简介
1. 本地开发Python3代码
虽然现在D2有Function Studio, 但是它只支持Python2.7, 不支持3的运行
所以我是本地先写个.py实现核心功能, 再弄到D2里面去, 用macOS开个terminal做测试特别方便.
2. 建立资源
2.1 ODPS Python改写
资源说白了就是实现核心功能的py模块, 但是这段代码需要进行ODPS规范的改造
1-引入模块odps.udf, 在每个类前加入一段@annotate的修饰符, 管理入参和出参格式, 但是入参的写法和python语法不太一样, 是遵循了ODPS的语法, 详见4条目
2-同时需要把本地的实现函数改为class
3-并且该类中只能调用evaluate方法实现核心功能
from odps.udf import annotate # 引入odps包
@annotate("array<string> -> bigint") # 修饰符, 入参 -> 出参
class CalMaxCycleCnt(object): # 类名, 即要发布的函数名称
def evaluate(self, date_lists): # 实现方法, 必须写evaluate
'''
date_lists.sort(reverse=True)
实现的功能的核心代码
'''
return max_cycle_cnt # 返回结果, bigint
4-ODPS和PYTHON3参数格式转换
可先下图, 或见链接: https://tech.antfin.com/docs/2/154431
2.2 D2建立资源流程图
第一步, 新建
第二步, 填写资源名称
第三步, 贴上代码+提交发布
3. 引入函数
资源发布后, 可以把资源中的class作为函数引入到ODPS中
第一步, 新建
第二步, 填写函数名称, 此处填写class名称
第三步, 配置函数+提交发布
类名要填资源名称(不含py).类名, 如图中hb_2XXXXXX3_test.CalMaxCycleCnt, 否则定位不到函数
资源列表填资源名称.py即可, 如图中hb_2XXXXXX3_test.py
4. 线上验收测试
SELECT user_id
,CalMaxCycleCnt( txn_dt_array ) AS max_cycle_cnt
FROM tbl_name
后记
1. 测试难
因为写的Python3, 无法测试环境测, 所以代码要尽可能写的完整, 不然BUG只能通过再次发布来修复
2. 注意NULL值
SQL的入参, 特别是多表关联的时候难免有NULL值, python的代码里面可以加这一段对NULL值初始化
if next_bill_date is None or len(next_bill_date)==0:
next_bill_date=next_month_fst_day(bizdate)
如果直接对NULL值判定len, 则会报错
TypeError: object of type 'NoneType' has no len()