AICon全球人工智能与机器学习技术大会9折特惠中,点击立减¥480! 了解详情
写点什么

Spotify 是怎样从 Postgres 切换至 Cassandra 的?

2015 年 8 月 04 日

【编者的话】2015 年 5 月,闻名全球的在线音乐平台 Spotify 将他们的存储系统从 Postgres 升级至 Cassandra,整个过程中完全没有停机时间。Spotify 为何要升级他们的存储系统,促进他们升级的导火线是什么,他们如何做到无停机时间的在线升级,以及在升级过程中遇到了哪些困难?负责 Spotify 登录服务的团队成员 Marcus Vesterlund 在博客中介绍了这次升级的整个过程。

介绍

Spotify 的所有用户信息目前已经从 Postgres 迁移至 Cassandra 数据库,最后的切换过程是在 5 月 11 日完成的。作为负责 Spotify 用户登录功能的团队,我们希望让读者了解一下我们所做的努力。

PostgreSQL 赞歌

仅仅去年,Spotify 的新增活跃用户就超过了 3 千 5 百万(详见 20 Million Reasons to Say Thanks 一文)。全部用户的详细信息都保存在 user 数据库中,包括用户名、国家及邮件等等。用户每次登录时都必须查询该数据库,同样,每次创建新的用户、升级为 Premium 用户、接受某个许可或是连接到 Facebook 时,也都要访问这个 user 数据库。这意味着 user 数据库非常繁忙,它担负着整个 Spotify 基础设施中的核心功能。

我们当时所采用的 Postgres 数据库是值得信任的,它已经为我们服务了许多年,但如今它所处理的数据量比设计时的目标已经高出了几倍,并且数据集的增长速度每天都在继续上升。

对这样的核心基础设施进行改动是一件令人畏惧的任务,但同时,我们确实不清楚 Postgres 还能坚持运行多久。

单点故障

我们的 Postgres 环境还受到另外一个问题的困扰:虽然读操作分布在所有的数据中心,但所有的写操作都发生在位于伦敦的一台孤零零的机器上。对此我们的一位网络工程师 Loke Berne 有一句名言:“从网络运维的角度来说,如果能把那个恐怖的机柜彻底干掉,那实在太美妙了。”他所指的机柜正是包含了 user 数据库写入功能的主节点。让仅仅这么一台机器作为 user 数据库的主节点可不是什么有趣的事,对于用户信息的所有更新,例如新创建的帐号、或者是让 7 千 5 百万活跃用户升级为 Premium 用户,这些操作都是由这台可怜的机器所处理的。一旦这台机器产生故障,以上这些操作都会失败,必须等到某台热备机器升级为主节点,并将访问量发送到新的主节点为止。

Postgres 的淘汰已不可避免

作为负责登录服务的团队,我们深知现有的解决方案将无法跟上用户的增长速度。它就像是一匹年迈的老马,虽然也许能够捱过这个冬天,但今后的日子将越来越难捱。我们心里都明白,现在是时候让它停下了,但扣住扳机的手却在不停地颤抖。

一切都开始于鲨鱼的一咬

2013 年 9 月,连接于伦敦与阿什伯恩数据中心之间的大西洋海底光缆突然断开了。有传言说是鲨鱼咬断了光缆,不论事实真相如何,它所造成的结果是我们的新用户数量在一周内大幅下降。如果我们能够在网络的另一边创建新用户,那么这次网络异常所带来的问题就不会那么严重了。

我们其实已经知道伦敦的单点故障是有问题的,但直到 9 月份的那一周,我们才清楚地知道,这种设计上的失败不仅仅停留在理论上而已。单点故障给我们造成了实实在在的商业损失,这种损失能够很容易地换算成欧元和美金。我们很久之前就开始考虑以 Cassandra 作为解决方案,但一直没有机会专注于这方面的工作。而现在情况已经很明显了,我们必须找到一种新的解决方案。

为行驶中的汽车更换引擎

“如果某件事你做的足够出色,那么人们甚至不会感觉到它。”

