优秀的编程知识分享平台

网站首页 > 技术文章 正文

架构设计:文件服务存储设计(文件服务器结构存储的数据能共享吗)

nanyue 2024-09-07 16:49:51 技术文章 8 ℃

架构设计:文件服务的设计与实现一文中,通过实现一个文件服务来梳理了一个架构设计的一般流程,并得到如下静态架构图

本文继续聊聊文件服务中的子模块:「存储模块」的设计,包括:

  • 引入「存储模块」后的架构调整
  • 本地文件存储
  • libfuse
  • RAID
  • 分布式文件存储

架构调整

前面的架构没有对存储进行特别设计,直接使用了本地存储。考虑到后期文件数量可能会越来越多,本地存储可能无法支撑,且本地存储的安全性也没有保障。为了便于后期扩展,需要对「存储」部分进行设计。

存储的方式有很多,本地存储、NAS、分布式存储,为了能支持不同的存储方式,需要对「存储模块」进行抽象。考虑到「存储模块」涉及到IO,是一个相对底层的模块。「上传」这个核心模块不能依赖于具体的存储,所以这里也需要对其进行依赖反转。

见紫色部分,UploadService调用了FileInfoRepository来存储FileInfo,而FileInfoRepository是个接口,具体实现由存储模块中的实现类来实现。

public interface FileInfoRepository {
 public Path save(FileInfo fileInfo) throws IOException;
}
public class LocalFileInfoRepository implements FileInfoRepository {
 public Path save(FileInfo fileInfo) throws IOException {
 ...
 }
}
public class NASFileInfoRepository implements FileInfoRepository {
 public Path save(FileInfo fileInfo) throws IOException {
 ...
 }
}
public class DistributedFileInfoRepository implements FileInfoRepository {
 public Path save(FileInfo fileInfo) throws IOException {
 ...
 }
}

本地存储

我们先看本地存储。最简单的实现,就是直接使用IO将文件写到对应的目录下就可以了。但是,本地存储会有如下几个问题:

  • 如果文件服务支持多租户,所有租户文件都写在同一个目录下,没有做区分,可能导致文件混乱,迁移繁琐。且文件数量增加会比较快。
  • 随着文件数量的增多,某些文件系统的访问速度可能会下降
  • 文件数增加,单机磁盘可能容量不够
  • 没有容错和备份,磁盘损坏可能导致数据的永久丢失

下面我们针对上面的问题,来一个个的解决。

多租户

首先,对于多租户来说,在我们的架构中,实际对应的是Group,我们按照Group的不同,来划分目录即可。即不同的租户有不同的文件根目录,后期某个租户迁移时,直接迁移对应目录即可。这也稍微解决了单目录文件数量多的问题。

单目录文加数量过多

对于单目录下,随着文件数量的增加导致访问速度下降的问题,我们该如何解决呢?

如果你做过分布式系统,那么想一想,我们是否可以把单目录看成是一个服务器,访问目录下的文件看成是一个个的请求呢?如果可以,那解决单目录下访问速度慢的问题是不是就变成了「如何解决单服务器下,负载过高」的问题了?那解决服务端负载过高的方法是否适用于解决目录访问速度下降的问题呢?

我们从下面几个方面来分析一下:

  • 解决服务端负载过高的方法
  • 目录访问和服务器的区别
  • 解决服务端负载过高的方法对目录的适用性

首先来看「解决服务端负载过高的方法」!答案很明显:分流+负载均衡

分布式服务的负载均衡有几种方式呢?

  • 随机
  • 轮询
  • 加权随机
  • 加权轮询
  • 源地址Hash
  • 一致性Hash

再来看「目录访问和服务器的区别」,虽然可以把目录看成服务器,但是两者还是有区别的:

  • 部署一个服务要花费较长的时间,启动服务最快也要几秒钟,且需要额外的硬件
  • 而目录是系统基本功能,创建目录非常的快速,只要磁盘够,创建目录基本没有任何限制

也就是说,对于目录来说,我们不需要考虑创建成本。

