2024年5月14日

数据服务是将数据转化为一种服务能力,通过数据服务让数据参与到业务中,激活整个数据中台的价值。数据服务是一套数据请求API,数据应用可以通过API获取相应的数据。通过数据服务,可以解决一些与数据相关的问题,例如数据的获取、处理、分析和应用。数据服务的建设思路是数据中台的一部分,完善数据服务的程度直接影响了数据对业务赋能的效率。因此,数据产品和中台架构师应该投入更多的精力来完善数据服务。

在解释数据服务含义之前,我们先看下,通常数据应用在数据使用过程中出现的一些问题。

数据接入方式多样,介入效率低。

数据开发加工好数据后,通常会以Hive表的方式保存到hdfs上。前端应用如果想使用这些数据的话,为了保证查询性能,需要将数据导入到一个中间存储上。根据数据的使用方式和数据规模,有如下存储方案:

数据规模小于500w,可以使用mysql,Oracle,sqlServer等DB。

数据规模大,且存在多维查询场景。可以使用StarRocks。

点查场景,数据量小可以使用redis,数据量大,采用HBase。

因为不同的中间存储,涉及的访问 API 也不一样,所以对数据应用开发来说,每个数据应用都要根据不同的中间存储,开发对应的代码,如果涉及多个中间存储,还需要开发多套代码,数据接入效率很低。

数据复用困难

完了满足A应用,数据开发生成了一张数据表tableA,包含订单量数据。B应用也有一个数据需求,需要订单量和点击量数据。因为tableA中不包含点击数据,数据开发不得不重新开发一份同时包含订单量和点击量的数据。

底层数据变更,影响数据应用

应用前期因为数据量不大,把数据存在了redis中。随着业务的发展,redis已经不能满足存储需求,需要把数据迁移到Hbase中,而与之带来就是应用段需要修改取数代码。还有就是为了特定的业务场景,底层数据字段发生变更,前端应用也不得不做出响应代码调整。

无法知道数据被哪些应用调用

数据开发了生产了很多数据,但随着业务的调整,有些数据会慢慢的被弃用,被弃用的数据是应该进行下线的,同时释放相应的存储和计算资源,但是数据开发根本不知道,数据被哪些应用使用,也就无法下线数据。

对于这些问题,我们可以通过数据服务来解决。那数据服务是什么?数据服务简单来说是一套数据请求API,数据应用通过api获取相应的数据。对于之上的四个问题,数据服务是如何解决的呢?

数据应用通过API来获取数据,数据API介于底层数据和数据应用之间。数据的存储方式对于用户来说就是透明的,用户自然不需要根据不同的存储方案开发对应的代码。

数据难复用最根本的问题在于,我们在构建数据模型无法将所有的应用场景都考虑全。为了满足应用需求,不得不重复造数据。例如下图:应用C需要同时使用数据A和数据B,因为数据A和数据B在不同的物理表中,或者这两份数据采用的存储方案不同。数据应用没办法使用这两份数据,不得不对合并数据A和数据B,得到满足需求的数据C。

数据服务解决思路如下:

最本质的区别就是不会构建“数据C”,数据服务将请求拆分成两个查数请求,分别从“数据A”和“数据B”中获得数据,并按照规则进行组装,最后返回,具体怎么实现的后续会讲解。

底层数据变更,影响上层应用这个问题中,底层数据变更主要有两个方面:存储变更、元数据变更。存储由Reids->Hbase,因为数据服务通过api的方式暴露给用户,这种变更是不会影响数据应用的。元数据变更倒是对上层应用影响很大。在数据服务api并不会把底层存储的字段名直接返回给用户使用,而是会维护映射关系,这样的话,底层元数据变更,我们只需要修改映射关系,上层应用就不会受到影响。

数据调用无法被追踪?最根本的原因是数据调用时没有记录数据使用记录。通过数据服务,可以对每一次请求进行鉴权,并记录请求信息。这样的话,数据出了问题可以立马获得受影响的数据应用,同样通过请求记录,我们能够很快知道数据是否被使用,对于没有使用、低价值的数据,就可以推动下线。


数据服务只是一套API集合么?我们从数据应用接入的整个流程来看下,到底经历了哪些步骤,对应这些关键节点,我们都需要做些什么?

如下图所示,一个数据应用想要一份数据,首先可以到数据API集市去检索,看看是否有现成的API能够满足需求,如果API集市中已经存在能够满足需求的API,数据应用需求方只需要使用认证凭证发起API请求权限申请(提供QPS,需求场景),并且数据API的提供者完成授权,这样数据应用就完成了数据对接。如果数据应用没有认证凭证,则需要申请认证凭证。