– 动画“飞出个未来”其中关于“Godfellas”的一集中, 上帝的实体的名言。

这条格言非常适用于基础设施的改动,虽然这一目标并非总是可行的,但确实是我们的努力方向。数据的迁移是个非常棘手的任务,但我们不希望在迁移时停下整个系统,这会影响用户登录以及新用户的创建。因此,我们必须进行一次无缝的切换。这就意味着我们需要让这两个存储系统同时运行一段时间,以 Postgres 作为主存储机制,同时隐蔽地运行 Cassandra 存储系统。所谓隐蔽就是指并行地进行实际请求的处理,但忽略其结果。

这种方式能够带来多种益处:

  • 我们能够确保新的存储方案其能力足以处理现有的负载,对于 Postgres 的能力需求我们已经很了解了,但 Cassandra 是一种不同的系统,因此必须对其进行实际评估。
  • 与 Cassandra 存储相关的代码也在实际运行中,因此我们能够在最终切换之前找到所有的 bug,以及健壮性和可伸缩性方面的问题。
  • 因为我们隐蔽地运行着 Cassandra,因此即使它发生了故障也不要紧。主进程将记录下这次错误的信息,但忽略它的错误结果。
  • 我们可以在新旧存储系统之间保持数据的同步。

迁移现有帐号

除了隐蔽地处理写入操作,我们还必须对所有用户进行迁移。为此,我们通过一个后台作业,让它将 Postgres 中的所有用户逐个复制到 Cassandra 中。我们必需确保将竞态条件最小化,因为隐蔽的写入操作有可能与帐号的迁移过程同时进行。

由于 Postgres 工作方式的限制(在进行一个长时间运行的查询时,复制过程会中止),我们不得不以一种特殊的方式进行迁移。我们必须保证在运行迁移脚本之前,所有的写入操作,包括新建帐号与帐号更新,都已经进行了适当的隐蔽式处理。

  1. 对一个只读的从节点进行一个长时间的查询,以得到大量的用户名。
  2. 对于每个用户名: A. 如果该用户名已经存在于 Cassandra 中,那么就无需进行迁移。还记得吧,我们假设隐蔽的写入操作有接近 100% 的覆盖率。 B. 如果该用户名不存在,那么就准备进行迁移。 C. 对另一个只读的从节点进行查询,以获得对应这个用户名的用户数据(请记住,在长时间的查询过程中,复制过程会中止。因此,如果我们还是对第一个从节点进行查询,万一用户在这个长时间运行的查询启动后修改了个人信息,那我们就有可能会获取到过期的数据)。 D. 从第二个从节点中获取数据,并插入 Cassandra 数据库(从这一刻开始,如果这个用户产生了任何数据变更,都会同时反映在 Postgres 与 Cassandra 数据库中)。

正确性验证

我们还需要一个脚本以验证这两个存储系统是否已经完全同步了。这个脚本也会以类似的方式逐个处理每个用户,从两个存储系统中同时获取用户数据并进行对比。它还能够为我们生成有用的统计数据,告诉我们比较结果的差别,以及差别的频度。这种方式已经证实对于 bug 的排查非常有帮助。

开始切换

在进行实际切换过程中,Spotify 后台所采用的微服务架构帮了我们一个大忙。它的思想是将对 user 数据库的实际调用封装在一个 RESTful 的服务中,这种方式确保只有一个服务了解存储层的细节。实际的切换过程其实仅包括:

  1. 将配置中的主节点与隐蔽式节点的角色进行切换。
  2. 确保新的配置在所有服务机器上都已生效。
  3. 同时重启所有服务的实例。

同时重启所有服务的实例是很重要的一步,它能够将冲突的风险降至最低。如果有部分服务将 Postgres 当作主节点,而另一部分服务将 Cassandra 当作主节点,会发生什么情况呢?我们可能会在新建帐号时产生冲突,导致同一个用户名指向不同的帐号!

同时重启所有服务实例也有一些负面影响。重启过程大概会持续几秒钟,这段时间内无法创建帐号或进行登录操作。好在我们的客户都很聪明,他们会自己尝试重新登录。

