阅读更多

0顶
0踩

企业架构
引用

原文:Inside the Magic Pocket
作者:James Cowling,在MIT就读PhD时师从图灵奖得主Barbara Liskov
翻译:孙薇




自从内部使用、扩展至EB级别的存储系统“Magic Pocket”发布之后,我们收到了很多正面反馈。我们会继续跟进Magic Pocket,陆续发布一系列技术博文,从各种有趣的角度来观察这个系统,包括我们的保护机制、操作工具以及软件与硬件之间的方法创新。不过首先,我们需要了解一些背景:在本文中,我们会从宏观角度对Magic Pocket的架构以及设计标准做以概览。

在之前的序篇中我们解释过(注:上篇的翻译请点击这里查看),Dropbox存储两类数据:文件内容与文件/用户的元数据。Magic Pocket是我们用来存储文件内容的系统,这些文件被分成块状存储,并存有副本以保证持久化,它们分布在整个系统中的多个物理位置上。

虽然Magic Pocket的基础是相当简单的核心协议组,但它本身仍是庞大而复杂的系统,因此我们需要略过一些细节。欢迎反馈,我们会在后续文章中尽量深入探究。
引用
注:在Dropbox内部,这个系统也被称为“MP”,这样我们就不用因为老提起“神奇(Magic)”这个词而感觉傻乎乎的了,本文中我们也会用MP来代指这个系统。

需求
不可变的数据块存储
MP是一个具有不变性的数据块存储系统,其中存储了多达4MB的加密块区文件,某个数据块一旦写入系统就不会再发生变化。不变性让一切简单得多。

当用户在Dropbox上对文件作出变更时,我们会在另一个单独的系统中(FileJournal)记录下所有的变更。这样一来,通过将支持易变性的逻辑转移到堆栈的更高层级,就能简单地存储不变的数据块了。许多大规模的存储系统都对可变数据块提供内部支持,但在较低层级中一般都是基于不可变的存储基元。

工作负载
Dropbox有很多数据,并具有高度的时间局部性(temporal locality)。许多数据在上传后一个小时之内会有频繁的访问量,而随着时间流逝,访问频率也会越来越低。这种模式合乎情理:Dropbox的用户有大量的协作任务,因此文件上传后很可能需要同步到其它设备上。但我们仍需要可靠的快速访问:也许从1997年开始你就没怎么看过税务记录了,但在需要的时候,你会想要立即看到。我们有一个相当“冷”的存储系统,但需要以低延迟快速访问所有数据区域。

为了处理这种工作负载,我们在构建系统时用到了硬盘驱动,由于硬盘具有持久、价廉、存储密集、延迟较低等优势,我们使用了SSD盘来保存数据库以及缓存内容。对于新近上传的内容,我们使用了高度的初始复制与缓存技术;而对于其余的数据,我们使用了更为高效的存储编码技术。

持久性
在MP中,持久性是必备的。理论上,我们要求系统的持久性能保持到地老天荒,除非小行星导致天灾——我们有更重要的事情要操心,不能因为随机的磁盘故障就出现数据损毁的问题。这些数据以效率更高的纠删码(erasure-coded)形式存储,同时还使用了跨区(地理位置)高度复制以保障在灾难或自然灾害发生时数据的安全性。

规模
对工程师来说,这个部分非常有趣,MP必须在差不多半年的时间里,从初始数十PB的原型成长到EB级别的庞然大物,这可真是空前的转变。因此,我们需要花费大量时间来思考、设计、构建原型,以克服能预见到的瓶颈问题。在这个过程中,我们也确认了这个架构完全可以扩展,因此在出现不可预知的需求时,也能进行相应的修改。

关于不可预知的需求,其实有很多例子:比如流量突然增长,网络集群间的路由器工作负载逐渐饱和。因此,我们需要修改数据存放的算法以及路径请求,以便更好地反映集群间的关联(以及可用存储量、集群成长计划等),并最终为集群间的网络架构带来改变。

