一.前言
-
架构设计的能力:在实时数仓的分层设计中,具体的分层设计方案是怎样的?和离线数仓又有什么区别?你设计的实时数仓是怎么兼顾时效性和通用性的?
-
架构设计的能力:你们公司的实时数仓用到的维表都有哪些类型?分别是通过什么样的方式构建的?
-
架构设计的能力:你碰到过哪些数据倾斜的问题,又是怎么缓解或避免数据倾斜问题的?
-
架构设计的能力:你一般是将实时数据存储到哪里提供对外服务?为什么这么选择?flink 写入的链路是怎样的,遇到过什么坑嘛?
-
数据保障的能力:你们公司在遇到大促时是怎么估算实时任务资源的,有没有成体系的方案可以参考?
-
解决问题的能力:ValueState 和 MapState 各自适合的应用场景?
二.在实时数仓的分层设计中,具体的分层设计方案是怎样的?和离线数仓又有什么区别?你设计的实时数仓是怎么兼顾时效性和通用性的?
(1) 思路:
-
陈述事实:离线数仓的分层设计的目标以及一般的设计方式是怎样的?
-
分析差异:实时数仓和离线数仓的核心区别是怎样的?(只有我们准确的识别出这个区别,才能对实时数仓的分层设计有更准确的理解)
-
分析差异:构建实时数仓肯定会参考离线数仓构建方法,但是如果实时数仓按照离线数仓分层设计去做会存在什么问题?
-
解决方案:实时数仓怎么分层设计才能兼顾时效性和通用性?
(2) 答案:
1. ⭐ 离线数仓的分层设计的目标以及一般的设计方式是怎样的?
-
清晰数据结构:每一个数据分层都有它的作用域,这样我们在使用表的时候能更方便地定位和理解。源系统间存在复杂的数据关系,比如客户信息同时存在于核心系统、信贷系统、理财系统、资金系统,取数时该如何决策呢?数据仓库会对相同主题的数据进行统一建模,把复杂的数据关系梳理成条理清晰的数据模型,使用时就可避免上述问题了。
-
数据血缘追踪:简单来讲可以这样理解,我们最终给业务呈现的是一能直接使用的业务表,但是它的来源有很多,如果有一张来源表出问题了,我们希望能够快速准确地定位到问题,并清楚它的危害范围。
-
数据复用,减少重复开发:规范数据分层,开发一些通用的中间层数据,能够减少极大的重复计算。数据的逐层加工原则,下层包含了上层数据加工所需要的全量数据,这样的加工方式避免了每个数据开发人员都重新从源系统抽取数据进行加工。通过汇总层的引人,避免了下游用户逻辑的重复计算, 节省了用户的开发时间和精力,同时也节省了计算和存储。极大地减少不必要的数据冗余,也能实现计算结果复用,极大地降低存储和计算成本。
-
把复杂问题简单化:将一个复杂的任务分解成多个步骤来完成,每一层只处理单一的步骤,比较简单和容易理解。而且便于维护数据的准确性,当数据出现问题之后,可以不用修复所有的数据,只需要从有问题的步骤开始修复。
-
屏蔽原始数据、业务的影响:业务或系统发生变化时,不必改一次业务就需要重新接入数据。提高数据稳定性和连续性。并且源头系统可能极为繁杂,而且表命名、字段命名 、字段含义等可能五花八门,通过数仓层来规范和屏蔽所有这些复杂性,保证下游数据用户使用数据的便捷和规范。如果源头系统业务发生变更,相关的变更由数仓层来处理,对下游用户透明,无须改动下游用户的代码和逻辑。数据仓库的可维护性:分层的设计使得某一层的问题只在该层得到解决,无须更改下一层的代码和逻辑。
良好的数仓分层设计可以更好地组织和存储数据,以便在性能、成本、效率和质量之间取得最佳平衡!
2. ⭐ 实时数仓和离线数仓的核心区别是怎样的?
实时数仓相比离线数仓的特点其实就两个字:实时。具体体现在:
-
产出速度比离线数仓快:离线 dwd,ads 通常都是小时、天延迟产出数据;相同的数据在实时数仓中,dwd 层常常是毫秒级别产出数据,ads 层常常是分钟级别产出数据。
-
数据时间粒度比离线数仓细:离线数据的时间粒度通常都是小时、天粒度,比如 ads 层计算 1 天的 GMV;实时数据,相同的 GMV 数据在实时数仓中,ads 数据聚合粒度通常为 1min 级别,比如当天实时GMV,实时的 ads 将会计算出 1440(1 天 1440 分钟)个点的数据,每一个点的结果都是当天 0 点到当前这一分钟的 GMV 总额。
3. ⭐ 构建实时数仓肯定会参考离线数仓构建方法,但是如果实时数仓按照离线数仓分层设计去做会存在什么问题?
如果你按照离线数仓分层方案去设计实时数仓分层后,并且尝试之后你就会发现实时数仓分层不适合特别多,因为:
-
分层太多,产出速度必然减慢 举例:ods -> dwd -> dws(1min 窗口)-> dws(1min 窗口)-> ads(1min 窗口)。这样 ads 层数据产出延迟肯定在 3 min 以上。
-
分层太多,实时数据粒度又细,多种粒度的 dws 的数据量基本一样,不如不建。举例:ods -> dwd -> dws1(uid\page\style\1min 粒度)-> dws2(uid\page\1min 粒度)-> ads(uid 1min 粒度),因为一个用户在 1min 内发生的行为很少,你可能会发现 dws1\dws2\ads 的 QPS(流量)都差不多;而离线适合多分层的原因在于离线通常都是 1 天的粒度,所以分这几层的数据量是会有骤减的,因此离线数仓分多层是有价值的。
4. ⭐ 实时数仓怎么分层设计才能兼顾时效性和通用性?
综合前面几个问题的答案,实时数仓分层不宜特别多。建议:
-
如果数据量不大,建立实时数仓只构建 ods -> dwd 就足够使用。ods -> dwd 是为了字段标准化,通用化,然后后面把 dwd 层导入到 OLAP 中进行查询使用;或者建立 ads 层,ads 层直接消费 dwd,这样时效性也可以得到保障。
-
如果数据量大,可以尝试进行 dws 聚合,聚合之后根据数据量(流量)缩减的实际效果来评估是否需要建立此 dws。
三.你们公司的实时数仓用到的维表都有哪些类型?分别是通过什么样的方式构建的?
(1) 思路:
-
描述现状:我们通常以为的实时数仓的实时维表是什么样的?
-
场景分析:一般实时数仓中的维表应用的场景都有哪些?
-
解决方案:针对这些场景,我们有哪些解决方案去构建实时维表?
(2) 答案:
- ⭐ 我们通常以为的实时数仓的实时维表是什么样的?
很多小伙伴对于实时数仓的维表理解都是实时维表一定要实时。但是这个想法不是非常的全面,具体实时维表怎样构建还是需要看场景。
- ⭐ 一般实时数仓中的维表应用的场景都有哪些?
一般的实时数仓中的维表按照使用场景可以分为两类。
-
缓慢变化维度的维表:比如用户画像,包含年龄、性别等维度的数据,其实很长时间用户的维度的变化都不明显。举个例子,当已经判定一个用户的年龄在 18-25 之间时,其实基本上这个维度后续很长时间内就不会发生改变了。基于这个特点,其实实时任务访问 t-2\t-1 或者实时构建的维表的差异是不大的,访问 t-2 和实时的维表产出的数据质量几乎是一样的,所以基于维表构建成本考虑的话,在实时数仓中,这类维表可以访问 t-1\t-2 的维表数据。
-
实时生成维度的维表:比如用户发生购买行为时,这个订单的维度信息。订单一般都是随着购买行为的发生而生成的,所以其维度信息也需要实时的构建生成,从而满足其他任务能够实时获取到这个订单的维度信息。基于这个特点,这种维表只能进行实时构建。
- ⭐ 针对这些场景,我们有哪些解决方案去构建实时维表?
-
⭐ 缓慢变化维度的维表:
-
a. 应用场景:比如画像类维表,一般画像类基本很少发生变化,比如性别、年龄区间等,所以这类在实时数仓中常常是访问 t-1 维表数据的就足够使用
-
b. 常用存储介质:redis,hbase,mysql
-
c. 维表构建方式:一般维表数据都存储在 hive 中,可以使用同步工具(比如 Apache SeaTunnel)定时调度(比如 Apache DolphinScheduler)将 hive 中的数据导入 redis,hbase,mysql 中
-
⭐ 实时生成维度的维表:
-
a. 应用场景:维度实时发生更新的,这类在实时数仓中需要访问最新的维度数据
-
b. 常用存储介质:redis,hbase,mysql
-
c. 维表构建方式:这种实时的维度数据一般是实时生成,存储在原始日志中,比如常见存储在 Kafka 这类消息队列中,可以通过 Flink 消费原始日志,然后实时构建维度数据写入 redis,hbase,mysql 中
四.你碰到过哪些数据倾斜的问题,又是怎么缓解或避免数据倾斜问题的?
- ⭐ 业务数据本身的特点导致倾斜:
-
场景:拿计算直播间的同时在线观看用户数来说,大 v 直播间的人数会比小直播间的任务多几个量级,因此如果计算一个直播间的数据需要注意这种业务数据倾斜的特点
-
解决方案:计算这种数据时,我们可以先按照直播间 id 将数据进行打散,如下 SQL 案例所示(DataStream 也是相同的解决方案),内层打散,外层合并:
select
id
, sum(bucket_uv) as uv
from (
select
id
, count(distinct uid) as bucket_uv
from source
group by
id
, mod(uid, 1000) -- 将大 v 分桶打散
)
group by id
- ⭐ 数据任务处理时参数\代码处理逻辑导致倾斜:
-
场景:比如有时候虽然用户已经按照 key 进行分桶计算,但是【最大并发度】设置为 150,【并发度】设置为 100,会导致 keygroup 在 sub-task 的划分不均匀(其中 50 个 sub-task 的 keygroup 为 2 个,剩下的 50 个 sub-task 为 1 个)导致数据倾斜。
-
解决方案:设置合理的【最大并发度】【并发度】,【最大并发度】最好为【并发度】的倍数关系,比如【最大并发度】1024,【并发度】512
- ⭐ 我已经设置【数据分桶打散】+【最大并发为并发 n 倍】,为啥还出现数据倾斜?
-
场景:你的【数据分桶】和【最大并发数】之间可能是不均匀的。因为 Flink 会将 keyby 的 key 拿到之后计算 hash 值,然后根据 hash 值去决定发送到那个 sub-task 去计算。这是就有可能出现你的【数据分桶】key 经过 hash 计算完成之后,并不能均匀的发到所有的 keygroup 中。比如【最大并发数】4096,【数据分桶】key 只有 1024 个,那么这些数据必然最多只能到 1024 个 keygroup 中,有可能还少于 1024,从而导致剩下的 3072 个 keygroup 没有任何数据
-
解决方案:其实可以利用【数据分桶】key 和【最大并行度】两个参数,在 keyby 中实现和 Flink key hash 选择 keygroup 的算法一致的算法,在【最大并发数】4096,【数据分桶】为 4096 时,做到分桶值为 1 的数据一定会发送到 keygroup1 中,2 一定会发到 keygroup2 中,从而缓解数据倾斜。
五.你一般是将实时数据存储到哪里提供对外服务?有没有标准的数据服务方式?
很多小伙伴都能提到我们是将数据写入到 ClickHouse,Doris,MySQL 提供服务的。
但是其实这个问题是聚焦于是否有规范的数据服务方式。这里的规范的数据服务方式怎么理解呢?
博主这里举一个需求案例:
电商场景中需要要给商家出一个实时 GMV 的数据,这个数据服务的整体链路实时数仓 -> 后端 -> 前端。
那么实时数仓就是数据的提供方,后端就是数据的使用方。
-
⭐ 后端作为数据的使用方来说,后端期望的能达到的最好的数据服务方式就是实时数仓能提供一个 RPC、HTTP 接口给我,后端只需要把商家 ID 传进去,这个接口就能把商家的实时 GMV 数据给我。
-
⭐ 实时数仓作为数据的提供方来说,很多数据开发同学都只具备数据开发的能力,不具备提供 RPC、HTTP 接口的能力。
那么为了解决上面这个实时数仓和后端之间数据服务的问题。就诞生了阿里的那套 OneService 能力。数据开发同学可以通过简单的拖拽就能生成一个 RPC、HTTP 的接口提供给后端进行使用,从而打通了数据服务化这个流程。
博主这里找了一篇关于快手的 OneService 体系的设计文章,小伙伴萌感兴趣可以进行参考:
六.你们公司在遇到大促时是怎么估算实时任务资源的,有没有成体系的方案可以参考?
看到很多小伙伴的回答就是: 能多要资源就多要。
但是其实如果我们能对资源预估有一个成体系、有数据支撑的方案在向 Sre 要资源时是更有说服力的。
一般有 3 种思路去成体系预估资源:
- ⭐ 目前在线任务的资源占用情况评估:
-
适用场景:目前存量(在线)任务要在大促中使用时的场景。
-
举例:比如历史大促时,流量是 n,资源会用 x,今年预估流量最大是 2n,则资源可以认为也是 2x 就足够。
-
预估的准确率:高
- ⭐ 按照目前很多云厂商提供的标准评估:
-
适用场景:大促新开发的任务,并且没有之前的经验可以借鉴的场景。
-
举例:比如我们的 dwd 任务(简单业务),一般就 1CU 处理 1w qps 数据,复杂的清洗可能流量会讲到更低;dws,ads 任务(复杂任务)一般就 1CU 处理 5k qps 数据;涉及到访问外部接口时,则要使用访问外部接口的 qps / 接口请求时延评估。
-
预估准确率:中。
这些标准都是云厂商经过无数的测试、压测得到的,大家可以参考。
- ⭐ 新模块、新任务评估:
-
适用场景:大促新开发的任务,之前的经验可以借鉴的场景。
-
举例:比如按照历史大促情况来看,一个模块、一类任务的处理能力。比如分模块来说,历史经验 1 个模块基本需要 n cu(云厂商 1cu = 1core 4GB),当前有 5 个模块,则大致需要 5n cu;又比如分任务类型来说,历史经验 dwd 可以达到 1CU x qps,dws、ads 可以到达 1CU y qps,根据需求来看总共 3 dwd,每个 dwd 2x qps,5 ads,每个 ads 3y qps,则 dwd 总共需要 6CU,ads 总共需要 15CU
-
预估准确率:高。
这个一般都是自己公司内部的历史经验,所以可参考性更高。
七.ValueState 和 MapState 各自适合的应用场景?
- ⭐ ValueState
-
应用场景:简单的一个变量存储,比如 Long\String 等。如果状态后端为 RocksDB,极其不建议在 ValueState 中存储一个大 Map,这种场景下序列化和反序列化的成本非常高,这种常见适合使用 MapState。其实这种场景也是很多小伙伴一开始使用 State 的误用之痛,一定要避免。
-
TTL:针对整个 Value 起作用
- ⭐ MapState
-
应用场景:和 Map 使用方式一样一样的
-
TTL:针对 Map 的 key 生效,每个 key 一个 TTL
网友评论