1 为什么要建设实时数仓
在开始说如何建设实时数仓之前,我们先说一下建设实时数仓的目的,实时数仓解决了什么问题。
其实在很多情况下,我们对于实时数仓的定位可能是没有那么准确的。我们都知道,传统数仓一个非常重要的功能是用于记录历史,而实时数仓恰恰相反,它更重视处理当前的状态。
因此,我们创建实时数仓的目的就在于解决传统数据仓库由于时效性低而解决不了的问题。传统数仓可以解决的问题,我们不解决;如果问题本身就不适合用数仓解决的话,我们也不去解决。这是我们建设实时数仓的一些基本原则。
实时数仓作为数仓的一种,他也包含面向主题、有集成性、相对稳定等的数据仓库本身的特性。不过于传统数仓不同的是,它只保留上一次批处理到当前的数据(一般只保留3天)。这样的话,我们就有两天完整的数据,在批处理还没来得及处理完昨天的数据的间隙中,数仓仍然可以提供较好的服务。
上面说了实时数仓不解决的问题,那么哪些问题是我们要解决的呢?
- 实时OLAP分析
- 实时数据看板
- 实时特性,用户画像的建立
- 实时业务监控,比如说核心业务指标的监控预警等等。
2 实时数仓的架构
在开始建设前,我们还需要了解他的架构,这就像造船的龙骨一样,如果没有好的架构,搭建起来的数仓也不会是一个好的数仓。
前面咱们说过,实时数仓用于解决传统数仓解决不了的问题,那么我们有必要先了解一下传统数仓的架构。
2.1 传统数仓架构
从图中可以看到,传统的数据仓库是基于hdfs进行建设的,它的架构主要分为三个部分,数据源、离线数据仓库、数据应用。
数仓的数据来源分为两部分,一部分是埋点行为日志,这部分数据来源于我们的web应用、APP等的前端埋点,记录的是用户的行为。另一部分是业务数据库中的数据,一般是mysql等关系型数据库中存储的业务数据。
离线数据仓库中一般分为ODS层、DWD层、DIM层、DWS层、应用数据层。当然不同的地方对每一层的叫法可能会有所不同。但是大概是这个样子,它使用hive/spark/mr等计算引擎进行计算,支持天或者小时级的延迟处理。
ods层存储的是原始数据,这里面的数据是最细粒度的,没有任何变动的数据。dwd明细数据层存储的是明细数据,及ods中的数据经过简单的ETL之后的明细数据。同时还有dim维表,这里面存储的是维度数据。dws汇总数据层中存储的是汇总层的数据,这里的数据一般是按照主题,或者业务进行区分的宽表数据。最后就是APP应用数据层,这里面存储的是最后提供给外部应用的结果数据。
数据应用是我们数据仓库中的数据的使用位置,我们可以将仓库中的数据存储到hbase等的数据库中作为数据服务为其他业务提供支持,也可以用作数据报表,数据产品等地方。
2.2 lamdba架构
而实时数仓于传统数仓不同,刚开始实时数仓的架构叫做lambda架构,如下图所示。
lamdba架构是一个比较经典的架构,在以前实时场景并不是很多,大部分的业务都已离线为主,再附加了实时场景之后,由于他们之间的时效性不同,导致其技术生态也不一样。lambda架构相当于在离线数仓的基础上附加了一条实时生产链路,在应用层面进行一次整合。双链路生产,相互独立。
不过这种架构也存在着一些问题,比如说加工逻辑、开发运维等都要进行double操作,成本高,开发维护麻烦。同时资源同样也是两个资源链路。
因此产生了kappa架构。
2.3 kappa架构
kappa架构移除了离线数仓的部分,全部采用实时生成。从架构设计来讲,比较简单,生产统一,一套逻辑同时生产离线和实时数据。
2.4 实时OLAP变体
在kappa架构上,我们可以将APP应用数据存储到OLAP引擎中,由实时OLAP引擎承担聚合分析运算,这样可以减轻实时计算部分的计算压力。这种可以成为kappa架构的一个变体。
2.5 小结
从以上几种架构我们可以发现,实时数仓相对于传统数仓来说,它的层级更少。这样可以保证它有更少的延迟。
ODS层在kafka中存储的是原始数据,这里业务数据库我们可以使用canal监控mysql的binlog日志传入kafka。DW层存储汇总数据,经过flink计算后同样存储于kafka。DIM维度表可以存储在redis、hbase、es等的热存储引擎中。至于APP应用数据层我们会直接写入应用数据库,也就是说实时数仓并不去维护APP应用数据。
我们也可以看一下这些架构之间的区别
lamdba | kappa | 实时OLAP变体 | |
---|---|---|---|
计算引擎 | 流批两套计算引擎 | 流计算引擎 | 流计算引擎 |
开发成本 | 高,需要同时维护实时、离线两套代码 | 低,只需要维护一套代码 | 低,只需要维护一套代码 |
OLAP分析灵活性 | 中 | 中 | 高 |
是否依赖实时OLAP引擎 | 非强依赖 | 非强依赖 | 强依赖 |
计算资源 | 需要流批两套计算资源 | 只需要流计算资源 | 需要流计算资源和实时OLAP资源 |
逻辑变更重算 | 通过批处理重算 | 重新消费消息队列 | 重新消费消息队列,重新导入OLAP引擎 |
3 实时计算的技术选型
在了解了架构之后我们还需要确定实时计算的技术选型。
目前,市面上已经开源的实时技术还是很多的,比较通用的有Storm、Spark Streaming以及Flink。
在几年前,业界使用的一般是Storm。当时的Storm,在性能稳定性、可靠性以及扩展性上是无可替代的。但随着Flink越来越成熟,从技术性能及框架设计又时尚已经超越了Storm,从趋势来讲就像Spark替代MR一样,Flink也会慢慢替代Strom。
Flink和Storm的对比
Flink | Storm | |
---|---|---|
状态管理 | 内部包含状态管理 | 无状态,需要用户自行进行状态管理 |
窗口支持 | 窗口支持比较完善,自带一些窗口聚合方式,会自动管理窗口状态 | 对事件窗口支持较弱,缓存整个窗口的所有数据,窗口结束时一起计算 |
消息投递 | 最多一次 最少一次 精确一次 | 最多一次 最少一次 |
容错方式 | 检查点机制,通过分布式一致性快照机制,对数据流和算子状态进行保存。发生错误是进行回滚。 | ACK机制,对每条消息进行全链路跟踪,失败或超时进行重发 |
性能 | 吞吐量是Storm的3倍更多,延时明显低于Storm | 稳定,可靠 |
4 如何建设实时数仓
现在我们就可以着手于实时数仓的创建了。
从上面的架构中我们其实可以感觉到,实时数仓相对于传统数仓而言,他的数据存储更多,同时层级更少。
-
存储更多
在离线数仓中,我们所有的表几乎都是hive表,而在实时数仓中,我们会根据表的用途将他们存储在不同的地方,比如说维度数据,我们可能存储在Redis、ES等数据库中。其他数据可能在HBase,kafka等地方。
-
层级更少
在以往建设数仓的时候,APP数据应用层是在仓库内部的。而在实时数仓中,APP层并不算在仓库中,而是放到了外部存储中,以作他用。
同时在DW层我们也要保证由更少的层级,因为在计算的时候,我们为了得到更加准确的结果,可能人为设置一些延迟,来等待迟到数据。层级越多,延迟也就也高。
4.1 ODS层
ODS层存储的是原始数据,在kafka中进行存储,我们不会对数据有任何的处理,保持原子粒度的数据。
当然在这里我们是讲一些需要注意的点,主要有两个方面。
-
数据来源尽量统一
这里统一的概念主要由两个。
- 一个是实时数据源本身要与自己相统一。比如说咱们需要从Mysql中去读取数据,我们尽可能用一种方式去读取,像binlog,不要同时使用多种读取方式。
- 实时和离线的统一。虽然咱们是在建设实时数仓,但是他也是属于数仓。在仓库中我们要保证数据来源的统一,不能让使用的人产生误解。
-
利用分区保证数据局部有序
我们在采集数据的时候,因为网络延迟等原因可能会造成数据乱序,也就是先发生的数据后消费。针对这个问题我们可以通过kafka的分区来保证数据的局部有序。
4.2 DWD层
DW层它的建设其实和离线数仓中DW层的建设基本一致。主要是为了解决一些原始数据中存在的噪点、数据不完整和数据格式不一致的问题,形成规范、统一的数据源。
这里的话尽可能做到和离线数仓一样的入仓方式(入什么仓,如何入仓)。比如说我们可以建设基于配置的入仓规则,实时和离线用同一套规则入仓。
DWD层主要的工作包括数据解析、业务整合、脏数据的清洗和模型规范化。
这里我们重点讲一下模型规范化,实时数仓和离线数仓不同的是,实时数仓他是7*24小时不间断运行的。如果需要更改表字段的话,会付出比离线更多的代价。因为要保证数据上游和下游的数据可以解析到新的表结构。还有就是我们存储使用的是kafka,kafka不像其他的数据库。它不能做结构化存储,同时也没有办法管理元数据。因此,我们建议在开始构建数仓前就做好数据的模型规范化。避免后续付出更多的代价来做数据治理。
同时,我们还可以针对数据本身做一些处理,为了应对一些生产环境中的常见问题,我们可以在数据上额外补充一些信息。
-
唯一键和主键
在数仓中我们针对日志进行处理,比如说我们通过binlog来处理MYSQL中的数据,MYSQL中存储的是一些业务数据,比如说订单数据,每一行订单数据可能会有多次改变,这些变化又通过一条条的binlog日志来体现。那这个时候我们就可以通过唯一键和主键来标记每条数据。其中唯一键用来标记唯一一条数据,这个可以解决数据重复的问题;主键用来标记唯一一行数据,这个可以利用kafka的分区来解决是数据乱序的问题。
-
版本号和批次
因为在数仓中,下游数据的处理逻辑是依赖于上游表的schame的,我们在计算过程中可能会遇到需要重新计算的问题或者说数据的表结构发生了变化的情况。这个时候我们就可以通过数据的版本和批次去分别解决。这里的版本号指的的是数据表结构的版本号,我们可以通过不同的版本号来判断表结构变化的问题。而批次指的是数据重导时的批次变化,可以通过更改数据的批次来保证及时数据的消费位置发生变化后也可以得到正确的结果。
4.3 DIM层
DIM层的数据都是数仓中的维度数据,维度数据我们可以根据他的变化频率分为两大类,变化频率低的和变化频率高的。针对不同的数据我们有不同的处理方式。
对于变化频率较低的维度数据,比如说地域信息等,我们可以通过离线中的维度数据同步到缓存或者通过一些公共服务或者维度服务进行查询,然后将其缓存中进行访问。
至于变化频率较高的维度数据,比如说一些商品的价格等信息,我们这个时候就需要监听其变化情况,去创建一张价格变动的拉链表。
当然,维度数据我们都是用作关联操作,也就是join,可以用于一般的ETL场景。其方式也有很多种,因为我们一般使用Flink作为实时计算引擎,所以我这里写一些Flink中常用的join操作方式。
-
维度join
在做维度join的时候,我们一般是将维度表全量预加载到内存中做关联。它实现简单,好操作。但是仅支持小数据量的维表,而且维表更新时必须重新启动任务。它适用于小维表,同时变化频率低,对变更及时性要求低的场景。
-
热存储关联
实时流于热存储(redis、es等)中维度数据的关联,同时使用cache减缓存储访问压力。这种方式中维度数据数据不受限于内存,可以支持更多的唯独数据,不过需要热存储资源,维度的更新反馈到结果也存在一定的延迟。适用于维度数据量较大,同时可以接受维度更新有一定的延迟的场景中。
-
广播维表
利用broadcast state将维度数据流官博到下游task做join操作。这种情况下可以及时察觉到维度数据的更新。但是数据保存在内存,所以支持较少的数据量。适用于需要实时感知维度数据变更的场景。
4.4 DWS层
DWS层即汇总层。这一层的建设也是和离线数仓的建设方法一致。
这一层主要是对共性指标的统一加工,同时根据主题进行多维度的汇总等操作。可以使用Flink中丰富的时间窗口实现。
5 总结
到此为止,数仓的建设基本上就搞定了。虽然从步骤上来看好像非常简单,但是当我们建立好一个仓库后,想要保证它高质量的运行是非常的复杂的过程。所以我们会在仓库建设好之后创建一些周边系统来保证仓库的正常运行。
法一致。
这一层主要是对共性指标的统一加工,同时根据主题进行多维度的汇总等操作。可以使用Flink中丰富的时间窗口实现。
5 总结
到此为止,数仓的建设基本上就搞定了。虽然从步骤上来看好像非常简单,但是当我们建立好一个仓库后,想要保证它高质量的运行是非常的复杂的过程。所以我们会在仓库建设好之后创建一些周边系统来保证仓库的正常运行。