导读
背景
不同人对于同一个中文名称的指标有着不一样的理解,会直接导致指标计算逻辑的差异从而导致结果的混乱,因为口径定义的差别,导致计算的数据指标存在差异,从而失去指标辅助决策的意义。比如:在58的体系中,购买网邻通用户数就有多种定义,由此带来的问题是,都是购买用户数,应该用哪个?为什么数据都不一样?
对于同一个指标,有着不同的中文命名方式,比如说“性别”这个指标,有的人叫 sex,有的人叫gender**,这个还好分辨,但使用起来就很不方便。如果换了个指标口径复杂一点的,就很难和别人进行解释了,比如“近1个月客户充值金额”和“近30天客户充值金额”,这个时候你就会懵了,究竟他们是一样的呢还是不一样呢?
这个还蛮多开发同事会犯错的,就是有的时候开发任务重,然后这块的文档维护就不怎么上心,当下就是简单的对指标口径进行简单描述,以为自己理解了就行,但事实上描述的准确性就不怎么好理解了,会让人产生歧义,有歧义那就有问题了。
比如,如果我描述指标“消费金额”的口径为:现金+优惠。那么不同人理解就会存在偏差了,这到底是否包括了优惠金额呢?还是优惠后的订单金额呢?所以说呢,描述的越详细越好,最好是引用于源表的哪个字段都标注清楚,下次别人在看的时候就一目了然了。
在流量分析数据产品中,有“7 日 uv”这个指标,口径的定义是 7 日内日均 uv。根据口径描述的计算逻辑,应该是最近 7 日,每日 uv 相加除以 7 取平均值。显然,这个定义在业务场景中是有问题的,正确的 7 日 uv 的口径定义应该是 7 日内去重的用户数。
比如我们在梳理效果类的指标体系过程中,有一个指标叫CVR,点击转化率。其实它在不同的业务线不同产品下计算逻辑是不一致的,比如非招聘业务线CPT产品CVR=成功电话数/反作弊的点击,CPC产品=成功电话数/有扣费的计费点击,所以单纯从指标命名来看很难理解这个指标描述的业务过程,也就很难决策怎样使用这个指标。
有些比较复杂一点的指标,直接用业务口径的文字去描述其实是很困难的,这个时候其实我们可以尝试去写一下伪代码以及标明源表来源和对应字段,这样子后续追溯起来也是十分方便的。
另一种现象是:
使用的数据源不同,这种常见的就是数据采集的方式不同,最常见的场景就是,前端采集和后端采集,这种数据源的不一致必然会完成数据加工后形成的指标是不一致的。
解决如何规范定义指标
为了提高指标管理的效率,你需要按照业务线、主题域和业务过程三级目录方式管理指标(业务线是顶级目录)。在58,有招聘、房产、黄页、二手车不同的业务线。在业务线之下,是主题域,指标中的主题域与数仓中的概念是一致的,划分标准最好是跟数仓保持一致(收入、流量、客户)。在主题域下面还有细分的业务过程,比如展现、点击、效果等。
对于数据口径不一致的问题,我们引入原子指标(原生指标)和 派生指标(复合指标)的概念。那么什么是原子指标,什么是派生指标呢?
统计周期、统计粒度、业务限定、原子指标,组成派生指标,所以原子指标可以定义为不能够按照上述规则进一步拆分的指标。
在例子中,你可以这样理解:
购买用户数是原子指标,原子指标的口径定义是“计算周期内去重的,下单并且支付成功的用户数量”;
会员和非会员都可以认定为业务限定词;
统计粒度是商品粒度的,比如xx网邻通;
统计周期是 30 天。
这样 30 天内,商品维度的会员购买用户数和 30 天内商品维度的非会员购买用户数就作为两个派生指标存在,但是他们继承自同一个原子指标。
对于原子指标,指标名称适合用“动作 + 度量”的命名方式(比如注册用户数、购买用户数),标识的命名用英文简写或者汉语拼音缩写比较好。
对于派生指标,指标名称应该严格遵循“修饰词(时间周期 + 统计粒度+其他修饰词)+ 原子指标”的命名方式,标识命名要用“修饰词 _ 原子指标 _ 时间周期”的方式。
对于使用指标的人(产品、运营、分析师)了解了这个指标的口径定义之后,下一步就是要看指标的数值。所以,在全局的指标字典中,还应该有指标来源与哪些表及被哪些应用使用,这样方便去对应的数据产品或者报表上查看指标的数值。
主题域 | 招聘 |
应用领域 | 商业化占比专题分析 |
指标一级分类 | 收入类 |
指标二级分类 | 日维度 |
指标中文名称 | 现金收入 |
指标英文名称 | Cash |
指标计算逻辑 | SUM(CASH) |
指标来源表 | T_DWD_KYLIN_DETAIL_ZP |
指标格式化方式 | ,### |
建设通用指标体系管理层
在了解如何管理指标之后,我们还需要一款好用的工具或者平台进行管理,帮助我们落实管理方法。当前我们实行两步走:
1、数据产品提出指标、定义指标、指标固化、制定计算逻辑、落实到指标白皮书
2、技术根据白皮书指标定义,落实到统一的指标计算层,通过数据平台提供统一的输出。
指标系统是基于元数据中心构建的一个指标管理工具,它与元数据中心同步数仓的主题域和业务过程,按照规范化定义创建指标。
新创建的指标同时会以特定类型的标签,下沉到元数据中心对应的表和字段上,这样在数据地图上就可以搜索到表关联的指标。
新建指标同时会以标签的形式,标注到元数据中心对应的表上,这样可以在数据地图上方便搜索到表关联的指标。
让使用方快速找到数据、便于使用数据
数据地图使用页面:
指标治理的最终结果,就是要形成一个全局业务口径一致的指标字典。让使用指标的人,可以通过指标字典,快速了解指标的业务含义和计算过程,不会对指标口径产生歧义。
下图详解描述了新建指标的流程,且流程中涉及了参与的各个角色。
指标需求评审,需要需求方、数据开发、应用开发都参加。评审首先要确认这是不是一个新的指标,并明确它是原子指标还是派生指标。评审的目的就是要大家达成一致。
评审的结果一种是不需要开发,是一个已经存在的指标,直接可以通过设计逻辑模型(具体我会在数据服务章节讲),发布接口,获取数据。第二种就是需要开发。前者交付时间短,后者需要排期,交付时间长。
上面我提到指标有一级和二级之分,这个流程适用于一级指标,对于二级指标,可以不需要评审,当然开发也是由业务方开发和发布上线。
搭建数据应用平台
有了指标体系的定义之后,怎样快速方便的使用搭建我们的数据应用呢,在指标体系基础上我们将此规范工具化到了我们应用创建的流程中,可以方便快速的搭建一套数据可视化分析页面,我们的方案如下:首先在数据层构建了数据应用层DWS,将不同类型(收入、流量、客户等)的数据按业务整合到一起,同时划分不同活动主题,按事件再向上聚合,做专题的数据支撑,统一数据出口。然后通过多维预计算引擎(Kylin、Doris等)对事实数据进行预计算,构建数仓与应用的管道,从而节省计算成本,并且提升了数据互通和消费的效率,最后建设统一应用报表层。通过丰富的可视化效果,及多样的分析对比操作,快速、全面地支撑、驱动业务。
数据应用分层架构图:
支持界面配置多种JDBC数据源接入,当前在我们的应用中已经成功上线了kylin 和 doris 适配解决不同的业务问题。
自动创建对应的数据库连接池,保证上层应用的查询效率和速度
通用的SQL引擎计算层,兼容各种SQL使用及查询
支持自定义的SQL的数据处理和转换
提供标准的指标计算模型,方便快速获取和查询
缓存层针对不同SQL缓存查询结果,保证上层应用展示的效率和用户体验
支持各种可视化组件展示
支持自由分析
下面主要讲下,指标管理层的技术实现细节,详细如下:
1、指标计算层,接口类:定义了通用的接口实现,包含:基本的指标计算方法定义,比如:日环比、周同比等
2、基类:实现类接口的所有方法,满足通用的指标计算逻辑,包含SQL校验、SQL解析、SQL执行、数据组装的实现
3、自定义指标,主要满足基类在不能通用指标计算逻辑的情况下,可以自己实现指标的计算逻辑
1、平台新建录入指标
2、代码实现自定义指标
1、继承基类进行自定义指标的重写;
2、实现接口类,自己实现所有指标的代码编写;
Demo实现(基于基类方法一实现):
//继承基类
public class Imp extends BaseAbstractTargetService {
public Imp(final Long id, final ViewExecuteParam executeParam, final User user) {
super(id, executeParam, user);
}
/**
* 重写实现日环比的方法
**/
/**
* 获取天维度指标数据信息
*/
public String getRiHuanBi(TargetDic targetDic) throws SQLException {
//获取请求参数信息
ViewExecuteParam viewExecuteParam = ObjectUtil.cloneByStream(executeParam);
//开始时间
String start0 = viewExecuteParam.getStart();
//结束时间
String end0 = viewExecuteParam.getEnd();
Assert.state(StrUtil.isNotBlank(start0), () -> "未设置start参数值");
Assert.state(StrUtil.isNotBlank(end0), () -> "未设置end参数值");
String start1 = DateUtil.format(DateUtil.offsetDay(DateUtil.parse(start0), -1), DatePattern.PURE_DATE_PATTERN);
String end1 = DateUtil.format(DateUtil.offsetDay(DateUtil.parse(end0), -1), DatePattern.PURE_DATE_PATTERN);
//交叉维度
List<String> groups = viewExecuteParam.getGroups();
String select = groups.stream().map(g -> StrUtil.concat(false, "a.", g)).collect(Collectors.joining(","));
String selecter = Joiner.on(",").join(groups.stream().filter(g ->
!StrUtil.equalsIgnoreCase(g, "DT")
).collect(Collectors.toList()));
String group = Joiner.on(",").join(groups);
//where条件
String where = SqlUtils.convertFilters(viewExecuteParam.getFilters(), Sets.newHashSet("dt"));
/**
* 组装SQL
*/
Map<String, Object> paramMap = Maps.newHashMap();
paramMap.put("SELECT", select);
paramMap.put("SELECTER", selecter);
paramMap.put("GROUP", group);
paramMap.put("START0", start0);
paramMap.put("END0", end0);
paramMap.put("START1", start1);
paramMap.put("END1", end1);
paramMap.put("WHERE", where);
paramMap.put("EXP", targetDic.getExpression());
paramMap.put("ZN", targetDic.getFirstTargetZn());
paramMap.put("EN", targetDic.getFirstTargetEn());
//当前时间端数据
StringBuilder currentDaySql = new StringBuilder(
"SELECT DT,{SELECTER}, COALESCE({EXP},\'-\') AS "XX" FROM "
+ targetDic.getTableName()
+ " WHERE dt BETWEEN \'{START0}\' AND \'{END0}\'");
if (StrUtil.isNotBlank(where)) {
currentDaySql.append(" AND {WHERE} ");
}
currentDaySql.append(" GROUP BY {GROUP}");
//上一个时间端的数据
StringBuilder yestodaySql = new StringBuilder(
"SELECT CAST(CAST(DT as INT) +1 AS varchar) AS DT,{SELECTER}, COALESCE({EXP},\'-\') AS "YY" FROM "
+ targetDic.getTableName()
+ " WHERE dt BETWEEN \'{START1}\' AND \'{END1}\'");
if (StrUtil.isNotBlank(where)) {
yestodaySql.append(" AND {WHERE} ");
}
yestodaySql.append(" GROUP BY {GROUP}");
paramMap.put("CURRENTDAYSQL", StrUtil.format(currentDaySql, paramMap));
paramMap.put("YESTODAYSQL", StrUtil.format(yestodaySql, paramMap));
String join1 = groups.stream().map(g ->
StrUtil.concat(false, "a.", g, "=", "b.", g)
).collect(Collectors.joining(" and "));
paramMap.put("JOIN1", join1);
String excuteSql = "SELECT {SELECT},CASE WHEN (a.XX - b.YY) / a.XX IS NULL THEN 0.0 ELSE (a.XX - b.YY) / a.XX END AS "{EN}" from ({CURRENTDAYSQL}) a LEFT JOIN ({YESTODAYSQL}) b ON {JOIN1}";
//返回SQL语句
return StrUtil.format(excuteSql, paramMap);
}
平台提供通用的指标查询API,详细如下
请求参数:
{
"cate1":1,
"cate2":2,
"start":"yyyyMMdd",
"end":"yyyyMMdd",
"zt":1, 1:商业化占比专题 2:商业漏斗专题 3:产品分析专题
{
"column":"pv0",
"func":"sum"
},
{
"column":"pv0_week_mom",
"func":"sum"
}
],
{column: "disp_src1", direction: "desc"}
],
"groups":[
"product_id",
"platform",
"is_diaoqi",
"disp_src1"
],
"filters":[
{
"name":"product_id",
"type":"filter",
"value":[
"\'10011\'",
"\'10042\'"
],
"sqlType":"VARCHAR",
"operator":"in"
},
{
"name":"platform",
"type":"filter",
"value":[
"\'APP调起\'",
"\'APP非调起\'"
],
"sqlType":"VARCHAR",
"operator":"in"
},
{
"name":"product_id",
"type":"filter",
"value":"\'详情页\'",
"sqlType":"VARCHAR",
"operator":"="
}
],
],
}
返回值
{
"payload": {
"columns": [
{
"key": "DT",
"name": "时间",
"sort": 100,
"type": "VARCHAR"
},
{
"key": "PRODUCT_ID",
"name": "产品",
"sort": 100,
"type": "VARCHAR"
},
{
"key": "PLATFORM",
"name": "平台",
"sort": 100,
"type": "VARCHAR"
},
{
"key": "计算指标",
"name": "计算指标",
"sort": 100,
"type": "VARCHAR"
},
{
"key": "Imp",
"name": "帖子的展现量-大盘(Imp)",
"sort": 100,
"type": "VARCHAR"
},
{
"key": "Vppv",
"name": "原始帖子点击数(Vppv)",
"sort": 100,
"type": "VARCHAR"
},
{
"key": "Resume",
"name": "简历量(Resume)",
"sort": 100,
"type": "VARCHAR"
},
{
"key": "Interest",
"name": "访客量(Interest)",
"sort": 100,
"type": "VARCHAR"
},
{
"key": "Ctr2",
"name": "展现点击率(Ctr2)",
"sort": 100,
"type": "VARCHAR"
},
{
"key": "Cvr",
"name": "点击转化率(Cvr)",
"sort": 100,
"type": "VARCHAR"
}
],
"firsrTargetName": "",
"pageNo": -1,
"pageSize": -1,
"resultList": [
{
"DT": "20210419",
"PLATFORM": "APP",
"Interest": "",
"Ctr2": "0%",
"计算指标": "当天值",
"Vppv": "0",
"PRODUCT_ID": "简历置顶",
"Imp": "0",
"Cvr": "0%",
"Resume": "0"
},
{
"DT": "20210419",
"PLATFORM": "M",
"Interest": "",
"Ctr2": "XX%",
"计算指标": "当天值",
"Vppv": "",
"PRODUCT_ID": "黄金展位",
"Imp": "",
"Cvr": "",
"Resume": ""
},
{
"DT": "20210418",
"PLATFORM": "M",
"Interest": "",
"Ctr2": "",
"计算指标": "当天值",
"Vppv": "",
"PRODUCT_ID": "",
"Imp": "",
"Cvr": "",
"Resume": ""
}
],
"secondTargetName": "",
"totalCount": -1
}
}
应用搭建好的指标体系,我们搭建了招聘专题分析,主要包含如下:
总结
整体我们了解了搭建一套指标体系:
1、通过系统 + 规范的方法,解决了数据中台指标一致性管理的难题,数据指标口径一致,可以让各种场景下看到的数据一致性得到保障,精准驱动业务的正确发展。
2、指标管理层,灵活、方便、可扩展维护指标信息,支撑不同的业务分析场景。
3、统一API接口层,通过建设通用的SQL查询、计算引擎,方便串联不同的多维引擎和上层图表层满足不同的业务分析,同时增强的指标的复用,精简数据表的存储量,节省研发成本和精力。
4、图表层,丰富的可视化组件,多方位支撑业务。
有奖互动
Perseverance Prevails
欢迎在评论区为作者大佬打call,将抽取点赞前十名送出58技术周边奖品一份。
开奖时间:6月11日 18:00