如果数据API集市没有满足数据应用API,则需要把数据使用需求提交到数据产品。数据产品根据现有的数据仓库,查找是否有满足数据需求的表。如果有,数据产品根据数据规模和已知的应用场景,在数据服务平台快速定义数据API,完成接口的测试、上线,并交付给数据需求方。如果数仓中没有满足需求的表,数据产品会将该需求转换成一个数仓需求,完成数仓建设后,数据产品再定义数据API,最后交付给数据需求方。

数据API上线后,数据服务还需要提供API的监控报警功能,实时监控API的调用情况,对于QPS暴增的情况要及时通知到应用方,对于数据API长时间没有调用,产品就需要启动API下线流程,释放对应的存储计算资源。

上图中,虚线框中的黄色部分就是数据服务需要提供的最基本功能。而这些功能中最重要的就是数据API定义。数据API将数据应用和底层数据进行解耦,进而通过逻辑模型完成数据的复用。接下来我们将重点介绍下数据API的实现思路,先看API定义设计图:

API定义包含四部分:api地址定义和数据源选择、输入参数设置、返回值设置以及DSL规则输入。

api地址定义和数据源选择。

“api地址定义”只要保证输入的地址是全局唯一就行,没有特别的命名规则。“数据源的选择”这部分要求数据产品选择api绑定的数据源。数据源可以有多个,也可以是不同类别。这里有一个强制约束:如果选择了多个数据源,所有数据源中必须包含相同定义的字段,这些字段名字可以不一样,但是代表的含义以及口径必须是一致的,而这些字段就是多数据源关联的主键。例如:一个api包含了两个数据源:A、B。A表中有orderid,B表中有order_id,A.orderid和B.order_id代表一个含义。

为什么要如此设计?主要有两个原因,第一个:一个API如果绑定了多个数据源,数据服务在获取数据时必然存在JOIN操作,这种基于明细层的join查询,性能肯定不会太好。我们希望api在获取数据时,可以将查询拆分成两个查询,这两个查询分别去A,B表获得数据,然后再通过关联主键将数据合并。第二个原因就是,API绑定的多个数据源可能是不同类型,本身也没办法进行JOIN操作。

输入参数

每个输入参数都需要绑定数据表的具体字段,数据服务在接受请求时,会将输入参数转化成对应表字段,后续即便底层数据表字段变更,数据应用也无需修改请求。数据产品只需要将最新的字段映射关系维护到数据服务。

返回值

这里只需要设置需要返回的字段,字段名可以随便定义,DSL中的返回值需要和此处设置的字段名绑定。

DSL规则

DSL是api定义最核心的部分。DSL的作用就是通过特定的语义规则,描述取数逻辑,数据服务通过DSL规则,翻译成取数代码,以此达到API快速上线的目的。这样设计主要考虑点:

数据服务的取数基本上是根据某些条件从底层物理表获取数据,通常不会有复杂计算逻辑,用DSL可以很容易描述取数逻辑。

同样因为取数逻辑比较简单,意味着同一类型数据源的取数代码,基本上只是字段和表名不同。所以完全可以把取数的代码进行封装,抽象出取数公共层,避免代码重复开发。同时也可以避免代码上线对其他API造成影响。

通过DSL规则描述取数逻辑,本质上就是在物理模型之上构建逻辑模型,逻辑模型的存在,解决了数据复用的问题,相同的物理模型之上,应用可以根据自己的需求,构建出不同的逻辑模型,每个应用看到不同的列。

最后介绍下,数据应用通过数据服务获取数据的底层实现,看下图。

“数据应用C”通过接口获取数据,数据服务接收到请求后,会找到api对应的DSL。数据服务根据DSL描述的逻辑模型,将查询请求拆解成不同的执行计划,并分解成具体的执行SQL,数据服务根据SQL去对应的物理表中,获取数据,最后将获得的数据根据主键进行合并返回给数据应用。

在数据服务的这个应用场景,DSL完全可以使用SQL语法。为了让计算结果和API定义的返回值对应上,我们添加了Return 关键字去映射SQL中的字段。

举个例子:数据需求方希望通过uid和day获得用户在某一天的订单累计金额和点击数。其中订单数据位于mysql表orders.orders中,点击数据位于starrocks表content.actions表中,DSL规则可以如下定义(仅供参考,并非真实环境)

1.Query:

3.Select uid,sum(money) from orders.orders where uid=#{user_id} and day=#{date} group by uid

3.Return uid,moneys;

4.Query:

5.Select uid,sum(clicks) from content.actions where user_id=#{user_id} and day=#{date} group by uid

6.Return uid,clicks;

7.Query:

8.Select uid,sum(money) from orders.orders where uid=#{user_id} and day=#{date} group by uid

9.Return uid,moneys;

10.Query:

11.Select uid,sum(clicks) from content.actions where user_id=#{user_id} and day=#{date} group by uid

12.Return uid,clicks;

数据服务会根据DSL规则,拆解成两个物理执行计划:从orders.orders中获得用户成交金额数据、从content.actions中获得点击数据。根据Return后的字段以及API定义的关联键,将两份结果数据合并,返回给用户。其中DSL解析过程是比较耗时的,通常会把取数逻辑缓存起来,当API取数逻辑发生变更时,实时刷新缓存。

数据服务作为数据统一服务平台建设的最上层,能够将数据仓库数据以服务化、接口化的方式提供给数据使用方,屏蔽底层数据存储、计算的诸多细节,简化和加强数据的使用。

如何通过数据API,对外提供数据服务,进行数据共享呢?

数据API,通过RestfulAPI的形式对外提供数据服务,用户可以通过参数配置的方式或是SQL高级查询的方式直接生成API服务接口,基本无代码化完成用户对服务发布的需求。使用场景如企业内部将数据统一服务平台加工的结果数据,通过数据API的方式,提供给上层数据应用、BI,可视化大屏等;企业将行情数据通过API的方式提供给外部经销商或者合作伙伴,解决数据对外快速共享的场景。

我以前认为,数据服务,就是通过数据接口提供数据的方式的统称。就是你调用一个接口,我传给你一些数据,数据处理逻辑接口内已经封装了,用这些数据你可以处理某些问题。而直接查询数据的不是数据服务。

后来看了《大数据之路-阿里巴巴大数据实践》的第六章,阿里服务架构演进,思路被打开了。

最简单的数据服务,接口应该是直接调用,就返回结果。

复杂的接口,应该还支持传参,可以从多个结果中选择一个结果。

更复杂的接口,传入的参数还可以参与接口内的逻辑运算。

那应该还可以设计个更复杂的接口(使用复杂),直接传入完整的计算逻辑,接口内只提供数据并根据逻辑进行计算返回结果。

基于我的工作经验,数据仓库的数据在被使用的时候,基本有三种途径。

一种是提供一个可视化窗口写SQL直接查询数据,如HUE,解决临时查询和下载数据的需求。

一种是提供接口让业务系统调用获取需要的数据,如用户画像系统,解决业务系统的数据支持需求。

一种是其他系统直接连接数据仓库,发送SQL并获取返回数据,如自助报表系统,解决可视化和在线分析需求。

但它们的本质都是生成SQL,传到hive运行并返回行列形式的结果数据。

我个人觉得对于数据的所有的需求,都可以通过一个表或多个表满足。既然如此,逻辑预先处理好,把表落地,提供一个统一的接口,调用接口时传入select * 表名即可。同样可以实现逻辑的封装,使用也简单。

也可以传入有计算逻辑的SQL,满足更多个性化需求。

对调用方来说,并不会增加额外学习成本。(这是阿里服务架构第三阶段,还有第四阶段)

只有一个接口,就可以做到所有对数据仓库的访问都经过这个接口。

统一接口的目的,实际就是统一输出,为了便于管理。当所有对数据仓库数据的访问的都从一个接口走,访问日志留存后,所有访问都在掌握之中。

哪些数据最多访问说明那些数据比较重要,可以考虑提升数据的重要等级,优先保障,优先优化。

那些数据访问时逻辑最复杂,耗时最久,可以考虑将数据进行再加工,将重复逻辑沉淀,简化使用。

那些数据无访问,可以尝试沟通下线。而下线时也可以根据数据最先访问人,找到对应的需求方。避免想下线缺找不到对应的需求方不敢下线的尴尬。

我觉得数据服务不复杂,也不高大上。它最有价值的地方就在于预先做了处理,使用时会非常简单。可数据仓库数据仓库本身就是做数据预处理的吧,所以数仓每一个表,实际上都可以认为是一个数据服务。

以数据服务的角度来考虑数据仓库的建设,应该能够让数据仓库的工作更偏数据应用一些,更偏数据价值一些,更偏为业务方解决实际问题一些

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注