这是数据架构解决方案系列博客的第二篇,系列的其他文章:

  1. 概述
  2. 数据架构组成

这篇文章主要介绍在构建数据流水线和数据湖时需要考虑的问题以及要面对的风险和如何管理这些风险。

数据流水线(Data Pipeline)和数据湖是这几个组件里最基础同时也是最重要的部分,因为它包含了从接入外部数据到提供有价值业务数据的完整流程,并且为其他组件提供了数据基础。

由于这个部分实在太重要,所以我要将这个过程拆分成多个方面来剖析。

主要考虑的问题

数据流水线主要包括了数据接入,ETL,数据处理;
数据湖主要包括数据暂存,数据存储;

从建设的角度来看,在构建之初需要从这几个方面来看待这个组件:

  • 怎么让源数据接入;
  • 怎么保证源数据的正常交付;
  • 怎么做数据治理;
  • 本(数据处理)系统怎么保证向外的正常交付;
  • 外部系统会如何访问本系统交付的数据;

接下来就详细看一下这几个方面。

源数据接入

源数据是指那些你构建的数据解决方案处理的最初的数据来源。源数据来源的范围很广:App 应用日志,传感器数据,打点日志,服务器日志,爬虫爬下来的网页,数据库 binlog,等等。通常情况下,源数据团队和数据处理团队是两个分开的团队,因此,你是很难控制源数据的产生方式和数据格式的。在这种场景下,有一个很方便的指标可以衡量源数据接入系统的成熟度:和源数据团队在一起沟通的时间越短,数据接入系统越成熟

主流方法

目前用于源数据收集的主流方法包括:

代码嵌入

提供依赖包,将代码嵌入到源数据生成系统中, 源数据方调用接口发送数据。用这种方式需要考虑一下几个点:

限制实现的语言

考虑用一种语言实现核心功能,而后通过其他调用、链接等方式实现对多种语言的支持。可以参考 Kafka 对多种客户端语言支持的方案

限制依赖包的依赖

提供的依赖包一定要尽可能少的使用其他依赖,原因是

  • 一方面减小引入其他 bug 的可能;
  • 另一方面减小依赖冲突的可能。

让依赖包对源数据方可见

让源数据方可以查看依赖包的源代码,从而尽可能打消他们使用的顾虑。

让依赖包可运维

一定保证这部分代码的可运维性。比如要保证这部分代码能生成可读的运行时日志和指标。

版本管理

由于依赖包运行在本团队不可控的代码中,所以很难保证依赖包的更新。因此,在依赖包的版本管理上,一定要考虑到向后兼容性。另外,在设计之初就要考虑到接口的兼容程度。

代理 Agent

一个独立的进程专门用于收集并发送数据。常见的包括服务器状态 Agent, 打点日志 Agent 等。

专用的接口

比如建一个专用于收集源数据的 RESTful 接口,不同的源数据方统一向这个接口发送数据。

版本管理

代码嵌入一样,版本管理对于接口也是重中之重,在构建之初就应该将版本演化的事情考虑进来。

其他工具

比如 Apache SqoopHDFS put

风险管理

在构建源数据收集系统时,除了要考虑上述这些接入方法,还必须考虑系统带来的各种风险:

版本控制

已经说过很多遍了,版本管理很重要!无论使用哪种接入方法,都应该在设计的时候考虑到向后兼容以及平滑更新的方法。

源数据生成系统故障

不管是什么原因,源数据方总是有可能出故障的。在设计之初,我们需要考虑到这种情况:

  1. 系统故障是不是由本系统提供的依赖包或者接口引起的,如果有这种可能的话,应该如何避免;

  2. 故障发生后,可能引起的后果会有哪些:

    • 比如数据会不会丢失;
    • 整个流水线会不会因此停止运作;
    • 影响的时间和范围有多大;
  3. 如果发生故障是不可避免的,那就应该建立一套完善的监控和报警机制,将故障持续的时间减少到最小;

  4. 做好高可用的准备。当这套流水线出现故障时,如果能做到及时上线另一套流水线,就能将由故障造成的影响降到最低了。

源数据收集系统被错误使用

永远不要相信你的用户会正确使用你的产品。只要你提供了某个功能,就要假设用户错误使用它们的可能。

在数据收集系统的关键部分做好防护措施是永远不会出错的,这些措施包括:

  • 限流

    根据优先级,限制某些源数据方能发送数据的上限。同时做好监控和报警,以防止是因为某些异常或错误造成的。

  • 丢弃数据

    限流有可能导致源数据端数据积压,最终导致整个源数据生成系统崩溃,这是得不偿失的。

    所以,在少量数据可以被丢失的情况(比如不重要的日志手机系统,或者本身就有采样的系统),直接丢弃部分数据是更好的解决方案。

  • 熔断

    丢弃类似,只是这个方案更激进。

    当资源(CPU, 内存,网络带宽)不能及时到位的情况下,为了保证最核心的功能能正常使用的情况下,将某些不重要的服务停掉是最佳方案。

