1. 前言
今天来聊聊我正在读的一本分布式对象存储的书籍。
前天11月10号,想着京东有满200-100的活动,就买了一些书,准备沉淀一下。自己打算在分布式系统上搞几年,所以买的书基本上都是关于分布式存储的。本身也没想着买一些分布式系统的经典教材,就随便选了几本京东上销量比较高的,偏实用一些的书籍。权当心血来潮,未经过任何调研。
当前看的这本是《分布式对象存储-原理,架构和go语言实现》。
2. 总体感觉
书今天上午开始看,还未看完,先说一下总体感觉,从半价角度上来说,这本书物超所值,如果是给有一定编程能力且打算入行分布式系统的同学看,那么就更值得了。对于想自己动手实现一个分布式系统的同学,按照书中的代码,一点点的敲,完整实现一个分布式的对象存储不成问题,应该会有不少的成就感。然而若是给架构师或者研究分布式的同学看,不是很建议,内容显得过宽泛且不够深入(主观感觉)。书中虽然知识点众多,但各个知识点有效篇幅内容明显不足,而且更大的烦扰是各种实现层代码散布全书导致关于分布式的核心内容就更少了(不过对于一个想自己实现一个系统的同学来说,更多的代码反而更好,这就显得仁者见仁智者见智了)。保守估计关于代码以及代码的函数流程的阐述占据了全书的1/2,甚至更多。
- 优点
完整的讲述了一个分布式对象存储从0到1的过程,不断改造和扩展新功能,循序渐进的方式来讲解,是其优势之处。书中涉及到知识点很多,有分布式系统的基本涉及,元数据管理,去重,纠删码,压缩等,也就是说的常见的分布式系统的feature方面都有,能够给一个不在这个领域的人一个宏观的感知。例如,本书能够回答的是目前的分布式系统的架构是如何,有什么主要特性。 - 缺点
大量的代码让我遇到代码的时候都大量的跳跃,一上午的时间,读了三章(其实很多代码都没有细看,主要看了架构设计以及思路设计),收获偏小。但是因为自己还没有读完,所以也没有太大的发言权,只是自己一厢情愿的看法而已了。
3. 内容总结
看书不总结书中内容,对于记忆力很差的我,等于白看,因此现在把我看到的内容写下来,权当一记录,也希望能够给其他想买这本书的同学一些参考。
3.1. 对象存储简介
传统的存储系统有NAS和SAN,它们本质上是两个点,NAS是相当于有一个文件服务器,可进行文件上传和下载等操作,而SAN更强调storage的概念,它将多个存储设备组成一个整体,对外只提供storage的功能,而没有文件等上层抽象概念,可以把它想象成一个大的网络硬盘。这些内容其实与对象存储并不矛盾。对象存储按照其名字来说,核心就是将对象进行存储。对象有唯一标识,可认为是key,然后对象的数据,可认为是value。
如果实现一个单机版本的对象存储呢?
基于Go语言,使用REST请求(GET用于下载,PUT用于上传)
- 启动一个Listen Service,并且绑定handler(处理函数)
- 在处理函数判断是GET还是PUT请求然后进入不同的处理
- 如果是GET请求,直接根据路径,拿到对象的内容(其实就是一个本地的file文件),返回给客户端
- 如果是PUT请求,根据路径,将post的body写入到指定的文件中
- 客户端模拟使用curl,可用于演示put和get请求
这里不得不说go实现真的好简洁,寥寥几十行,就实现了基本的HTTP服务,并能读写文件数据返回到客户端。
书中很多地方说了REST,原来并不知道这种称呼,简单看了一下网上的介绍也不甚了了,基本感觉就是提供了数据访问的接口的标准定义,都按照这样的标准请求和返回,方便解析。有点像HTTP的规范定义版本?
3.2. 分布式系统
对于3.1的单机对象存储,最先要做的就是实现分布式,也就是可扩展性。要想做到这一点,一个重要的概念就是请求和数据分离。其实就是有专门的接口服务负责接收请求并转发(感觉好像代理服务器,但是不同的是它需要维护数据节点的状态信息),有专门的数据服务负责数据的存储。这样的话,如果容量不足需要扩容数据存储,直接拉几台机器加入到数据集群中即可。
CSDN上的PDF版本需要5分,奈何没那么多积分,只能手机拍照截图了。如上图所示,为了实现两个服务的分离,接口存储和数据存储需要加入中间层,以隔离两个服务。 本书使用RabbitMQ(看着像消息队列Kafka),负责上下层之间的消息转发。当客户端的写入操作到达接口层,需要找到相应的数据节点,完成写入;当客户端的读取操作到达接口层,也需要找到存储的节点,发起读取;另外接口层需要知道哪些数据节点是alive的,这样以便于写入的时候避开挂掉的数据节点。
欲完成以上功能,需要如下:
- 心跳
数据节点每间隔一断时间(5s)发起心跳请求消息,扔到apiServer exchange中(感觉像kafka的topic)。接口服务作为消费者,消费apiServer中的消息,收到某个节点心跳就记录当前节点。注意,这个心跳消息会广播到所有的接口服务(说是广播,其实从本质上是所有的接口服务都独立订阅了apiServer,都能收到apiServer中的全部消息)。这样每个接口服务都能看到所有的数据节点,接口服务节点之间没有任何差异,这也就实现了接口服务的平行扩容。 - 读
- 客户端发GET到接口服务
- 接口服务生产消息到dataServer
- dataServer中的消息被所有的数据节点消费,只有具有指定对象的data节点才会返回,并返回其监听地址
- 接口服务收到data节点的监听地址后,向data节点发起数据读取请求
- 接口服务节点将读取到的数据返回给客户端
- 写
与读的过程基本一致,不同的是写入的节点是随机选取的其中一个,然后是将数据传递到数据节点,交给数据节点写入。
总体来说,读和写的过程可以归结为两个步骤:
- 定位数据节点(对于读,只有存储了请求对象的节点返回;对于写,随机选择)
- 发起数据读写(接口节点和数据节点传递对象的内容,或者写入,或者读出)
这里面有一些问题:
- 所有的数据读写请求,都完整了经过了接口服务和数据服务,似乎有点浪费,是否能够做到将数据服务的节点返回给客户端,由客户端发起读取操作呢?
- Put同一个对象,可能到达不同的数据节点,从而存储多份数据
- 不可容错,当前的数据节点丢了一台,数据就丢了
- 是否需要一致性Hash?目前的对象的节点定位要求所有数据节点都参与,也就是说接口节点不知道对象的位置,从而不得不向所有的数据节点发起询问,如果采用hash求模的方案,就可直接根据hash模值定位到数据节点。
- 数据版本号。如果能够使得一份数据有多份版本?
上面的很多问题,需要很多内容才能完善。例如版本号是一种数据一致性的方案(MVCC),那也有很多其他的方案,不过文中并无涉及;关于数据hash,有不少保证一致性hash的方案,也无涉及。
3.3. 元数据服务
说实话,在看到元数据服务的时候,以为要自己实现一套元数据的存储方案,仔细一读发现使用的是ES(elasticSearch)。对于ES,同样不熟悉,等后面看是否专门写个博客介绍一下。简单来说,ES负责存储对象的元数据信息,接口服务利用ES返回的元数据从而数据节点中取得对象的Value。
为什么会有这个ES? 对象要支持多版本访问(既有最新版本,也有历史版本),使得对象要有版本号的概念,另外数据本身可能还有长度属性,类别属性(是图片还是文本等)等等的内容,这些内容不可能存储在接口服务器中的。因为如果存在接口节点中,接口服务就无法做到数据一致性了,例如通过接口节点A增加了一个新的对象,接口节点B就无法感知到,客户端向B请求,会报告对象不存在。从本质来说,正是由于接口服务节点的无状态特性(只负责转发请求,不过本文中还维护的data节点的alive),才赋予了接口服务节点的平行扩容能力。ES充当了中心化的有状态服务,给接口节点提供元数据查询服务,所有的更新操作都会经过ES,从而确保数据的一致性。
那如何设置一个对象的元数据metadata,简单起见,使用四种属性:name,version,hash,size
。
唯一标识:
- name和version唯一标识一个对象。客户端可使用name+version向ES发起请求,ES返回其metadata;
- hash唯一标识一个对象。数据存储以hash作为唯一标识,请求数据服务时候,需要传入hash值。
这种上层name+version,下层hash的操作有一定的好处。从上层客户端使用来说,name才是最容易记忆和关心的,对外公开使用name更易记忆和理解;从底层存储服务来说,只需要唯一标识?那为何不使用name+version呢?这主要因为hash值提供区分文件是否unique的属性。假设文件A和文件B的内容完全一样,如果使用name+version的方式数据需要存储两份,而使用hash值,只需要存储一份(这其实是属于Dedup研究的范畴Dedup简介, Dedup一种应用),只要在元数据映射中记录A -> Hash Identifier, B -> Hash Identifier
即可。
以GET操作为例
- curl向接口服务发起Get object请求,传入name;
- 接口节点向ES发起name的元数据;
- ES返回name的元数据,未指定版本则返回最新版本(
name, version, hash, size
)的元数据; - 接口节点使用hash值向data节点发起定位请求(数据存储在哪个data节点上);
- 存储了该object的data节点返回自己监听的地址;
- 接口节点利用返回的data地址,向其发起对象读取请求;
- data节点返回对象的value信息;
- 接口节点将value转发给curl客户端。
额外的问题:
- 上传数据的时候需要指定hash值,这个hash值服务器并无校验
- 数据传输通路完整的经过接口服务节点
- 元数据需要保证数据一致性,例如多个写请求同时到达,进行版本增加以及数据存储要保证原子操作。
3.4. 校验去重
3.5. 冗余恢复
3.6. 断点续传
3.7. 数据压缩
3.8. 数据维护
4. 书籍的额外建议
- 每一章前面一个作者的ppt的照片,感觉不是很好,技术书籍很少见,给人以凑页之嫌。
参考资料: