- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
在这篇基于 Iceberg 的湖仓一体架构在 B 站的实践我们介绍了B站基于Iceberg的湖仓一体架构实践,本篇我们将继续介绍B站在取数服务方向的演进之路,这也是湖仓一体架构的实践的重要表现方式。
01
引言
数据平台部作为B站的基础部门,为B站各业务方提供多种数据服务,如BI分析平台,ABTest平台,画像服务,流量分析平台等等,这些服务、平台背后都有海量数据的取数查询需求。伴随着业务的发展,取数服务也面临越来越多的挑战:
基于这些问题的思考,我们在取数服务上经过了2次大的架构升级,不断探索服务化,平台化之路,下面介绍我们在这方面的工作,欢迎大家一起学习交流。
02
演进之路
我们在取数服务的升级道路上,大概分为三个时期:最开始是石器时代,这个时期主要以响应业务需求,技术可复用为主;第二时期是铁器时代,我们开始尝试做一些通用服务来支持基础需求,比如统一出仓,统一查询,降低研发成本;第三时期是工业时代,为了更快的响应业务需求,我们尝试引入湖仓技术来进一步提升取数研发的效率。下面分别介绍。
2.1 石器时代 - 烟囱式开发
取数服务在早期建设时,按照常规方式,我们将过程分为4个阶段:数据模型(数仓建模)、数据存储,查询接口(取数接口),数据产品(业务定制),如下图所示:
2.1.1 早期面临的挑战
这个模式我们有2个角色来支持业务需求,一个是数仓同学,另一个是应用开发同学,整体研发流程如下:
在取数需求中承担了主要职责,包括前期的数据探查,确定技术可行性之后,设计数据链路、取数方案,最后实施数据建模、数据出仓,交付可用数据给应用开发同学。
在早期业务量不大的情况下,上述架构和流程能支持业务需求,分工较为明确,但随着业务规模增大,取数需求增多,其带来的问题也逐渐凸显:
基于上述问题的思考,我们开始将一些标准化的能力升级为统一服务。
2.2 铁器时代 - 统一化服务
这个时期我们重点考虑存储和计算统一。通过将数据的出仓过程标准化,引入数据构建流程,将数据进行统一存储。然后在上面搭建了一个基于SQL DSL的取数引擎,内部代号叫Akuya SQL Engine(ASE)。整体架构如下图所示:
2.2.1 数据构建
数据构建任务是基于Flink实现的流批一体作业,内部代号为Ark。Source对接了kafka和hive hcatalog,分别支持实时数据和离线数据,Sink主要兼容4种引擎来做统一存储:
上图为数据构建系统的架构,用户通过数据构建平台可视化配置数据出仓任务,平台会根据不同离线、实时数据类型触发相应的Ark任务,并托管在内部的AiFlow(内部自研的机器学习平台)调度平台上。
2.2.2 数据查询
ASE为了兼容不同存储引擎的标准化数据查询,我们引入了SQL语法,参考了Apache Calcite,Tidb Parser项目,考虑到内部服务Go语言为主,我们最终基于TiDB Parser扩展实现了SQL DSL解析,并独立成Service,同时基于Calcite实现自定义JDBC Driver,兼容其他大数据平台。下面以指标数据查询为例,介绍下主要实现思路:
根据维度查询多指标
select dim1, dim2, pv, uv from business.metric where log_date = '20210310'
根据维度分组统计
select dim1, sum(pv) from business.metric where dim1 is not null and dim2 is not null and log_date='20210310' group by dim1
根据年月汇总指标
select month(), sum(pv) from business.metric where dim1 is not null and log_date>'20200101' and log_date<='20201231' group by month()
单指标年月环比计算
select log_date, pv, year_to_year(pv) from business.metric where dim1 is not null and dim2 is not null and log_date >= "20210301" and log_date <= "20210310"
select log_date, uv, month_to_month(uv) from business.metric where dim1 is not null and dim2 is not null and log_date >= "20210301" and log_date <= "20210310"
month(), year_to_year(), month_to_month() 为系统UDF,标准函数
派生指标计算
select CTR(点击pv, 展现pv) from business.metric where dim1 is not null and dim2 is not null and log_date>'20200101' and log_date<='20201231'
CTR是UDF,支持业务自定义实现
2.2.3 中期面临的挑战
有了数据统一存储和查询之后,我们对应的研发模式上也随之改变如下:
进一步的实践发现,我们依然面临几个突出问题:
为了能更好的解决这些问题,我们在21年开始尝试引入湖仓一体架构来优化。
2.3 工业时代 - 湖仓一体架构
目前我们在B站探索搭建湖仓架构上的取数服务,主要思路是降低数据出仓成本,在低成本存储架构上,完善数据处理和管理功能。并形成PaaS能力。
我们兼容历史架构,新的湖仓一体平台有几个核心能力:
(1)基于HDFS的数据湖仓,数据无需出仓:
我们为DB,服务器,消息队列等数据源提供统一的数据接入层,可以快捷将生产环境中的结构化离线数据,实时数据抽取到数据仓库中。实现标准存储。
(2)打通元数据,湖仓元数据共享:
湖仓基于Iceberg统一建设,通过HCatalog实现湖仓元数据的统一化管理,Hive表和Iceberg表可以无缝切换。
(3)支持数据索引,提升查询性能,支持数据事务,保障一致性:
直接基于Hive表查询数据较慢,无法直接对接业务取数,但在Iceberg中引入数据索引机制,能大幅度提升取数性能,平均在秒级响应,经过定制调优甚至能到毫秒级,可以满足大部分的取数需求。同时有了大数据上的事务ACID能力,对于一些敏感型业务,可确保并发访问的一致性。感兴趣的同学,可以参考另外一篇文章《B站基于Iceberg的湖仓一体架构实践》。
(4)开放更多数据处理服务能力,加速数据应用:
有了Iceberg强大的数据查询性能作为后盾,同时数据无须出仓,我们在湖仓上逐步完善数据服务能力,并使之平台化,直接开放给用户使用。比如:
insert overwrite table iceberg_rta.dm_growth_dwd_rta_action_search_click_deeplink_l_1d_d
select
search_time
, click_time
, request_id
, click_id
, remote_ip
, platform
, app_id
, account_id
, rta_device
, click_device
, start_device
, source_id
from b_dwm.dm_growth_dwd_rta_action_search_click_deeplink_l_1d_d
where log_date='20220210'
distribute by source_id sort by source_id
2.3.1 现阶段的挑战
在湖仓一体架构上,我们通过完善平台能力,支持多人协同参与研发,提升效率,变成如下:
当前我们在湖仓架构上的应用工具还在完善中,后续会开放更多的数据处理服务方便用户取数。
03
总结展望
湖仓一体架构的引入,让数据处理变的更加高效。我们围绕湖仓架构大胆探索,小心求证。在新老架构上做了较多的融合工作,比如元信息管理,查询服务等工作。通过实践经验来看,湖仓一体能促进更多研发协同,降低用户生成数据、使用数据的门槛。未来,我们将继续提升平台化能力,进一步提升取数体验。
我有两种结构,Header 和Session,它们都符合协议(protocol)TimelineItem。 我有一个 Array 由 TimelineItem 组成,如下所示: [Header1, S
这个问题在这里已经有了答案: Multiple assignment and evaluation order in Python (11 个答案) 关闭 6 年前。 我刚接触python所以想问你
我试图找到一种方法来在 R 中获取 A、A、A、A、B、B、B、B、B 的所有可能的唯一排列的列表。 组合最初被认为是获得解决方案的方法,因此组合的答案。 最佳答案 我认为这就是你所追求的。 @bil
我怎样才能将两个给定的向量混合成一个新的向量,它以交替的顺序保存它们的值。 (f [a a] [b b]) ; > [a b a b] 这是我想到的: (flatten (map vector [:a
这是我的第一个问题,我开始学习Python。之间有区别吗: a, b = b, a + b 和 a = b b = a + b 当您在下面的示例中编写它时,它会显示不同的结果。 def fib(n):
这个问题在这里已经有了答案: Why is there an injected class name? (1 个回答) 12 个月前关闭。 我不知道如何解释: namespace A { struct
我尝试了一些代码来交换 Java 中的两个整数,而不使用第三个变量,使用 XOR。 这是我尝试过的两个交换函数: package lang.numeric; public class SwapVars
假设类 B 扩展类 A,并且我想为 B 声明一个变量。什么更有效?为什么? B b或 A b . 最佳答案 您混淆了两个不同的概念。 class B extends A { } 意味着B 是 A .
我不确定这个问题的标题是什么,这也可能是一个重复的问题。所以请相应地指导。 我是 python 编程的新手。我有这个简单的代码来生成斐波那契数列。 1: def fibo(n): 2: a =
我在谷歌上搜索了有关 dynamic_cast 的内容,我发现显式地将基类对象转换为派生类指针可能是不安全的。但是当我运行一些示例代码来检查它时,我没有收到任何错误。请在下面找到我的代码: class
这个问题在这里已经有了答案: What is this weird colon-member (" : ") syntax in the constructor? (14 个答案) 关闭 8 年前。
在不重现产生非整数值的表达式的情况下实现以下目标的惯用方法是什么(在我的真实情况下,该值是在我不想重现的冗长查询之后计算为百分比的): SELECT * FROM SomeTable WHERE 1/
在析构中,这两个代码的结果确实不同。我不确定为什么。 提示说 const [b,a] = [a,b] 将导致 a,b 的值为 undefined (从左到右的简单分配规则)。我不明白为什么会这样。 l
C++ Templates - The Complete Guide, 2nd Edition介绍max模板: template T max (T a, T b) { // if b < a th
我最近开始学习代码(Java),并根据第 15.17.3 节在 Oracle 网站上查找了模运算符。以下链接: http://docs.oracle.com/javase/specs/jls/se8/
无法理解以下行为。 d1 := &data{1}; 的区别d1 和 d2 := 数据{1}; &d1。两者都是指针,对吧?但他们的行为不同。这里发生了什么 package main import "f
这个问题在这里已经有了答案: How to make loop infinite with "x = y && x != y"? (4 个回答) How can i define variables
在我的程序中,当我调试我的代码时,它似乎在我生成的代码中的某处 X1=['[a,a,a]','[b,b,b]'] 还有我生成的其他地方 X2=[[a,a,a],[b,b,b]] 当我想添加这两个列表然
我试图使用递归将两个整数相乘,并意外编写了这段代码: //the original version int multiply(int a, int b) { if ( !b ) retu
我有一个列表中数字之间所有可能的操作组合: list = ['2','7','8'] 7+8*2 8+7*2 2*8+7 2+8*7 2-8*7 8-2/7 etc 我想知道是否可以说像 ('7*2+
我是一名优秀的程序员,十分优秀!