这种切换方式也让我们产生了一个良好的回滚计划,一旦出了什么问题,我们就能够以同样的方式简单地撤消,重新使用 Postgres 作为主节点。

实际的切换结果如何?

首先,我们要确保处于一个良好的状态,通过执行验证脚本将当前的不一致数量降至最低。然后我们启动了切换过程,并注视着日志与图形信息。整个过程相当平静,我们并没有发现什么令人振奋或吃惊的事。事实上,这是我经历过的最乏味的一次部署了。接下来唯一要做的事就是手动地修复一些不一致的地方。

这一路所遇到的各种阻碍

在迁移至 Cassandra 的过程中,我们遇到了许多阻碍。其中有半数我已经记不太清了,但我还是想说明一下有哪些最大的阻碍是我们所必须克服的。

Paxos 算法出错?

Cassandra 1.2 版本中引入了一个别出心裁的的特性,名为 LWT“轻量级事务”,或者叫“条件式插入”。它的本质在于能够确保键的唯一性,这一点对于 Spotify 来说相当重要,它可以保证一个用户名仅属于一个用户。我们可不想在两个用户同时创建帐号时允许冲突或竞态,然后决定让他们共享同一个用户名。

因此,为了避免冲突,我们决定使用 LWT,它的底层使用了一个著名的分布式一致性算法 Paxos 。它在 Cassandra 上的实现需要为复制节点设置一个仲裁(quorum),并且需要经过四个来回的通信过程,这种操作的代价相当之高。

我们进行了一次基准测试,并得出了一个结论:在创建新帐号时使用 Paxos 算法的代价不算太高。但在生产环境中实际测试时,却发现有大量的创建帐号操作都失败了,因为 Paxos 认为这些用户名与帐号已经存在。

我们为此提交了一个错误报告 CASSANDRA-9086 ,并等待官方的回复。

而最终的回复表示,这是一种预期行为,我们可以在自己的服务代码中处理这一问题。

Paxos 需要求所有节点参与 CAS 操作?

这是个有趣的 bug。正如我之前所说,Paxos 算法要求设置仲裁节点以实现一致性。如果这一过程失败,整个操作也会失败,而后将返回一些额外的信息。比方说,你将得到能够参与 Paxos 过程的节点数量。

我们注意到了一点,在帐号创建失败时,错误消息中所显示的数字是所有复制节点的数目,而不是作为仲裁的复制节点的数目。这其实是我们所使用的 Cassandra 版本中的一个 bug( CASSANDRA-8640 ),随后我们将整个集群进行升级,简单地解决了这个问题。

Java 驱动 (撤消了 2.10.0 版本中的 JAVA-425 变更)

我们使用了一个由 Datastax 创建的开源 Cassandra 客户端,这家公司 雇用了大量的 Cassandra 贡献者。经过几个星期的运行,系统的负载也有所上升,此时我们注意到:从我们的服务到 Cassandra 的连接数量在下降,却没有重新建立起新的连接。

这个 bug 将影响真实环境中的访问量,我们使用了异步的 Java 驱动 API,而没有使用分离的线程池。一旦无法建立新的连接,API 就将开始阻塞。最后我们终于触及了并发的上限,连续几个小时不停地收到系统的警报。所幸经过手动重启后服务又能够继续运行了,同时我们也开始追踪其根本原因。

最后证明我们所使用的客户端版本中有一个新的回归缺陷(由 JAVA-425 修改所造成),于是我们升级至最新的版本,其中撤消了 JAVA-425 这个修改,问题得以解决。

对备份的依赖

当迁移至 Cassandra 后,我们需要开始对 Cassandra 数据库进行每日备份。这项任务之前是在 Postgres 数据库上进行的,有许多大数据批处理作业(用于进行个性化、业务分析等等)依赖于它。但 Cassandra 的备份与 Postgres 的备份有着细微的差别,而有些批处理作业无法处理这种差别。这个问题至今也没有完全解决,但我们正在积极地处理它。

总结

