拍摄纸牌屋的Netflix为何要迁移数据库入云?
Netflix CDE(云数据库工程)团队最近对这一最重要的数据库子系统进行了迁移。本次项目的目标在于将所有这一切都搬入云中,不在数据中心内运行任何计费应用程序或数据库,但所有操作不能影响业务的正常运转。我们的前路十分艰巨!
前言
毫无疑问,在不中断业务的情况下迁移高度敏感的应用程序和重要数据库是一项意义深远的工作,与此同时我们还将继续构建新的业务功能和服务。
计费系统的一些重要用途和面临的挑战包括:
计费团队负责管理整个公司的重要财务数据。我们每天会通过用户的付费订阅、礼品卡、信用额度、退款等行为生成大量数据,这些数据将汇总至财务部门并创建成报表交给公司会计。为确保每天的收入情况可以准确记录,我们在日常处理流程中实施了严格的SLA。处理管线的任何延迟都是无法接受的。
计费系统对数据丢失持零容忍态度。
大部分情况下,现有数据使用了一种关系型模式的结构,因此需要通过事务确保这类数据实现“全有或全无(all-or-nothing)”的行为。换句话说,我们的运维必须符合ACID(原子性、一致性、隔离性、持久性)要求。但某些情况下还必须让这些数据实现跨区域高可用,同时确保不同区域间复制的延迟最小化。
计费系统与公司的DVD业务相集成,而DVD业务与在线流播业务使用了截然不同的体系结构,这也大幅增加了集成工作的复杂度。
支付团队还希望向Netflix客户服务代理提供数据支持,帮助他们回答会员提出的有关计费操作的问题。因此迫切需要向客户支持人员提供此类数据的概括性视图。
当我们着手进行该项目时,计费系统是这样的:
数据中心内部署2个Oracle数据库 – 一个存储客户订阅信息,另一个存储发票/支付数据。
多个基于REST的应用程序 – 为www.netflix.com和客户支持应用程序的调用提供服务。这些应用程序主要执行CRUD(创建、读取、更新、删除)操作。
3个批处理应用程序:
服务续订 - 这个每天运行一次的作业会扫描所有客户信息以确定当天需要计费的客户,并通过这些客户的订阅计划、折扣等信息确定需要计费的金额。
订单和支付处理 - 通过一系列批处理作业为需要续订的客户创建发票,并负责在发票生命周期内的不同阶段处理有关发票的任务。
营收报表 - 这个每天运行一次的作业会检索计费数据并生成Netflix财务部门需要的报表。
一个计费代理应用程序(位于云中) - 用于将Netflix在云中的其他应用程序的调用路由至数据中心。
使用老版本格式的Weblogic队列负责不同过程之间的通信。
规划
我们制订了一个三步规划:
第1步 – 服务新落地国家的计费系统直接从云中运行,并将所产生的数据同步回数据中心,供原有批处理应用程序继续使用。
第2步 – 对面向用户的数据进行建模,以实现最终一致性并且不再需要符合ACID特性,将这些数据持久保存在Cassandra(Cassandra使得我们能够在一个区域执行写操作,并用非常低的延迟让写入的数据可在所有区域使用。同时还可以帮助我们实现跨区域高可用性)。
第3步 – 最终将SQL数据库迁移至云中。
从每个国家迁移过程的每一步操作中学习经验,进行迭代和完善,确保后续工作能取得更好的成绩。
第1步 – 将新落地国家重定向至云中,将数据同步回数据中心
Netflix很快将在6个新国家落地。我们决定利用这一机会直接通过云环境运行这些国家的部分计费系统。这意味着面向用户的数据和应用程序将从云中运行,但依然需要将数据同步回数据中心,这样数据中心内的批处理应用程序才能继续运行,不至于影响到业务运转。这些新落地国家客户的数据将保存在云中,但批处理任务依然在数据中心内处理。这是我们的第一步。
我们将2个面向用户的应用程序中的所有API移植到使用Spring Boot和Spring Integration开发的云应用程序中。通过使用Spring Boot可以快速着手创建新应用程序,这个产品内建了开发工作所需的基础结构和组件,可以让我们更专注业务逻辑本身。
通过使用Spring Integration,只需一次开发码即可重复使用大部分工作流风格的代码。借助这些产品对Header以及基于Header的路由技术提供的支持,我们可以在应用程序内部实现Pub-sub模式,将消息放入一个渠道(Channel),并让每个用户通过各自独立的方式使用。
在将数据存储于Cassandra的情况下,现在可以通过任意AWS区域处理这6个新国家会员的API调用。就算某个AWS区域彻底故障,这些国家的计费操作也不会受到影响,而这也是我们首次真正意义上认识到云计算的威力!
我们在AWS多个区域的EC2实例上部署了自己的应用程序,另外为现有的云代理应用程序增加了一个重定向层,以便将新落地国家用户的计费调用切换至云中新部署的计费API,并让原有国家用户的计费调用继续由数据中心内原有的计费API处理。
我们从一个AWS区域建立了到数据中心内现有Oracle数据库的直接连接,并开发了一个程序,通过SQS将另外3个区域中的Cassandra数据与这个建立了直接连接的区域进行同步。我们还使用SQS队列和Dead Letter Queues(DLQ)在故障的区域和过程之间移动数据。
在新国家落地通常意味着会员数量的激增。我们也明白,为了确保数据中心不超载,还必须将现有的续订应用程序从数据中心搬入云中。因此对于通过云服务运行的6个新落地国家,我们编写了一个爬虫程序,可以每天一次遍历Cassandra中的所有客户,借此找出所有当天需要收费的会员。
这种“逐行迭代”的方法目前在这些国家很好用,但我们也清楚,在将其他国家,尤其是美国(目前我们的绝大部分会员都在美国)的数据迁移到云中之后这种方式将会失效。但我们想先行一步试试水有多深。这也是目前这一阶段唯一在云中运行的批处理应用程序。
为了能够在任何一个区域执行写操作,并将写操作快速复制到其他区域,我们选择用Cassandra作为数据存储。我们定义了一种数据模型,在其中使用customerId作为行,并创建了一系列复合的Cassandra列借此体现数据之间的关系性。下图展示了这些项之间的关系,以及我们是如何在Cassandra中使用单列族(Single column family)进行体现的。用单列族形式设计这样的关系使得我们能为相关项提供事务支持。
通过对应用程序的逻辑进行设计,只需要在任何操作开始执行时读取一次,随后即可在内存中更新对象,并在操作结束后将其以单列族的形式持久存储。在操作过程中读取或写入Cassandra的操作会被看作一种反模式(Anti-pattern)。我们使用Astyanax(Netflix自行开发并已开源的Cassandra客户端)编写了自定义的ORM,这样就可以针对Cassandra读/写域对象。
我们通过这种方式将服务落地到新的国家,虽然遇到了几个小问题,但在迅速修复后整个系统运转很稳定。目前来说一切都挺不错的!
经过第1步工作后计费系统的体系结构如下图所示:
第2步 – 迁移所有应用程序,并将原有国家迁移至云中
第1步成功完成后,我们开始考虑在不迁移数据库的情况下将其他应用迁至云中。大部分业务逻辑位于批处理应用程序中,多年来已经发展得极为成熟,但这也意味着必须深入到每个条件的代码中并花费大量时间重写。这些应用程序无法“照原样”直接搬到云中运行。
同时我们也借助这次机会尽量移除了所有不再使用的代码,将不同功能拆分为多个专用的小应用程序,并为了更好的扩展性重构了现有代码。这些遗留应用程序被我们设计为会在启动时读取磁盘上的配置文件,并使用了其他一些静态资源,例如从Weblogic队列读取消息,由于实例与生俱来的“短暂”本质,这些特征在云环境中都是反模式的。
因此为了让应用程序在云平台上顺利运行,只能重新实现这些模块。为了通过异步模式将消息穿过队列移动到不同区域,我们还更改了一些API,并在这些区域建立了到数据中心的安全连接。
云数据库工程团队为我们的数据需求搭建了多节点Cassandra集群。我们也清楚,在将所有Netflix会员的计费数据迁移到Cassandra之后,以前用来为最早的6个国家的客户提供续订服务所用的“全行(Row)式”Cassandra迭代器续订解决方案将无法很好地伸缩。
因此我们使用Aegisthus设计了一个系统,可从Cassandra SSTable拉取数据并将其转换为JSON格式的行,将其暂存在S3 Bucket中。随后我们写了一些Pig脚本,借此每天针对大量数据集运行Mapreduce作业,找出需要续订的客户清单并向他们收费。
我们还写了Sqoop作业以便从Cassandra和Oracle中拉取数据,并将其以可查询格式写入Hive,这样就可以将这两套数据集汇总至Hive,实现更快速的排错。
为了让DVD服务器能够连接云环境,我们为DVD设置了负载平衡端点(包含SSL客户端证书),DVD服务器可以通过云代理对所有调用进行路由,在迁移美国系统之前可以借此将调用重新发回数据中心。美国系统的数据迁移完成后,即可断开云和数据中心之间的通信链路。
为了对这一大规模数据迁移的结果进行验证,我们编写了对已迁往云中的数据,以及数据中心内部现有数据进行比较和验证的对比工具。反复运行该对比工具可找出迁移过程中可能存在的Bug,修复发现的问题,清理数据并再次运行。
随着运行结果愈发整洁,完全没有出现任何错误,这也进一步增强了我们对数据迁移工作的信心。对于针对各国数据进行的迁移工作我们感到十分激动。最开始我们选择了一个Netflix会员数量比较少的国家,并通过下列步骤将数据迁入云中:
禁用待迁移国家的非GET API(该操作不会影响会员服务,但可能导致计费系统中订阅更新工作延迟)。
使用Sqoop作业将数据从Oracle转移至S3和Hive。
使用Pig将其转换为Cassandra格式。
将该国家所有会员的记录插入Cassandra。
启用非GET API,通过云平台为被迁移国家的用户提供数据。
在确认一切正常后开始迁移下一个国家。随后我们开始突击对所有类似国家一起进行迁移。最后迁移的是美国,因为美国的会员数量最多,并且还提供有DVD订阅服务。至此所有面向Netflix会员客户的数据都已通过云环境提供。对我们来说这是一个巨大的里程碑!
经过第2步工作后,我们的体系结构如下图所示:
第3步 – 再见,数据中心!
至此还有最后(并且最重要)的一件事:数据中心内运行的Oracle数据库。Oracle中存储的数据集具有高度的关系性,我们觉得这些数据并不适合以NoSQL的方式进行建模。
这种数据不能像以前处理面向客户的订阅数据那样构造为单列族的形式,因此我们评估了Oracle和Aurora RDS这两种选项。但Oracle作为云数据库运行的许可成本,以及Aurora依然是Beta测试版这一现状使得则两种方式都不适合我们。
在计费团队忙于执行前两个步骤时,我们的云数据库工程团队正在创建用于将计费数据迁移至EC2上MySQL实例所需的基础结构。在开始执行第三步操作时,在他们的帮助下数据库基础结构部分已经就绪。
因为一些应用程序使用了不包含任何ORM的纯JDBC,我们还需要将批处理应用程序的代码基转换为兼容MySQL的格式。另外我们处理了大量遗留的Pl-sql代码,并重写了应用程序中的逻辑,同时尽可能去除了不再使用的代码。
至此我们的数据库体系结构已经由部署在某一AWS区域内EC2实例上的MySQL主数据库组成。我们还搭建了一个从主数据库复制数据的灾难恢复数据库,如果主数据库故障,该数据库将成为新的主数据。另外我们还在在其他AWS区域设置了从数据库,这些数据库主要为应用程序提供只读访问。
至此我们的计费系统已经全部搬入云中,现在看起来是下面这个样子:
数据库的迁移过程
接下来将深入介绍数据库的迁移过程。希望我们的经验能帮你顺利完成自己的迁移任务。
你是否考虑过为了顺利完成复杂的数据库迁移任务,都需要考虑并解决哪些问题?但你可能也会问,“这有什么复杂的?”
想想数据库迁移过程中遇到的下列挑战吧,我们本次迁移几乎遇到了所有这些问题:
源和目标硬件存在差异;
使用了不同的操作系统;
需要跨域异构数据库进行迁移;
涉及多个数据中心 – Netflix数据中心(DC)和AWS云;
要迁移的是非常关键的交易计费数据;
有选择地迁移数据集;
需要在最小停机时间的前提下迁移持续变化的数据。
在任何迁移项目中,数据库的迁移都是最基本要素,数据库能否成功迁移直接决定了整个项目能否成功。下文将介绍为确保迁移项目成功完成所采取的一些关键措施。
数据库的选择
为顺利处理付款过程中产生的事务,计费应用程序的事务须符合ACID(原子性、一致性、隔离性、持久性)要求。RDBMS似乎是此类数据存储的最佳选择。
Oracle:由于源数据库使用了Oracle产品,直接迁移至云中运行的Oracle数据库可避免进行跨数据库迁移,降低代码开发和配置工作量。我们过去在生产环境中使用Oracle产品的体验也让自己对该产品的性能和伸缩性更有信心。然而考虑到许可成本以及“依原样”迁移遗留数据所要产生的技术债,最终只能寻求其他解决方案。
AWS RDS MySQL:理想情况下我们会选择MySQL RDS作为后端,毕竟亚马逊在关系型数据库即服务产品的管理和升级方面做的挺好,为了实现高可用还提供了多可用区(AZ)支持。然而RDS的主要不足之处在于存储容量有着6TB上限。我们迁移时的容量已接近10TB。
AWS Aurora:AWS Aurora可以满足我们对存储容量的需求,但目前还是Beta测试版。
PostgreSQL:PostgreSQL是一种强大的对象-关系开源数据库系统,但我们团队内部缺乏足够的PostgreSQL使用经验。在自己的数据中心内我们主要使用Oracle和MySQL作为后端数据库,更重要的是选择PostgreSQL会导致未来无法无缝迁移至Aurora,因为Aurora使用了基于MySQL的引擎。
EC2 MySQL:最终我们的计费系统选择使用EC2 MySQL,这种技术无须许可成本,同时未来可以直接迁移至Aurora。该方式需要在i2.8xlarge实例上使用InnoDB引擎配置MySQL。
生产数据库体系结构
为确保计费应用程序可以承受基础结构、区域和地域故障,并将可能的停机时间降至最低,高可用性和伸缩性是我们设计整个体系结构时最主要的考虑因素。
通过在另一个区域内为数据库主副本创建DRBD副本,即可承受区域故障,节点出错等基础结构故障,以及EBS卷故障。当本地和远程写操作均完成后,会使用“同步复制协议”将主要节点上的写操作标记为已完成。借此可确保一个节点的故障绝对不会导致数据丢失。虽然这样的设计会影响写操作的延迟,但延迟依然在SLA可接受的范围内。
读取副本可设置为本地或跨区域配置,这样不仅可以满足对高可用的需求,而且有助于增强伸缩性。来自ETL作业的读取流量会分流至读取副本,借此降低主要数据库执行繁重ETL批处理的负担。
一旦主要MySQL数据库故障,工作负载将被故障转移至使用同步模式进行复制的DRBD辅助节点。辅助节点开始承担主节点的角色后,会更改数据库主机的route53 DNS记录将其指向新的主节点。按照设计,计费应用程序与生俱来的“批处理”特性可顺利应对此类停机事件。CNAME记录传播工作完成后,客户端连接不会回退(Fallback),而是会建立指向新主节点的连接。
迁移工具的选择
我们在迁移工具的选择方面花费了大量时间和精力。概念验证工作成功与否的最主要条件在于能否重启动批载荷(Bulk load)、双向复制,以及数据完整性。在评估迁移工具时我们主要侧重于下列几个条件。
批/增量载荷的重启动;
双向复制;
每个表并行性(Parallelism per table);
数据完整性;
传输过程中错误报告;
上线后回滚的能力;
性能;
易用性。
GoldenGate以丰富的功能脱颖而出,该产品很好地满足了我们的需求。GoldenGate可以在遇到故障后重启动批载荷(很少的几张表就达到数百GB容量),该产品的双向复制功能可以让我们从MySQL轻松回滚到Oracle。
GoldenGate的主要不足在于了解该工具工作原理所面临的学习曲线。此外该产品使用了易于出错的手工配置过程,这也增大了项目难度。如果源表没有主键或唯一键,GoldenGate会使用所有列作为提取和复制操作的增补日志键对。但我们发现了一些问题,例如复制到目标的数据仅仅是相关表的增量载荷,因此决定在切换这些表的过程中执行不预定义主键或唯一键的完整加载。GoldenGate的优势和包含的功能远远超过了所造成的困难,我们最终选择使用该工具。
架构转换和验证
由于源和目标数据库存在差异,数据类型和长度也有所不同,为了在迁移数据的同时确保数据完整性,验证工作变得必不可少。
数据类型误配造成的问题需要花些时间来修复。例如因为一些历史遗留原因,Oracle中的很多数值已定义为Number数据类型,MySQL缺少类似的类型。Oracle中的Number数据类型会存储定数和浮点数,这一点比较难以处理。
一些源表中的列使用Number代表整数,另一些情况则会代表十进制数值,其中一些值的长度甚至达到38位。作为对比,MySQL使用了明确的数据类型,例如Int、bigInt、decimal、double等,而bigInt不能超过18位。因此必须确保通过恰当的映射以便在MySQL中反应精确的值。
分区表(Partitioned table)需要特殊处理,与Oracle的做法不同,MySQL会将分区键视作主键和唯一键的一部分。为确保不对应用逻辑和查询产生影响,必须用恰当的分区键重新定义目标架构。
默认值的处理在MySQL和Oracle之间也有不同。对于包含NOT NULL值的列,MySQL会确定该列暗含的默认值,在MySQL中启用Strict模式即可看到此类数据转换问题,这样的事务会执行失败并显示在GoldenGate的错误日志中。
架构转换工具:为了实现架构转换并进行验证,我们评估了多种工具,但由于原有架构设计中所存在的问题,这些工具默认提供的架构转换功能无法使用。即使GoldenGate也无法将Oracle架构转换为相应的MySQL版本,因此只能首先由应用程序的所有者重新定义架构。
优化架构也是我们此次迁移的目标之一,数据库和应用程序团队合作审阅了数据类型,并通过多次迭代找出了所有误配的内容。在存在误配的情况下,GoldenGate会对这些值进行截断以符合MySQL数据类型的要求。问了缓解这一问题,我们主要借助数据对比工具和GoldenGate错误日志找出源和目标之间数据类型的误配。
数据完整性
完整加载和增量加载执行完毕后,又遇到另一个让人气馁的问题:必须核实目标副本的数据完整性。由于Oracle和MySQL使用了不同数据类型,无法通过用普通封装脚本对比行键(Rowkey)哈希值的方式保证数据的精确性。
虽然有几个第三方工具能跨越不同数据库对实际值进行数据对比,但总量10TB的数据集比较起来也不容易。最终我们使用这些工具对比了样本数据集,借此找出了少数由于架构映射错误导致的不一致问题。
测试刷新:确保数据完整性的方法之一是使用应用程序对生产数据库的副本进行测试。为此可安排从MySQL生产数据库进行刷新并用于测试。考虑到生产环境使用EBS作为存储,只要创建EBS快照即可轻松创建测试环境,同时可在测试中执行时间点恢复。为确保足够高的数据质量,这一过程重复了多次。
Sqoop作业:我们在数据校正过程中使用了ETL作业和报表,并使用Sqoop作业从Oracle中拉取创建报表所需的数据。此外还针对MySQL配置了这些作业。在源和目标之间进行持续复制的过程中,会在ETL的特定时间窗口内运行报表,这样即可找出增量加载过程中产生的变化。
行计数(Row count)是用于对源/目标进行比较和匹配的另一种方法。为此需要首先暂停目标的增量加载,并对Oracle和MySQL的行数进行匹配。在使用GoldenGate完整加载表之后也会对行计数的结果进行比较。
性能调优
基础结构:计费应用程序将数据持久保存在数据中心内两个Oracle数据库中,运行数据库的计算机性能极为强大,使用了IBM Power 7,32颗双核心64位处理器,750GB内存,通过SVC MCS集群分配TB级别的存储,集群使用了4GB/s接口,运行RAID10配置的8G4集群。
迁移过程中最大的顾虑是性能,目标数据库将整合到一个装备有32颗vCPU和244GB内存的i2.8xlarge服务器上。为了优化查询性能,应用程序团队在应用层进行了大量调优。在Vector的帮助下,性能团队可以方便地发现性能瓶颈,通过调整特定的系统和内核参数解决这些问题。详细信息请参阅附件。
我们用EBS供应的IOPS卷组建RAID0实现了极高的读写性能。为了通过每个卷获得更高吞吐率,共使用5个容量各4TB的卷,而没有使用更大容量的单个卷。这样做也可以加快创建快照和还原的速度。
数据库:对于MySQL的使用我们还有一个比较大的顾虑,担心计费应用程序在对数据执行批处理过程中MySQL的吞吐率无法满足数据规模的需求。Percona为此提供了顾问支持,在迁移过程中以及迁移之后,MySQL数据库的性能表现都让我们感到满意。
这里的诀窍在于使用两个cnf文件,一个用于迁移数据的过程中对innodb_log_file_size之类的参数进行优化,以便执行批量插入;第二个cnf文件用于在实时生产应用程序工作负载中对innodb_buffer_pool_instances之类的参数进行调整,借此促进事务的实时加载。详情请参阅附件。
数据加载:在概念验证过程中,我们针对开启和关闭索引两种情况测试了表的初始加载,并决定在加载前启用所有索引。这样做的原因在于MySQL中索引是通过单线程方式创建的(大部分表有多个索引),因此我们改为使用GoldenGate的并行加载功能在合理的时间内为表中填入索引。最后一次割接过程中还启用了外键约束。
我们学到的另一个窍门是按照实例的内核数量执行相同遍数的完整和增量加载过程。如果这些过程的执行遍数超过内核数量,数据加载性能将大幅降低,因为实例需要花费更多时间进行上下文切换。通过完整加载和增量加载将10TB数据装入目标MySQL数据库,这一过程用了大约两周时间。
结论
虽然对任何迁移项目来说,数据库的迁移都是最大挑战,但真正决定项目成功与否的关键在于要确保一开始就选择了正确的方法,并且在整个执行过程中与应用程序团队密切合作。
回顾整个迁移过程,这个项目的成功完全是组织内部不同团队通力合作的成果,大家一起制定的整个迁移计划是促成这一切的关键!为了在不影响业务的前提下顺利完成整个充满挑战的迁移项目,除了人员和团队之间的相互协调,自由的文化和责任感也是促成这一切必不可少的要素。
附件
批量插入时对数据库的调节
高事务吞吐率的数据库调节
存储
使用5个4TB EBS PIOPS卷组建RAID0
使用LVM管理同一卷组中的两个逻辑卷(DB和DRBD元数据)
CPU调度器方面的调节
虚拟机的调节
文件系统和IO存储指标
本文翻译已获授权有删节,原文地址:
http://techblog.netflix.com/2016/07/netflix-billing-migration-to-aws-part-ii.html
http://techblog.netflix.com/2016/08/netflix-billing-migration-to-aws-part.html
本文译者:大愚若智
▽
延展阅读(点击标题):
喜欢我们的会点赞,爱我们的会分享!