设计模式在软件工程领域已经非常成熟,但在数据工程领域则是近年来才逐渐受到关注。因此,在这里我需要向你简单介绍一下设计模式,并解释它在数据工程中的含义。
什么是设计模式?
你可能会惊讶于自己日常生活中有多少次依赖了“模式”。我们来看一个做饭的例子,具体来说,是我最喜欢的甜点之一 —— 焦糖布丁(flan);如果你喜欢奶香浓郁的甜点,又没吃过布丁,那我强烈推荐试一试!
当你想做布丁时,你需要准备好所有食材,并按照一系列步骤来完成制作。最终,你就能得到一道美味的甜点。
为什么我要用这个做饭的例子来引入一本讲设计模式的技术书?因为“食谱”恰好是设计模式最好的类比:它是一个预定义、可定制的解决问题的模板。那么这个布丁的例子是如何体现这种定义的呢?
食材和制作步骤构成了预定义模板。它们为你提供指引,但又具有可定制性,比如你可以选择用红糖代替白糖。
这个模板可以使用一次,也可以重复多次。布丁可以是你下午茶时与家人分享的甜点,也可以是你用来出售谋生的产品。这体现了设计模式的“上下文”概念。设计模式始终是为了解决某个具体问题,比如:如何与朋友分享一份美味的甜点?又或是,如何将这款甜点制作成产品来盈利?
你可以选择只做一次,也可以因为喜欢它而重复制作多次。每次制作时,你不会重新发明方法,而是会继续使用之前验证过的成功食谱。这就是“可复用性”。
但你也要意识到,做布丁和吃布丁会对生活和健康有一些影响。如果你天天做、天天吃,那可能会减少锻炼时间,久而久之会对健康产生影响。这就是“设计模式的代价”。
最后,食谱能为你节省时间,因为它已经被无数人测试过了。此外,食谱还提供了一套通用的语言,能让你与他人交流更顺畅。比如,直接说“布丁”的食谱会比说“焦糖奶蛋羹”的食谱更容易被人理解。
那这和数据工程有什么关系?
再举一个例子:你需要处理一个半结构化数据集,该数据是由一个持续运行的作业产生的。但有时候,会出现格式完全错误的数据记录,这些记录会抛出异常并导致整个作业中断。但你并不希望整个作业因为一个格式错误的数据而失败——这就是问题的上下文。
为了解决这个处理问题,你会在数据处理逻辑中应用一组最佳实践,比如将容易出错的转换操作包裹在 try-catch 块中,将坏数据记录单独写入另一个地方供分析使用。这就是“预定义模板”。你可以根据具体需求对其进行调整,比如你可以选择不将这些坏数据写入数据库,而是仅统计它们出现的次数。
事实证明,这种处理错误记录而不影响整个处理流程的方法是有名字的,叫做“死信处理(Dead-Lettering)”。当你以后在其他情境下,比如在数据仓库中直接执行 ELT 过程时遇到类似问题,你也可以应用同样的逻辑。这就是“可复用性”。死信处理是第 3 章中介绍的错误管理模式之一。
当然,你也不应该盲目使用死信处理模式。就像每天吃布丁一样,模式的实施会带来一些你需要权衡的代价。比如,它会引入额外的逻辑,使代码变得更复杂,你必须做好接受这种复杂性的准备。
总结来说,数据工程设计模式提供了某类问题的一整套系统性解决方案,它不仅可以节省时间,还能为你和同事或刚认识的数据工程师之间的交流提供一套共通的语言。
还有更多设计模式?
如果你是做软件开发的,那么你一定听说过“四人帮”的设计模式(Gang of Four’s Design Patterns),甚至可能把它们视为“整洁代码”的基石之一。那你现在可能会问:这些设计模式还不够应对数据工程项目吗?很遗憾,不够。
软件设计模式确实是一套优秀的“食谱”,可以帮助你构建易于维护的代码库。由于这些模式是表达特定概念的标准化方式,项目中的新成员可以快速理解这些代码的意图。
举个例子,单例模式(Singleton) 是一种避免重复分配不必要对象的常见模式。如果一个新同事熟悉这种设计模式,那么他可以立刻识别出代码中这个结构的存在,并明白其作用。
可维护的代码在数据工程中当然也很重要,但它远远不够。除了软件层面的关注点,数据工程还需要处理数据层面的问题,比如前面提到的失败处理、数据回填、幂等性以及数据正确性等方面。
常见的数据工程设计模式
上一节中提到的“错误记录处理”只是数据工程设计模式的一个例子。本书中的其余模式则是从数据的采集开始,一直到数据输出、监控和告警的整个数据流过程。因此,你将在本书中看到以下内容:
第 2 章:《数据采集设计模式》
将数据引入系统始终是你架构中的第一步。毕竟,没有数据,你就无事可做。
第 3 章:《错误管理设计模式》
错误就像数据本身一样,是数据工程中不可避免的一部分。错误可能是因为编码问题,也可能是来自数据提供方的失误,比如提供的数据集中缺失了必要字段。
第 4 章:《幂等性设计模式》
错误发生后,自然会触发“重试”,这些重试可以是自动的,也可以是手动触发的。在自动重试的场景中,部分或整个数据处理流程可能会被重新执行,这通常意味着之前已经保存的数据会被尝试再次写入。如果是手动触发,那么你可能会执行所谓的“数据回填(backfill)”,去重新运行历史的数据任务。幂等性确保了这些重试不会导致重复或错误的结果。
第 5 章:《数据价值设计模式》
在能处理错误与重试之后,就可以专注于生成对业务用户有价值的数据集了。这可能需要你对数据进行聚合、清洗或与其他数据源进行融合。这些加工操作能够为最终用户带来额外价值。
第 6 章:《数据流设计模式》
当你已经能够将加工后的数据直接暴露给用户之后,下一步就是将数据生成流程整合进整个数据流中。数据流定义了你的数据价值生成流程与组织中其他数据组件之间的关系和交互方式。
第 7 章:《数据安全设计模式》
在前六章学习了如何采集、加工和展示数据之后,你还需要保证这些数据是安全存储的,并且符合数据隐私合规要求。
第 8 章:《数据存储设计模式》
数据安全固然重要,但如何通过优化存储方式来降低处理延迟、提升用户体验也是关键所在。本章将介绍如何合理使用数据存储技术来提升整体性能。
第 9 章:《数据质量设计模式》
坏消息是:即使你已经实现了上述所有步骤,如果没有处理好数据质量问题,甚至都没有意识到问题的存在,你的数据依然可能对用户毫无价值。
第 10 章:《数据可观测性设计模式》
这是你数据工程旅程的最后一步,在这里你将定义一系列用于监控的数据指标。这些指标和前一章的“数据质量设计模式”相辅相成,在系统出错或即将出错时发出告警,从而确保你提供的数据是值得信赖的。
本书使用的案例研究
本书中的设计模式并不局限于某一个特定的业务领域。然而,如果完全脱离业务场景来理解这些模式,尤其是对于经验较少的读者来说,将会非常困难。因此,每个设计模式都会结合一个案例项目来进行介绍,这个案例项目是一个博客数据分析平台。
我们的项目遵循通用的数据工程实践,并按照图 1-1 所示的分层架构进行划分。
在线与离线数据采集组件
“在线”部分指的是用户在我们平台上与博客交互时所产生的数据;而“离线”部分(图中标注为“数据提供方”)则是指静态的外部或内部数据集,例如参考数据集,这些数据不像访问事件那样频繁生成(例如可能每小时生成一次)。
实时处理层
这一层用于处理来自流处理代理(如 Kafka)的事件数据流。该层的作业通常分为两类:
- 第一类是面向业务的作业,用于为利益相关方生成实时数据,例如实时会话聚合。
- 第二类是技术支持型作业,通常为其他业务场景提供技术能力支撑,例如与静态存储的数据进行同步,以便进行临时查询。
数据组织层
这一层采用目前常见的数据集结构 —— 基于“奖牌架构(Medallion Architecture) ”的分层体系。数据集可处于以下三种层级之一:
- 铜牌层(Bronze) :保存的是原始格式的数据,未经加工,通常伴随着明显的数据质量问题;
- 银牌层(Silver) :负责对数据进行清洗和增强;
- 金牌层(Gold) :以最终用户期望的格式对外提供数据,例如数据集市(Data Marts)或参考数据集。
为什么这三层数据存储方式在本书中很重要?因为它们恰好代表了不同的数据成熟度水平,就像本书中要介绍的各种设计模式一样。那些影响业务价值的设计模式,通常最终会在金牌层暴露数据,而其他模式则可能止步于铜牌层或银牌层。因此,在每个模式的问题描述部分,我们会引用这些层级,帮助你更好地理解所面临的挑战。
关于架构图的说明
图中的架构刻意没有展示任何技术实现细节。因为关注技术容易让你偏离本书的主题 —— 设计模式的通用解决方案。但这并不意味着本书不会包含技术细节。**相反!**每一个模式都有专门的“示例”部分,会向你展示该模式在不同技术场景下的实际实现方式。
总结
现在你应该明白了,不仅布丁是一道美味的奶香甜点,它的“食谱”更是对你即将学习的数据工程设计模式的一种极佳类比。在接下来的九章中,我们将一起踏上一段充满技术干货的学习之旅。带上一杯咖啡或茶,再配上一份你最爱的甜点(为什么不是布丁呢?),让这段旅程更加轻松而精彩吧!