【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

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

  • 2015-08-04
  • 本文字数:4677 字

    阅读完需:约 15 分钟

【编者的话】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 读者交流群)。

公众号推荐:

跳进 AI 的奇妙世界,一起探索未来工作的新风貌!想要深入了解 AI 如何成为产业创新的新引擎?好奇哪些城市正成为 AI 人才的新磁场?《中国生成式 AI 开发者洞察 2024》由 InfoQ 研究中心精心打造,为你深度解锁生成式 AI 领域的最新开发者动态。无论你是资深研发者,还是对生成式 AI 充满好奇的新手,这份报告都是你不可错过的知识宝典。欢迎大家扫码关注「AI前线」公众号,回复「开发者洞察」领取。

2015-08-04 10:345889
用户头像

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

关注

评论

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

Linux之lastb命令

入门小站

在线正则表达式可视化测试工具

入门小站

工具

Rust中值销毁前的清理动作

Shine

rust

探索SeekTiger生态,Tiger DAO VC有哪些新期待

小哈区块

用JAVA捋一下设计模式3-抽象工厂模式

下雨了

设计模式 抽象工厂模式 4月月更

在线JSON压缩工具

入门小站

工具

完美结合,10款提升编程能力的游戏项目!

Jackpop

2022第12周-程序接盘侠

印哥爱学习

离职交接

计算机网络: IP地址,子网掩码,网段表示法,默认网关,DNS服务器详解

喀拉峻

网络安全 IP

津厦两地托育行业发展线上视频交流会成功召开

InfoQ 天津

Redis(二)分布式锁与Redis集群搭建

神农写代码

阿里云与达摩院合作 AHPA 弹性预测论文被顶会 ICDE 录用

阿里巴巴云原生

好身体,从增加睡眠时间开始

石云升

睡眠 4月月更

Flutter 简单实用的 fluro 路由管理插件简介

岛上码农

flutter 大前端 ios开发 安卓开发 跨平台开发

前端食堂技术周刊第 31 期:Vue 3、Vitest 中文文档上线、Pinia 正式成为 Vue 官方默认推荐的状态管理库、Vite v2.9.0

童欧巴

JavaScript 前端 Web web前端 前端工程师

模块二:作业微信朋友圈的高性能复杂度

本人法海

「架构实战营」

用JAVA捋一下设计模式 4-单例模式

下雨了

设计模式 单例模式 4月月更

用JAVA捋一下设计模式23-解释器模式

下雨了

设计模式 4月月更 解释器模式

吹爆Python,解决了10个痛苦已久的难题

Jackpop

Redis集群架构剖析(4):槽位迁移,重新分配

非晓为骁

redis 分布式架构 redis cluster

中国信通院联合OpenMLDB邀您参加《开源数据库发展研究报告》调研问卷

第四范式开发者社区

数据库 大数据 开源

Kubernetes官方java客户端之六:OpenAPI基本操作

程序员欣宸

4月月更

【PIMF】《伟大的计算原理》提炼“六脉神剑”认识OpenHarmony技术路线

离北况归

《伟大的计算原理》 技术路线 IMF

一文浅谈:我们为什么需要云原生

穿过生命散发芬芳

4月月更

探索SeekTiger生态,Tiger DAO VC有哪些新期待

西柚子

用JAVA捋一下设计模式1-简单工厂模式

下雨了

设计模式 简单工厂模式 4月月更

Linux驱动开发-proc接口介绍

DS小龙哥

4月月更

用JAVA捋一下设计模式2-工厂方法模式

下雨了

设计模式 工厂方法模式 4月月更

《数据密集型型系统设计》LSM-Tree VS BTree

懒时小窝

哈希 B-tree 列式存储 LSM-Tree

【限时免费】阿里云 ACR EE 增强型扫描引擎限时体验中,多维度保障容器镜像安全

阿里巴巴云原生

PlatoFarm空投社区的逻辑,效仿无聊猿还是Dao理念使然

小哈区块

Spotify是怎样从Postgres切换至Cassandra的?_数据库_邵思华_InfoQ精选文章