《分布式对象存储--原理、架构及GO语言实现》 读书笔记

前言

云存储已经成为大家司空见惯的一个网络服务了,比如常用的百度云盘、已经成为实质上的业界标准的亚马逊S3、微软的oneDrive、苹果公司的iCloud和谷歌的Google Cloud等。本书从云存储的需求出发讲述对象存储的原理,循序渐进、从无到有地建立起一个分布式对象存储的架构。

对象存储于云储存的关系

对象储存是云存储的一部分,它提供了云储存后端的存储服务。云存储是建立在对象存储之上的一个整体的解决方案,除了后端的存储服务之外,它还需要提供包括各种各样操作系统和平台上运行的客户端、身份认证、多种管理和监控功能等。

分布式存储的好处

传统的高端服务器性能强劲、成本高昂,以前只有大公司用来搭建自己的私有储存。互联网生态下的云存储则是用数量弥补质量,以大量低成本的普通PC服务器组成网络集群来提供服务。相比传统的高端服务器来说,同样价格下分布式存储提供的服务更好、性价比更高,且新节点的扩展以及坏旧节点的替换更为方便。

对象存储的yzui

  1. 提升了存储系统的扩展性;
  2. 已更低的代价提供了数据冗余的能力;

对象存储简介

和传统网络存储的区别

传统的网络存储主要有两类,分别是NAS(Network Attached Storage)和SAN(Storage Area Network)。

NAS是一个提供了存储工功能和文件系统的网络服务器。客户端可以访问NAS上的文件系统,还可以上传和下载文件。对于客户端来说,NAS就是一个网络上的文件服务器。

SAN和NAS的区别是SAN只提供块存储,而把文件系统的抽象交给客户端来管理。对于客户端来说,SAN就是一块磁盘,可以对其格式化、创建文件系统并挂载。

数据的管理方式 访问数据的方式
网络文件系统 以一个个文件的形式来管理的。 客户端通过NFS等网络协议访问某个远程服务器上存储的文件。
块存储 以数据块的形式来管理的,每个数据块都有它自己的地址,但是没有额外的背景信息。 客户端通过数据块的地址访问SAN上的数据块。
对象存储 以对象的方式来管理数据,一个对象通常包括3个部分。对象的数据、对象的元数据以及一个全局唯一的标识符。 通过REST网络服务访问对象。

单机版对象存储的架构

在一台服务器上运行一个HTTP服务器提供REST接口,该服务通过访问本地磁盘来管理对象的存取。

REST接口

单机版的REST接口只实现了对象的PUT和GET方法。

  1. PUT /objects/<object_name>

    请求正文(Request Body)

    客户端通过PUT方法将一个对象上传至服务器,服务器则将改对象保存在本地磁盘上。

  2. GET /objects/<object_name>

    响应正文(Response Body)

    客户端通过GET方法从服务器下载一个对象,服务器在本地磁盘上查找并读取该对象。

可扩展的分布式系统

什么是分布式系统

一个分布式系统要求各节点分布在网络上,并通过消息传递来合作完成一个共同的目标。分布式系统的三大关键特征是:节点之间并发工作、没有全局锁以及某个节点上发生的错误不影响其他节点。

分布式系统的好处在于可扩展性,只需要加入新的节点就可以自有扩展集群的性能。

接口和数据存储分离的架构

接口服务层提供了对外的REST接口,而数据服务层则提供数据的存储功能。接口服务处理客户端的请求,然后向数据服务存取对象,数据服务处理来自接口服务的请求并在本地磁盘上存取对象。

image-20190929103130296
(图)接口和数据存储分离的架构

这里的架构,接口服务于数据服务之间的接口有两种。

  1. 实现对象的存取使用REST接口。也就是说服务服务本身也提供REST接口。
  2. 通过RabbitMQ消息队列进行通信。在这里的架构中对RabbitMQ的使用分为两种模式,一种模式是向某个exchange进行一对多的消息群发,另一种模式则是想某个消息对象进行一对一的消息单发。

每个数据服务节点都需要向所有的接口服务节点通知自身的存在,为此,创建了一个名为apiServers的exchange,每一台数据服务节点都会持续向这个exchange发送心跳消息。

另外,接口服务需要在收到对象GET请求时定位到该对象被保存在哪个数据服务节点上,所以还需要创建一个名为dataServers的exchange。所以的数据服务节点绑定在这个exchange并接收来自接口服务的定位消息。

之所以必须使用REST和消息队列这两种不同类型的接口是为了满足不同的需求:对象存取的特点是数据量有可能很大,不适合将一个巨大的对象通过消息队列进行传输。而REST接口虽然能够处理大数据量传输,但是对于群发却显得力不从心。