简单性
是个工程师都知道,复杂性通常会导致不可靠性。有很多人在花费大量时间编写复杂的一致性协议后发现:在Paxos算法的重实现上浪费一整天可不是什么好主意。MP尽可能避免了quorum一致或分布式协作的情况,并在使用容错及可伸缩方式时大量利用集中的协作点。有时在数据块索引(Block Index)中,我们可以选择分布式哈希表或trie树,而不仅是巨大的分片MySQL集群。这一决策在简化开发与减少未知因素方面表现非常优秀。

数据模型
在讨论架构自身之前,我们先来研究一下所存储的内容。

在Dropbox的MP系统中,存储的是最大4MB的文件数据块:



经过压缩加密后,这些数据块(block)被发送到MP中进行存储,每个数据块都需要一个键值或者名称,大多情况下我们会使用SHA-256哈希。

然而在EB级别的存储系统中,4MB的数据量犹如沧海一粟,如果需要替换磁盘或者对某些数据使用纠删码技术,这个大小就显得太小了。为了简化这个问题,我们将这些数据块整合成1GB大小的逻辑存储容器,称为bucket。指定bucket中的数据块无需有什么相同的特性,只是上传的时间差不多相同。

为了保证可靠性,我们需要将这些bucket复制到多台物理机器上,新近上传的数据块可直接复制到多台机器上。最终系统会将包含数据块的bucket整合到一起,再通过纠删码技术提高存储的效率。我们使用volume来指代复制到一系列物理存储节点的一个或多个bucket。



总结一下:数据块可通过哈希进行识别,并且要写入bucket中。每个bucket都存储在volume中,volume分布在多台机器上,这些bucket或是复制的,或是纠删码形式的。

架构
那么现在我们了解了自身需求与数据模型,实际中MP又到底是什么样子呢?其实有些类似下图这样:



下面我们对上图的组件一一进行讲解。

前端(Frontends)
这些节点负责从系统外部接收存储请求,它们是MP的网关,负责确认某个数据块应当存储在什么地方,并在MP内部发布读取/写入数据块的命令。

数据块索引(Block Index)
这项服务将每个数据块映射到相应的存储bucket中,我们可以将其视为如下结构的大型数据库:

hash → cell, bucket, checksum
其真实结构要比上面的简化版稍微复杂些,还需要支持类似删除、跨区复制等操作。
数据块索引是一个巨大的分片MySQL集群,以RPC服务层为前端,再加上许多操作数据库和确保数据库可靠性的工具。起初我们计划建立一个专门的键值存储库,不过MySQL完全能胜任这一职责。我们已有数以千计跨Dropbox堆栈的数据库节点可提供服务,因此也能规模化地管理MySQL。

最终建立的系统可能略有些复杂,不过到目前为止我们都很满意。键值存储方式十分流行,性能表现优秀,但数据库有着高度的可靠性,所提供的数据模型让我们今后能容易地对结构和功能进行扩展。

跨区复制(Cross-zone replication)
跨区复制的后台程序负责异步执行操作,将一个区的所有数据块复制到另一个区中。在数据上传后一秒之内,系统就会将各个数据块写入远程区域。我们将这种复制延迟作为持久性模型的一部分,并确保在本地区域内对数据进行了足够广泛的复制。

单元(Cells)
每个单元都是独立的逻辑存储集群,存储着约50PB的原始数据。一般我们想给MP增加容量时,就会添加新的单元。由于各单元在逻辑上完全独立,各个单元分布在各个机架上,以确保每个单元的物理多样性最大化。

下面我们来深入探究一下其工作原理:



对象存储设备(OSD)
单元中最重要的角色就是OSD,整个磁盘上都是这种可以在单台机器上存储超过1PB数据、在各机架上存储超过8PB数据的存储容器。在这些设备上,缓存管理、负责磁盘调度与数据验证都有一些非常复杂的逻辑,不过从系统其他设备的角度来看,这些都属于“傻瓜”节点:它们存储数据块,但并不了解单元拓扑结构,也不参与分布式协议。

