一、目标
- 建设一个支撑PB级别,数据秒级/分钟延迟的实时数据仓。
- 用户报表查询的响应时间控制在秒级以内。
- 用户根据自己的需求,灵活的选择查询条件,系统能够根据用户的选择生成相应的统计报表。
- 建设一个公共的数据服务器平台,能够灵活的清洗数据,支撑各个业务线的大数据量查询需求。
二、结构
数据仓库,主要是对从业务系统拉取的数据进行加工,生成报表,到最后的展示。作为数据规划者,我们肯定是希望数据能够有秩序的运行。随着业务增多,数据量增多,往往会事与愿违:
早期web开发的时候,一个servlet就可以完成全部需求,但是随着需求的迭代,整个工程项目变得难以维护。因此,我们将工程划分为了controller, service, dao三层。因此,我们有必要在项目建设的起步阶段,对数据仓中的数据处理进行一个划分。
生命周期
数据从业务系统生成,一直到数据仓的拉取,加工,生成报表,我们可以概括为4个过程:
数据分层
按照数据的4个生命周期,我们还可以将其归纳总结为4层:
-
ODS,原始数据层,持久化从数据源拉取的原始数据,数据保持原貌不做任何处理。
-
DWD,数据明细层,对ODS层产生的原始数据,进行清洗,统一数据格式,拆分,打宽。
-
DWS,数据汇总层,对DWD层产生的数据进行初步汇总(比如按照天为粒度汇总某天某地用户增长情况),后续在基于DWS的数据进行三天、七天的报表统计,效率上会更高。
-
ADS, 应用数据层,根据数据仓中加工后的数据,生成面向需求的报表存放生成的结果报表数据,比如三天、七天报表。
数据清洗、格式统一甚至后续的查询分析过程中,会涉及到一部分的公共数据,如比如区域、日期、行业等数据,我们可以专门抽取一层,用于管理这些公共的维度数据:
- DIM: 维表层,用于存放维度数据,是整个数据仓的公共财产。
数据层之前,禁止互相调用,应该是层层递进的关系,避免造成数据依赖复杂问题。在每一个数据生命周期处理结束后,我们还可以将数据进行落盘:
- 数据复用:存储数据处理的中间状态,能够减少极大的重复计算以及重复需求开发,避免烟囱式开发。
- 质量保障:能够快速的定位到数据从何而来,历史变化过程是怎么样,保障数据质量,追踪数据血缘。
- 数据重放:在某个处理过程出现问题或者增加统计维度的时候,能够清楚数据的影响范围,并从指定层开始重放数据。
维度建模
数据仓建模设计上,按照 Ralph Kimball在《数据仓工具箱》 提出的维度建模方法论,自上而下进行设计,在一些场景中,还会进行一些数据冗余,用空间换时间,已期达到数据查询快速响应的效果。
维度建模以分析决策的需求出发构建模型,构建的数据模型为分析需求服务,因此它重点解决用户如何更快速完成分析需求,同时还有较好的大规模复杂查询的响应性能。
在维度建模方法体系中,将所有的表分为两类:
- 维度表:描述事实的角度,统计分析的入口,如日期、商品、地址等。
- 事实表:度量的指标,如用户数、销售额等,同时会存放维度表的外键。
比如,2021年8月30日学员A在某宝,下了一个订单,花费100元,分别购买了《Java编程思想》以及《并发编程的艺术》。
维度:
- 日期维度:2021年8月30日
- 用户维度:学员A
- 商品维度:《Java编程思想》、《并发编程的艺术》
事实:
- 下单事实,度量值为 100 元。
维度建模最理想的状态便是事实表中只存放各个维度表的外键以及度量值。
建模方法
举个例子,针对用户统计分析需求:
选择业务过程
业务过程的选择上,我们选择的是用户注册这个过程,这个是由需求决定的。
确认粒度
用户注册的时候,我们通常会生成两张表的记录:
- 用户信息表
- 用户角色关联表
一条用户信息对应多条用户角色关联表数据,根据最细粒度原则(从最细粒度我们可以查询到粗粒度,而从粗粒度,我们是没有办法查询到细粒度的,粗粒度不利于数据扩展),我们选择按照用户角色粒度进行分析,即一个用户角色,一行记录。用户信息表中的属性,会通过打宽,冗余至用户角色宽表中。
确认维度
学员注册信息,我们可以按照下面的几个维度进行分析:
一些维度属性,如行业、日期、年龄、区域、职称、学历,我们可以针对这些维度属性,扩展出其他属性(比如日期可以扩展出年、月、日、是否假期等)。我们还可以将这些数据单独的拎取出来,形成维度表:
- 行业维度表。
- 日期维度表。
- 年龄维度表。
- 区域维度表。
- 职称维度表。
- 学历维度表。
同时,用户的属性数据中,还存在着大量的离散数据,这些离散数据不在一致性维度之列,若进行维度退化,直接放置在事实表中,又会造成事实表存储空间的浪费。
因此,通常会使用杂项维设计,几个字段的不同取值组成一条记录,生成代理键,存入维度表,并将代理键存入对应的事实表中:
理论上,组合的数量较小,比如只有几百行时,可以预装载所有组合的数据;而如果组合的数据量大,那么在数据获取时,当遇到新标志或指标时,再建立杂项维度行。
确认事实
针对用户注册这个过程,表面上是没有一个可分析的度量值的,所以,这种事实表会被称作 无事实事实表。
无事实事实表中通常仅放置各个维度的外键值,也可以看做是维度与维度之间的关系表。
同时,用户属性中,会存在一部分数据如分析从来不用的数据列,但是在查看详情的时候却需要用到,如证件号、手机号等,常见的做法是将这些数据直接存放到事实表中,也就是维度退化。
建模结果(简)
三、技术选型
Canal
负责业务系统数据的全量以及增量拉取
诉求分析
现有数据全部集中存储在MySQL数据库中,MySQL主要是用于事务处理,不擅长数据分析,因此,我们需要做的是将数据从 MySQL 抽取出来,放到一个擅长进行数据统计分析的数据库引擎(OLAP)中,并且还需要避免对业务系统造成影响,最佳的方案还得是基于MySQL BinLog的CDC技术。
在数据仓建设之前,各个业务系统生产环境往往是已经积攒了大量的存量数据,因此,选择的CDC工具必须包含存量以及增量数据拉取两个功能。
工具选择
CDC技术,原理实现上大同小异,都是伪装成数据库从库,发送数据库协议,来拉取数据库日志,在将读取到的二进制日志数据转换成指定格式的信息,通过TCP、消息中间件等方式发送到订阅方。目前常见的CDC技术有四种:
-
Canal,阿里巴巴开源,下游设施完善且相对稳定,当前版本新增了Canal-adpater端,用于数据的存量拉取,支持 MySQL -> HBase | elasticsearch | kudu | rdb,功能比较简陋。常见的做法是通过另一个开源工具DataX进行全量同步后,在进行增量拉取,这样会导致全量以及增量逻辑无法统一,维护难度高。
-
MaxWell,自带初始化程序,能够对存量数据进行拉取,但自身不支持高可用。一旦服务宕机,而MySQL正好触发了BinLog日志文件的清理策略,存在部分数据丢失的风险。当然,我们也可以结合Zookeeper自行编码实现高可用。
-
Debezium,RedHat开源,支持多种数据库,支持多种数据拉取模式,功能完善。但进行全量拉取的时候,会进行一次锁表操作,容易对业务系统造成较大的影响。
-
Flink CDC,本质上是Flink 社区对Canal、MaxWell、Debezium的封装,使得拉取到的数据可以直接对接到Flink,而无需进行一次消息中间件的流转,节省了存储以及时间开销。但FlinkCDC目前存在一个较为致命的问题,每新增一张表的拉取需求,都会占用一个数据库链接。当数据表较多的情况下,需要消耗大量的业务系统资源。
权衡利弊后,我们最终选择Canal作为数据同步工具。全量同步方面,则扩展了canal-adapter端,新增了canal-kafka-adapter,将全量数据接入到Kafka中,在进行增量拉取,统一了全量以及增量的处理逻辑,也避免了多引入一个中间件(DataX),增加系统的复杂度。
Filebeat
Filebeat,在数据中台负责应用日志、行为日志的采集工作,将日志采集到统一的中央仓库
诉求分析
统计分析若要计算PV(Page View)、UV(Unique visitor)等指标,那么,业务系统就需要将用户的访问行为数据进行记录。这类数据数据量大,且对业务系统的业务处理无帮助,若存放到数据库中,会占用大量的数据库资源,对业务系统产生影响。
因此,通常会在前端做埋点,比如将脚本隐藏到某个图片中,图片加载完成,自动将行为信息发送到Nginx的日志服务器。
为了缓解服务器压力,日志服务器通常是散落在各处的,所以,我们需要一个日志采集工具,将行为日志推送到数据中台。
工具选择
应用日志采集工具,主流的选择有Flume以及Filebeat。Flume 相较于 Filebeat 而言,资源占用相对较高,且目录中文件回滚时,filebeat 会自动处理,Flume 不能自动处理,处理起来比较麻烦。
虽然 Flume 支持高可用,但对于应用行为日志,我们是允许一部分数据丢失的。
因此,我们选择 Filebeat 作为应用日志采集工具。
Kafka
Kafka,在数据中台中负责数据接入,流转数据的存储以及数据回溯的工作。
诉求分析
数据仓库,需要接入多个数据源系统,我们无法预料是否存在某个时刻某个数据源生产的数据暴增,数据仓库消费不及时,冲垮数据仓库的服务器,因此,有必要引入一个消息中间件,统一数据入口以及对瞬时流量进行削峰。在数据处理过程中,利用消息中间件,还能对数据处理步骤进行解耦。
工具选择
常见的消息中间件有 ActiveMQ、RabbitMQ、Kafka、RocketMQ 以及后起之秀 Pulsar、Pravega:
-
ActiveMQ、RabbitMQ目前应用相对较少,小项目应用较多,但也在慢慢的向RocketMQ以及Kafka转变。
-
Pulsar、Pravega是后期之秀,号称实时数据仓的最后一块拼图。但经过调研,两个目前文档资料较少,风险较高。
-
RocketMQ 目前主流的消息中间件解决方案,吞吐量在十万级别,支持的功能相对较多,commitLog日志的设计,在创建较多Topic的情况下,依然不影响其写入性能,能够满足我们的需求。但RocketMQ最早的定位在业务系统,因此,大数据领域周边工具较少。
-
Kafka 也是目前主流的消息中间件解决方案,RocketMQ的很多设计,借鉴于Kafka,能够支持百万级的吞吐量,一开始就定位于大数据领域日志传输,有较为完善的大数据生态以及成熟的应用案例。
考虑到当前的应用场景定位是大数据,而Kafka的生态正好契合。因此,我们采用Kafka作为数据接入的统一入口。
Topic设计
数据仓中,数据流转必须要严格保障数据的顺序,否则会产生不可信的结果。比如 11:00,业务系统将用户名更新为 A,11:05,业务系统将用户名更新为了B,如果产生了乱序,我们最终得到的结果是用户名为A。
Kakfa要保证消息的顺序性,原则上需要确保顺序消息处于同一个分区中。我们可以采取的设计措施有:
1. 一张表一个Topic,一个Topic仅设置一个分区。
2. 根据数据记录的某个字段进行Hash,取Hash结果作为Kafka分区的依据。
值得注意的是,Kafka数据处理效率之所以高,很大程度上在于利用到了磁盘的顺序写入以及多分区机制,若采取第一种方式,不仅无法利用到多分区的带来,且在Topic主题多的情况下,会触发磁盘的随机写。带来性能上的严重下降。
方案二相对而言会更加合适。当然,方案一也可以通过多部署几个Kafka集群来解决,成本较高。
Apache Flink
Apache Flink 是一个框架和分布式处理引擎,用于对无界和有界数据流进行有状态计算。Flink 被设计为在所有常见的集群环境中运行,以内存速度和任何规模执行计算。
诉求分析
截止目前为止,我们做的Web应用,都是一条请求进来,经过应用程序处理,落地到OLTP数据库,然后返回结果。等下一次请求进来,又是启动新的一个流程,两个请求之间互不关联,也就是无状态处理。
数据仓数据的处理,往往充斥着大量的如统计XXX分钟XXX的数量,我们尝试着推导一下实时数据仓数据请求流程:
- 一条数据请求,进入数据仓处理应用程序。
- 将这条数据存到应用内存中,等待下一条请求的到来。
- 重复这两个步骤,等待XXX分钟后,在进行聚合计算,落盘。
观察上面的几个步骤,几个请求之间,存在了关联关系。这种处理,又被称作有状态处理。
接着,我们还需要考虑一些问题:
- 数据在聚合前,被暂存到了内存中,服务器宕机了,数据丢失了怎么办?
- 应用分布式部署的情况下,如何保障数据的有序性呢?
我们需要增设一个机制,周期性的将内存中的数据进行落盘,宕机重启后,数据能够恢复回来。还需要一个分配机制,能够有序的分配数据处理任务。
以上这些,就是计算引擎需要负责的内容了,当然计算引擎不仅仅只有这些功能。
工具选择
目前常见的计算引擎有:Storm、Spark、Flink:
-
Storm作为第一代的实时计算引擎,目前已经被慢慢的被Spark以及Flink所替代。
-
Spark以及Flink既支持流处理(来一条处理一条),也批处理(攒一批处理一次)。两者最大的区别在于世界观不同,Spark以批处理作为世界观,Flink以流处理为基本世界观。在实时流处理领域,Spark采取微批的形式模拟实时流,即攒一小批次的数据,提交一次,相对而言,Flink则来一来一条数据,提交一次数据。Flink的实时性会更高。Spark 适用于离线计算多的场景,而Flink在实时场景上,表现的更为出色。
我们的应用场景中,主要以实时流为主,Flink更加契合我们的场景。
Apache Doris
Apache Doris,在数据中台中负责主要数据的存储。
诉求分析
前面分析了拉取、计算,接下来,要开始进行存储库的选择了。
大量的数据从各个业务系统汇总过来,需要一个能够支撑大数量的存储引擎,进行数据存储,统一提供一致的查询引擎:
-
支持数据更新:存在需要对历史存量数据进行更新的场景,比如昨天有一个用户是男,但是今天用户将性别更改为了女,那么,我们需要将昨天的男性用户统计数据 -1,今日 +1。
-
支持即席搜索:存在用户根据自己的需求,灵活的选择查询条件,系统能够根据用户的选择生成相应的统计报表的需求。
-
支持多表关联:存在多维度表关联的情况,比如搜索福建区域软件开发行业的用户数量,需要关联用户注册事实、区域维表、行业维表。
-
支持灵活收缩:平台建设初期,很难去评估接入的数据量有多少。随着时间的推移,数据的占用空间必然是在不断增加的。
-
支持并发查询:数据中台在后续,存在开放API给业务系统使用的可能,在极端的情况下,还会存在并发查询报表的需求。
工具选择
目前具有代表性的大数据实时数据仓存储方案有 Hudi、Greenplum、ClickHouse、Doris:
-
Hudi,定位于数据湖,支持结构化以及非结构化的数据存储,相较于其他集中存储而言,能够提供更大量级的存储能力。但体系复杂,组件依赖较多,运维复杂,原有的运维生态也开始闭源了。适用于有非结构化数据或者数据量级大于PB量级的存储需求。
-
ClickHouse,MPP数据库,单表查询极快,但不支持快速更新,多表join效率低,运维困难,数据扩容需要人工介入,适用于数据仓的应用层,用于结果数据的存储。
-
Greenplum,MPP数据库,既支持事务,又支持大数据量查询。但恰恰因为什么都会,又显得什么都不精通,适用于既需要大数据查询,又需要事务支持的场景。
-
Apache Doris,MPP数据库,大数据界的MySQL。采用MySQL协议,因此可以兼容绝大部分的 MySQL BI 工具以及 MySQL SQL 语法。支持低延迟(秒级)数据更新,支持映射 ElasticSearch 作为外表进行关联查询,增强了全文检索能力,并且支持标准的SQL语法以及现代化的物化视图,提高查询速度。
相较而言,Apache Doris 能够满足我们的需求。但是需要注意的是,Apache Doris 容易因为内存溢出而出现宕机的情况,重启后会自动的修复数据。我们也可以通过编写守护进程,对 Doris 进程进行检测重启。
HBase
一个分布式的、面向列的开源数据库
诉求分析
数据在数据清洗转换的过程中,往往需要获取区域、行业以及用户资料等数据参与辅助计算。比如要统计注册区域为福建省的用户下单情况。用户表以及订单表属于大表,如果在统计的时候,在关联查询,整个检索效率大打折扣。通常我们会在清洗订单的时候,将对应用户的区域信息冗余到订单记录中,以空间换时间。
这就涉及到了一个数据的快速存取,能够根据数据Key快速的查询到目标数据,类似于web系统中的缓存作用。类似于用户资料等数据的数据量是十分可观的,因此,k-v数据库需要能够支撑海量数据的存储。
工具选择
常用的NoSQL数据库有如 HBase、mongoDB、Redis、Couchbase、LevelDB 等,排除掉不支持海量数据存储的数据库,则仅剩下:
- HBase
- Couchbase
- LevelDB
Couchbase资料相对较少,LevelDB 更适用于写多与读的场景。而我们的数据处理场景,大部分是数据的读取。
因此,我们选择HBase作为数据仓非关系型数据的存储。
ElasticSearch
一个分布式、RESTful 风格的搜索和数据分析引擎
诉求分析
数据中台对外提供事实明细的模糊匹配,比如通过身份证查询注册的用户数据,OLAP引擎如 Apache Doris、ClickHouse 等更偏向于数据统计分析的场景,数据搜索支持普遍不高。
工具选择
全文搜索引擎,可以选择的较少。OLAP引擎,前面我们选择了 Apache Doris,而 Doris 提供了 Doris on ES插件,在Doris数据库中创建对应的 ElasticSearch 外表后,编写常规的SQL语句,就能检索到ES中的数据,还能进行多表的关联查询,极大的降低了学习难度以及编码复杂度。