那么针对服务器负载高的解决方案是否适合目录访问呢?或者哪种方式适合目录访问呢?我们一个个来分析:

  • 随机:在创建文件时,我们随机的选择一个目录进行创建。那么问题来了,我们一开始该创建多少目录呢?新增目录后,是否需要调整程序?因为随机基数变了。对于服务来说,一开始需要做容量规划,确定有几个服务,因为创建一个服务的成本还是挺高的,比重启服务的成本要高不少。但是创建目录的成本却非常的低,初期先确定目录数量的方式并不合适。
  • 轮询:问题和随机类似。
  • 加权随机:同随机
  • 加权轮询:同轮询
  • 源地址Hash:对于服务来说是对源地址hash,对文件来说,可以对文件进行hash。hash完如何与目录进行匹配呢?
  • 一致性Hash:同上

可以看到,主要的问题就是创建目录的问题!如何保证在目录数量改变时,不需要调整程序呢?

实际上git已经给出了答案:

  • 对文件内容取sha1散列
  • 得到的散列值的前两位作为目录
  • 目录不存在就新建
  • 如果已存在就直接保存
  • 后面的散列值作为文件名

也就是说,根据sha1散列的前两位对文件进行归类。这样既解决了目录创建问题,也解决了文件分布问题。可能的问题是,「sha1散列2^80次,可能会发生一次碰撞」。这个问题对于一般文件系统来说,好像也没有担心的必要。

数据安全

解决了「单目录文件过多,导致访问速度下降」的问题,我们来看下一个问题:数据安全

文件数据是存放在电脑磁盘上的,如果硬盘损坏,可能导致文件的丢失。这实际还是一个「单点问题」!

「单点问题」的解决方案是什么呢?冗余啊!

最简单的方案就是定时去备份数据,可以有如下几种方案:

  • 人工备份
  • 代码实现
  • libfuse
  • RAID

我们继续一个个的讨论。

人工备份

首先是人工备份,这是最low的方案,当然也是最简单的,即有人定期去备份就行了。问题是时效性不高,例如一天备份一次,如果磁盘在备份前坏了,那就会丢失一天的数据。同时恢复比较耗时,需要人工处理。

代码实现

第二个方案是代码实现,即在上传文件时,程序就自动备份。以上面的架构为例,可以添加一个BackupListener,当上传完成后,通过事件,自动备份上传的文件。同时下载时需要判定文件是否完整,如果有问题则使用备份数据。此方案时效性得到了保障,但是将数据备份和业务放到了一起,且需要编码实现,增加了业务代码量。

libfuse

第三个方案是libfuse,libfuse是用户态文件系统接口。下面是libfuse官方简介:

FUSE (Filesystem in Userspace)是一个构建用户态文件系统的接口。libfuse项目包括两个组件:一个fuse内核模块以及libufuse用户态库。libfuse用户态库提供了与FUSE内核模块的通讯实现。

通过libfuse可以实现一个用户态文件系统。libfuse提供方法,支持挂载文件系统、取消挂载文件系统、读取内核请求及作出响应。lifuse提供了两类API:高层级的同步API和低层级的异步API。不过无论哪种方式,内核都是通过回调的方式和主程序通讯。当使用高层级API的时候,回调基于文件名和路径而不是索引节点(inodes),并且回调返回后这个进程也同时结束;当使用低层级API的时候,回调基于索引节点(inodes)工作并且响应必须使用独立的API方法返回。

简单来说,就是可以用libfuse构建一个用户态文件系统。之前在老东家做了一个日志分析平台,日志的收集就使用了libfuse,大致架构如下:

业务系统写日志到挂载的用户态文件系统中,用户态文件系统自动转发到了后续的处理中间件:redis、消息队列、文件系统。

在这里也可以用类似的功能,即在文件上传后,用户态文件系统自动备份。此方案解耦了文件备案逻辑与业务逻辑。

RAID

最后一个方案是RAID,即廉价冗余磁盘阵列。RAID不但可备份文件,还支持并发读写,提高上传下载速率。

常用的RAID有:RAID0,RAID1,RAID01/RAID10,RAID5和RAID6等。我们来看看这几种RAID的特点,以及是否适用于我们的文件服务。你会发现从RAID0到RAID6,又是一个从单点到分布式的过程。