复制表(Replication Table)
复制表是单元的索引,从数据的每个逻辑bucket映射到该bucket所存储的volume与OSD上。与数据块索引类似,复制表是作为MySQL数据库存储的,但比MySQL数据库要小得多,更新也远没那么频繁。复制表的工作集完全适用于这些数据库中的存储,赋予我们对少量物理机器的高读取吞吐能力。

复制表的模式就像下面这样:
bucket → volume 
volume → OSDs, open, type, generation

这里有一个重要的概念就是open flag,指示着volume是“打开”还是“关闭”,开启的volume只在要写入新数据时打开,而关闭的volume则是保持不变的,可以在单元间安全移动。只有很少一些volume会在任何时候都保持开启。

类型(type)指定了volume的类型:是复制的volume,还是以某种纠删码形式编译的volume。生成(generation)序号则用于确保在从磁盘故障中恢复或优化存储布局时,能确保移动的volume一致。

Master
Master被认定是最适合单元的管理或协调程序,包含系统中大部分复杂的协议逻辑,主要任务就是监控OSD,并在出现错误时触发数据修复操作。同时,Master也负责协调类似创建新存储bucket这样的后台操作,在删除数据时触发垃圾回收操作,或者在垃圾回收过后,bucket过小时对其进行合并的操作。

复制表存储了volume状态,因此Master自身是完全soft-state的。注意:Master并不在数据层面中,其中并没有实时的流量,如果Master宕掉的话,单元是可以继续负责读取的。单元甚至可以在没有Master的情况下接收写入的操作,尽管最终会由于缺乏Master产生的新内容,而导致可用存储bucket耗尽的情况。如果Master没有时间创建新的bucket,总会有许多其他的单元可以支持写入操作。

逐单元运行单独的Master,能够为复杂的数据分布决策提供集中式的协调方案,并且不会因分布式协议而导致情况太过复杂。这种集中式的模式确实导致每个单元的大小受限:在内存与CPU开销达到瓶颈前,可以支持的容量只有大约100PB。幸好,从部署角度来看拥有多个单元也是非常方便的,可以提供更大的隔离性以避免级联故障。

volume管理器
volume管理器负责单元的重型搬运工作,根据Master发回的请求来移动volume,或者对volume执行纠删。这通常意味着系统要读取一大堆的OSD,向其他OSD写入,再将控制权交回给Master以完成操作。

volume管理器进程运行在与OSD相同的物理硬件上,这样可以让单元中闲置的存储硬件分担其繁重的网络容量需求。

协议
到这里,希望大家对较高层面的MP架构有了合理的了解。我们会对一些核心MP协议做个粗略的概览,并在今后的系列文中再进行详细的阐述。不过,幸好这些协议已经非常简单了。

Put
在接收Put请求前,前端便存有一些信息:它们定期与每个单元联系,以确认其中有多少可用空间,并包含能够接受新写入请求的open volume列表。

当收到Put请求时,前端会(通过数据块索引)首先检查数据块是否存在,然后选择存储数据块的目标volume。volume的选择方式如下:将单元负载均匀分担,使得存储集群之间的网络流量降到最低。然后前端会询问复制表,以确定目前存储在volume中的OSD。
前端向这些OSD发出存储命令,而OSD在响应前会fsync所有数据块到磁盘上(或者随身SSD上)。如果成功的话,前端会向数据块索引增加新的条目,并向客户端返回成功信息。如果有OSD出错,那么前端会尝试其他volume(可能在另一个单元中)。如果数据库索引出错,那么前端会将请求转发到另一个区域。Master会定期运行后台任务,清理出错操作部分写入的内容。



虽然这里有一些细节问题,最终还是相当简单的。如果采用仅需前端在volume中写入OSD一个子集的那种基于quorum的协议,我们就能避免一些重试的问题,并有可能实现低尾延迟,但会以增加复杂性为代价。在基于重试的方案中,系统对超时进行合理的管理便已实现了低尾延迟,性能也很让人满意。

