B 站数据服务中台建设实践
哔哩哔哩技术 稿
01 背景
随着公司业务的发展,对于数据的需求会越来越多。怎么在业务系统中高效的使用数据,让业务系统处理大数据时化繁为简,数据服务化基本是必经之路。那么什么是数据服务化,简单理解就是数据SaaS,通过一些数据库语言把数据转化成服务,如API、RPC、数据文件 等不同的数据方式提供给业务系统使用。
经过多年对各个业务系统对接调研发现,大家感受到了无标准、不统一及烟囱式建设的服务接口的痛点,希望可以建设一套标准的,中台化的数据服务平台。数据服务中台以解决业务中痛点为优先,主要的痛点如下:
- 数据接入方式多样,对接效率较低
- 数据出口多,口径不统一
- 数据及接口无法共享,建设成本高
- 数据链路不清晰,故障影响难以评估
- 服务质量标准不统一,质量问题较多
针对以上痛点,我们需要思考一个问题:我们到底需要一个什么的数据服务平台,平台应该有哪些特性?从功能上看,我们认为一个完整的数据服务平台应包括以下功能:
- 接口的规范化定义
- 通用的数据服务网关
- 数据链路可管理可维护
- 服务可以观测可运维
- 服务可复用
除了满足以上功能外,我们也希望这个数据服务平台具备以下特性:
- 灵活性:数据的迭代及变更,可以让下游无感
- 便捷性:能够快速的完成数据的服务化,接入效率高
- 低成本:让数据可以复用而非复制,减少数据的冗余生产
02 架构设计
数据服务中台的经过多次迭代,如上图所示。中台构建于数据仓库之上,自下向上包括:数据构建层、数据查询层、服务接口、服务网关及服务的应用体系,除此之外,中台还包括数据标准的管理、数据安全管理、运维监控等模块。今天主要介绍中台的核心服务化链路,即:数据构建-->数据查询-->服务接口与网关。
2.1 数据构建
我们知道数仓中的数据一般是hive表,维度建模思想下,单表并不能完整表达业务逻辑,需要对数据表进行重新构建,构建统一的面向业务的数据模型层,同时数仓中物理表对于线上业务系统的使用有很高的延迟,也不可以直接读取,所以在数仓的基础上,抽象出数据构建层。
数据服务中台中,我们把用户分为两大类角色即:数据生产者及数据消费者。数据构建层是面向数据生产者(一般指数仓开发) 进行数据定义的层次,核心能力包括:模型定义、模型加速及API的构建。
2.1.1 模型定义
B站数仓建设以业务过程为驱动,采用经典的维度建模的方式。维度建模以分析决策的需求出发构建模型,构建的数据模型为分析需求服务,因此它重点解决用户如何更快速完成分析需求,同时还有较好的大规模复杂查询的响应性能。
为了适应各种数据分析场景的数据获取需求,我们支持构建多种逻辑数据模型,根据事实表与维表的关系,经常将数据模型分为单例模型、星型模型、雪花模型及星座模型。
单例模型:1张事实表,一般为dws或者ads层的汇总实时表;
星形模型:1张事实表+N张维度表,如播放相关事实表可以通过稿件ID关联稿件维表获取稿件相关属性信息;
雪花模型:1张事实表+N张维度表+M张非直接关联事实表的维度表,如播放事实表通过稿件ID关联稿件维度表,又通过稿件维度表中的品类ID关联品类维度表;
星座模型:N张事实表+M张与事实表关联的维度表,如播放模型与DAU模型通过公共维度关联,获取公共维度下的DAU指标与播放指标。
2.1.2 模型加速
我们知道,数仓的模型一般存在hive中,hive表对于线上API的使用有极大的困难,所以就需要把2.1.1中构建的数据模型进行模型加速。数据服务作为一个通用的数据获取平台,既要满足不同的应用场景,同时也要考虑成本因素。我们从性能及灵活度两个方便,对使用场景了做了划分。不同的使用场景,其加速方式及目标引擎有所不同。我们从性能及灵活性上对使用场景进行划分:
明细加速: 实现把逻辑模型中的数据从冷引擎到热引擎的镜像复制,极大保留了模型的原始数据的业务特性,可以更好的支持对模型中不同的维度及指标做多维分析或者范围查询,在热引擎中,加速数据的查询效率。
预计算加速: 根据数据获取需求,从逻辑模型中的明细数据进行预计算及处理,直接聚合到所需粒度的数据,并将数据写入热引擎中。由于数据已经预计算处理,在数据查询时极大减少了引擎的计算,查询效率更高,但也损失了如多维分析及维度下钻查询的灵活性。
不同的场景有不同的加速方式及引擎选择的组合,针对四种类型的场景,推荐的组合如下:
在线场景: 预计算+kv存储,一般应用于C端及B端的数据需求,有着极高的服务性能要求,且线上数据不会快速的变化,灵活度要求低。
准在线场景: 预计算 + tidb/mysql,一般应用于活动或者运营系统后台,服务性能要求不会同线上场景那么高,但同时要求支持范围查询,有一定的查询灵活度;对于数据量较大的查询场景,可以选择分布式数据库 Tidb,其他可以选择Mysql。
OLAP场景: 明细+ ClickHouse/Iceberg,一般应用于数据分析系统,需要对数据进行多维分析,所以需要明细加速。ClickHouse 作为一款OLAP分析引擎,比较适合作为目标引擎,如果考虑到成本因素,也可以使用湖仓一体技术Iceberg,数据无需出仓既可在仓内完成数据的加速,性能虽不及ClickHouse,但也可以达到秒级的响应,特别是在使用成本及查询语法通用性上更有优势。
离线场景: 离线数据获取,一般访问原始hive数据源即可。
2.1.3 API构建
API参数定义
为了对API标准化,我们调研了公司内的各业务的API的使用要求,统一API标准元素,包括: APIID、API名称、请求方式、请求路径、返回参数、请求参数、响应时间要求、QPS预估、应用场景等。
API取数逻辑构建
API的取数逻辑的配置,中台支持多种配置方法,包括:自定义sql构建、模型构建及指标维度构建。下面针对这三种构建方法分别介绍:
自定义sql构建:
为了支持复杂逻辑的数据获取,系统通过过引入mybatis相关的包,能够支持类似mybatis动态sql方式,通过支持Mybatis标签的SQL语法来编写查询逻辑。目前支持的标签类型包括:if、foreach和where。根据用户传入的变量动态构建SQL实例,从而扩展了模型API化的能力。
select
a.field1 AS alias_1,
a.field2 as alias_2,
a.field3 as alias_3,
b.field1 as alias_4
from
fact_table a
left outer join
table_dim b
on a.id = b.id
<where>
a.field = ${ input_1,type = number }
<if test = 'input_2 !=null' >
and b.field = $ { input_2,type = number }
</if >
</where>
模型构建:
模型构建是一种可视化的配置方式,用户不用要sql的代码编辑能力,搜索想要服务化的模型,即可在模型上配置出API的取数逻辑;
指标维度构建:
指标维度构建是在模型构建的基础上的一种更高级模式,用户无需实现指定模型进行构建,可以先配置请求参数及返回参数,系统根据管理的模型元数据信息,按照一定的规则自动筛选出可以服务化的模型列表并给出推荐优先级。
2.2 数据查询
数据查询层是服务接口层与数据模型层的中间层,主要功能是通过接口获取请求参数,经过解析、调度、翻译及计算后返回数据结果。数据查询层分为两种查询方式:原子计算及复合计算。原子计算处理返回格式相对单一、指标维度统一及引擎单次处理即可用的数据查询,一般用于线上的单点查询、范围查询场景等;复合计算是在原子的计算的基础上,按照一定的算法对原子计算的结果进行二次加工,一般包括数据编排、指标间结果或维度间结果的处理等,返回的数据结构一般可以直接应用于数据产品中。
2.2.1 原子计算
原子计算需要把获取到的接口参数进行解析、拆分、翻译及多次引擎的单次查询获取结果并返回,整体流程如下图所示。流程整体分为三部分:调度、翻译、引擎查询。
调度:
调度是一次原子计算的入口及出口,负责任务的解析、拆分及结果的处理。调度获取到的参数是一段DSL,包括指标、维度、筛选条件、分页信息等。
步骤1,解析的过程是把DSL信息与API配置进行匹配,并得出本次数据请求需要的模型。在一个API中,用户可以配置多个数据模型,不同的指标维度查询中,需要用的模型就会不同,解析过程需要根据查询条件按照预设计好的规则进行排序择优,追求最好的查询准确性及效率。排序择优的算法参考Oracle查询优化器的做法,结合CBO及RBO算法选择最优的查询模型。
步骤2,任务拆分是把解析的结果拆成子任务查询,解决异构数据源的结果获取问题。因为一次查询中可能会从多个模型中获取,模型也可以在多个引擎或集群中。为保证结果的的准确获取及查询效率,我们采用了拆分子任务的方式进行并发数据查询,子任务的查询结果经过二次加工处理返回。
步骤3,结果处理器是一个内存计算器,负责把子任务的查询结果进行拼接、排序、分页及格式转换等,返回统一格式的数据结果,让下游无感知数据的处理过程。
翻译:
步骤3-1,翻译模块是把子任务的查询信息翻译成对应引擎可以识别的sql语法。AST是abstract syntax tree的缩写,也就是抽象语法树。翻译模块区别于引擎对执行SQL进行语法树解析的过程,而是逆向从构建AST开始,并最终翻译成SQL的过程。构建AST的算法如下图所示:
第一层AST从下面的"SELECT"开始,第一层语法树是构建出本次查询需要用的有效数据,包括构建模型关系、模型字段的筛选及数据范围的筛选
第二层AST从上面的"SELECT"开始,第二层语法树是基于第一层构建的有效数据,进行运算表达式计算,如 sum,count(distnct ..),sum(case when then end),sum()/count() 等其他一些自定义语法表达式。
通过两次的AST构建,可以完整表达一次原子计算所需的数据获取逻辑。根据构建的AST,系统会各个引擎的查询特性及方言,优化查询语法,提升查询性能,最终转化成可被引擎识别的查询SQL。
引擎适配器:
步骤3-2,引擎层负责执行各个子任务的查询SQL,系统适配了目前公司内部多种数据引擎,包括自研KV、TiDB、Mysql、ClickHouse、Iceberg等。除接入不同的引擎连接协议,在不同引擎的连接方式上也做了优化,如:mysql、tidb 由单次短连接改为连接池,减少连接的频繁创建及销毁带来的开销问题;自研KV多可用区连接,扩大在线服务可用的服务范围;OLAP引擎如ClickHouse、Iceberg的超时自取消功能,减少服务占用及降低引擎压力等。
2.2.2 复合计算
二次计算是解决单一sql或者单一引擎不能直接获取数据结果的数据二次加工过程。基于原子计算得出的一个m*n的二维数组,可以计算指标在时间轴上的变化趋势,如:同比、环比、增长率等,也可以算指标在不同维度间的关系,如:占比、漏斗、下钻等,同时也可以支持一些统计分析,如:协方差、TGI、置信度等。二次计算不仅支持通用的算法口径,也可以支持自定义函数,二次计算的能力避免了数据输出后在外部不受管控的加工,让数据所得及所用,处理流程如下图所示:
2.3 服务网关及接口
数据服务层是对模型中的数据进行服务化的过程,主要包含三个过程:服务网关、服务接口及数据查询。
2.3.1 服务网关
API Gateway(API GW / API 网关),系统在系统边界上提供给外部访问内部接口服务的统一入口。在数据服务中,网关需要核心具备的功能包括:鉴权、限流及监控。网关使得业务对接数据服务时有了统一的标准,使得API有了复用的可能。
鉴权: 数据服务中,数据安全非常重要。数据服务中,为每个应用分配一对appKey 及 secret;对于已经发布上线的API,负责人可以对应用进行授权,只有API被授权给某个应用,应用才可以通过appKey 及 secret 访问该API。
限流: 限流是对系统的保护及减少API相互影响的重要手段。应用在申请API调用时,需要给出合理的QPS预估,API会根据总QPS需求调整资源部署;过大的QPS的请求且没有限制时,容易超出服务负载而发生故障,对于复用的API也可能产生相互影响。所以每个应用申请的API都有QPS限制,超出阈值,多余流量会废弃。
监控: 服务监控可以比较直观的观察的api的运行状态,监控类型包括访问总请求数、访问成功率、访问失败数及访问失败类型分布,另外也补充了对API的安全监控及QPS监控等。
** 2.3.2 服务接口**
根据数据场景及数据内容,接口形式分为同步查询与异步查询,接口类型包括DSL接口、模板查询接口、自定义SQL接口等。
同步查询: 对于线上应用查询,要求响应速度快且数据结果较小的查询,建议同步查询,可以快速获取到结果。
异步查询: 对于离线分析或者大数据量下载场景,响应速度要求不高,但是返回的数据量比较大,建议走异步查询,异步查询的流程如下图所示:
DSL接口: 用 DSL (Domain SpecificLanguage ,领域专用语言)来描述取数需求,计算层可以解析DSL并计算,通过DSL所有的简单查询服务减少到只有一个接口。
message OpenApiReq {
OsHeader osHeader = 1;
repeated OperatorVo filters = 2;
repeated string metrics = 3;
repeated string dims = 4;
repeated string orderFields = 5;
PageVo pageVo = 6;
repeated OperatorVo metricFilters = 7;
}
模板查询接口: 数仓同学按照业务需求,把数据的计算逻辑固定为一个模板,下游在调用时不必关心表在哪里及怎么计算,只需根据模板中设置的变量传参获取数据,减少了不必要的沟通,提高了对接效率。
message SqlQueryReq {
OsHeader osHeader = 1;
repeated OperatorVo filters = 2;
}
SQL接口: 业务方可以通过写 SQL 的查询数据 ,由服务提供者自己来维护 SQL ,走向 DevOps。
message AsyncSqlQueryReq{
string appKey = 1;
string secret = 2;
string engine = 3;
string sql = 4;
}
03 通用解决方案
3.1 口径统一及溯源
如前文提到,口径不统一及链路不清晰造成较高的维护成本,总体解决思路是统一入口及统一出口,并在过程中进行全链路管理,方案如下图所示:
统一定义: 口径不易维护的原因之一是管理不统一,散落在各个地方,导致大家没有统一的视角管理及查看口径。我们的的解决方案是口径统一在指标平台管理,其中把指标的定义及模型的定义解耦。指标定义是对分析对象的业务过程进行描述,计算方法的定义约束,可在数据模型建设之前确定,一般有数据产品角色实施;模型定义是根据数据需求及指标定义进行构建生产,模型中指定了模型字段与指标的关系,决定了模型中哪些字段可以生产哪些指标,一般有数仓开发角色实施。通过把指标定义与模型定义解耦,减少了不同角色间的沟通成本,让口径的定义可以延续到数据生产中。
统一出口: 口径不易维护的另外的一个原因是定义与生产分离,出口不可控。我们打通了指标的定义及生产流程:模型的出仓可以自动化完成,出仓过程中的计算逻辑是基于指标平台中的定义自动生成的,减少了人工的干预,避免定义与生产的不一致;其次,API的取数逻辑非人工定义,也是基于指标定义,自动翻译取数逻辑及路由计算引擎,避免生产与消费过程中的不一致。通过定义与生产的打通,生产与消费的打通,数据流通过程中完全基于统一的定义,达到了最终的定义与消费的一致性。
全链路监控: 数据从定义到生产到消费环节,有完整的监控链路,以此来确保口径的持续一致性。指标一致性监控:维度建模的数仓中,同一个指标由于分析思路或者需求场景,最终生产此指标的模型可能是多个,那么就需要保障指标在不同模型间的口径一致性。通过一致性对比或者闭环公式校验等手段,发现指标口径不一致,然后通过口径变更、模型换绑等治理方法来持续保障指标口径的一致;出仓一致性监控:出仓过程中数据集成工具保障了实时的一致性,这里需要保障的是,由于历史数据变更导致的出仓前后数据不一致的问题;服务质量监控:从数据出口监控数据的质量情况,主要包括阈值监控,波动率监控等,发现质量问题,保障出口的最终口径一致性。
3.2 降本增效
中台的建设最终是要为企业降本增效的。过往中,不同业务线重复建设,垂直产品线烟囱式的开发,使得数据成为一个个孤岛,虽能满足单一业务场景,但是却增加了各个业务线的合作成本。
我们的建设思路是,首先将公共、通用的部分抽离出来,形成可复用服务,让业务可以快速完成数据链路的搭建,减少业务重复造轮子,降低研发成本;其次,统一服务标准,通过快速复用已建设的数据链路搭建的能力,让数据从定义-->生产-->消费的整个周期缩短,提升对接效率,为数据在不同部门间流转创造可能。
降本:
- 数据建设成本:通过模型、指标标准化的定义与管理,数据建设中的一个个表或者模型成为一个可以复用的标准数据资产,数据建设中只需增量建设,规避重复建设,降低数据的建设成本。
- 服务研发成本:服务通过标准化方式构建,下游使用无歧义,使得API复用成为可能,减少了服务研发成本,服务资源可以重复利用。
提效:
- 数据构建提效:数据构建时,通过元数据的管理,自动化及半自动化的构建出模型及指标,提高了数据构建效率;另外构建出的数据资产是标准化的,消除了数据格式、生产、存储等差异性,下游消费时不用关心底层的逻辑,提高了应用的效率。
- 服务使用提效:标准化的API,在系统对接时可以实现一次对接沟通,多次复用,提高了API的对接效率。
3.3 服务高可用
服务隔离: 隔离是将服务或资源分割开。服务隔离是为了在服务发生故障时,能减少传播范围和影响范围,故障发生后不会发生滚雪球效应,从而保证只有出问题的服务不可用,我们根据服务的保障等级,划分为5个服务资源组,不同资源组相互独立,不会产生相互影响;存储资源隔离是通过资源隔离来减少资源竞争,保障服务间的相互不影响和可用性,我们将资源隔离做到API级别,不同API间及不同等级间做隔离,充分保障资源间的相互不影响。
异地双活: 异地双活的目的也就是容灾,当某个地方服务出现了灾难性故障,而服务仍然能正常提供服务。我们根据业务的部署情况,选择距离业务较近的A、B两地机房部署服务,正常情况下同机房的请求链路为主链路,当某地服务宕机,则会自动降级到另外一地的服务。
缓存: 缓存可以让数据更接近使用者,目的是让访问的速率更快。对于数据时效性要求低但是请求热度高的接口可以进行缓存,命中缓存后,请求不会到达引擎执行层,可以有以下优势:1. 请求链路减少 2. 系统响应时间更快 3. 降低了服务后端及引擎的负载。但是,缓存也会带来问题,如:1. 需要保障缓存一致问题 2. 缓存维护成本。数据服务平台中,缓存有两层:本地缓存及分布式缓存。
本地缓存:
- 数据存储在本地内存,没有网络开销,速度最快
- 缺点:受服务内存限制,存储容量较小,数据在多个服务间不可以共享
- 场景:对于热key及响应时间要求极高的api,优先使用本地缓存
分布式缓存:
- 优点:存储容量更大、可靠性更好、可以在集群间共享
- 缺点:访问缓存有网络开销
- 场景:缓存数据量较大、可靠性要求较高、需要在集群间共享;不同的缓存策略引擎选择会不同,对于缓存时间长且数据量稍大的API,分布式缓存会使用公司内自研kv,自研kv在使用成本及大Value上更有优势,其他缓存策略可以使用redis缓存,单API只会在一种引擎内。
缓存版本管理:
- 多进程间及缓存层级间缓存不一致问题,维护全局视角下API粒度的版本号,来统一进程间的缓存数据
- 底层数据变更时,利用版本号的切换,解决由于缓存清除时效性不一致带来的缓存问题
04 落地成果
平台从零到一建设一年左右,已经逐步承接公司C端及B端的数据需求。在体量上,平台上的 API 数量已达500以上,日常QPS达数十万,经历B站多种大型赛事及活动的洗礼,支持数据平台内各种数据服务需求,包括在线应用、数据产品、BI工具产品及报表等。除了以上业务成果外,数据服务平台通过沉底技术、制定标准、优化流程等,目前平台的新流程在效率上有极大提升,创建API从近5天降低到1天内。在成本方面,平台也通过多项综合措施,包括模型复用、API复用、应用治理,API的生产成本可以降低18%左右。数据服务中台化加快了研发周期、节省了研发成本,让有限的资源快速高效迭代出新的业务。
05 未来规划
稳定性
服务稳定性在任何时候都是生命线,当前的服务框架中实现细节仍有优化空间,鲁棒性有待加强。未来需要针对服务框架中的实现问题,特别是在隔离级别,资源分配,单点故障等进行优化及迭代,让服务可以轻松扛过灾难级故障,未来也需要更多的故障演练来持续维护服务的稳定性。
智能化
当前服务由于从源头管控了数据标准,使得数据在提供服务时需要较多的元数据注册及数据构建工作,从中长期的角度看,这些注册的元数据虽可以更好的维护数据标准,减少重复建设,但也提高了数据服务化的难易程度,未来需要打通更多的系统,减少不必要的信息录入,引入自动化检测手段,让服务化更加智能。
服务治理
随着数据服务越来越多的被广泛使用,服务的治理越来越凸显重要。我们发现,API市场中存在着api无热度、api资源使用率低、api访问成功率低等问题,针对这些问题需要建立长效的治理机制,长期保障API的稳定、可靠与低成本。