写点什么

飞书深诺数仓低代码方案实战

  • 2023-08-02
    上海
  • 本文字数:3910 字

    阅读完需:约 13 分钟

背景介绍

当前,大数据的重要性对于任何大中型公司来说都不言而喻 ,飞书深诺也不例外 ,大数据在多个维度,多个环节都帮助公司更好的提升业务效果,为用户赋能,包括:媒体达标,风控违规,广告诊断,素材中台,行业基准等;在大数据的使用场景中,数据的获取是其中特别重要的一环,在很多业务流程中,都涉及到获取数据以支撑下一步的行为和决策,所以如何快速高效且准确的帮助团队获取数据是数据基础设施建设中的重点;我们最初为公司提供数据的方式主要有:数据看板,数据导出,数据同步;但这几种方式还是有一些固有的缺陷,包括:

  • 数据输出形式混乱,直接同步到业务库,推送文件发队列通知,导致业务直接访问大数据的底层库

  • 效率低下,每个任务都需要单独开发。无法沉淀公司的统一接口平台,复用性和透明度不够

  • 定制化程度不够

所以我们一直在思考如何提供一种数据获取方式,其既能帮助用户高效的获取数据,又不失灵活性,同时不需要太多的开发资源投入

设计思路

我们可以先来回顾下一般的数据 API 的构建流程,其基本上都是硬编码的方式,一般一个 API 的交付在开发层面需要:

  • 封装控制层,服务层,实体层

  • 从数据研发那里获取可执行 SQL

  • 将执行 SQL 改造成 mapper 中可用的动态 SQL

仔细分析这个过程,会发现其工作可以抽象为:

  • 配置:需要编写配置文件,配置数据库连接、MyBatis 映射文件、日志记录等

  • 开发:需要编写控制器层、服务层、DAO 层、实体层等代码,处理请求、处理业务逻辑、访问数据库等

  • 调试:由于涉及多个层级,调试起来可能比较困难,需要花费更多的时间和精力

继续思考这些工作内容,我们发现如下特点:

  • 当获取数据的以列表的形式输出时,返回的结果集结构相对统一与确定

  • 数据字段,枚举,类型的定义来自数据中台或更上游(数据源头)

  • 业务端对于获取数据的指标字段与聚合,分组方式灵活多变

  • 库表由数仓提供,无需服务层设计与灌入

基于这些特点,有哪些工作可以标准化,自动化,哪些是一定需要人工处理?

最后我们发现:

  • 出参入参结构大体相同,可以归纳共性来代替定制化的出入参设计 

  • 人只需要参与核心 SQL 逻辑,其它层工作规范标准自动化即可

所以可以看出如果想要达成上述目标就要处理两个关键点:精简结构及 SQL 配置化

精简结构

舍弃出参入参对象的封装,取而代之与业务端约定:

  • 入参方式以 map 形式的 key,value 对传入

  • 出参设计提供两种类型

一种是 map:适用于汇总与统计

{    "data": {        "key1": "value1",        "key2": "value2"    }}
复制代码

一种是分页列表:适用于明细

{    "totalNum": 2,    "pageNum": 2,    "pageSize": 1,    "data": [        {            "key1": "value1",            "key2": "value2"        },        {            "key1": "value3",            "key2": "value4"        }    ]}
复制代码


SQL 配置化

如何将 SQL 逻辑设计成可配置,在 MyBatis 中,SQL 脚本无论是写在注解中还是 xml 文件中,都免除不了发布或加载的动作。SQL 脚本的解析要在代理 mapper 后执行,所以我们的思路是将 SQL 脚本储存在数据库的配置表中,舍弃 mapper 的代理动作,将 MyBatis 中的脚本解析功能拉出来,这样使得 SQL 逻辑的读取可以随时调整,配置表结构及核心字段如下:

请求路径从 request.getRequestURI() 获取,和表中 api_path 字段匹配,拿到 SQL 脚本。MyBatis SQL 脚本 结合入参形成一条可执行 SQL,再利用 java.sql.PreparedStatement SQL 预备声明 执行这条可执行 SQL 获取返回结果

带着上面这两个关键需求,我们开始寻找可行的解决方案

选型调研

在寻找解决方案的初期,我们首先把目光投向了外部已有的方案,首先是阿里数据服务。因为数据中心本身就在用 Dataworks,而数据服务本身就在 Dataworks 全家桶中,当时并没有额外的人力资源来开发数据接口,所以数据服务充当的那段时间的应急方案。

DataWorks 数据服务

DataWorks 是阿里巴巴提供的一种一站式大数据开发与管理平台,它可以帮助企业高效进行数据开发、数据上线、数据管理和数据运维 ,这个平台将数据的采集、清洗、转换、分析和可视化等全过程整合在一起,使数据开发工作更加得心应手,它的优点是:

  • 数据服务支持通过可视化配置的向导模式,快速将关系型数据库和 NoSQL 数据库的表生成 API ,无需具备编码能力,即可快速配置一个 API 

  • 为满足高阶用户的个性化查询需求,数据服务为您提供自定义 SQL 的脚本模式,您可以自行编写 API 的查询 SQL ,在脚本模式下,支持多表关联、复杂查询和聚合函数等功能

缺点有:

  • 缺少详细的日志输出,除了入参出参再无其他日志信息

  • API 接口返回字段在利用到个别数据库时字段无法做到驼峰映射

  • 非开源,改造升级被动

DataWorks 数据服务的产品模式实际上提供给我们很多参考,但是其中的问题排查,架构适配等问题隐患也是不容忽视的,后来我们目光转移到开源的解决方案上

DBApi