Get
一旦我们知道了Put协议,Get的过程就不言自明了。前端会从数据块索引中查找单元与bucket,然后从复制表中查看volume与OSD,再之后从某个OSD中获取数据块,并在出现故障时进行重试。

如前所述,我们在MP中存储了复制数据与纠删码数据。由于volume中的每个OSD都存储了所有数据块,从复制volume中读取数据非常简单。



从纠删码volume中读取数据可能有些复杂,根据编码的方式,每个数据块我们都能从单个指定OSD中整体读取,因此大多读取请求都只会涉及单个磁盘,这极大地降低了我们硬盘的负载。如果OSD不可用,那么前端需要通过从其他OSD中读取编码数据,来重建数据块。volume管理器会在重建方面提供协助。



在上述的编码结构中,前端可以从OSD1读取Block A(以绿色圈出)。如果读取失败,系统会从其他OSD中读取足够数量的数据块,用以重建Block A(以红色圈出)。实际中编码要更复杂一些,并且经过了优化——在大多数故障情况下,会从更小的OSD子集中进行重建。

修复
Master运行着一些不同的协议,来管理单元中的volume,并在操作出错时执行清理,但Master所执行的最重要操作是修复。

修复是用来在磁盘出错时重新复制volume的操作,Master会继续通过我们的服务探测系统监控OSD的健康性,并在OSD离线15分钟之后触发修复操作——这个时间足以在不触发不必要修复的情况下重启一个节点了,但却不足以执行快速恢复并让任何有漏洞的窗口最小化。

在一个单元中,volume的分布是比较随机的,每个OSD包括几千个volume。这代表着如果我们失去了单个OSD,是可以同时通过其它数百个OSD来重建整套volume的:



在上图中我们失去了OSD3,但可以通过OSD 1、2、4、5来恢复volume A,volume B以及volume C。在实践中,每个OSD有几千个volume,它们与其它数百个OSD共享数据。这样我们就能通过数百个网卡和数千个碟盘来分担重建的流量,使得恢复时间减到最低。

当OSD出错时,Master首先会关闭OSD上的所有volume,并命令其它OSD在本地反映出这一变化。现在volume关闭,我们知道它们不会再接受写入操作,因此可以安全地移动。
然后Master创建了重建计划,选择一组OSD作为复制源头,另一组作为复制目标,通过这样的方式将负载分布到尽可能多的OSD上。这一步可以避免在特定磁盘或机器上出现流量峰值。重建计划让我们在每个OSD硬件配置上的花费减到最低,不过若没有将Master作为协调的中心点的话,将很难产生。

我们会让数据传输进程尽可能平滑,不过其中的volume管理器要负责将数据从源头复制到目标,在必要时进行纠删,然后将控制权返给Master。

最后的步骤相对简单一些,但却很重要:此刻在源与目标OSD上都存在有volume,但尚未执行移动。如果Master此时出错,volume会存在原来的位置,等待新的Master来修复。为了完成修复操作,Master首先要修改新OSD上的volume生成序号,然后更新复制表以便在新生成时(提交点)存储新的volume-OSD映像。现在随着生成序号递增,我们知道哪个volume在哪个OSD里,这一点将不会产生混淆,即便出错的OSD恢复了活动。

这个协议确保任何时候在任何节点出错时,都不会导致系统出现不一致的状态。在生产环境中我们见过各种类型的案例,其中一例中,数据库前端停滞了整整一个小时,之后恢复并向复制表转发请求,期间Master也出错并重启,发出了一套完全不同的修复操作,我们的一致性协议需要在面对这类故障时也完全可靠。Master还运行着很多其他的后台进程,比如Reconcile进程,它会确认OSD状态,并回滚出错的修复进程或者未完成的操作。

开放或闭合的volume模型是确保实时流量不会干扰后台操作的关键,让我们得以使用更为简单的一致性协议。

封装
感谢阅读本文,希望能让大家对MP的工作原理以及我们的一些设计动机有些了解。