地球人都知道,软件项目所花的时间总是比预计的要长,我们的迁移项目也不例外。但我们确实做到在整个切换过程几乎没有什么闪失。通常来说,如果事情太过顺利,我们有时反而会认为一定出现了什么要命的错误。好在这一次是个例外,原因是我们在隐蔽式运行阶段已经触及了大量的问题,这对于终端用户几乎没有什么影响。我们也编写了验证脚本、跟踪日志,并且不断地查看各种图表。因此在最终切换时,我们非常有信心不会遇到太大的问题。

编后语

《他山之石》是 InfoQ 中文站新推出的一个专栏,精选来自国内外技术社区和个人博客上的技术文章,让更多的读者朋友受益,本栏目转载的内容都经过原作者授权。文章推荐可以发送邮件到 editors@cn.infoq.com。


感谢郭蕾对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群)。

2015 年 8 月 04 日 10:345357
用户头像

发布了 428 篇内容, 共 153.0 次阅读, 收获喜欢 27 次。

关注

评论

发布
暂无评论
发现更多内容

网络协议学习笔记Day3

穿过生命散发芬芳

网络协议 4月日更

【Node专题】Node 与 Go 的认识

南吕

Node 后端开发 4月日更

【go专题】Context的理解

南吕

Golag 4月日更

比微信文件传输助手更好用的传输工具|Telegram

彭宏豪95

微信 效率 文件传输 4月日更 Telegram

你的故事,触动了我的心

小天同学

读后感 读书总结 4月日更 皮囊

系统梳理Java全栈知识,阿里大牛熬夜亲码Java成长笔记也太香了!

程序员小毕

Java spring 程序员 架构 面试

Redis的常见问题

赖猫

c++ redis Linux 后端

区块链如何推动数字化转型?

CECBC区块链专委会

区块链

Vue源码思想在工作中的应用

执鸢者

Vue 前端

翻译:《实用的Python编程》InstructorNotes

codists

Python

Java虚拟机原理

风翱

JVM 4月日更

怎么做到的?3个月入职蚂蚁金服(Java岗)从年薪10W到年薪30W

Java架构师迁哥

解决方案的设计与积累——课程总结

Deborah

150页的剑指Offer解答PDF,它来了!!!

秦怀杂货店

小米java社招面试记录,带备战思路

Java架构师迁哥

Web3.0时代需要什么样的企业级协作工具?

猫Buboo

深入理解Spring框架之AOP子框架

邱学喆

aop 动态代理 cglib ProxyConfig AspectJ

2个月从0到1,一年5次迭代,百度“量桨”效率喷涌背后的工作秘诀

脑极体

聪明人的训练(二十四)

Changing Lin

4月日更

2021|南吕

南吕

生活随想 4月日更

MBP恢复记(体验rm -rf /*)

SamGo

学习经验

想拿到10k-40k的offer,这些技能必不可少!作为程序员的你了解吗?

Java架构师迁哥

四面拿到京东Java岗 30K offer 全过程分享

Java架构师迁哥

当时尚撞上区块链,为潮酷创意赋予专属

CECBC区块链专委会

时尚产业

安卓rxjava使用,4面字节跳动拿到Offer,面试必问

欢喜学安卓

android 程序员 面试 移动开发

如何减少管理层级?

石云升

团队建设 28天写作 职场经验 管理经验 4月日更

当我看技术文章的时候,我在想什么?

why技术

Java

一场关于演讲的演讲

Jxin

都在看的18WJava核心成长笔记 让我成功收获字节、阿里、腾讯大厂Offer!

学Java关注我

Java 编程 架构 架构师 程序人生】

BUG!从编写 Loader 到窥探大佬 Debug 全过程

HZFEStudio

小程序 webpack 构建工具

安卓rxjava面试,面试一路绿灯Offer拿到手软,吊打面试官系列!

欢喜学安卓

android 程序员 面试 移动开发

新晋管理者都会遇到的6个问题

新晋管理者都会遇到的6个问题

Spotify是怎样从Postgres切换至Cassandra的?-InfoQ