DBApi 基于 orange(开源动态 SQL 引擎,类似 MyBatis 的功能,解析带标签的动态 SQL,生成?占位符的 SQL 和?对应的参数列表) 的面向数仓开发人员的低代码工具 ,其优点是:

  • 开箱即用,不需要编程,单机模式不需要依赖其他软件(只需要 Java 运行环境)

  • 支持单机模式、集群模式;支持云原生容器化部署

  • 支持动态创建、修改 API;动态创建、修改数据源 ,热部署全程无感 

  • 支持 API 级别的访问权限控制,支持 IP 白名单、黑名单控制

但是 DBApi 也有一些我们当前需求无法接受的缺陷,包括:

  • 返回结构比较单一,只提供无界列表,在不明确总数据量级的情况下,每次全量返回数据会有不必要的流量损耗

  • 没有做 SQL 返回中对键的处理,例如 PostgreSQL 返回的字段名称都是小写的,而公司规范中需要做驼峰式处理

基于此,同时考虑到实现复杂度我们可以控制,且后续也会有不少定制化需求,所以决定走自研道路

技术要点

首先,自研方案有几个需要重点考虑的问题:

  • 如何借用 MyBatis 的能力来处理动态 SQL 脚本

  • 如何处理结果返回

  • 如何设置接口路径

脚本处理

实际上 SQL 脚本的标签处理本身就是 MyBatis 现有的,只不过强关联在了 mapper 文件中。我们所要做的就是把这个处理过程拉取出来,直接执行我们所关注的部分为了便于理解,这里补充一张从 SQL 脚本至标签处理完成的时序图:

  • XMLLanguageDriver 将 SQL 脚本和入参传入 XMLScriptBuilder,XMLScriptBuilder 将 SQL 脚本传入 SqlNode 中

  • 将 SqlNode 传入 DynamicSqlSource 处理脚本逻辑

  • DynamicSqlSource 执行 getBoundSql 方法 ,在 getBoundSql 中 SqlNode 的 apply 方法处理 DynamicContext

  • 这样我们就从 DynamicContext 中我们可以获取我们需要的 2 个重要组件: 处理后的入参 map 和处理后的待传参 SQL

获取解析后的入参 & 获取解析后的 SQL

//获取解析后的入参Map<String, Object>  bindings = context.getBindings();
//获取解析后的SQLString sql = context.getSql();
复制代码

结果处理

个别数据库返回字段不分大小写,如 PostgreSQL;配置化平台设计了返回字段映射功能,可以将 SQL 的返回集字段名称映射成期望字段名称,从而解决特定数据库无法将字段处理大小写的问题

编辑规范

SQL 的编辑规范注意和 MyBatis 一样,SQL 里的小于号不要写成<,要写成 &lt;

动态路径

动态路径写法参考

//Spring注解@PostMapping("/api/result/**")RestResponse<Data> getSinoTypeAPI(@RequestBody Map<String,Object> inputMap); //或重写HttpServerletpublic abstract class HttpServlet extends GenericServlet {    protected void doGet(HttpServletRequest req, HttpServletResponse resp);}
复制代码


多数据源

多数据源匹配处理,启动时加载读取 Apollo 在配置好数据源并通过数据源命名来注册 bean,数据源通过 bean 名称与表中 dbName 匹配的方式找到对应数据源


数据校验

数据产品同学,数据开发同学或测试同学需要实际的执行 SQL

配置化平台提供了获取 SQL 功能,将/sql 拼在原接口后面即可获得当前接口执行 SQL

应用情况

经过改造后的配置化 API 可支持:

  • 扁平结构的出入参

  • 常见数据库,如 MySQL、PostgreSQL、ClickHouse 等

  • 与敏捷开发模式无缝衔接 ,做到高频率的产品发布

最终我们原来 99%的读库场景都可以基于配置化 API 快速支持;应用以来,累计提供 API 给到 10 余个服务,上线并稳定运行 API 共 200 余条,每条 API 约节省成本(开发+发布) 0.3 人日,新增 API 经用数仓接口配置化平台应用率达到接近 100%;且我们提供了可视化界面供数据开发同学和服务开发同学使用



未来规划

基于配置化 API 在上述场景中一系列实践与应用,其主要功能基本满足当前的需求,但还是存在不少需要优化的点;同时考虑到后续业务发展方向,该服务后续的迭代方向为:

  • 配置化 API 文档化:基于对配置化 API 出入参的统一处理,虽然我们可以将 API 的暴露规范化自动化,但也导致无法借助 Swagger 等插件自动生成接口文档;后续我们考虑在配置 API 的时候将一些接口相关信息录入并存储,以便接口文档工具可以基于这些信息自动生成文档

  • 业务支持::配置化 API 舍弃掉了服务层逻辑,导致一些基础的业务需求都落在了 SQL 逻辑或业务服务上。后续计划归纳出通用前置插件,整合空值筛选、日期判定、数值型范围筛选 、基本聚合函数类型选择 等功能

  • 出参结构支持:当前绝大部分数据库的字段类型已经支持了。但是还有个别字段类型的显示是有问题的,类似 PostgreSQL 数组字段如数组字段(int4[] ,int8[] ,float4[] ,float8[] ,boolean[] ,text[]) ,JSON 等

  • 接口 &表关联统计:从数仓角度来说,数仓开发需要理清接口与应用表之间的关系,完善数据血缘。所以配置化平台会理清数据接口到数据表之间的流动和变化,提供表和对应 API 之间的双向查询功能

  • 非只读操作:当前配置化接口仅应用在读领域,未来规划在业务领域中会做更多'读写操作'和'更新操作'方面的尝试

参考资料


作者

李南多 (飞书深诺技术中心,高级 JAVA 研发工程师)

用户头像

还未添加个人签名 2020-05-21 加入

还未添加个人简介

评论

发布
暂无评论
飞书深诺数仓低代码方案实战_飞书深诺技术团队_InfoQ写作社区