这里主要的设计原则在于保持简单性!设计分布式存储系统是很大的挑战,不过构建一个能够大规模执行可靠操作的分布式存储系统\支持所有监控与验证系统还有工具,确保其正常运行更是困难得多。为对的问题给出正确的解决方案,在进行技术决策方面也有着非同寻常的重要性。MP大部分都是由一支不足6人的团队完成构建的,这要求我们将精力放在重要的事情上,所有人都要在项目成功上起到重要的作用。

很明显有很多细节本文没有提及,有些问题可能尚未涉及,不过我们已经在考虑这些问题了。在今后的博文中,我们会就大规模构建与运行系统的细节问题进行更细致的讨论,敬请期待。
  • 大小: 78.5 KB
  • 大小: 15.5 KB
  • 大小: 11.3 KB
  • 大小: 51.1 KB
  • 大小: 64.3 KB
  • 大小: 63.3 KB
  • 大小: 20.7 KB
  • 大小: 33.4 KB
  • 大小: 74.8 KB
0
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • Struts1.x工作原理概述

    1、Struts的核心是ActionServlet,它本质上就是一个Servlet,在Web.xml中配置ActionServlet为自动启动,指明Struts的配置文件所在位置,并指明Struts中工作条件 <servlet> <servlet-name>str...

  • Struts1.x ActionForm

    1.ActionForm   原理  继承于org.apache.struts.action.ActionForm,在jsp发出请求后,实例化servlet并载入struts-config.xml。通过执行action方法来获取mapping中的actionform的name,首先会在session、...

  • Struts1.x中ActionForm的应用探究

    Struts1.x中ActionForm的应用探究  2010-11-28 15:35:38| 分类:软件开发技术 | 标签:表单 salary  form  action  html |字号大中小订阅 (一)、ActionForm的特性 1.

  • Struts1.x原理

    1,启动项目,初始化ActionServlet,读取struts-config.xml中action信息存入ActionMapping里面 2,http请求,访问前端控制器ActionServlet 3,根据path路径找到在配置文件struts-config.xml中找到action,如果有...

  • Struts1.x使用入门

    Struts1框架是apache公司的开源子项目,是基于mvc逻辑分层的web层框架实现。是出现最早的web层框架,应用最广发。Struts1框架重点关注的是控制层,对于视图层只是提供了大量的标签;对于model层的影响微乎其微。 ...

  • Struts1.X 源码分析之ActionForm赋值原理

    struts1在处理请求参数之前,首先会根据配置文件action节点的name属性创建对应的ActionForm。如果配置了name属性,却找不到对应的ActionForm类也不会报错,只是不会处理本次请求的请求参数。 如果找到了对应的...

  • struts1.x和struts2.x的区别

    记的当初从Struts1.x转Struts2时,给我的感受是原理好像还是一样的,但是配置就比Struts1.x要简单多了。用起来很方便。其实他们的区别并不在于此,为了对Struts2和Strtus1.x进行全面的比较,让读者了解这两种框架...

  • struts1.x

    Day1: overview: 1.MVC概述 2.Struts框架 (技术概览)  3.Struts开发步骤 4.Struts新手上路 5.Struts详解 6.使用工具构建struts应用 ************************************************************** ...

  • Struts1.x工作原理

    Struts1.x是MVC的最基础实现,那么在整个Struts1.x的开发过程之中,请求的处理流程是最需要关注的, 同时也是我们可以发现程序问题的所在。 整个Struts之中时将一个Servlet的控制变为了两个部分:ActionForm,...

  • struts 1.x 原理

    Struts   当我接触到这个框架的时候,我就在想为什么是struts,而不是什么CraigFramework。结构、支撑,这样来理解也不难怪了。   为什么需要struts?   在struts in action这本书里,struts的开发者也讲到。...原理

  • java struts1_Struts1.X与Spring集成

    spring+struts(第一种方案)集成原理:在Action中取得BeanFactory,通过BeanFactory取得业务逻辑对象,调用业务逻辑方法。一,新建一个项目Spring_Struts_01二,步骤1、spring和Struts的依赖包配置*Struts(1)拷贝Struts...

  • Struts 1.X工作原理

    Struts 是Apache所开发的一个web应用框架,用来简化web开发的中常用、重复的工作。 原理图: 在以前的的开发中,浏览器的请求会找一个Servlet处理,然后返回浏览器,而在struts中所有的请求都会...1.引入Struts的jar包

  • Struts1.X与Spring集成——另外一种方案

    Struts1.X与Spring集成——第一种方案 此篇博客还以上篇博客的登录样例为例,介绍 Struts 1.X 与 Spring 集成的还有一种方案。 1,第一种方案 原理 回顾 第一种方案 集成原理:在 Action 中取得 BeanFactory,...

  • Struts 1.x 的工作原理

    初始化 ModuleConfig 对象,对web.xml中配置成自动启动的 ActionServlet ,读取 struts-config.xml 的配置信息,为不同的struts模块化初始化相应的ModuleConfig 对象 2. 发送请求。客户端提交表单,调用URL请求的...

  • Struts1.x框架基本原理

    在struts1.x中,常用的组件是JSP,ActionServlet,ActionForm,Action,JavaBean,配置文件等,关系如图: 其执行步骤是: 1,用户输入,JSP表单提交给ActionServlet 2,ActionServlet将表单信息封装在...

  • 起点小说解锁.js

    起点小说解锁.js

  • 299-煤炭大数据智能分析解决方案.pptx

    299-煤炭大数据智能分析解决方案.pptx

  • 299-教育行业信息化与数据平台建设分享.pptx

    299-教育行业信息化与数据平台建设分享.pptx

  • 基于Springboot+Vue酒店客房入住管理系统-毕业源码案例设计.zip

    网络技术和计算机技术发展至今,已经拥有了深厚的理论基础,并在现实中进行了充分运用,尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代,所以对于信息的宣传和管理就很关键。系统化是必要的,设计网上系统不仅会节约人力和管理成本,还会安全保存庞大的数据量,对于信息的维护和检索也不需要花费很多时间,非常的便利。 网上系统是在MySQL中建立数据表保存信息,运用SpringBoot框架和Java语言编写。并按照软件设计开发流程进行设计实现。系统具备友好性且功能完善。 网上系统在让售信息规范化的同时,也能及时通过数据输入的有效性规则检测出错误数据,让数据的录入达到准确性的目的,进而提升数据的可靠性,让系统数据的错误率降至最低。 关键词:vue;MySQL;SpringBoot框架 【引流】 Java、Python、Node.js、Spring Boot、Django、Express、MySQL、PostgreSQL、MongoDB、React、Angular、Vue、Bootstrap、Material-UI、Redis、Docker、Kubernetes

  • 时间复杂度的一些相关资源

    时间复杂度是计算机科学中用来评估算法效率的一个重要指标。它表示了算法执行时间随输入数据规模增长而变化的趋势。当我们比较不同算法的时间复杂度时,实际上是在比较它们在不同输入规模下的执行效率。 时间复杂度通常用大O符号来表示,它描述了算法执行时间上限的增长率。例如,O(n)表示算法执行时间与输入数据规模n呈线性关系,而O(n^2)则表示算法执行时间与n的平方成正比。当n增大时,O(n^2)算法的执行时间会比O(n)算法增长得更快。 在比较时间复杂度时,我们主要关注复杂度的增长趋势,而不是具体的执行时间。这是因为不同计算机硬件、操作系统和编译器等因素都会影响算法的实际执行时间,而时间复杂度则提供了一个与具体实现无关的评估标准。 一般来说,时间复杂度越低,算法的执行效率就越高。因此,在设计和选择算法时,我们通常希望找到时间复杂度尽可能低的方案。例如,在排序算法中,冒泡排序的时间复杂度为O(n^2),而快速排序的时间复杂度在平均情况下为O(nlogn),因此在处理大规模数据时,快速排序通常比冒泡排序更高效。 总之,时间复杂度是评估算法效率的重要工具,它帮助我们了解算法在不同输入规模下的性

Global site tag (gtag.js) - Google Analytics