本文要点
- 为你的微服务选择适当的持久化存储
- 将混合持久化作为一种服务,开发人员可以专注于构建出色的应用程序,不用担心各种后台的调优、调整和容量
- 运作大规模的不同持久化存储涉及独特性挑战,但是通用组件可以简化流程
- Netflix 的通用平台在管理、维护和扩展持久性基础架构上推动卓越运营(包括在不可靠的基础架构上构建可靠系统)
以下内容来自 Netflix 的工程经理 Roopa Tangirala 在 2017 年旧金山 QCon 上的演讲。
我们都在小小起家的公司工作过,公司会有一个独立应用程序作为单独的单元构建起来。那个应用程序产生了很多数据,对此,我们选择了数据存储。很快,数据库成为公司的生命线。
由于我们做的工作非常出色,增长速度加快了,我们需要扩展那个独立应用程序。它在高负载的情况下开始失败,还遇到了扩展问题。现在,我们必须做正确的事情。我们把我们的独立应用程序分解成多个微服务,这些微服务有更好的回退功能,能很好地进行水平扩展。但是,我们不担心后端的数据存储,我们继续让微服务与最初选择的后端相匹配。
很快,在我们的后端部分就变得复杂了。我们的数据团队感到不知所措,因为他们要管理我们数据存储的正常服务时间。他们尝试支持所有类型的反模式,而那些可能是数据库不具备的。
想象一下,我们不是尝试让我们所有的微服务与一个持久化存储相匹配,而是利用我们后端数据层的优势和功能满足我们应用程序的需求。我们不再担心我们的图形用法与RDBMS 的匹配,或是尝试把特殊搜索查询放入Cassandra。我们的数据团队可以平和地工作了,进入了禅的境地。
混合持久化让微服务如虎添翼
在Netflix,我负责云数据库工程团队。我已经在Netflix 工作了近十年,看到公司从独立的数据中心转向微服务和云的混合持久化。Netflix 已经接纳了混合持久化。我将介绍5 个用例,并探讨选择不同后端数据存储的原因。
作为一个中心平台团队,我的团队面临很多挑战,要提供不同风格的数据库作为服务横跨所有Netflix 的微服务平台。
Netflix 简介
自 1997 年以来,Netflix 一直引领数字内容潮流。我们在 190 个国家有超过 1 亿 9 百万的订户,在流视频方面,我们领先全球。Netflix 通过各种设备给用户提供了出色的观影体验,带给你精彩绝伦的内容,包括 Stranger Things、Narcos 以及其他众多的影片。
作为 Netflix 的客户,所有你和 Netflix 客户端的交互,所有你的数据信息,譬如用户信息或观影历史,所有影片需要从程序脚本移向屏幕的存储容量,以及更多的信息都以某种形式存储在我们管理的数据存储中的其中之一。
Netflix 的云数据库工程(Cloud Database Engineering,简称 CDE)团队在亚马逊云平台上进行运维,我们支持种类广泛的混合持久化。我们拥有 Cassandra、Dynomite、EVCache、Elastic、Titan、ZooKeeper、MySQL、Amazon S3 支持一些数据集和 RDS。
Elasticsearch 提供了出色的搜索、分析以及几乎实时的以任何形式对任何数据集的可视化。EVCache 是基于 Memcached 的分布式内存缓存解决方案,Netflix 在 2011 年开源了 Memcached。Cassandra 是分布式 NoSQL 数据存储,可以处理大型数据集以及提供高可用性、多区域复制和高扩展性。Dynomite 是分布式 Dynamo 层,也是 Netflix 开源的,提供对不同存储引擎的支持。目前,它支持 Redia、Memcached 和 RocksDB。受 Cassandra 的启发,它给非分布式数据集添加了分片和复制功能。最后,Titan 是可扩展的图形数据库,为存储和查询图形数据集进行了优化。
我们来看看架构、云部署和数据集是如何在 Amazon Web Services(AWS)中持久存在。我们在 3 个 AWS 区域运行,所有的流量都经过这三个区域。用户流量被路由到最近的区域,主要是:US West 2、US East 1 和 EU West 1。如果某个区域出现问题,我们的流量团队可以在 7 分钟之内把流量转到其他两个区域,并且很少或没有停机时间。因此,我们的所有数据存储都需要是分布式的和高扩展的。
用例 1:CDN URL
如果你像我一样是个 Netflix 粉丝(也喜欢看《Stranger Things》和其他系列剧),你知道你得点击播放键。从你点击的那一刻到你在屏幕上看影片,在后台发生了很多事。Netflix 必须查看用户许可和内容版权。Netflix 拥有一个遍布全球的开放连接设备(Open Connect Appliances,简称 OCAs)网络。这些 OCAs 是 Netflix 存放视频数据的地方,它们的唯一目的是把这些数据尽可能快速高效地发送到你的设备上,与此同时,我们有处理微服务和数据持久化存储的 Amazon plane。这个服务负责生成 URL,从那里,我们可以把电影传送给你。
对于该服务最首要的需求是高可用性。我们不希望在你要看电影时,有任何不好的用户体验,因此,高可用性是摆在首位的。接下来,我们希望读写延迟很小,不到一毫秒,因为该服务位于视频流的中间,我们希望在你点击播放键时,电影就开始播放。
我们也希望每个节点具有高吞吐量。尽管文件在所有这些缓存中是预先定位的,它们可以基于所持有的缓存或在 Netflix 引进新电影时进行更改,有多个维度可以改变这些电影文件。因此,该服务接收高读写吞吐量。我们希望每个节点的吞吐量能高些,这样我们可以进行优化。
我们使用了一个特定的服务,叫做 EVCache。它是分布式缓存解决方案,该方案具有低延迟的优势,因为数据都在内存中。对于这个用例的数据模式很简单:它是一个简单的键值对,你可以从缓存中轻松得到该数据。EVCache 是分布式的,我们在不同的 AWS 可用区中有多个副本,因此,我们也获得了更好的容错性。
用例 2:播放错误
想象一下,你点击了播放键,想看电影,但是遇到了播放错误。任何时候你点击片名,就出现播放错误,就是播放不了。
片名有多个特征和元数据。它有评分、类型和描述。它拥有它支持的音频语言和字幕语言。它具有 Netflix 开放连接网络的 URL,就是我们在第一个用例里提过的,URL 就是一个位置,电影是从那里传送给你的。我们把所有这些元数据叫做“播放清单(playback manifest)”。我们需要它来为你播放影片。
导致播放元数据错误的有数百个维度,调整用户播放体验的也有数百个维度。比如,某些内容只在特定国家有许可证,如果你不在这些国家境内,我们就无法为你播放。也许有个用户想在西班牙观看《Narcos》。我们也许不得不根据你用的是 Wi-Fi 还是固定网络来改变速率为你传送影片。有些设备不支持 4K 或 HD,我们就必须根据设备改变视频流。除了这几个例子外,你的播放体验取决于数百个维度。
对于这个服务,我们希望能够快速解决问题。我们希望有某个地方可以快速查找问题的原因:哪个维度没有同步,是什么导致了你的播放错误。如果我们已经排除了推送问题,我们就希望看看根据问题的范围是否需要回退,或快进:该错误发生在所有三个区域,还是某个特定区域,或只是某个特定的设备?我们需要很多个维度来找出数据集。
另一个需求是交互式控制面板。我们希望能够切割和分割数据集以看到错误的根源。近乎实时的搜索是重要的,因为我们希望找出是否是最近的推送引起了当前的错误。因为引起错误的维度太多,所以我们需要特别查询;我们不知道我们的查询模式。也许有多种方式供我们查询数据集得以找出引起错误的原因。
我们把 Elasticsearch 用于这项服务。它为任何形式的数据提供了出色的搜索和分析,通过 Kibana 提供了交互式控制面板。我们在 Netflix 经常使用 Elasticsearch,特别是用于调试和日志用例。
Kibana 为交互式探索提供了一个出色的用户界面,它允许我们检查数据集以查找错误。我们可以确定错误存在于跨多设备的某个特定区域的某个特定设备中,或局限于特定的影片。Elasticsearch 也支持像“Netflix 中排名前 10 的设备是什么?”这样的查询。
使用 Elasticsearch 之前,从事故发生到找到解决方案的时间超过两个小时。整个过程涉及查看日志、记录日志、查看引起错误的原因、在清单和传送给你的内容之间有什么不匹配的。有了 Elasticsearch,解决问题的时间减少到 10 分钟以下。这真是件了不起的事。
用例 3:观看历史
在你观看 Netflix 时,你建立了我们称之为“观看历史(viewing history)”的东西,它基本上是过去几天,你一直在看的影片。它保存着你所看的影片的标签,你可以点击标签,从上次中断的地方继续看。如果你查看你的账户活动,你可以看到你观看特定影片的日期,如果查看片名发现问题,你可以报告给 Netflix。
对于观看历史,我们需要一个数据存储,它可以在一个数据集中存储时间序列。我们需要支持大量的写操作。很多人在看 Netflix,这很棒,因此观看历史服务收到了大量的写入操作。因为我们部署在三个区域,我们需要跨区域的复制,那样的话,就算一个区域里有问题,我们也可以把流量转移,让用户的观看历史也在另外的区域中可用。对大型数据集的支持至关重要,因为观看历史已经呈指数级增长。
对于这个问题,我们采用了 Cassandra。Cassandra 是一个出色的 NoSQL 分布式数据存储,提供多数据中心、多向复制。这个很棒,因为 Cassandra 在为我们进行复制。它具有高度可用性和高度扩展性。它也有出色的故障检测和多个副本,因此一个节点的停机不会引起网站停机。我们可以定义不同一致性级别,因此我们从未经历停机,就算在我们的区域内总是会有节点停机。
数据模型
用于观看历史的数据模型开始很简单。我们有一个行关键字,它是客户或用户的 ID。用户观看的每部影片是一个列数据,属于一个特定的列族。当你观看影片时,你在写入观看历史,而我们只是写入一个小小的有效负载:你所看的最新的影片。观看历史随着时间增长,Casandra 能够处理宽行,因此不会有问题。你可以查看你的整个观看历史,并且当你这么做时,你正在通过你的行进行分页。
我们很快就遇到了这个模型的问题。观看历史非常受欢迎,因此我们的数据集也在飞快增长。一些用户拥有很长的观看历史,因此那一行就变得非常宽。尽管 Cassandra 处理宽行很在行,在内存中读取所有这些数据引起了堆压力,并降低了第 99 百分位的延迟。
新的数据模型
于是,我们有了新的数据模型,它分成两个列族。一个是实时观看历史,有着类似的模式,每一列有个片名,因此,我们可以继续写入小的有效负载。然后,我们有个汇总列族,它是所有历史数据集的组合,汇总到另一个压缩了的列族。这意味着我们必须做两次读取,一次是从压缩了的列族,另一次是从实时列族读取。这绝对有助于规模大小。我们大大减小了数据集的规模,因为一半数据被压缩了。
汇总在读取的过程中产生。当用户尝试读取观看历史,该服务知道他们已经读取了多少列的数据。如果列的数量比我们认为的要多,那么,我们会压缩历史数据,并把它移到另一个列族。根据你的读取操作,这个过程一直在进行,效果很好。
用例 4:数字资产管理
在 Netflix,我们的内容平台工程团队处理大量的数字资产,需要一种工具来存储那些资产和存在于这些资产中的联系和关系。
举例来说,我们有很多艺术作品,那是你在网站上所看到的。这些艺术作品可以有很多格式,包括 JPEG、PNG 等等。我们也有各种艺术作品目录:电影可以是艺术、角色可以是艺术、演员也可以是艺术等等。
每个片名是被打包的不同东西的组合。这个包可以包括:视频元素,比如预告片和剪辑、视频、音频和字幕的组合。比如,我们可以在法国和西班牙把法语字幕放入视频格式里。然后,你有了一些关系,像剪辑是视频的一种。
我们希望有一个数据存储,我们可以存储所有这些实体以及关系。
我们对于数字资产管理服务的需求是一个后端来存储数据资产的元数据、关系和连接的数据集,以及快速搜索的能力。我们使用 Titan,它是一个分布式图形数据库。对于存储图形数据集,它很称职,还支持各种存储后端。由于我们已经支持 Cassandra 和 Elasticsearch,因此很容易整合到我们的服务中。
用例 5:分布式延迟队列
Netflix 的内容平台工程团队负责大量的业务流程。推出新影片、内容输入和编码,或上传到 CDN 是所有需要在多个微服务之间进行异步编排的业务流程。延迟队列形成这个编排的一个组成部分。
我们希望延迟队列是分布式的并且是高度并发的,因为多个微服务在访问它们。我们也想要至少一次为该队列和延迟队列传递语义,因为在所有这些微服务之间存在关系,同时,我们不知道该队列何时会被用到。一个关键的需求是在碎片中拥有优先级,那样的话,我们可以选择有最高优先级的队列。
对于这个特定服务,我们使用了 Dynomite。前不久,Netflix 开源了 Dynomite。它是一个可插入的数据存储,配合 Redis、Memcached 和 Rocks DB 一起工作。它适用于这个用例的原因是 Redis 具有非常支持队列的数据结构。早前,我们尝试让队列能与 Cassandra 一起工作,但是我们失败了,遇到了各种各样的边界问题。Dynomite 在这个用例里运行地很好。它提供了多数据中心复制和分片,因此,作为应用程序的拥有者,我们无需担心数据被跨区域或数据中心复制。
Netflix 为每个队列维护三组 Redis 结构。一个是按分数排列的队列元素集合。第二个是包含有效负载的哈希值集合,关键字是消息的 ID。第三个是包含由用户所使用的、但是还没确认的消息的排序集合。因此,这第三个是未知集合。
认清挑战
我喜欢这句话,但是我的待命团队估计不喜欢:“我期望这么一个时刻,但是我从未感到它们会那么糟糕、那么长和那么频繁。”
我的团队面临的第一个挑战是多样性和规模。我们有这么多不同的数据存储方式,我们得管理和监控所有这些不同的技术。我们需要建立一个能够做所有这些工作的团队,同时确保该团队有技能来满足所有这些不同的技术的要求。处理这些变化,特别是用个小团队来做,变成一个对管理的挑战。
接下来的挑战是预测未来。有了所有这些技术的组合,我们拥有数以千计的集群,数以万计的节点、PB 级别的数据量。我们需要预测我们的集群何时会遇到容量不够的风险。我的中心平台团队应该知道每一个集群的容量极限,这样如果应用程序团队说他们在增加容量或吞吐量或添加新功能,而那些会导致后端 IOPS 的增加,我们应该能够告诉他们,他们的集群是足够大的或需要扩展。
对于所有集群、软件和硬件的维护和升级,我们需要知道我们是否能实施维护而不影响生产服务。我们是否能够构建我们自己的解决方案或是我们购买外面现成的东西?
另一个挑战是监控。我们有数以万计的实例,所有这些实例在发送指标。一旦出现问题,我们应该知道哪个指标最有用,哪个是我们应该关注的指标。我们必须保持一个高信噪比。
克服挑战
应对这些挑战的第一步是要有专家。我们在我们的 Cassandra 云数据库工程团队中有 2 到 3 个核心人物,我们称他们为主题专家(subject-matter expert)。这些人提供最佳实践,并和微服务团队紧密合作来理解他们的需求,并提出后端存储建议。他们是那些推动功能和最佳实践,以及产品未来及愿景的人。
团队中的每个人都在呼唤所有这些技术,因此有理解所发生的事并知道该怎样解决后端问题的核心人物团队是很有用的。我们为开源或后端数据层继续做贡献,产生一个功能,而不是构建在崩溃的基础上应用补丁的自动化。
接下来,我们构建智能系统为我们工作。这些系统承担所有自动化和修复工作。它们接受告警信号,查看配置,使用我们为每个应用程序制定的延迟阈值来做出决策,从而把人们从每个告警信号都会收到通知的情形中解放出来。
CDE 服务
CDE 服务帮助 CDE 团队提供数据存储服务。它的第一个组件捕获阈值和 SLAs。我们有数千个微服务,我们如何知道哪个服务需要第 99 个百分位的延迟?我们需要一种方法来看集群,同时查看需求和我们已经承诺的内容,以便我们判断集群的规模大小是否有效,还是需要扩展。
集群元数据有助于提供一个所有集群的全景视图:每个集群运行的软件和内核版本、其大小和管理成本。该元数据有助于应有程序团队理解和特定的后端以及他们尝试存储的数据相关的成本,以及他们的方法是否合理。
CDE 服务的自服务功能允许应有程序用户自行创建集群,无需 CDE 团队的干预。用户无需了解后端 YAML 所有的重要细节,他们只需要提供最少的信息。我们创建集群并确保它采用了正确的配置,有正确的版本和有内置的最佳实践。
在 CDE 服务之前,联系信息只保存在系统外部。对于每个应用程序,我们需要知道该联系谁,要通知哪个团队。当你在管理这么多集群时,它就变得很棘手,还有,有一些中心位置来捕获这些元数据是至关重要的。
最后,我们跟踪维护窗口。一些集群可以在夜晚拥有维护窗口,与此同时,其他集群接收高流量。我们根据集群的用例和流量模式决定相应的维护窗口。
架构
图 1 显示了该架构,包含其中心的数据存储。对于左侧的调度器,我们用基于计划任务(cron)的 Jenkins,它允许我们点击按钮来升级或进行节点替换。它的下面是 CDE 服务,它捕捉集群元数据,是所有信息(像 SLAs、PagerDuty 信息等等)的来源,最上面的是监控系统。在 Netflix,我们使用 Atlas,它是一个开源遥测系统,用来捕捉所有的指标。只要有问题发生,我们不能满足第 99 个百分位的时延,就会触发警报。在最右边的是修复系统,是个实施框架,运行在容器上,可以执行自动化。
图1:CDE 架构
一旦报警被触发,监控系统将发送一个告警信息给修复系统。该系统将实施在数据存储上的自动修复,甚至不会让该告警信息送去给CDE 团队。只有在我们还没有构建自动化的情形下,告警信息会直接发送给我们。我们团队最感兴趣的是构建尽可能多的自动化,以限制我们需要响应的告警信息数量。
SLA
图 2 显示了可以查看所有集群的集群视图。我可以看到它们正在运行的版本、它们所在的环境、它们所在的区域,以及节点的数量。这个视图也显示了客户电子邮件、Cassandra 版本、软件版本、硬件版本、平均节点数和各种成本。我还可以查看我最老的节点,这样,我可以查看该集群是否有需要更换的老节点,然后,我们只需运行修复。有个扫描旧节点,运行终止的工作。对于空间的兴趣,我还没有显示很多列,但是你可以选择你想要查看的信息。
图2:CDE 自服务用户界面
我们有另一个用户界面用于创建新集群,具体到每个数据存储。应用程序用户只需要提供集群名、电子邮件地址、他们准备存储的数据数量和在哪个区域创建集群,然后在后台自动创建集群。这个过程让用户可以很容易地在他们需要的时候创建集群,由于我们拥有这个架构,我们确保集群的创建使用数据存储的正确版本和所有内置的最佳实践。
当一个升级程序在运行时,确定测试集群和产品集群在数以千计的数量上已经升级了多少百分比是非常棘手的。我们有自服务用户界面,应用程序团队可以登录查看我们在升级过程中的进展情况。
机器学习
早些时候,我提到必须预测未来。我们的遥测系统存储两周的指标,先前的历史数据被推送到S3。我们用Kibana 指示板分析这个数据来预测该集群何时会耗尽容量。
我们有个叫预测分析的系统,它运行模型来预测集群何时会耗尽容量。该系统在后台运行,当它觉得一个集群在90 天内会耗尽容量时,它会通知我们。有了Cassandra,我们只想把三分之一的容量用于数据集,三分之一的容量用于备份,最后三分之一用于压缩。有个监控系统和可以提早而不是出现问题时通知我们的系统是非常重要的,否则会导致各种各样的问题。
由于我们在处理有状态持久性的存储,因此不容易对它进行扩展。使用无状态服务更容易,你可以选红色或黑色,或是用自动扩展群来扩展集群,该集群的尺寸大小也可以增加。但是对于持久性存储来说,这很棘手,因为它是节点上所有的数据,这些存储必须流动到多个节点。这是我们用预测分析的原因。
主动维护
云里的东西会失败,硬件会有故障。我们注册了接收亚马逊的通知,我们提前终止那些节点而不是等着亚马逊为我们终止它们。因为,我们是主动的,我们可以在我们喜欢的窗口做维护,还有硬件的更换、终止或任何我们想做的事。
比如,我们不依赖Cassandra 的引导能力来产生节点,因为这很费时。对于像我们那些有超过一太字节(terabyte)数据的节点的集群,要花好几个小时,有时甚至花上几天。在这些情况下,我们构建了一个过程,用来从节点复制数据,将其放入新节点,然后终止第一个节点。
升级
跨所有这些混合持久性的不同实例的软硬件升级是大费周章的,因为对后端的任何改变都会引起巨大的影响。像一个错误版本问题,可以影响你所有的正常运行时间。我们已经对用Netflix Data Bench(NDBench)来进行我们的升级建立了信心,NDBench 是一个开源基准测试工具。它是可扩展的,因此,我们可以把它用于Cassandra、Elasticsearch 或任何我们想要的存储。在NDBench 客户端,我们指定了我们要用在我们集群的操作的数量上,有效负载和我们想要的数据模型。这允许应用程序团队使用NDBench 来测试他们自己的应用程序。
当我们升级时,我们会查看4 到5 个流行用例。例如,我们也许会试试捕捉80% 的读操作,20% 的写操作或是读写操作各一半。我们尝试只用几个用例来捕捉更多人们在集群中用到的更常见的有效负载。在升级前,我们运行基准测试,捕捉第99 个百分位和平均时延。我们实施升级,再次运行基准测试。我们比较前后两次的基准测试来查看升级是否已经引入了回归或已经引起了增加时延的问题。这有助于在生产中出现很多问题之前就调试它们。当这个特别的比较暴露问题时,我们从不升级。那是因为我们能够在幕后推出所有这些升级,我们的应用程序团队甚至不会意识到我们在升级他们的集群。
实时健康检查
我们也在节点层和集群层处理健康检查。节点层是数据存储是否在运行,以及我们是否有任何硬件故障。集群层是一个节点对集群中其他节点的看法。
常用的方法是用计划任务轮询所有节点,然后用该输入来判断集群是否健康。这是有噪声的,如果有从计划任务系统到节点的网络问题或是计划任务系统出现故障的话,将产生误报。
我们从基于投票的系统转向持续的、流动的健康检查。我们有源源不断的细粒度快照,从所有的实例推送到一个我们称之为Mantis 的中心服务,它聚合所有的数据并创建健康评分。如果分数超过了一定的阈值,就认为集群是不健康的。
我们有一些控制面板,可以用来查看实时健康状况。宏视图用颜色编码显示集群的相对大小,以指示集群健康与否。点击一个不健康的节点会显示该集群和该节点的详细试图。点击坏实例,显示是什么引发问题的细节,这有助于我们轻松调试和排除问题。
总结
从以上内容中总结出,平衡是生命的关键。你不能让你所有的微服务使用一个持久性存储。同时,你不希望每一个微服务都使用独特的持久性存储。总有一种平衡,我希望用我所涵盖的东西,你可以找到你自己的平衡,并建立你自己的数据存储服务。
作者简介
Roopa Tangirala 在 Netflix 领导云数据库工程团队,负责 Netflix 的云持续运行时存储,确保数据的可用性、持久性和可扩展性,以满足不断增长的业务需求。该团队专门提供混合持久性服务,包括 Cassandra、ElasticSearch、Dynomite 和 Mysql 等。
Thomas Betts 是 IHS Markit 的首席软件工程师,拥有 20 年的专业软件开发经验。他一直致力于提供令其客户满意的软件解决方案。他曾在不同的行业工作,包括零售、金融、医疗保健、国防和旅游业。Thomas 和其妻子及儿子住在丹佛,他们热爱远足和探索美丽的科罗拉多。
阅读英文原文: https://www.infoq.com/articles/polyglot-persistence-microservices
感谢罗远航对本文的审校。
评论