0%

一些目前自己对data方面的理解和总结

光阴如梭,转眼间又到了新的一年。感觉过去这一年对自己来说确实挺艰南的,不管是上半年的网课,还是下半年的戛然而止的第一份工,亦或是之后简历石沉大海,以及屡败屡战的求职经历,都为自己的这一年增加了不少魔幻的特征。

虽然说这第一份data相关的工作比较短暂,不过自己从中也积累了一些经验,也对整个项目作出了相关的贡献。因此感觉还是有必要记录以下自己在这过程中积累的一些data相关的理解吧。(不定期更新)

为什么我们需要数据仓库

当数据量相对较小的时候,一般都是使用数据库来进行数据的存储和读取,常见的主流关系型数据库包括MySQL、SQL Server以及PostgreSQL等。但是当业务不断发展,数据量不断扩大的时候,使用数据库就会遇到一定的瓶颈。

  1. 随着数据量的增加,查询效率降低 (数据库层面有读写分离、分库分表等方法,但当数据量更大的时候,这些解决方法依然存在瓶颈)
  2. 进行业务查询的时候,表的数量繁多,同时由于范式约束,很多时候查询需要对不同表进行各种join,费时
  3. 表里可能有非法字段,影响查询结果

当遇到这些瓶颈的时候,可以考虑使用数据仓库来解决这些问题。笔者之前做的是离线数仓,因此下文中的数仓,如无特别说明,均指离线数仓。

离线数仓和数据库的一个很大的区别在于,数据库是需要同时支持实时的增删查改操作的,而离线数仓的话主要专注于读取数据这一方面,因为离线数仓主要存储的是历史数据,所以在现实的生产环境中,根据实际需要,可能一天写一次或两次历史数据,也有可能几天写一次数据,因此它对于写的时效性要求不是很高(一般来说数据写入数仓都是在凌晨进行,可以用像Airflow这样的工具来进行调度),所以它的设计主要专注于读取数据的效率。

举个例子,当时我们使用Parquet作为数仓中数据的存储格式,它是属于列式存储方式(不同于数据库的以行存储),读的话只需读取所需的列,而不需要读取所有表的内容,这样就可以提高读取的效率。相反,对于写数据而言,由于写入一行新的数据的时候需要同时修改多个列,所以列式存储的话写数据的话是比较耗时的。(Hadoop家族的实时数仓用的HBase也是同样采用列式存储方式)

此外,与数据库不同的是,数据仓库里的表是允许冗余的,不需考虑各种范式的约束。因此读数据的话可以减少join的使用。这样的话可以减少join表的时间,提高读取效率。(毕竟join是个很耗时的操作)由于数仓的话很少会修改(update)数据,因此不需要过多考虑到冗余会导致修改一个字段的值会影响到其它表的情况。

用个形象的比喻来说的话,关系型数据库就相当于六边形战士,技能树点得很均衡,啥事都能做,读取数据、写入数据、范式、事务支持等等,但是这也影响了它每个部分的效率;而数据仓库的话相当于是专精于读取数据这方面,所以使用起来效率会高很多。

数据进入数仓的整个过程

数据库侧重于OLTP(Online Transaction Process,联机事务处理),而数仓的话侧重于OLAP(Online Analytical Process,联机分析处理)。因此离线数仓的话主要是用于读取历史数据进行相关的分析。它主要有以下几个层级(自下而上),STG、ODS、DWD、DWS、ADS,以及贯穿于多个层的DIM。接下来通过层级来讲一下相关的过程。

1. 接收/读取数据(ODS/STG)

ODS层或STG+ODS层主要是接收业务数据进入数仓,可以说是数仓的数据入口。数据的来源可以有以下几种方式:

(1)应用本身产生的数据(用户行为日志),比如浏览或分享了哪些应用内的链接,或者说完成小游戏后的结算信息等等。这些可以定时采集发送到服务器,也可以通过用户特定行为触发。由于这方面数据量很大,所以一般来说大部分不走数据库,而直接进入到载入进数仓的准备阶段。这个过程可以用Kafka进行实现。
(2)数据库相关的数据(直连数据库读取)
(3)外部数据(比如第三方网站数据/爬虫得到的数据等)

一般来说在接收/读取数据环节是不对数据进行加工处理的,或者只是进行简单的数据格式转换操作。为了不影响白天服务器的日常使用,数据进入数仓的整个操作一般是在凌晨进行。

2. 对数据进行处理(DWD)

DWD全名叫Data Warehouse Detail,顾名思义,这一层主要存储的是经过转换的数据,同时保留它们的详细信息。关于这一层的转换,我的理解是包括了两个部分,一个是表层级的转换,另外一个是数据层级的转换。

根据处理的数据所属的业务不同,可以将相关的数据归类为不同的数据集市。