元数据服务

上边的实现是一个分布式对象存储系统的雏形,这个雏形的问题是无法区分同一个对象的不同版本。为了记录对象版本以及其他一些元数据,我们将一个新的组件加入到我们的架构:元数据服务。

什么是元数据

元数据指的是对象的描述信息,例如对象的名字、版本、大小以及散列值等,这些都是系统定义的元数据。除了系统定义的元数据以外,用户也可以为这个对象添加自定义的元数据,通常是以键值对的形式保存的任意描述信息,比如一张照片的拍摄时间和拍摄地点,一首歌的作者和演唱者等。

加入元数据服务的架构

我们选择了ElasticSearch(以下简称ES)作为元数据服务。

image-20190929110349182
(图)加入元数据服务的架构

数据校验和去重

何为去重

去重是一种消除数据多余副本的数据压缩技术。对于一个对象存储系统来说,通常都会有来自不同(或相同)用户的大量重复数据,如果没有去重,每一份重复的数据都会占用存储空间。

需要数据校验的原因

  • 客户端可能是一个恶意的客户端,故意上传不一致的数据给服务;
  • 客户端有bug,计算出来的数据是错误的;
  • 客户端计算的数据正确,但是在传输过程中发生了错误。

校验客户端提供的散列值和我们自己根据对象数据计算出来的散列值是否一致。

实现数据校验的方法

在数据服务商提供对象的缓存功能,接口服务不需要将用户上传的对象缓存在自身节点的内存里,而是传输到某个数据服务节点的一个临时对象里,并在传输数据的同时计算器散列值。当整个传输完毕以后,散列值计算也同步完成,如果一致,接口节点需要将临时对象转换成正式对象;如果不一致,则将临时对象删除。

数据冗余

在计算机领域,数据冗余是指在存储和传输过程中,除了实际需要的数据,还存在一些额外数据用来纠正错误。显而易见的冗余策略是每个对象都保留两个或多个副本,由接口福谁复杂将其存储在不同的数据服务节点上。多副本冗余的策略胜在实现很简单,但是代价也比较高。本书用的是Reed Solomon(以下简称RS)纠删码。

在编码理论学中,RS纠删码属于非二进制循环码,它的实现基于有限域上的一元多项式,并被广泛应用于CD、DVD、蓝光、QR码等消费品技术,DSL、WiMAX等数据传输技术,DVB、ATSC等广播技术以及卫星通讯技术等。

RS纠删码允许我们选择数据片和校验片的数量,本书选择了4个数据片加2个校验片,也就是说会把一个完整的对象平均分成6个分片对象,其中包括4个数据片对象,每个对象的大小是原始对象大小的25%,另外还有两个校验片,起大小和数据片一样。这6个分片对象被接口服务存储在6个不同的数据服务节点上,只需要其中任意4个就可以恢复出完整的对象。

在没有任何冗余策略的情况下,我们的对象占用存储空间大小是100%,而抵御能力为0;如果采用双副本冗余策略,存储空间要求是200%,抵御数据损坏能力是1(可以丢失2个副本中的任意1个)。而使用4+2的RS码策略,存储空间要求是150%,抵御能力是2(可以丢失6个分片对象里的两个)。

断点续传

断点续传功能分两部分,分别是断点下载和断点上传。

断点下载

断点下载的实现非常简单,客户端在GET对象请求时,通过设置Range头来告诉接口服务需要从什么位置开始输出对象的数据。

接口服务在成功打开对象数据流之后,会额外对用rs.RSGetStream.Seek方法跳至客户端请求的位置,然后才开始输出数据。

断点上传

断点上传的流程则要比断点下载复杂得多,这是由HTTP服务的特性导致的。客户端在下载时并不在乎数据的完整性,一旦发生网络故障,数据下到哪算哪,下次继续从最后下载的数据位置开始续传就可以了。

但是对于上传来说,接口服务会对数据进行散列值校验,当发生网络故障时,如果上传的数据跟期望的不一致,那么整个上传的数据都会被丢弃。所以断点上传在开始就需要客户端和接口服务做好约定,使用特定的接口进行上传。

客户端在知道自己要上传大对象时就主动改用对象POST接口,提供对象的散列值和大小。接口服务搜索6个数据服务并分别POST临时对象接口。数据服务的地址以及返回的uuid会被记录在一个 token里返回给客户端。

客户端POST对象后会得到一个 token。对 token进行PUT可以上传数据。在上传时客户端需要指定 range头部来告诉接口服务上传数据的范围。接口服务对 token进行解码,获取6个分片所在的数据服务地址以及uuid,分别调用PATCH将数据写入6个临时对象。