Airbnb的变更数据捕获系统,实现数据突变实时响应
大数据文摘出品
编译:JIN、汤圆、Aileen
干货前的小情景剧:小长假马上就要到啦!Karim决定要好好享受这个假期。他登陆Airbnb为去旧金山的旅行订房间,偶然发现Dany登出的好房子,这可真是太好啦。没过一会儿,Dany收到了房间被预定的提示。他检查了下日程表,确定自己已经把这些日期预留了出来。也注意到那个时段房间的推荐价格上涨了一些。“这个假期一定有很多人来旧金山玩儿啊”,他自言自语道。Dany把这个周其他几天的房间也显示可用。这时在美国另一边的东海岸,Sara正在位于曼哈顿切尔西区的公寓里喝茶,为她去旧金山公司总部的商务旅行做准备。她在Airbnb上找了一会儿房子,并没有看到什么好的,正打算休息的时候,看到Dany的房间出现在了搜索页面上。看了看介绍,Sara觉得这个房间太适合了!她马上给Dany发了消息:“亲爱的Dany,我要去旧金山旅行,你的房间看起来非常棒…”在过去的几年中,适应数据演变已经成为Airbnb许多新兴应用的经常需求。上面的场景是个很好的示例,其中动态定价,房间可用性和预留工作流需要近乎实时地响应系统中不同部分的变化。
从基础架构的角度来看,设计我们的架构以扩展是必要的,因为我们的数据和服务数量不断增长。然而,作为努力面向服务架构的一部分,在微服务之间传播有意义的数据模型突变的有效方式同时保持保留数据所有权边界的解耦架构同样重要。
作为回应,我们创造了SpinalTap;一种可扩展,高性能,可靠,无损的变更数据捕获(CDC)服务,能够检测跨不同数据源类型的低延迟的数据突变,并将它们作为标准化事件传播给下游的消费者。SpinalTap已经成为Airbnb基础架构和衍生数据处理平台中不可或缺一部分,几个关键应用流水线都依赖于此。在这篇文章中,我们将概述它的系统体系结构,使用案例,性能保证以及它是怎样扩展的。
背景介绍
变更数据捕获(CDC)是一种设计模式,可以捕获数据更改并通知参与者,以便他们做出相应的反应。这遵循发布--订阅模型,其中数据集的更改是关键。
系统要求
我们的这些使用案例需要该系统拥有这些高级要求:
- 无损:对数据丢失零容忍,这是针对基于流的会计审计应用程序的需求
- 可扩展性:水平扩展以适应日益增长的负载和数据集群,以避免随着增长重复设计系统
- 性能:变更近乎实时地反馈给订阅的用户(亚秒级)
- 一致性:强制维持排序和时间线一致性以保留特定数据记录的更改顺序
- 容错:具有可配置的冗余度,可以抵御故障
- 可延伸性:可适应不同数据源和接收器类型的通用框架
曾考虑过的解决方案
文献中有用于构建CDC系统的几种解决方案,其中最常用是:
- 轮询:通过跟踪状态属性(例如上次的更新或版本),可以使用时间驱动策略定期检查是否已将任何更改记录在数据存储中
- 触发器:对于支持数据库触发器的存储引擎(例如:MySQL),它们是基于行触发存储过程,可以以无缝方式将更改传播到其他数据表
- 双重写入:在请求期间,数据更改可被传递给应用程序层中的订阅消费者,例如通过发出事件或在写入提交后调度RPC
- 审计跟踪:大多数数据存储解决方案使用事务日志(或更改日志)来记录和跟踪提交到数据库的更改。这通常用于群集节点之间的复制和恢复操作(例如意外服务器关闭或故障转移)
使用数据库更改日志来检测更改有几个好处:与触发器和轮询策略相比,从日志中读取允许采用异步非侵入式方法来捕获更改。它还支持提交时的强一致性和排序保证,并保留事务边界信息,这两点都是双写操作无法实现的。它允许从特定时间点重放事件。考虑到这一点,SpinalTap是基于此设计的。
构架
工作流程概述
在高层次上,SpinalTap被视为一个通用解决方案,它抽象了变更捕获工作流程,足以轻松适应不同的基本架构(数据存储,事件总线,客户服务)。该方案由3个主要组件构成,有助于实现这些特性:
来源
左图:源组件图;右图:源事件工作流程
源表示来自特定数据存储的变更事件流的来源。只要有可访问的更改日志来流式传输事件,就可以使用不同的数据源类型轻松扩展源抽象。从更改日志解析的事件将被过滤,处理并转换为相应的突变。
突变是应用程序层级的,表示数据实体的单个更改(插入,更新或删除)。它包括更改前后的实体值,唯一标识符,事务信息和来自源事件的元数据。源还负责检测数据模式演变,并借用相应的突变传播模式信息。这对于确保在客户端反序列化实体值或从早期状态重放事件时的一致性非常重要。
目的地
左图:目的地组件图;右图:目的地事件工作流程
在处理并转换为标准化事件之后,目标表示突变的接收器。目的地还跟踪最后成功发布的突变,该突变用于将源状态位置导出到检查点。该组件抽象出所使用的传输介质和格式。在Airbnb,Apache Kafka作为活动总线,在基础设施中广泛使用。Apache Thrift被用作数据格式,定义标准化的变异模式并提供跨语言支持(Ruby和Java)。
通过对系统进行基准测试确定了主要瓶颈是变异发布。考虑到系统设置有利于延迟的强一致性,这种情,我们采用了一些优化措施:
缓冲目标:为了避免在等待发布突变时阻塞源,我们使用内存中有界队列来缓冲从源发出的事件(消费者-生产者模式)。当目标发布突变时,源将向缓冲区添加事件。一旦可用,目标将从缓冲区中提取事件并处理下一批突变。
目标池:对于在传入事件速率中显示不稳定尖峰行为的源,内存缓冲区偶尔会饱和,从而导致性能的间歇性降级。为了减轻系统的不规则负载模式,我们将源事件的应用程序级分区应用于由线程池管理的一组可配置的缓冲目标。事件多路复用到线程目标,同时保留排序模式。这使我们能够实现高吞吐量,同时不会影响延迟或一致性。
管道
左:管道组件图;右:管道管理者协调管道生命周期
管道协调给定源和目标之间的工作流程。它代表了并行的基本单位。它还负责定期检查源状态,并管理事件流的生命周期。如果出现错误行为,管道将执行正常关闭并启动故障恢复过程。根据最后的状态检查点,采用保持活动机制来确保在发生故障时重新启动源流。这允许在保持数据完整性的同时自动修复间歇性故障。管道管理器负责在给定的集群节点上创建,更新和删除管道以及管道生命周期(启动/停止)。它还确保在运行时相应地传播对管道配置的任何更改。
集群管理
左图:集群资源管理;中图:节点故障恢复;右:使用实例标记进行隔离
为了实现某些理想的体系结构方面-例如可扩展性,容错性和隔离性-我们采用了一个集群管理框架(Apache Helix)来协调跨计算资源的流处理分布。这有助于我们实现确定性负载平衡,并通过集群中源处理器的自动重新分配实现水平扩展。
为了通过可配置的容错来提升高可用性,每个源都被指定为集群节点的某个子集来处理事件流。我们使用Leader-Standby状态模型,其中只有一个节点在任何给定点流式传输来自源的事件,而子群集中的其余节点处于待命状态。如果领导者关闭,那么其中一个备用节点将担任领导。
为了支持源类型处理之间的隔离,群集中的每个节点都标记有可以委派给它的源类型。流处理跨群集节点分布,同时保持此隔离标准。
为了解决来自网络分区的不一致性,特别是在多个节点承担来自特定源(分裂脑)的流式传输的领导的情况下,我们维持每个源的全局领导者时期,其在领导者转换时原子递增。随着每个突变传播领导者时代,并且通过忽略具有比观察到的最新时期更小的时期的事件,通过客户端过滤来减轻不一致性。
保证
某些保证对于系统维护是必不可少的,以适应所有下游用例。
数据完整性:系统保持至少一次交付保证,其中对底层数据存储的任何更改最终都会传播到客户端。这表明更改日志中不存在任何事件永久丢失,并且是在SLA指定的时间窗口内传递。我们确保不会发生数据损坏,并且突变内容保持源事件的奇偶校验。
事件排序:根据规定的分区方案强制执行排序。我们维持每个数据记录(行)的排序,即给定数据库表中特定行的所有更改都将按顺序接收。
时间线一致性:在时间线上保持一致要求在给定时间范围内按时间顺序接收变化,给定变异集的两个序列在发送过程中不可交错。裂脑情景可能会影响这种保证,但如前所述,它可以通过纪元围栏得到缓解。
Validation
SpinalTap验证框架
证明SpinalTap在设计方面的保证没有出现事故是不够的,我们需要一种更实用的数据驱动方法来验证我们的假设。为了解决这个问题,我们开发了一个连续的在线端到端验证管道,负责验证消费者在真实来源上收到的突变,并断言在预生产和生产环境中都没有检测到错误的行为。
为了实现可靠的验证工作流,消耗的突变被分区并存储在本地磁盘上,同样的分区方案应用于源事件。一旦接收到对应于分区事件的所有突变,就通过声明前面描述的保证的测试列表来验证分区文件与原始源分区。特别是对于MySQL,binlog文件被认为是一个干净的分区边界。
我们在沙箱环境中设置了离线集成测试,以防止将回归部署到生产环境中。验证器也通过为每个源流消费实时事件在线生产。这有助于检测未在我们的测试管道中捕获的任何漏洞,并通过将源状态回滚到先前的检查点来自动修复。这强制了在解决任何问题之前不会继续流式传输,并最终保证一致性和数据完整性。
模型突变
左图:同步与异步应用程序工作流程; 右图:将模型突变传播给下游消费者
消费者服务直接攻击给定服务数据库的SpinalTap事件的缺点是数据模式泄露,从而产生不必要的耦合。此外,用于处理封装在拥有服务中的数据突变的域逻辑也需要复制到消费者服务。
为了缓解这种情况,我们在SpinalTap之上构建了一个模型流媒体库,它允许服务从服务的数据存储中侦听事件,将它们转换为域模型突变,并在消息总线中重新注入它们。这有效地允许数据模型突变成为服务接口的一部分,并且请求/响应周期与异步数据摄取和事件传播的隔离。它还有助于解耦域依赖关系,促进事件驱动的通信,并通过隔离同步和异步应用程序工作流来提供服务的性能和容错改进。
使用案例
SpinalTap用于基础架构中的许多用例,其中最突出的是:
高速缓存失效:CDC系统的常见应用是高速缓存失效,其中后备数据存储的更改由高速缓存无效器服务或进程检测,从而驱逐(或更新)相应的高速缓存条目。优先采用异步方法允许我们将缓存机制与请求路径以及为生产流量提供服务的应用程序代码分离。这种模式在服务中广泛使用,以保持真实数据源存储和分布式缓存集群(例如Memcached,Redis)之间的一致性。
搜索索引:Airbnb上有多个搜索产品使用实时索引(例如,评论搜索,收件箱搜索,支持票搜索)。SpinalTap被证明非常适合构建从数据存储到搜索后端(例如ElasticSearch)的索引管道,特别是它的有序和至少一次传递语义。服务可以轻松地使用相应主题的事件并转换突变以更新索引,这有助于确保搜索新鲜度和低延迟。
离线处理:SpinalTap还用于以流方式将在线数据存储导出到离线大数据处理系统(例如Hive,Airstream),这需要高吞吐量,低延迟和适当的可扩展性。该系统在历史上也用于数据库快照管道,连续构建在线数据库的备份并将它们存储在HBase中。这大大减少了每日备份的时间,并允许以更精细的时间粒度(例如:每小时)拍摄快照。
信令:用于在分布式体系结构中传播数据变化的另一个经常性用例是作为信令机制,其中依赖服务可以近实时地订阅并响应来自另一服务的数据变化。例如,可用性服务将通过订阅预订服务中的更改来阻止列表的日期,以便在预订时收到通知。风险,安全,支付,搜索和定价工作流程是我们的生态系统中采用此模式的几个示例。
结语
在过去几年中SpinalTap已成为我们基础架构不可或缺的一部分,并为许多核心工作流程提供了动力。如果是寻求可以与您的基础架构轻松集成的可靠通用框架,它很实用。在Airbnb,SpinalTap用于传播来自MySQL,DynamoDB和我们内部存储解决方案的数据突变。Kafka是首选事件总线,但系统的可扩展性也允许我们考虑其他媒介(例如:Kinesis)。