“数据智能” (Data Intelligence) 有一个必须且基础的环节,就是数据仓库的建设,同时,数据仓库也是公司数据发展到一定规模后必然会提供的一种基础服务。从智能商业的角度来讲,数据的结果代表了用户的反馈,获取结果的及时性就显得尤为重要,快速的获取数据反馈能够帮助公司更快的做出决策,更好的进行产品迭代,实时数仓在这一过程中起到了不可替代的作用。
传统意义上的数据仓库主要处理T+1数据(即:今天产生的数据分析结果明天才能看到)
随着互联网的发展,越来越多的业务指标需要实时查看,以便于更好的进行业务分析,尤其在举行活动的时候,能够更好的把握活动的各项指标趋势,从而更好的调整策略,达到活动的目标。
随着数据时效性在企业运营中的重要性日益凸现,例如,实时推荐、精准营销、广告投放效果、实时物流等。数据的实时处理能力成为企业提升竞争力的一大因素,最初阶段企业主要采用来一个需求,编写一个实时计算任务的方式来处理实时数据,随着需求的增多,计算任务也相应增多,并且不同任务的开发人员不同,导致开发风格差异化,该阶段的实时数据处理缺乏统一的规划,代码风格差异化严重,在维护成本和开发效率上有很大障碍。
为避免上述问题,人们参照数据仓库的概念和模型来重新规划和设计实时数据处理,在此基础上产生了实时数据仓库(实时数仓)。
离线实时数仓对比图
与flink实时处理最密不可分的就是kafka,其次flink实时数据存储还有mysql、hbase、es、Druid等还有部分公司内部开发的数据存储。作为刚开始的公司构建实时数仓,选用市面上开源的数据库就足够可以使用了。
1、实时数仓各层级的技术选型
实时数仓流转图
综上,实时数仓主要解决数据时效性问题,结合机器学习框架可以处理实时推荐、实时获取广告投放效果等智能化业务场景。实时数仓的建设应早日提上日程,未来企业对数据时效性的要求会越来越高,实时数仓会很好的解决该问题。
本案例参考自菜鸟仓配团队的分享,涉及全局设计、数据模型、数据保障等几个方面。
注:特别感谢缘桥同学的无私分享。
5.1 整体设计
基于业务系统的数据,数据模型采用中间层的设计理念,建设仓配实时数仓;
计算引擎,选择更易用、性能表现更佳的实时计算作为主要的计算引擎;
数据服务,选择天工数据服务中间件,避免直连数据库,且基于天工可以做到主备链路灵活配置秒级切换;
数据应用,围绕大促全链路,从活动计划、活动备货、活动直播、活动售后、活动复盘五个维度,建设仓配大促数据体系。
5.2 数据模型
不管是从计算成本,还是从易用性,还是从复用性,还是从一致性,我们都必须避免烟囱式的开发模式,而是以中间层的方式建设仓配实时数仓。与离线中间层基本一致,我们将实时中间层分为两层。
第一层DWD公共实时明细层
实时计算订阅业务数据消息队列,然后通过数据清洗、多数据源join、流式数据与离线维度信息等的组合,将一些相同粒度的业务系统、维表中的维度属性全部关联到一起,增加数据易用性和复用性,得到最终的实时明细数据。这部分数据有两个分支,一部分直接落地到ADS,供实时明细查询使用,一部分再发送到消息队列中,供下层计算使用;
第二层DWS公共实时汇总层
以数据域+业务域的理念建设公共汇总层,与离线数仓不同的是,这里汇总层分为轻度汇总层和高度汇总层,并同时产出,轻度汇总层写入ADS,用于前端产品复杂的olap查询场景,满足自助分析和产出报表的需求;高度汇总层写入Hbase,用于前端比较简单的kv查询场景,提升查询性能,比如实时大屏等;
注:
1.ADS是一款提供OLAP分析服务的引擎。开源提供类似功能的有,ElasticSearch、Kylin、Druid等;
2.案例中选择把数据写入到Hbase供KV查询,也可根据情况选择其他引擎,比如数据量不多,查询压力也不大的话,可以用mysql
3.因主题建模与业务关系较大,这里不做描述
5.3 数据保障
集团每年都有双十一等大促,大促期间流量与数据量都会暴增。
实时系统要保证实时性,相对离线系统对数据量要更敏感,对稳定性要求更高。
所以为了应对这种场景,还需要在这种场景下做两种准备:
在看过前面的叙述与菜鸟案例之后,我们看一下实时数仓与离线数仓在几方面的对比:
首先,从架构上,实时数仓与离线数仓有比较明显的区别,实时数仓以Kappa架构为主,而离线数仓以传统大数据架构为主。Lambda架构可以认为是两者的中间态。
其次,从建设方法上,实时数仓和离线数仓基本还是沿用传统的数仓主题建模理论,产出事实宽表。另外实时数仓中实时流数据的join有隐藏时间语义,在建设中需注意。
最后,从数据保障看,实时数仓因为要保证实时性,所以对数据量的变化较为敏感。在大促等场景下需要提前做好压测和主备保障工作,这是与离线数据的一个较为明显的区别。
1.0 版本的实时数仓主要是对流量数据做实时 ETL,并不计算实时指标,也未建立起实时数仓体系,实时场景比较单一,对实时数据流的处理主要是为了提升数据平台的服务能力。实时数据的处理向上依赖数据的收集,向下关系到数据的查询和可视化,下图是实时数仓 1.0 版本的整体数据架构图。
第一部分是数据采集,由三端 SDK 采集数据并通过 Log Collector Server 发送到 Kafka。(客户端埋点流程、模型和平台技术)
第二部分是数据 ETL,主要完成对原始数据的清洗和加工并分实时和离线导入 Druid(Druid 数据库)。
第三部分是数据可视化,由 Druid 负责计算指标并通过 Web Server 配合前端完成数据可视化。
Lambda 架构有高容错、低延时和可扩展的特点,为了实现这一设计,我们将 ETL 工作分为两部分:Streaming ETL 和 Batch ETL。
这一部分我会介绍实时计算框架的选择、数据正确性的保证、以及 Streaming 中一些通用的 ETL 逻辑,最后还会介绍 Spark Streaming 在实时 ETL 中的稳定性实践。
在 2016 年年初,业界用的比较多的实时计算框架有 Storm 和 Spark Streaming。Storm 是纯流式框架,Spark Streaming 用 Micro Batch 模拟流式计算,前者比后者更实时,后者比前者吞吐量大且生态系统更完善,考虑到知乎的日志量以及初期对实时性的要求,我们选择了 Spark Streaming 作为实时数据的处理框架。
Spark Streaming 的端到端 Exactly-once 需要下游支持幂等、上游支持流量重放,这里我们在 Spark Streaming 这一层做到了 At-least-once,正常情况下数据不重不少,但在程序重启时可能会重发部分数据,为了实现全局的 Exactly-once,我们在下游做了去重逻辑,关于如何去重后面我会讲到。
ETL 逻辑和埋点的数据结构息息相关,我们所有的埋点共用同一套 Proto Buffer Schema,大致如下所示。
|
BaseInfo:日志中最基本的信息,包括用户信息、客户端信息、时间信息、网络信息等日志发送时的必要信息。DetailInfo:日志中的视图信息,包括当前视图、上一个视图等用于定位用户所在位置的信息。ExtraInfo:日志中与特定业务相关的额外信息。
针对上述三种信息我们将 ETL 逻辑分为通用和非通用两类,通用逻辑和各个业务相关,主要应用于 Base 和 Detail 信息,非通用逻辑则是由需求方针对某次需求提出,主要应用于 Extra 信息。这里我们列举 3 个通用逻辑进行介绍,这包括:动态配置 Streaming、UTM 参数解析、新老用户识别。
由于 Streaming 任务需要 7 * 24 小时运行,但有些业务逻辑,比如:存在一个元数据信息中心,当这个元数据发生变化时,需要将这种变化映射到数据流上方便下游使用数据,这种变化可能需要停止 Streaming 任务以更新业务逻辑,但元数据变化的频率非常高,且在元数据变化后如何及时通知程序的维护者也很难。动态配置 Streaming 为我们提供了一个解决方案,该方案如下图所示。
我们可以把经常变化的元数据作为 Streaming Broadcast 变量,该变量扮演的角色类似于只读缓存,同时针对该变量可设置 TTL,缓存过期后 Executor 节点会重新向 Driver 请求最新的变量。通过这种机制可以非常自然的将元数据的变化映射到数据流上,无需重启任务也无需通知程序的维护者。
UTM 的全称是 Urchin Tracking Module,是用于追踪网站流量来源的利器,关于 UTM 背景知识介绍可以参考网上其他内容,这里不再赘述。下图是我们解析 UTM 信息的完整逻辑。
流量数据通过 UTM 参数解析后,我们可以很容易满足以下需求
对于互联网公司而言,增长是一个永恒的话题,实时拿到新增用户量,对于增长运营十分重要。例如:一次投放 n 个渠道,如果能拿到每个渠道的实时新增用户数,就可以快速判断出那些渠道更有价值。我们用下图来表达 Streaming ETL 中是如何识别新老用户的。
判断一个用户是不是新用户,最简单的办法就是维护一个历史用户池,对每条日志判断该用户是否存在于用户池中。 由于日志量巨大,为了不影响 Streaming 任务的处理速度,我们设计了两层缓存:Thread Local Cache 和 Redis Cache,同时用 HBase 做持久化存储以保存历史用户。访问速度:本地内存 > 远端内存 > 远端磁盘,对于我们这个任务来说,只有 1% 左右的请求会打到 HBase,日志高峰期 26w/s,完全不会影响任务的实时性。当然本地缓存 LruCache 的容量大小和 Redis 的性能也是影响实时性的两个因素。
Streaming ETL 除了上述几个通用场景外,还有一些其他逻辑,这些逻辑的存在有的是为了满足下游更方便的使用数据的需求,有的是对某些错误埋点的修复,总之 Streaming ETL 在整个实时数仓中处于指标计算的上游,有着不可替代的作用。
Spark Streaming 消费 Kafka 数据推荐使用 Direct 模式。我们早期使用的是 High Level 或者叫 Receiver 模式并使用了 checkpoint 功能,这种方式在更新程序逻辑时需要删除 checkpoint 否则新的程序逻辑就无法生效。另外,由于使用了 checkpoint 功能,Streaming 任务会保持和 Hdfs 通信,可能会因为 NameNode 的抖动导致 Streaming 任务抖动。因此,推荐使用 Direct 模式,关于这种模式和 Receiver 模式的详细对比,可以参考官方文档。
保证 Spark Streaming 任务的资源稳定。以 Yarn 为例,运行 Streaming 任务的队列能够分配到的最小资源小于了任务所需要的资源,任务会出现频繁丢失 Executor 的情况,这会导致 Streaming 任务变慢,因为丢失的 Executor 所对应的数据需要重新计算,同时还需要重新分配 Executor。
Spark Streaming 消费 Kafka 时需要做数据流限速。默认情况下 Spark Streaming 以尽可能大的速度读取消息队列,当 Streaming 任务挂了很久之后再次被启动时,由于拉取的数据量过大可能会导致上游的 Kafka 集群 IO 被打爆进而出现 Kafka 集群长时间阻塞。可以使用 Streaming Conf 参数做限速,限定每秒拉取的最大速度。
Spark Streaming 任务失败后需要自动拉起。长时间运行发现,Spark Streaming 并不能 7 * 24h 稳定运行,我们用 Supervisor 管理 Driver 进程,当任务挂掉后 Driver 进程将不复存在,此时 Supervisor 将重新拉起 Streaming 任务。
接下来要介绍的是 Lambda 架构的第二个部分:Batch ETL,此部分我们需要解决数据落地、离线 ETL、数据批量导入 Druid 等问题。针对数据落地我们自研了 map reduce 任务 Batch Loader,针对数据修复我们自研了离线任务 Repair ETL,离线修复逻辑和实时逻辑共用一套 ETL Lib,针对批量导入 ProtoParquet 数据到 Druid,我们扩展了 Druid 的导入插件。
数据架构图中有两个 Kafka,第一个 Kafka 存放的是原始日志,第二个 Kafka 存放的是实时 ETL 后的日志,我们将两个 Kafka 的数据全部落地,这样做的目的是为了保证数据链路的稳定性。因为实时 ETL 中有大量的业务逻辑,未知需求的逻辑也许会给整个流量数据带来安全隐患,而上游的 Log Collect Server 不存在任何业务逻辑只负责收发日志,相比之下第一个 Kafka 的数据要安全和稳定的多。Repair ETL 并不是经常启用,只有当实时 ETL 丢失数据或者出现逻辑错误时,才会启用该程序用于修复日志。
前面已经介绍过,我们所有的埋点共用同一套 Proto Buffer Schema,数据传输格式全部为二进制。我们自研了落地 Kafka PB 数据到 Hdfs 的 Map Reduce 任务 BatchLoader,该任务除了落地数据外,还负责对数据去重。在 Streaming ETL 阶段我们做到了 At-least-once,通过此处的 BatchLoader 去重我们实现了全局 Exactly-once。BatchLoader 除了支持落地数据、对数据去重外,还支持多目录分区(p_date/p_hour/p_plaform/p_logtype)、数据回放、自依赖管理(早期没有统一的调度器)等。截止到目前,BatchLoader 落地了 40+ 的 Kakfa Topic 数据。
采用 Tranquility 实时导入 Druid,这种方式强制需要一个时间窗口,当上游数据延迟超过窗值后会丢弃窗口之外的数据,这种情况会导致实时报表出现指标错误。为了修复这种错误,我们通过 Druid 发起一个离线 Map Reduce 任务定期重导上一个时间段的数据。通过这里的 Batch 导入和前面的实时导入,实现了实时数仓的 Lambda 架构。
到目前为止我们已经介绍完 Lambda 架构实时数仓的几个模块,1.0 版本的实时数仓有以下几个不足
随着数据量的暴涨,Druid 中的流量数据源经常查询超时同时各业务消费实时数据的需求也开始增多,如果继续沿用实时数仓 1.0 架构,需要付出大量的额外成本。于是,在实时数仓 1.0 的基础上,我们建立起了实时数仓 2.0,梳理出了新的架构设计并开始着手建立实时数仓体系,新的架构如下图所示。
实时数仓 1.0 我们只对流量数据做 ETL 处理,在 2.0 版本中我们加入了对业务库的变更日志 Binlog 的处理,Binlog 日志在原始层为库级别或者 Mysql 实例级别,即:一个库或者实例的变更日志存放在同一个 Kafka Topic 中。同时随着公司业务的发展不断有新 App 产生,在原始层不仅采集「知乎」日志,像知乎极速版以及内部孵化项目的埋点数据也需要采集,不同 App 的埋点数据仍然使用同一套 PB Schema。
明细层是我们的 ETL 层,这一层数据是由原始层经过 Streaming ETL 后得到。其中对 Binlog 日志的处理主要是完成库或者实例日志到表日志的拆分,对流量日志主要是做一些通用 ETL 处理,由于我们使用的是同一套 PB 结构,对不同 App 数据处理的逻辑代码可以完全复用,这大大降低了我们的开发成本。
明细汇总层是由明细层通过 ETL 得到,主要以宽表形式存在。
业务明细汇总是由业务事实明细表和维度表 Join 得到,
流量明细汇总是由流量日志按业务线拆分和流量维度 Join 得到。
流量按业务拆分后可以满足各业务实时消费的需求,我们在流量拆分这一块做到了自动化,下图演示了流量数据自动切分的过程。
Streaming Proxy 是流量分发模块,它消费上游 ETL 后的全量数据并定期读取埋点元信息,通过将流量数据与元信息数据进行「Join」完成按业务进行流量拆分的逻辑,同时也会对切分后的流量按业务做 ETL 处理。 只要埋点元信息中新增一个埋点,那么这个埋点对应的数据就会自动切分到该业务的 Kafka 中,最终业务 Kafka 中的数据是独属于当前业务的且已经被通用 ETL 和业务 ETL 处理过,这大大降低了各个业务使用数据的成本。
指标汇总层是由明细层或者明细汇总层通过聚合计算得到,这一层产出了绝大部分的实时数仓指标,这也是与实时数仓 1.0 最大的区别。知乎是一个生产内容的平台,
对业务指标的汇总我们可以从内容角度和用户角度进行汇总,从内容角度我们可以实时统计内容(内容可以是答案、问题、文章、视频、想法)的被点赞数、被关注数、被收藏数等指标,从用户角度我可以实时统计用户的粉丝数、回答数、提问数等指标。
对流量指标的汇总我们分为各业务指标汇总和全局指标汇总。对各业务指标汇总,我们可以实时统计首页、搜索、视频、想法等业务的卡片曝光数、卡片点击数、CTR 等,对全局指标汇总我们主要以实时会话为主,实时统计一个会话内的 PV 数、卡片曝光数、点击数、浏览深度、会话时长等指标。
不同于明细层和明细汇总层,指标汇总层需要将实时计算好的指标存储起来以供应用层使用。
我们根据不同的场景选用了 HBase 和 Redis 作为实时指标的存储引擎。
Redis 的场景主要是满足带 Update 操作且 OPS 较高的需求,例如:实时统计全站所有内容(问题、答案、文章等)的累计 PV 数,由于浏览内容产生大量的 PV 日志,可能高达几万或者几十万每秒,需要对每一条内容的 PV 进行实时累加,这种场景下选用 Redis 更为合适。
HBase 的场景主要是满足高频 Append 操作、低频随机读取且指标列较多的需求,例如:每分钟统计一次所有内容的被点赞数、被关注数、被收藏数等指标,将每分钟聚合后的结果行 Append 到 HBase 并不会带来性能和存储量的问题,但这种情况下 Redis 在存储量上可能会出现瓶颈。
指标口径管理依赖指标系统,指标可视化依赖可视化系统,我们通过下图的需求开发过程来讲解如何将三者联系起来。
应用层主要是使用汇总层数据以满足业务需求。
应用层主要分三块:
1. 通过直接读取指标汇总数据做实时可视化,满足固化的实时报表需求,这部分由实时大盘服务承担;
2. 推荐算法等业务直接消费明细汇总数据做实时推荐;
3. 通过 Tranquility 程序实时摄入明细汇总数据到 Druid,满足实时多维即席分析需求。
相比实时数仓 1.0 以 Spark Streaming 作为主要实现技术,在实时数仓 2.0 中,我们将 Flink 作为指标汇总层的主要计算框架。Flink 相比 Spark Streaming 有更明显的优势,主要体现在:低延迟、Exactly-once 语义支持、Streaming SQL 支持、状态管理、丰富的时间类型和窗口计算、CEP 支持等。
我们在实时数仓 2.0 中主要以 Flink 的 Streaming SQL 作为实现方案。使用 Streaming SQL 有以下优点:易于平台化、开发效率高、维度成本低等。目前 Streaming SQL 使用起来也有一些缺陷:1. 语法和 Hive SQL 有一定区别,初使用时需要适应;2.UDF 不如 Hive 丰富,写 UDF 的频率高于 Hive。
从实时数仓 1.0 到 2.0,不管是数据架构还是技术方案,我们在深度和广度上都有了更多的积累。随着公司业务的快速发展以及新技术的诞生,实时数仓也会不断的迭代优化。短期可预见的我们会从以下方面进一步提升实时数仓的服务能力。