GMTC深圳站售票最后一周,点击查看最新日程>> 了解详情
写点什么

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

  • 2015 年 8 月 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 读者交流群)。

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

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

关注

评论

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

电信的标准化组织

Geek_古藤模根

标准化 电信

翻译:《实用的Python编程》01_06_Files

codists

人工智能 后端 python 爬虫 数据结构与算法 文件操作

5.state更新流程(setState里到底发生了什么)

全栈潇晨

React React Hooks react源码

linux内核协议栈 邻居协议之ARP协议处理初始化

赖猫

Linux 协议栈 Linux内核

TDD测试驱动开发的实践心得

微言码道

架构 TDD 测试驱动开发

【STM32】ST-LINK下载器下载后需复位,程序才运行的问题

AXYZdong

硬件 stm32 2月春节不断更

竞猜商城系统软件制作

v16629866266

CoralCache:一个提高微服务可用性的中间件

华为云开发者社区

数据库 微服务 中间件 内存 CoralCache

百度Hydra工具在移动端UI兼容性测试上的高效应用

百度Geek说

测试 UI

如何提升网页核心指标

Vincent

GitHub上爆火的Java性能优化100+小技巧!(干货建议收藏)

Java架构师迁哥

TCP 协议灵魂问题,巩固你的网路底层基础

对DevOps的九大误解,是时候纠正了!

禅道项目管理

开源 DevOps 敏捷 自动化 持续交付

6.render阶段(厉害了,我有创建Fiber的技能)

全栈潇晨

React React Hooks react源码

使用 Tye 辅助开发 k8s 应用竟如此简单(三)

newbe36524

Docker 微服务 k8s dotnet

前端学习总结,经验分享,项目经验分享过程

魔王哪吒

学习 程序员 Vue 前端 2月春节不断更

元旦立下的Flag,春节后该如何实现?

脑极体

如何读懂CNN、BBC、经济学人、卫报、纽约时报?看完这本经典即可事半功倍!

wbliu85

学习 英语

LeetCode题解:1143. 最长公共子序列,动态规划,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

日记 2021年2月19日(周五)

Changing Lin

2月春节不断更

让虞书欣、李诞拍到停不下来!AR+AI双引擎的互动小游戏,如何打开IP新玩法?

爱奇艺技术产品团队

DPDK大页内存原理

赖猫

Linux DPDK

地表建筑物识别Dayo1

IT蜗壳-Tango

七日更 2月春节不断更

Elasticsearch Search API 基础语法

escray

elastic 七日更 死磕Elasticsearch 60天通过Elastic认证考试 2月春节不断更

Hive HMS Canary 时间较长异常分析

笨小康

大数据 hadoop hive

诊所数字化:诊所医护人员绩效指标评估方式

boshi

绩效 数字化转型 医疗 七日更

【LeetCode】K 连续位的最小翻转次数Java题解

HQ数字卡

算法 LeetCode 2月春节不断更

最新Hadoop的面试题总结

大数据老哥

15. Python 程序运行速度如何提高十倍?第一遍滚雪球学 Python 收工

梦想橡皮擦

Python 2月春节不断更

技术需求文档,应当这么写!

穿甲兵

需求 文档

一、MongoDB简介

Kylin

数据库 mongodb 学习 七日更 二月春节不断更

数据cool谈(第2期)寻找下一代企业级数据库

数据cool谈(第2期)寻找下一代企业级数据库

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