需要注意的是,这些方案一定是所有相关方都知道的情况下才能决定的。

数据交付

在设计数据流水线时,我们需要向下游提供关于交付的数据的质量保证。一般情况下,有下面几个级别的数据交付保证:

  • 至多一次

    对每条接收到的数据的处理至多处理一次,也就是说系统可能会丢失数据。这种保证是比较容易实现的,但是只对那些对数据精确性不十分在意的系统(比如只需要计算某些聚合指标的日志统计系统)可用。

  • 至少一次

    系统可能会重复某条数据,但不会丢失任何一条。

    可以保证所有数据都被处理,但是处理系统可能需要花额外的精力来做去重处理。

    这是业务中比较常见的能提供的交付保证

  • 恰好一次

    这是最好但也最复杂的一种机制。有一些工具声称提供这种机制,比如 Apache Flink,但是为了实现这种保证,需要一些额外的组件配合才行。
    在业务系统中,需要在 维护工具的复杂度重新考虑真正的业务需求 之间再仔细衡量。

    如果真的需要实现恰好一次的交付保证,那就要仔细考量工具的使用和维护了。

在实际业务中,需要根据业务的真实需求和实现的复杂度来选择交付保证。一旦确定了之后,需要和所有相关方沟通和解释,并且将情况写进相关文档中以便大家都清楚。

数据管理和数据治理

一套健壮的数据流水线还必须有以下两个功能:

  1. 数据模型管理;
  2. 对数据的存储和使用做好规范;

数据模型管理

数据模型有多种定义,在这里,我们主要指的是我们设计的数据流水线系统处理的数据模型,简化的描述就是schema和其他元信息(比如归属方,权限等级,更新频率,版本,更新时间等)。

我们的数据流水线系统应该具有很好的自描述性,这意味着使用我们系统的用户(这里主要指数据接入方)能使用简便易用的方式(API, 接口,SDK,Web UI 等)对数据模型做增删查改的操作。

其他的数据模型管理机制还包括:

  • 路由

    哪个数据模型该路由到哪条流水线处理;

  • 采样

    在接入数据时,只取其中的部分数据,适用于只需要采样数据的场景或者测试环境。

  • 权限控制

    谁能接触到哪些数据模型。

  • 元数据捕获

    在处理数据时将元数据管理起来。

高级的处理系统还有这些功能:

  • Transform 逻辑

    用户(数据接入方)可以在注册数据模型时附加上 Transform 的逻辑。

数据规范

数据存入数据湖以后,会有各种随之而来的问题:

  • 访问控制;
  • 数据的错误使用;
  • 脱敏;

因此,在设计数据湖的时候,就应该考虑好如何做数据规范:关键点在于你要对存入数据湖的数据有一个总体上的认知,以及对这些数据的元信息了如指掌。
在后续的文章中会详细的讨论这个问题。

数据延迟和交付时确认

一般情况下,和面向用户的线上实时系统不同,离线的数据处理系统对于数据延迟和交付确认的容忍度更高。

但这也意味着,对于这两个方面,你需要有更明确的定义,确保相关利益方对此都有明确的认识。

延迟

总的来说,对于数据流水线而言,数据延迟指的是从源数据进入流水线到处理后的数据进入暂存系统花的时间。在和相关利益方沟通时,一定要明确这是总的延迟时间。以免对方有不合实际的预期。

由于流水线是由多个子系统组成的,因此每个子系统也有对应的自己的延迟。

交付确认

在数据流水线中,交付确认扮演着帮助各利益方沟通的角色:告诉上下游他们关心的数据目前正在处于什么阶段。

对于构建交付确认机制,有几个可供参考的点:

是否真的需要一个交付确认机制

通常情况下,上游在交付数据后如果能收到一个成功或者失败的确认信息,是最好不过的:对于交付失败的数据,可以按照相应的协议进行处理(比如,可以根据返回的错误码进行重传、忽略或者改变格式再重传)。

但是下游处理确认机制是有代价的,最主要的两个影响:

  1. 增加了下游处理数据的复杂度
  2. 增加了总的延迟

因此,在某些情景下,交付确认可能不是必须的,比如:

  1. 对延时非常敏感的 Streaming 应用;
  2. 可以容忍数据丢失的应用 – 比如有采样的日志收集系统。

因此,在开发之前,需要和相关利益方确认,交付确认是否是必须的(通常情况下,只要你提出了可以做,就很有可能一定要做。。。)。

怎么做交付确认

就像上面说的,如果要在系统中增加交付确认机制,一定会增加子系统的复杂度。如果可以的话,最好考虑使用经过实战考验的开源系统。

风险

如果你提供了数据交付的保证,就一定有风险随之而来。。。

