DFS分布式文件存储系统实践
本文最后更新于 843 天前,其中的信息可能已经有所发展或是发生改变。

在早期尝试Android开发的时候,我用了一些非常拙劣的方法来传输图片,现在想起来自己都想笑。

之前我的想法很粗暴,上传图片到后端处理不用多说,肯定就是把文件上传到后端的指定目录,然后数据库内存放文件的地址。前端需要用图片的时候就从数据库里面找到地址,找到对应的文件,再传给后端,后端拿到统一资源定位符后显示图片(在Android开发的流程中,这个步骤非常复杂,特别是Google现在采用了沙箱机制,导致能操作的目录非常有限)。

(正在想办法让WordPress支持SVG格式图片,所以目前还是会有一些不清晰,用的是PNG)

Android与Spring Boot后端交换图片文件

这个办法一直被我沿用了大概一年左右,直到我在Web开发中也产生了类似的需求(当然只有图片的预览,不涉及保存操作,前面提到的Android客户端也不涉及到保存图片需求,仅限预览),这个方法就不好使了,总不能让浏览器先保存图片吧。不过很快我就找到了解决方案,那就是搭建一个图片服务器。

方法很简单,无非就是用一个Nginx做一个反向代理。让Nginx从相应的目录找到图片后,直接返回相应的URL,这样就舒服多了。于是我把我开发的多个Android APP也做了相应的功能更新,不过之前写的代码也不能浪费啊,索性改成保存到本地这个功能好了,真省事儿(实际上没有,因为我懒到连按钮都不想加了)。

通过Nginx反向代理

实际上这样就已经完整可用了,毕竟咱做开发都是自娱自乐,东西除了自己用之外,就没别人的了。但实际应用中,文件的存储都不是保存在单个服务器的某个目录中的,而是通过DFS来存储。

这是一篇为了在课上演示而写的文章,所以我在这里简单介绍一下DFS系统的常见工作原理。

我们以HDFS为例。

图源:Hadoop在Apache的介绍页面

我在这里引用一下官方的一部分介绍。

Namenode 和 Datanode

HDFS采用master/slave架构。一个HDFS集群是由一个Namenode和一定数目的Datanodes组成。Namenode是一个中心服务器,负责管理文件系统的名字空间(namespace)以及客户端对文件的访问。集群中的Datanode一般是一个节点一个,负责管理它所在节点上的存储。HDFS暴露了文件系统的名字空间,用户能够以文件的形式在上面存储数据。从内部看,一个文件其实被分成一个或多个数据块,这些块存储在一组Datanode上。Namenode执行文件系统的名字空间操作,比如打开、关闭、重命名文件或目录。它也负责确定数据块到具体Datanode节点的映射。Datanode负责处理文件系统客户端的读写请求。在Namenode的统一调度下进行数据块的创建、删除和复制。

应该没多少人看,实际上写的还挺好的。我在这里简单的解释一下。

Namenode即负责管理文件的服务器,由于服务器集群往往结构复杂,而且文件是分块存储的,每一个Datanode存放的部分不尽相同,这个时候就需要Namenode服务器做统一的管理,其管理的数据可以被称为“MetaData”,即“元数据”。数据是指文件中的实际数据,即文件的实际内容;而元数据是用来描述一个文件特征的系统数据,诸如访问权限、文件拥有者以及文件数据块的分布信息等等。如果文件是一张图片,元数据就是图片的宽,高等等。

而Datanode主要是负责存储数据和处理读写请求的。就这么简单。听着原理就知道这很分布,给你把文件都拆了。

我在实践部分介绍的是FastDFS,原理与这个大差不差,但实际上并没有把文件拆分成很多块,原因我现在来举个小例子说明一下。

在这里先提一下,在Hadoop2.X版本中,HDFS单个文件块的大小是128M。了解完这个再回过头来看我们的需求,在服务器里存图片。这个就很难办了,因为我们的图片在未拆分之前,体积大小往往远小于128M。再说另一个拆分文件带来的另一个问题,文件的拆分和合并往往会造成性能和时间的额外开销,如果我们浏览的网页存在大量的图片,加载这些图片并合并就会造成比较可观的开销。

所以在应付我们这个简单的需求时,这个分布式系统带来的缺点反而比优点多的多。FastDFS解决这个问题的方法很简单,干脆就不拆分了,直接存。当然了,我们还是将其视作一个分布式的存储系统,因为除了不拆文件外,它和其他DFS并没有太大的区别。

以FastDFS为例,讲一下实践过程。配置如下:

  • Java 1.8
  • Debian 11 on ARM64(FastDFS Server)
  • macOS 12.5.1(Client)
  • Spring Boot 2.7.1
  • Nginx 1.23.1
  • Vue 3.0

首先还是放一张架构图(来源于GitHub项目主页,非常的不清晰就对了)

FastDFS架构图

Tracker集群可以看作上述HDFS的Namenode,Storage集群可以看作Datanode,具体我就不多说了。

在项目的Wiki页面有详细的安装说明,自行移步GitHub:Wiki。写的比较详细,注意一下Nginx的配置就行,最好换成新的主线版本。

由于在家实在搞不出来服务器集群,所以我直接就单机部署了。

部署完成后利用以下命令查看服务器集群情况:

/usr/bin/fdfs_monitor /etc/fdfs/storage.conf
Tracker集群情况
Storage集群情况

单机部署时,Tracker和Storage实际上为同一台服务器,IP地址相同。

下面通过Java API来实现文件的上传和下载功能。