具体RAID相关内容可参考wiki,文末有链接!

  • RAID 0:相当于是一个支持并发的单点应用。就是将两个以上的磁盘并联起来,成为一个大容量的磁盘。同时在存放数据时,将数据分段后分散存储在这些磁盘中。这样就能并行的处理读写,提高了读写速度。但是没有数据冗余和容错能力,假如一个磁盘坏了,那所有数据都会丢失。也就是说RAID0只起到了扩容和提高读写速度的作用。就我们的文件服务来说,选RAID0肯定是不合适的,因为最重要的数据安全没有得到保障,它比单磁盘的数据安全还要差!
  • RAID 1:相当于是个单线程的主从应用。将磁盘分为两组,一组工作磁盘、一组镜像磁盘。当写入时,同时写工作磁盘和镜像磁盘,所以写入速度上会比RAID0慢一点。当一个工作磁盘坏了,可以用镜像磁盘。RAID1提供了数据安全保障,性能也足够高。缺点就是利用率低,只用了磁盘的50%。不过对于一般场景来说,RAID1是个不错的选择。我们的文件服务也可以选择RAID1
  • RAID10/RAID01是RAID0和RAID1的组合
  • RAID10:相当于是支持并发的集群应用。即先对数据备份,然后数据被分段写入
  • RAID01:相当于是支持并发的分布式系统。即先将数据分段,然后对分段数据备份写入

看下面的两张图应该能更好的理解:

无论是RAID10还是RAID01,对磁盘的使用效率都不高。那如何提高磁盘使用率呢?就有了RAID3。

  • RAID3:相当于有注册中心的分布式系统。RAID3的一个前提是,一般情况下,磁盘不会同时坏两个。所以RAID3使用一个磁盘记录校验数据,其它盘作为数据盘,写入数据时,将数据分为n-1份,写到数据盘中,将校验数据写入校验盘。当其中一个磁盘损坏了,可以通过校验盘和其它数据盘来恢复数据。RAID3读取速度很快,但是写入时因为要计算校验值,所以较慢。适合读多写少的情况。我们的文件服务刚好是这样的场景,所以也适合RAID3。但是眼尖的朋友肯定已经发现问题了,这里有个单点问题,就是校验盘。因为校验盘读写次数明显大于数据盘,损坏的几率也就更大,频繁更换校验盘,重建校验数据也是一件很麻烦的事情。那怎么解决这个问题呢?
  • RAID5:通过将校验数据也分散到各个磁盘中来解决这个问题。RAID5相当于是去中心化的分布式系统。当一个磁盘损坏时,可以通过其它磁盘的数据和校验数据来恢复该磁盘的数据和校验数据。和RAID3同样的问题,重建数据速度比较慢,同时对于多个磁盘同时损坏的情况也无能为力。RAID5也同样适用于我们的文件服务。
  • RAID 6:相当于融合了注册中心和去中心化的分布式系统,也可以说有两套注册中心的分布式系统。它在RAID5的基础上增加了两个校验盘,以另一种校验算法来进行校验。当磁盘损坏时,通过其它数据盘的数据和校验数据以及校验盘的校验数据来恢复数据。RAID6能恢复两块磁盘同时损坏情况下的数据,相应的其写入数据更慢,实现难度也更高。除非数据安全要求很高,一般不常用。

对于本地存储来说,RAID是个相对实用的解决方案,既能提高数据安全、快速扩容,也提高了读写速率。但是无论扩展多少磁盘,容量还是相对有限,吞吐也相对有限,同时由于其还是单点,如果文件服务本身挂掉,就会导致单点故障。所以就有了分布式文件系统。

分布式文件系统下次单独讨论!

参考资料

  • libfuse:https://github.com/libfuse/libfuse
  • wiki RAID:https://zh.wikipedia.org/wiki/RAID
  • 《Git版本控制管理》
  • 《操作系统概念》

既然你已经看到这里了,就点击@架构思维关注下呗!


最后打个广告,帮朋友开的专栏《零基础Unity3D游戏开发》,适合没有基础、想从事游戏开发的小白!朋友从事游戏多年,开发了多款游戏,收了30多个徒弟,技术杠杠的!

Tags:

最近发表
标签列表