[关闭]
@sambodhi 2017-03-03T09:12:15.000000Z 字数 3279 阅读 3502

GitHub将持久数据从Redis迁出

前言

Redis是一个开源、支持网络、基于内存、键值对存储数据库,使用ANSI C编写。从2015年6月开始,Redis 的开发由Redis Labs赞助,在 2013年5月至2015年6月期间,其开发由Pivotal赞助。在2013年5月之前,其开发由VMware赞助。根据月度排行网站DB-Engines.com的数据显示,Redis是最流行的键值对存储数据库。

Redis通常将全部的数据存储在内存中。2.4版本后可配置为使用虚拟内存,一部分数据集存储在硬盘上,但这个特性废弃了。

目前通过两种方式实现持久化:

当数据依赖不再需要,Redis这种基于内存的性质,与在执行一个事务时将每个变化都写入硬盘的数据库系统相比就显得执行效率非常高。写与读操作速度没有明显差别。

但是,Redis的两种持久化方式也有明显的缺点:

这两个缺点是个很大的痛点。为了解决这些痛点,GitHub的两位工程师Bryana KnightMiguel Fernández日前写了一篇文章,讲述了将持久数据从Redis迁出的经验,

经Bryana Knight和Miguel Fernández的独家授权,InfoQ翻译并整理了他们合著的文章,以飨广大读者,以下是正文。


GitHub怎样使用Redis?

我们用GitHub作为一个LRU缓存来方便地将那些最初坚持认为有用的大量计算结果数据存储在Git存储库或MySQL中。我们称之为瞬态化Redis。

我们还启用了持久性,这给我们提供了不存储在任何其他地方的数据的持久性保证。我们用它来存储各种各样的数值:从具有高读/写比的稀疏数据(如配置设置、计数器或质量度量),到为核心功能提供极其动态的信息(如垃圾邮件分析)。我们称之为持久化Redis。

最近我们决定禁用Redis的持久性,不再使用它作为我们数据的事实来源。这个选择背后的主要动机是:

透明地过渡这些信息涉及规划和协调。对于使用持久化Redis的每个问题域,我们考虑了操作量、数据结构和不同的访问模式,以预测对当前MySQL容量的影响,以及配置新硬件的需求。

对于大多数Callsite,我们用GitHub::KV替换了持久性Redis,这是存储在我们自己构建的InnoDB上的MySQL键值,具有密钥过期的功能。我们几乎像曾经使用过的Redis那样使用GutHUB::KV,从趋势库和浏览页面的用户,来以速率限制进行垃圾用户的检测。

我们最大的挑战:迁移活动feed

我们在GitHub有很多“事件”。为存储库加星标,关闭问题并推送提交都是我们在活动feed中显示的事件,正如你在GitHub主页上发现的那样。

我们将Redis作为存储所有事件的MySQL表的辅助索引器。以前,当事件发生时,我们将事件标识符“分派”给与应该显示事件的每个用户的feed对应的Redis键。有很多写入操作和Redis键,以至没有单个表能够处理这个fanout。我们不能简单地用这个代码路径中的GitHub::KV替换Redis,并称之为完工。

我们的第一步是收集一些度量,让它们告诉我们该做什么。我们为不同类型的feed提取了数字,并为每个时间线类型计算了每秒的写入和读取数(例如,问题库中的事件由用户执行的公共事件等。)一个时间线从来没有被读取过,所以我们能够立即砍掉它,并立即拟出一个名单。在剩余的时间线中,这两个写操作如此密集,以致我们都不知道能否移植到MySQL。这就是我们要开始的地方。

让我们逐步解释如何处理这两个有问题的时间线之一。Redis每天针对这些时间线写入次数总计超过3.5亿次,如果您将主页上的事件feed切换到您所属组织的主页,将能看到“组织时间线”就占据了67%。记得吗,我说过给每个应该看到它们的用户“分派”事件ID给Redis?长话短说吧,我们正在推送事件ID来为每个事件和组织中每个用户分离出Redis键。因此对于每天产生100个事件并具有1000个成员的活动组织,对于仅有100个事件的Redis就可能有十万次的写入。这样效率很低,所需的MySQL容量远远超过我们的意愿。

甚至在考虑MySQL之前,我们改变了适用于此时间线的Redis键的读写方式。我们将为组织的一个键编写每个发生的事件,然后在检索时,我们将拒绝请求用户不应该看到的那些事件。而不是每当事件被fanout时进行过滤,我们会在读取时进行。

这导致该功能的写操作显著降低了65%,让我们更接近这个目标:将活动feed完全迁移到MySQL。

虽然我们目标是停止使用Redis作为持久性数据存储,但我们认为,鉴于这是一个遗留的代码,在过去多年来一直演变,还有一些提高效率的空间。由于数据紧凑且被正确索引,因此读取速度很快。了解这点后,我们决定停止单独编写某些时间线,我们可以从其他人包含的时间构成,因此减少了剩余的写入的30%(总共11%)。我们到达了这一点:98%的时间,做到了每秒写入不到1500个键,峰值每秒不到2100个键。这是我们想到的一系列操作,可以继续使用当前的MySQL基础设施而不必添加新服务器。

当我们准备将活动feed迁移到MySQL时,我们尝试了不同的模式设计,尝试了每事件记录的标准化和每记录的feed子集大小固定化。我们甚至尝试使用MySQL 5.7 JSON数据类型来为事件ID列表建模。然而,我们最终采用类似GitHub::KV的模式,只是没有我们不需要的一些功能,像记录的上次更新和到期时间戳。

在该模式之上,并且受到Redis流水线的启发,我们创建了一个小型库,用于批处理和限制分派到不同提要的同一事件的写入。

万事俱备后,我们开始迁移所拥有feed的每种类型,从最小的“风险”开始。我们测量了迁移任何基于其写操作的数量给定类型的风险,因为读取不是真正的瓶颈。

迁移每种提要类型后,我们检查了集群能力、争用和复制延迟。我们的特性标志已经就位,能够写入MySQL,同时仍可写入持久化的Redis,如此一来,如果我们必须回滚的话,就不会中断用户体验。一旦我们确认写入性能良好,并且Redis中所有事件都拷贝到MySQL,我们就翻转另一个特性标志以从新的数据存储读取,再次测试能力,然后继续处理下一个活动feed类型。

当我们确定一切都迁移并正常执行时,我们部署了一个新的pull请求,删除持久化Redis的所有调用。这些是截至今天的结果性能数据:

从上述两图中,可以看到存储级别的表现,写入(mset)在峰值低于270wps,读取(mget)低于460ps。由于事件写入之前采用了批处理的方式,值得这些值远低于正在写入的事件数。

复制延迟在峰值时低于180毫秒。与写入操作次数相关的蓝线显示在写入任何批处理之前如何检查延迟,以防止副本失去同步。

从中领悟到了什么

到了最后,我们就将Redis作为一些我们的用例的持久性数据存储。由于需要一些可以用于github.com和GitHub Enterprise的东西,因此,我们决定利用MySQL的运营经验。然而,显然MySQL不是一个一刀切的解决方案,我们不得不依靠数据和指标来指导我们在GitHub的事件提要中使用它。我们的第一个优先事项是不再使用Redis,我们的数据驱动方法使我们能够优化和提高性能。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注