2.1 表层级的转换

这一部分转换主要解决的是源表(Raw Table)的表数量繁多,查询复杂的问题。比如对于同样一个业务对象,它的表可能会根据地区不同分为多个类似的表。举个栗子,一个游戏可能在多个地区有自己的服务器,那么对于记录玩家信息的play表而言,就会有带各自区域后缀的表出现,比如play_cn,play_sg,play_my,play_vn等。这个时候如果说业务部门需要查询所有服务器的玩家play情况,就得需要进行大量的union操作(如果有的话)。所以像这种情况的话,进行表层级的转换是很有必要的。

其中一种转换是多表合一,以之前的例子来说,就是把多个带区域后缀的play表合成一张表,并且添加一个字段来说明这是哪个服务器的数据。

另外一种情况是,业务部门想要知道一个业务对象的相关数据,但是没有可以和它直接相关的源表,所以需要将多个源表(或者是源表加上数仓中已存在的其它表)join起来进行查询。这种情况的话就可以根据业务方的需求在DWD层建立该业务对象的表,并且在表的设计开发过程中来join它们所需的表的数据。

2.2 数据层级的转换

这个转换主要针对的是每行数据,转换可以包括以下几个方面:

  1. 检测数据合法性,对不合法的数据进行修改或删除
  2. 相关映射的转换,举两个例子。
    (1)源表用数字代表某个字段的属性(比如1代表男性,2代表女性),那么为了方便业务部门的分析,我们可以在这一层直接把这种数字转换为人可以快速理解的文字特征。像刚才这个例子的话就是直接在性别这一字段中把1和2转换为男性和女性。
    (2)数字和数字之间的转换。以之前多区域服务器的例子为例,不同区域的话使用的货币可能是不同的。比如说国区服务器用的是人民币,新加坡区用的是新加坡元,大马区用的是令吉等等。那么在DWD这层的话就可以让多种货币通过汇率转换用单一的货币类型进行表示。

值得注意的是,表层级的转换和数据层级的的转换同样可以作用于DWD层的内部,而不是仅局限于源表与源表,源表与DWD表之间的连接。比如我们在DWD中存储了一张记录所有玩家登录信息的登录表,那么如果我们根据业务方需求想单独建立一个表来存储每个玩家的最早登录时间,那么也是可以读取DWD中的所需表的数据来进行相应的转换,并存储在新建立的这张表中。

2.3 判断数据转换是否正确的方法

这里说一下我自己之前的实战经验吧,可能会有一些不足之处,欢迎读者指出。

我自己的经验是,在写相关的数据处理代码之前,先准备好若干个的测试样例,写好之后对相关的代码进行样例的测试。

通过样例测试之后更新代码,进入测试环境分支做Data Quality Check。主要看两个地方,一个是建立的表是否有显示出我们想要的内容(比如新增字段,数值转换等),另外一个是通过对源表本身以及DWD表中对应源表的数据进行count(*)检测来判断前后数据是否一致,如果两者count结果不同,则说明数据不一致,需要检查是哪里出现了bug。

3. 对数据进行汇总(DWS)

相比于详细的数据,很多时候业务方对一些聚合汇总后的数据更加感兴趣,比如说一个游戏的当日游玩人数,或者说玩家男女比例等。这个时候DWS层就可以大展身手了,其全名叫做Data Warehouse Summary,这一层的数据主要是对之前DWD的表的数据进行汇总。

汇总主要用到的是像在数据库里面的聚合操作(sum,count,avg等等),它可以一个表汇总成一个表,多个表汇总成一个表,甚至于不同数据集市间的表也可以join起来进行汇总放进DWS层中的表里面。这取决于业务方的需求以及数仓本身的实际情况。

4. 应用数据层(ADS)

这一层主要是为特定应用来进行设计的,如果说DWD和DWS层的数据对于特定数据集市中的业务都有通用的话,那么ADS层就是为单一应用进行“深度定制”的版本了,它的数据来源主要是来自DWD和DWS,而且和DWD、DWS的表名侧重于object或用户行为不同的是,ADS的表名可以看出有特定的业务特征。

例如对于游戏相关的数据集市,可能DWD表和DWS表的名字都是针对所有游戏的某一object/行为来设立的:

dwd_play, dwd_purchase, dwd_task, dws_play

但是对于ADS层的话,可能从名字上看就知道这是专门为该游戏的数据设计的“特供版”了。

game1_ads, game2_ads

以上即是我自己理解的数据如何从数据源进到数仓、经过处理后呈现给业务方的整个过程了。数据仓库的话可以解决在数据不断增长的情况下,用数据库查询业务遇到的瓶颈,也方便了业务方(比如说Marketing, BI或者产品经理)的数据查询分析,对于提升整体的生产力是很有帮助的。