分布式对象存储系统

怎么实现他是一个分布式的呢?

通过rabbitmp实现

怎么知道我的数据存储在哪一个节点呢?

怎么保证高可用的?

  • 系统层面上:首先我们需要保证数据服务节点的高可用,我们可以通过数据服务节点的集群来保证高可用,当一个数据服务节点出现问题的时候,我们可以通过消息队列的方式将请求转发到其他的数据服务节点,这样就可以保证数据服务节点的高可用。另外我们还可以通过数据服务节点的心跳机制来保证数据服务节点的高可用,当数据服务节点出现问题的时候,接口服务节点会通过心跳机制发现,然后将这个节点从集群中移除,这样就可以保证数据服务节点的高可用。

  • 存储对象层面上:主要就是对用户上传的一个数据做备份,如果是小文件的话,就直接保存两份,如果是大文件的话,就将文件分成多个分片,然后每个分片保存在不同的数据服务节点上,这样就可以保证数据的高可用。因为如果我们直接保存多份数据,那么对于大文件来说,就是会浪费很多存储空间,而且对于数据服务节点来说,也会增加很大的负担,所以我们可以将大文件通过RS纠删码算法来实现分成多个分片,然后每个分片保存在不同的数据服务节点上,这样就可以保证数据的高可用而且RS纠删码在数据片大于检验片的时候,他的储存空间占用是不会超过百分之200的,如果直接备份双份,那么他的。

  • 对存储的对象进行版本管理

如何做到一个负载均衡

你想一下,现在你部署了 5 台机器,如果我要分片上传大文件,你咋做,怎么把分片都打到一个机器上合并

先把所有机器 ip 注册到 redis 里,第一次获取上传链接的时候,比如打到了 A 机器,就在 A 机器在本地创建一个 uid 对应的目录,后面上传分片时,分片打到 B 的时候,B 会去 redis 里获取所有机器 ip,然后问这个 uid 是否在你本地,如果在的话就把请求转发到对应机器

我们这里我也是通过rs纠删码分片之后,直接每个节点服务区都存一份来保证一个负载均衡,我们有几个数据服务器就分多少数据片

如何保障数据的一致性

我这个的话没有特别的去做一个保障数据的一致性的操作,因为我们的rs纠删码会给文件进行一个分片,我们只需要得到足够的数据片的数量就可以对数据片进行一个修复就是如果哪个节点上的数据丢失了或者损坏了什么之类的。而且因为我这个项目不是特别的复杂嘛,所以我在本地测试的时候就是我有几个节点,我的rs就分片成多少片,也就是说不管怎么样,每个储存节点上都至少存着一个数据片或者是校验片

我们的数据检查主要就是在get的时候,如果我们get的存储节点不全,就说明我们的数据出现了问题,这个时候我们就执行修复操作。

为什么不选用kafuka而是rabbitmq

首先是因为kafuka是一个分布式的消息队列,它的设计目标是高吞吐量,低延迟,高可用性,但是它的设计目标和我们的需求不太一样,我们的需求是数据的一致性,所以我们选择了rabbitmq,rabbitmq是一个开源的消息代理软件,它的设计目标是实现高可靠性,高可用性,高扩展性,这样就可以保证我们的数据的一致性。

监听对象储存节点达不到一个就是说高吞吐量,高并发的的要求

为什么不选用redis而是elasticsearch

首先是因为redis是一个内存数据库,它的设计目标是高性能,高可用性,但是它的设计目标和我们的需求不太一样,我们的需求是数据的一致性,所以我们选择了elasticsearch,elasticsearch是一个开源的搜索引擎,它的设计目标是实现高可靠性,高可用性,高扩展性,这样就可以保证我们的数据的一致性。

这个项目是如何设计与实现的?

