将持久数据从Redis中迁出

WalterFfj 8年前
   <h2>前言</h2>    <p>Redis 是一个开源、支持网络、基于内存、键值对存储数据库,使用ANSI C编写。从2015年6月开始,Redis 的开发 由Redis Labs赞助 ,在 2013年5月至2015年6月期间,其开发由Pivotal赞助。在2013年5月之前,其开发 由VMware赞助 。根据月度排行 网站DB-Engines.com的数据 显示,Redis是最流行的键值对存储数据库。</p>    <p>Redis通常将全部的数据存储在内存中。2.4版本后可配置为使用 虚拟内存 ,一部分数据集存储在硬盘上,但这个特性废弃了。</p>    <p>目前通过两种方式实现持久化:</p>    <ul>     <li>使用快照,一种半持久耐用模式。不时地将数据集以异步方式从内存以RDB格式写入硬盘。</li>     <li>1.1版本开始使用更安全的AOF格式替代,一种只能追加的日志类型。将数据集修改操作记录起来。Redis能够在后台对只可追加的记录作修改来避免无限增长的日志。</li>    </ul>    <p>当数据依赖不再需要,Redis这种基于内存的性质, 与在执行一个事务时将每个变化都写入硬盘的数据库系统相比就显得执行效率非常高 。写与读操作速度没有明显差别。</p>    <p>但是,Redis的两种持久化方式也有明显的缺点:</p>    <ul>     <li>RDB需要定时持久化,风险是可能会丢两次持久之间的数据,量可能很大。</li>     <li>AOF每秒fsync一次指令硬盘,如果硬盘IO慢,会阻塞父进程;风险是会丢失1秒多的数据;在Rewrite过程中,主进程把指令存到mem-buffer中,最后写盘时会阻塞主进程。</li>    </ul>    <p>这两个缺点是个很大的痛点。为了解决这些痛点,GitHub的两位工程师 Bryana Knight 和 Miguel Fernández 日前写了一篇 文章 ,讲述了将持久数据从Redis迁出的经验,</p>    <p>经Bryana Knight和Miguel Fernández的独家授权,InfoQ翻译并整理了他们合著的文章,以飨广大读者,以下是正文。</p>    <h2>GiHub怎样使用Redis?</h2>    <p>最初我们坚持用它作为 LRU缓存 ,方便地将大量的计算结果存储在Git存储库或MySQL中的数据上。我们称之为瞬态化Redis。</p>    <p>我们还启用了 持久性 ,这给我们提供了对不存储在其他任何地方的数据持久性的保证。我们用它来存储各种各样的数值:从具有高读/写比率的稀疏数据(如配置设置、计数器或质量指标),到为核心功能提供支持的动态信息,如(垃圾邮件分析)。我们称之为持久化Redis。</p>    <p>最近我们决定禁用Redis的持久性,不再使用它作为我们数据的真实来源。这个选择背后的主要动机是:</p>    <ul>     <li>通过消除复杂性,来降低我们的持久性基础架构的运营成本。</li>     <li>利用我们的专长来操作MySQL。</li>     <li>通过在将服务器状态的重大变化写入磁盘的过程中消除I/O延迟,来获得一些额外的性能。</li>    </ul>    <p>转换所有这些信息透明地参与规划和协调。对于使用持久化Redis的每个问题域,我们考虑了操作量、数据结构和不同的访问模式,以预测对当前MySQL容量的影响,以及配置新硬件的需求。</p>    <p>对于大多数Callsite(动态调用站点),我们用 GitHub::KV 替换了持久性Redis,这是我们自己在InnoDB上构建的一个MySQL键值对存储,具有密钥过期的功能。我们能够使用与我们曾经使用过的Redis几乎相同的 GutHUB::KV ,从趋势存储库和用户的浏览页面,来限制垃圾用户的检测。</p>    <h2>我们最大的挑战:迁移活动提要</h2>    <p>我们在GitHub有很多“事件”。为存储库加星标,关闭问题并推送提交都是我们在活动源中显示的事件,正如你在 <a href="/misc/goto?guid=4959738350213109457" rel="nofollow,noindex">GitHub主页</a> 上找到的事件那样。</p>    <p>我们使用Redis作为存储所有事件的MySQL表的辅助索引器。以前,当事件发生时,我们将事件标识符“分派”给与应该显示事件的每个用户的提要对应的Redis键。有很多写入操作和Redis键,以至没有单个表能够处理这个扇出。我们不能简单地用这个代码路径中的 GitHub::KV 替换Redis,并称之为完工。</p>    <p>我们的第一步是收集一些指标,让它们告诉我们该做什么。我们为不同类型的提要提取了数字,并为每个时间轴类型计算了每秒的写入和读取数(例如, 在存储库中发出事件 , 由用户执行的公共事件 等。)一个时间轴没有被读取,所以我们能够立即砍掉它,并立即敲出一个名单。在剩余的时间轴中,这两个写操作如此密集,以致我们都不知道能否移植到MySQL。这就是我们要开始的地方。</p>    <p>让我们来看看如何处理这两个有问题的时间轴之一。如果您将主页上的事件提要切换到您所属的其中某个组织,那么您可以看到的“组织时间轴”占了Redis针对这些时间轴每天总写入次数超过3.5亿次的67%。记得吗,我说过给每个应该看到它们的用户“分派”事件ID给Redis?长话短说吧,我们正在推送事件ID来为每个事件和组织中每个用户分离出Redis键。因此对于每天产生100个时间并具有1000个成员的活动组织,在Redis的100个事件可能就会达到10万次写入。这样效率很低,所需的MySQL容量远远超过我们的意愿。</p>    <p>甚至在考虑MySQL之前,我们改变了适用于此时间轴的Redis键的写入和读取的方式。我们将为组织的一个键编写每个事件,然后在检索时,我们将拒绝请求用户不应该看到的那些事件。而不是每次事件被扇出时进行过滤,我们会在读取时进行。</p>    <p>导致为此功能的写操作降低了65%,让我们更接近这个目标:将活动提要完全移动到MySQL。</p>    <p>(点击放大图像)</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/0ff47235bb95d78a4df46d340f41fc85.png"></p>    <p>虽然单一目标是停止使用Redis作为持久性数据存储,我们认为,鉴于这是一个遗留的代码,在过去多年来一直演变,还有一些提高效率的空间。读取很快,因为数据紧凑、而且被正确索引。了解这点后,我们决定停止单独写入某些可以从其他人包含的事件中的时间轴。因此,可以减少剩余写入的另外30%(总共~11%)。我们到达了这一点:每秒在98%的时间写入少于1500个键,每秒写入低于2100个键。这是我们想到的一系列操作,可以继续使用当前的MySQL基础设施而不必添加新服务器。</p>    <p>当我们准备将活动提要迁移到MySQL时,我们尝试了不同的模式设计,尝试了每事件记录的标准化和每记录的提要子集大小固定化。我们甚至尝试使用 MySQL 5.7 JSON数据类型 来为事件ID列表建模。然而,我们最终采用类似 GitHub::KV 的模式,</p>    <p>然而,我们最终采用了类似 GitHub::KV 的模式,只是没有我们不需要的一些功能,像记录的上次更新和到期时间戳。</p>    <p>在该模式之上,并且受到 <a href="/misc/goto?guid=4959738350306264716" rel="nofollow,noindex">Redis流水线</a> 的启发,我们创建了一个小型库,用于批处理和限制分派到不同提要的同一事件的写入。</p>    <p>万事俱备后,我们开始迁移所拥有的提要的每种类型,从最小的“风险”开始。我们测量了任何特定类型基于其写入操作的数量的迁移风险,因为读取不是真正的瓶颈。</p>    <p>迁移每种提要类型后,我们检查了集群能力、正用和复制延迟。我们有一些特征标志,能够写入MySQL,同时仍可写入持久化的Redis,如此一来,如果我们必须回滚的话,就不会中断用户体验。一旦我们确认写入性能良好,并且Redis中所有事件都拷贝到MySQL,我们就翻转另一个特征标志以从新的数据存储读取,再次测量容量,然后继续下一个活动提要类型。</p>    <p>当我们确定一切都迁移并正常执行时,我们部署了一个新的pull请求,删除所有的调用者持久性Redis。这些是截至今天的结果性能数据:</p>    <p>(点击放大图像)</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/c28f741392e980d1c7aa5c5b1a7145b7.png"></p>    <p>(点击放大图像)</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/147886265b53175511e8bf96fdce3fca.png"></p>    <p>从上述两图中,可以看到存储级别的表现如何,写入( mset )在峰值低于270wps,读取( mget )低于460ps。由于事件写入之前采用了批处理的方式,值得这些值远低于正在写入的事件数。</p>    <p>(点击放大图像)</p>    <p><img src="https://simg.open-open.com/show/29206d820c855f5fd4f1dd8c3ac78d11.png"></p>    <p>复制延迟在峰值时低于180毫秒。与写入操作次数相关的蓝线显示在写入任何批处理之前如何检查延迟,以防止副本失去同步。</p>    <h2>从中领悟到了什么</h2>    <p>到了最后,我们就将Redis作为一些我们的用例的持久性数据存储。由于需要一些可以用于github.com和GitHub Enterprise的东西,因此,我们决定利用MySQL的运营经验。然而,显然MySQL不是一个一刀切的解决方案,我们不得不依靠数据和指标来指导我们在GitHub的事件提要中使用它。我们的第一个优先事项是不再使用Redis,我们的数据驱动方法使我们能够优化和提高性能。</p>    <p> </p>    <p> </p>    <p>来自:http://www.infoq.com/cn/articles/github-moves-persistent-data-out-of-redis</p>    <p> </p>