本文是 Netflix 计费系统迁移至云中系列文章的后续。此次迁移项目的概述早前已发布在这里。本篇详细介绍了将计费应用程序和数据存储从本地数据中心迁移至AWS 云的技术细节。
你可能已经从早前发布的 Netflix 云迁移博客文章中了解到,Netflix 的所有流媒体基础结构现已完全在云中运行。随着 Netflix 规模不断扩大,尤其是 Netflix Everywhere 业务即将正式登场,我们已经意识到计费系统迟早也需要迁入云中,因为现有的本地系统已经无法继续扩展了。
毫无疑问,在不中断业务的情况下迁移高度敏感的应用程序和重要数据库是一项意义深远的工作,与此同时我们还将继续构建新的业务功能和服务。
计费系统的一些重要用途和面临的挑战包括:
- 计费团队负责管理整个公司的重要财务数据。我们每天会通过用户的付费订阅、礼品卡、信用额度、退款等行为生成大量数据,这些数据将汇总至财务部门并创建成报表交给公司会计。为确保每天的收入情况可以准确记录,我们在日常处理流程中实施了严格的 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,并在这些区域建立了到数据中心的安全连接。
云数据库工程(CDE)团队为我们的数据需求搭建了多节点 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 内部很多工程团队的支持。来自云数据库工程团队、订户和帐户工程团队、支付工程团队,以及消息工程团队的成员与我们一起用 2 周乃至数月的时间帮助我们一起完成这一重大举措。Netflix 文化最棒的地方正在于,所有人心中有着相同的目标:为全球会员提供更精彩的体验。如果将计费解决方案迁入云中对此有帮助,不管大家属于哪个团队,都会为这一目标贡献自己的力量!
前路 …
将计费系统迁入云中后,Netflix 流媒体基础结构已经完全在云中运行。我们可以按需对 Netflix 的任何服务进行缩放,根据使用模式进行预测式缩放,使用 Spinnaker 进行一键点击部署,并在不同的 Netflix 应用程序之间实现一致的部署体系结构。计费基础结构现在可以使用 Netflix 平台的所有二进制程序和框架进行监控,并通过云平台获得所需的工具支持。目前我们已经可以为全球超过 190 个国家和地区,8100 万 Netflix 会员的计费任务提供支持。为了完成所有计费事件,每天我们会生成并处理数 TB 数据。为了实现全球扩张并顺利迎接业务挑战,我们计划在未来对会员工作流进行重构。作为全新体系结构的一部分,我们将重新设计现有服务,以便在云环境中以原生的方式实现伸缩。随着服务在全球发布,我们可以借此机会为新落地的市场研究并改进计费和支付方法,将其与全球合作伙伴以及不同区域的本地支付流程进行集成。我们正在面向未来努力打造功能更丰富,缩放能力更强的体系结构。
本文作者: Subir Parulekar , Rahul Pilani ,阅读英文原文: Netflix Billing Migration to AWS - Part II
感谢陈兴璐对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论