一开始我们只是一个单体服务,客户端发送put请求存储数据,我们就将其存到磁盘中,客户端发送get请求下载数据,我们从磁盘中读取出来给客户端。但是这样我们存在一个问题,当客户端请求骤增,服务器磁盘IO负载过高时,都会导致性能下降,并且不好扩展。
针对上面的问题,我们将接口服务和数据服务解耦,接口服务只负责接受客户端请求,数据服务只用来请求磁盘,这样一来我们就可以轻松地往集群中扩展新的接口服务节点和数据服务节点,而接口服务和数据服务之间通过消息队列RabbitMQ进行信息的传递。但是我们也存在了一个问题,当客户端多次put同一个对象的时候,我们在数据服务节点都会存在很多同样的数据,这样非常浪费存储空间,对此我们需要解决数据去重这个问题;但是如果我们put同一对象,而每次数据都不一样,这时候我们可以保存对象的多个版本,对数据进项版本控制。
我使用的是ElasticSearch进行版本控制,它类似于数据库,索引相当于数据库,类型相当于表,每一个属性相当于列。利用ES客户端可以找到指定版本的数据,可以查询所有的版本,ES会保存对象的元数据,包括名字,大小,散列值,这个散列值是客户端通过Sha-256计算出来的,当客户端put一个对象的时候,首先还是会保存在数据服务中,然后会在ES服务器中添加一个版本的元数据,每次版本号加一,当get一个对象的时候,还是一样请求ES服务器返回对应的元数据信息,之后再去请求数据服务返回相应的数据。
另外一个就是数据去重的问题我们需要解决。同时因为客户端数据在传输中可能出现数据丢失问题,或者有一些恶意的客户端发送不一致的信息,这时候服务器不能将这些错误的信息保存下来,还有一种情况是服务器因为数据放久了出现数据降解的问题,这时候都需要对数据进行验证,保证接收和发送的数据完整性。解决去重问题,我们可以在接口服务节点转发请求之前,先发送定位信息,通过交换机发送对象数据散列值的信息,数据服务节点会搜索本地磁盘是否存在这个对象,存在的话就会反馈保存的数据节点的监听地址,否则什么也不返回。对数据进行校验我们就需要通过对象的内容计算出散列值,然后和客户端传进来的散列值进行比较,如果不同则拒绝服务,因为我们要接受完数据才能进行计算校验,但是如果文件内容比较大的话,很有可能会超出接口服务节点的内存,所以我们需要将数据转移到数据服务保存到一个临时的地方,当数据校验通过的话,将文件存储到正确的地方,还有就是客户端下载一个对象,我们也需要对取出来的对象进行数据检验。因为有数据降解问题的存在,我们又会想到一个新的问题,如果服务器上一个数据丢失了怎么办?客户端就拿不到数据了
对于这个问题,一种方法是保存多份,还有另一种方法就是将一个对象分成很多分片,然后每个分片保存在不同的数据服务节点,我使用的是RS纠删码来设计的,其中有我们将对象分成四个数据分片和两个校验分片,大小都是对象的25%,我们将每个分片保存在六个数据服务节点中,只有其中四个我们就可以还原完整对象,所以我们可以允许两个节点数据出错,这时候用户put一个对象的时候,我们就需要选择六个数据节点,每一个节点进行上面的post,patch,put操作。另外就是如果某个数据节点的数据出现了问题我们还需要对其进行数据修复,当客户端get一个对象的时候,接口服务可以通过心跳机制获得所有数据节点的监听地址,但是如果某个出问题了,我们就可能只能收到其中的五个,那么就需要对另一个数据进行修复,根据RS的原理我们可以很容易进行修复。
消息队列是如何设计的?接口节点和数据节点如何交换信息的?
首先数据服务得知道有哪些可用的数据服务节点可以请求,那么数据节点就要发送信息给数据节点,这个过程叫做心跳机制,接口服务节点会绑定一个接口交换机,数据服务每隔五秒给接口服务发送自己的监听地址,接口服务会将这些监听地址按照时间保存到内存中,并且会清除10s没有发送心跳信息的数据节点,因为可能出问题了。这样接口服务转发请求的时候就可以直接选择一个数据节点发送请求

另外接口服务节点还会给数据服务节点发送数据的定位消息,以确定数据在磁盘中的具体情况。这个时候数据服务节点也会绑定一个数据交换机,当数据服务节点收到定位信息的时候,就会反馈这个数据保存在的数据节点地址。

心跳机制是如何设计的