软件的复杂度和硬件的脆弱性决定了你的系统一定会出问题。但是你又提供了数据交付保证,当系统出现问题后,要怎么解决呢?下面提供三种方案:

  • 和相关利益方(特别是你的上游)分享你的设计思路和方法,以便你得到的源数据能有助于确保系统的稳定性。另外,和上游团队共享甚至让他们参与也有助于共同承担风险。
  • 一定要设计一个方便好用的指标和日志系统,最好能够在需要的时候获取到足够的丰富的信息,这对于确保系统的正常运行非常有帮助。
  • 上面两点都是事前的手段。考虑到你的系统肯定会出问题,所以事后手段同样重要。。。如果系统真的出现问题,你的交付没有得到保证,需要有一个上传下达的机制,确保相关利益方都知道数据会延迟交付,以便上下游做相应的处理。(P.S. 这个通知应该包括:1. 简述出现了什么问题;2. 原因是什么;3. 影响的范围;4. 修复的 ETA;)

数据访问模式

在设计和构建数据湖的时候,一定要考虑到数据会如何被访问,因为不同的访问方式可能会完全改变你的设计。

这个考虑可以分为两个部分:

  1. 访问数据的方法
  2. 数据在系统中的保留时间

访问数据的方法

先列出最常见的数据被访问的形式:

  • 批量计算;
  • 流式计算;
  • 随机访问的点查;
  • 搜索查询。

批计算

不管是传统数仓架构,还是数据湖架构,大规模数据的扫描读取是离线数据分析和数据挖掘最常见的场景。

批量计算又可以分成 2 种不同的小场景:

  • 分析向的 SQL 查询

    常见于固化的业务报表,比如日报,周报,月报;对不同指标的同比,环比。
    一般情况下,会在固定的时间运行,计算量和数据读取量都很大,运行时间长。

  • 训练模型

    用于推荐和预测算法的模型训练需要使用大量的数据并进行迭代计算。

批量计算的特点就是需要用到的数据在时间维度跨度很大。在处理这种场景的时候我们需要关注的是流水线处理引擎的计算能力。

流式计算

流式计算和批量计算的差异主要在两个方面:

  1. 两次任务之间的间隔时间

    批量计算的任务间隔可能是小时级,天级,周级,月级;流式计算的间隔可能是毫秒级到分钟级别。

  2. 累积式的工作

    这可能是更根本的差别。因为这是一个任务的输出的根本差异。

  • 分析向的 SQL 查询

    场景和批处理类似,不同点在于,分析的时间粒度更细:秒级到分钟级,并且能提供指标近实时的变化,这有助于决策者能根据指标的变化更快的做出决策,已达到更好的收益。

    由于每次任务的时间间隔小,所以在理想情况下,每次任务处理的数据量也会比批处理的要少,并且每次处理的都是新增的数据,一般不会处理过去的数据。

点查

不同于批处理和流处理,点查的重点在于,我们需要支持在高并发的条件下实时获取想要信息的场景。

为了支持这种场景,我们需要使用和批流都不同的存储模型,后面我们会详细说明。

搜索

搜索是另一个常见的数据湖使用场景。搜索的重点在于:需要支持对大量数据的实时且灵活的搜索查询场景。同样的,这需要另外的存储模型。

风险管理

和之前构建数据流水线不同,维护数据湖的可用性涉及到另一组利益相关者。

前者关心源数据在系统中的处理和暂存,后者关心存储在数据湖中的数据的可用性。

根据前面总结的四种使用场景,对于数据湖可用性的管理风险可以归纳为:

  • 帮助用户明确他们的使用场景对应的正确的数据访问方式;
  • 确保你的数据湖的可用性和可扩展性;
  • 确保你提供的数据湖是他们需要的。这是比较麻烦的问题,如果你的用户只是简单的把你提供的数据拷贝出去再另作处理,说明你并没有完全理解用户的使用需求,需要再另作沟通。

数据流水线和数据湖团队组成

用户和服务支持

这是非常重要的一个角色,通常直接由 Team Leader 担任。主要负责和系统上下游对接沟通,工作包括:

  • 承接来自上下游团队的需求;
  • 为上下游团队寻找可以更好使用系统的方式;
  • 帮助上下游团队顺利使用本系统;
  • 接 Bug;

系统管理员

负责维护本系统的稳定运行以及持续优化本系统性能;这类角色不太关心真实的用户需求,如果能关心,那是最好的。

数据开发/架构

负责数据被正确地接入和及时交付。
这类角色是真正处理和存储数据的,他们熟悉常见的使用场景对应的技术解决方案,并且在维护已有的系统的同时能持续交付新的需求。他们通常是数据处理和数据存储领域的专家。

总结

本篇文章非常粗略地介绍了构建一套数据流水线和数据暂存系统需要考虑的各方面,以及伴随而来的风险管理。