作者在Github项目主页给出了Java Client的源码以及使用方法,利用此API以及SpringBoot搭建一个简单的后端服务器,利用Vue写一个简单的单页面应用。该用例会实现图片的上传和预览功能。

下面把重点放在后端服务器的搭建上,前端自己玩去,也没几行代码,放张图,非常简陋。

前端页面截图

首先需要把基础的环境配好,创建一个Spring Boot项目,引入Spring Web包。我在这里给出Maven依赖。也可以直接创建标准的Maven项目,然后导入依赖即可。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>edu.njucm</groupId>
    <artifactId>fastdfs_java</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>fastdfs_java</name>
    <description>fastdfs_java</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/net.oschina.zcx7878/fastdfs-client-java -->
        <dependency>
            <groupId>net.oschina.zcx7878</groupId>
            <artifactId>fastdfs-client-java</artifactId>
            <version>1.27.0.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.11.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

这里需要注意的是,最关键的client依赖用的并不是作者提供的,而是第三方的,原因非常简单,作者提供的依赖无法导入。

然后我们在main目录的resources文件夹内创建一个配置文件,命名为fastdfs-client.properties,实际上这个配置文件的内容也可以在GitHub找到,我只是做了一些简单的替换,将IP地址替换为我的服务器地址。GitHub地址

fastdfs.connect_timeout_in_seconds = 5
fastdfs.network_timeout_in_seconds = 30

fastdfs.charset = UTF-8

fastdfs.http_anti_steal_token = false
fastdfs.http_secret_key = FastDFS1234567890
fastdfs.http_tracker_http_port = 80

fastdfs.tracker_servers = 192.168.31.33:22122

在这之后编写一些测试类,用作测试,当然是放在test目录。

首先编写一个合适的上传测试类,在这里我就不解释了,直接贴代码,注释有空再补。

@Test
    public void testUpload() {
        try {
            ClientGlobal.initByProperties("/Users/zhangjin/Java/IdeaProjects/FastDFS_Test/Java/fastdfs_java/src/main/resources/config/fastdfs-client.properties");
            System.out.println("network_timeout=" + ClientGlobal.g_network_timeout + "ms");
            System.out.println("charset=" + ClientGlobal.g_charset);
            TrackerClient tc = new TrackerClient();
            TrackerServer ts = tc.getConnection();
            if (ts == null) {
                System.out.println("getConnection return null");
                return; }
            StorageServer ss = tc.getStoreStorage(ts);
            if (ss == null) {
                System.out.println("getStoreStorage return null");
            }
            StorageClient1 sc1 = new StorageClient1(ts, ss);
            NameValuePair[] meta_list = null;  //new NameValuePair[0];
            String item = "/Users/zhangjin/Downloads/SpringBoot- Android传输流程.png";
            String fileid;
            fileid = sc1.upload_file1(item, "png", meta_list);
            System.out.println("Upload local file " + item + " ok, fileid=" + fileid);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

运行后,可以看到,我们的文件已经传输成功了!仔细看打印的日志,给出了在Linux服务器中存储的实际位置,我们可以去那边看一下。为了演示清晰,这里用VNC连接到可视化页面查看那张图片。

上传测试程序运行结果
Linux文件目录中查看的图片

下面测试分布式文件系统的查询功能,看看是否可以查询出我们刚才上传的那张图片的信息,依旧只给出代码,解释的话,有空再说。

@Test
    public void testQueryFile() throws IOException, MyException {
        ClientGlobal.initByProperties("/Users/zhangjin/Java/IdeaProjects/FastDFS_Test/Java/fastdfs_java/src/main/resources/config/fastdfs-client.properties");
        TrackerClient tracker = new TrackerClient();
        TrackerServer trackerServer = tracker.getConnection();
        StorageServer storageServer = null;
        StorageClient storageClient = new StorageClient(trackerServer,storageServer);
        FileInfo fileInfo = storageClient.query_file_info("group1",
                "M00/00/00/wKgfIWMUspCAcHSdAAA2rW7tWd8845.png");
        System.out.println(fileInfo);
    }

运行结果,非常简单明了,不做解释,该有的信息都有了。

查询文件测试程序运行结果

最后需要提到的是最重要的下载文件,其实还是比较好办的,看代码。

@Test
public void testDownloadFile() throws IOException, MyException {
       ClientGlobal.initByProperties("/Users/zhangjin/Java/IdeaProjects/FastDFS_Test/Java/fastdfs_java/src/main/resources/config/fastdfs-client.properties");
       TrackerClient tracker = new TrackerClient();
       TrackerServer trackerServer = tracker.getConnection();
       StorageServer storageServer = null;
       StorageClient1 storageClient1 = new StorageClient1(trackerServer,
               storageServer);
       byte[] result =
   storageClient1.download_file1("group1/M00/00/00/wKgfIWMUspCAcHSdAAA2rW7tWd8845.png");
       File file = new File("1.png");
       FileOutputStream fileOutputStream = new FileOutputStream(file);
       fileOutputStream.write(result);
       fileOutputStream.close();
}

实际运行后,我们的项目根目录就有了刚才上传的那份儿文件。

下载文件程序测试结果

这样的话,基本的功能就测试完了。其实这部分是比较容易出问题的,测试通过后,下面与Web整合的部分就好办多了,只需要操心一下Nginx的配置问题,其他就没什么了。

写的太多了,估计课上讲不了这么多,所以我决定Web整合部分再水一篇文章,没了。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