数据服务会启动一个协程每隔五秒 通过数据服务绑定的交换机给接口服务发送自己的监听地址,接口服务收到之后会保存收到的时间以及节点地址到哈希表中,每次保存的时候都需要上锁,保证数据的正确性;并且会清除10s仍没有发送心跳信息的数据节点,因为这些节点肯定是出现问题了,同样接口服务也是开辟的一个协程来处理心跳信息。

怎么进行版本控制的?

我是通过Elasticsearch实现的,当客户端发送put请求的时候,我们会根据元数据想ES服务器发送put请求保存这个版本,并且每次版本号加一,当客户端需要获取一个版本的元数据信息,直接发送get请求到ES服务器,接着ES服务器会响应对应的元数据信息,如果客户端想知道所有的版本信息,接口服务会请求ES服务器查询所有的版本,并且我们可以让它进行版本号降序排列,这样有时候客户端不指定版本号,我们就可以返回最新的版本信息。

怎么对数据进行校验和去重的?

首先是去重,去重意味着如果客户端上传了一个内容相同的对象我们就拒绝保存,客户端put一个对象的时候,接口服务通过消息队列向数据服务节点发送定位消息,数据服务节点会在磁盘中寻找,当然为了提高查找速度,数据服务节点在启动的时候会通过一个协程,将所有的文件名即对象的散列值保存到哈希表中,这样接口服务节点请求定位信息的时候,数据服务节点直接在哈希表中查找是否存在,通过消息队列反馈给接口服务。如果不存在的话才会继续去请求保存。

其次是数据校验,当客户端发送put请求的时候,我们需要通过对象的内容计算出散列值,然后和客户端传进来的散列值进行比较,如果不同则拒绝服务,因为我们要接受完数据才能进行计算校验,但是如果文件内容比较大的话,很有可能会超出接口服务节点的内存,所以我们需要将数据转移到数据服务保存到一个临时的地方,具体我们可以让接口服务节点发送post请求,让数据服务提前做好准备,新建一个信息文件包括对象的散列值和大小,以及一个用来存储数据的文件,当然这时候还是空的,返回给接口服务节点uuid,接着接口服务节点通过uuid发送patch请求,将客户端请求的数据保存到数据服务的临时数据文件中;当所有内容已经保存到临时数据文件的时候,这时候接口服务也通过内容将散列值结算出来了,如果和客户端传进来的散列值相同的话,意味着数据校验通过,紧接着接口服务发送put请求,数据服务节点将文件转正,存储到正确的地方,但是如果没有通过校验的话,接口服务会通过delete请求,数据服务将会删除临时的信息文件和数据文件。还有就是客户端下载一个对象,我们也需要对取出来的对象进行数据检验。

怎么对数据进行存储和修复的?

对于这个问题,一种方法是保存多份,还有另一种方法就是将一个对象分成很多分片,然后每个分片保存在不同的数据服务节点,我使用的是RS纠删码来设计的,其中有我们将对象分成四个数据分片和两个校验分片,大小都是对象的25%,我们将每个分片保存在六个数据服务节点中,只有其中四个我们就可以还原完整对象,所以我们可以允许两个节点数据出错,这时候用户put一个对象的时候,我们就需要选择六个数据节点,每一个节点进行上面的post,patch,put操作。另外就是如果某个数据节点的数据出现了问题我们还需要对其进行数据修复,当客户端get一个对象的时候,接口服务可以通过心跳机制获得所有数据节点的监听地址,但是如果某个出问题了,我们就可能只能收到其中的五个,那么就需要对另一个数据进行修复,根据RS的校验片我们可以很容易进行修复。

参考资料

https://github.com/JIeJaitt/goDistributed-Object-storage

https://github.com/RobKing9/Distributed_Object_Storage/blob/master/Redis%E5%8A%A0%E8%BD%BD%E6%96%87%E4%BB%B6%E4%BF%A1%E6%81%AF%E5%AE%9E%E7%8E%B0%E5%8E%BB%E9%87%8D.md

作者

JIeJaitt

发布于

2023-03-08

更新于

2024-03-15

许可协议

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×