zoukankan      html  css  js  c++  java
  • 畅购商城(二):分布式文件系统FastDFS

    好好学习,天天向上

    本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航

    FastDFS介绍

    1. 简介

    FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。

    2. 架构

    FastDFS由跟踪服务器(Tracker Server)、存储服务器(Storage Server)和客户端(Client)构成。

    • TrackerStorageClient

    Tracker不负责文件的存储,它主要负责文件管理负载均衡操作,就像是一个中介,为Client提供Storage的信息。而Storage才是负责文件上传,修改,删除等工作。

    Client:“嗨,Tracker!你那里还有没有正在闲着的Storage呀?我现在有些文件需要存储。”

    Tracker:“我来看看,嗯.... 找到了,Storage5现在正好闲着,他的地址是192.168.200:8888/group5,你直接去找他吧。”

    Client:“OK!”

    Client:“嗨,Storage5!我这里现在有一堆文件,就放你这里吧。”

    Storage5:“好的,老板,帮你存好了,放在我这儿你就放心吧,我还让我家二弟帮你做了备份,不会丢的。”

    Client:“好的,谢谢啦!”

    ......

    通过上面的对话,就应该能明白这三者之间的关系了吧。

    从图中可以看出,Storage是一个集群,集群里面分为一个一个的组,每个组里面有不止一台Storage(也可以是一台),这么设计有什么好处呢?首先并不是只有一台Storage为客户端提供服务,而是由Tracker动态地分配相对空闲的Storage给客户端提供服务,这样就做到了负载均衡,使得不会有个别Storage压力过大。还有就是每个组里面不止一个Storage,一个组里面的所有Storage都是同步备份数据的,为的就是实现容灾,一台Storage挂了数据也不会丢失,除非全挂了。还有一个好处就是可以很方便地实现线性扩容,如果哪天Storage的空间不够用了,就可以直接添加一组Storage,然后注册到Tracker中。同样的,Tracker也是集群。

    3. 文件上传流程


    首先Storage会定时向Tracker上报自己的状态信息,这样Tracker就会知道Storage还是不是活着;当Client给Tracker发送请求的时候,Tracker会查询是否有可用的Storage;有的话就将Storage的信息给Client,Client在得到Storage的信息后,就将文件上传到StorageStorage先是生成一个file_id,再将文件写入磁盘中保存,最后将file_id返回给Client

    4. file_id的格式


    group1是Storage所在的组名,M00是Storage的虚拟路径,02/04是两级目录,后面的就是文件名了,这个文件名是自动生成的。

    FastDFS搭建

    我们要把FastDFS安装到docker中,首先要有docker的环境,资料提供的虚拟机里面已经安装好docker了。

    1. 下载FastDFS

    接下来将FastDFS的镜像下载到本地

    docker pull morunchang/fastdfs
    

    下载完成之后看一下有没有:

    docker images
    


    已经下载好了,接下来就开始安装吧。

    2. 安装Tracker和Storage

    安装Tracker

    docker run -d --name tracker --net=host morunchang/fastdfs sh tracker.sh
    
    • docker run:运行容器
    • -d:后台运行
    • --name trackr:起个名字,这里叫tracker,也可以用等号连接 --name=tracker
    • --net=host:host网络模式,容器与宿主机共享IP
    • morunchang/fastdfs:镜像
    • sh tracker.sh:执行一个名为tracker.sh的shell脚本

    现在Tracker就安装好了。

    安装Storage

    docker run -d --name storage --net=host -e TRACKER_IP=192.168.31.200:22122 -e GROUP_NAME=group1 morunchang/fastdfs sh storage.sh
    
    • -e:将Storage添加到环境变量
    • TRACKER_IP:Tracker的IP以及端口
    • GROUP_NAME=group1:当前Storage的组名

    Tracker和Storage开机自启动

    现在我们的Tracker和Storage已经安装好了,但是如果每次开机都要去手动启动的话还是太麻烦了,所以设置一下开机自启动:

    docker update --restart=always tracker
    docker update --restart=always storage
    

    需要注意的是这里的“tracker”和“storage”是前面--name设置的名字,前面设置的是什么,这里就写什么。到此为止,FastDFS就已经安装好了。

    3. 配置ngx_fastdfs_module


    当我们访问Storage中的文件资源的时候,中间是经过了Nginx,然后通过ngx_fastdfs_module去访问Storage的,ngx_fastdfs_module在安装Tracker和Storage的时候已经自动帮我们安装好了,我们来看一下。

    docker exec -it storage  /bin/bash			//进入到storage容器中
    vim /etc/nginx/conf/nginx.conf			    //修改/etc/nginx/conf/目录下的nginx。conf文件
    

    里面会有这么一段内容

    location ~ /M00 {
    	root /data/fast_data/data;
    	ngx_fastdfs_module;
    }
    

    上面这段内容就说明ngx_fastdfs_module已经配置好了。

    4. 设置禁止缓存

    当浏览器访问过Storage中的资源后,即便将Storage中的资源已经被删除了,浏览器还是会访问缓存中数据,那如果我们不想这样,就可以配置一下禁止缓存。还是上面的步骤,在里面添加一行内容:

    location ~ /M00 {
    	add_header Cache-Control no-store;	#告诉浏览器不要缓存数据
    	root /data/fast_data/data;
        ngx_fastdfs_module;
    }
    

    5. 文件位置

    nginx配置文件

    前面我们说过,访问Storage先是通过了Nginx,那么Nginx的配置文件在哪呢?

    docker exec -it storage  /bin/bash			//进入到storage容器中
    cd etc/nginx/conf
    


    可以看到,这里有个nginx.conf文件,这个就是nginx的配置文件。里面配置了Nginx的端口等信息。

    Tracker和Storage配置文件

    cd etc/fsds		//切换到etc/fsds/conf目录下
    


    storage.conf和tracker.conf分别是Storage和Tracker配置文件。

    6. 存储的文件位置

    上一节中提到了file_id的格式,其中有个虚拟目录M00,看配置文件中的M00,其实就是/data/fast_data/data目录。切换进来看看:

    cd data/fast_data/data
    


    这些都是目录,前面不是说了file_id的虚拟目录后面跟着二级目录么,再进入00目录看一下:

    还是一堆目录,再进入00目录:

    这个jpg文件是我刚才传上去的。

    文件存储微服务

    1. 创建微服务工程changgou-service-file

    在changgou-service下新建一个Module叫做changgou-service-file,因为用到了fastdfs,自然需要添加依赖

    <dependencies>
        <!-- FastDFS客户端程序包-->
        <dependency>
            <groupId>net.oschina.zcx7878</groupId>
            <artifactId>fastdfs-client-java</artifactId>
            <version>1.27.0.0</version>
        </dependency>
    </dependencies>
    

    接下来就需要在resource目录下创建FastDFS的配置文件fdfs_client.conf

    #连接超时,单位是秒
    connect_timeout=60
    #通信超时时间,发送或接收数据时。假设在超时时间后还不能发送或接收数据,则本次网络通信失败
    network_timeout=60
    #字符集
    charset=UTF-8
    #Tracker的http端口
    http.tracker_http_port=8080
    #Tracker服务器IP和端口设置
    tracker_server=192.168.31.200:22122
    

    注释的部分请删掉,我在使用的过程中总是连接失败,然后把注释删了就好了。

    微服务工程自然也需要配置,在resource目录下创建application.yml

    spring:
      servlet:
        multipart:
          max-file-size: 10MB       #上传文件最大大小
          max-request-size: 10MB    #请求数据最大大小
      application:
        name: file                  #该微服务的名字
    server:
      port: 18082                   #该微服务的端口
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:7001/eureka
      instance:
        prefer-ip-address: true
    feign:
      hystrix:
        enabled: true
    

    最后再创建一个启动类即可,在com.robod包下新建一个类FileApplication.class

    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    @EnableEurekaClient
    public class FileApplication {
        public static void main(String[] args) {
            SpringApplication.run(FileApplication.class,args);
        }
    }
    

    这里有个地方需要强调一下,就是(exclude = {DataSourceAutoConfiguration.class}),它的作用是取消数据源自动导入。SpringBoot会自动从配置文件中查找spring.datasource.相关属性并自动配置单数据源。因为在这个微服务工程并没有配置数据库的相关属性,所以不加exclude的话就会报错。

    ***************************
    APPLICATION FAILED TO START
    ***************************
    
    Description:
    
    Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
    
    Reason: Failed to determine a suitable driver class
    
    Action:
    
    Consider the following:
    	If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
    	If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
    

    因此我们需要添加这行内容来取消数据源自动导入。

    接下来我们先启动Eureka工程后再来启动一下该项目试试,启动后访问http://127.0.0.1:7001

    OK! 项目成功启动,接下来就实现一下文件上传,删除等功能。

    2. 文件操作功能的实现

    为了方便管理,我们将文件信息封装成一个JavaBean,在com.robod.file包下新建一个类FastDFSFile

    @Data	//不要忘了导入Lombok的依赖
    public class FastDFSFile {
    
        //文件名
        private String name;
        //文件内容
        private byte[] content;
        //文件扩展名
        private String ext;
        //文件MD5摘要
        private String md5;
        //文件作者
        private String author;
    
        public FastDFSFile(String name, byte[] content, String ext) {
            this.name = name;
            this.content = content;
            this.ext = ext;
        }
    
        public FastDFSFile
                (String name, byte[] content, String ext, String md5, String author) {
            this.name = name;
            this.content = content;
            this.ext = ext;
            this.md5 = md5;
            this.author = author;
        }
    }
    

    现在再创建一个工具类来实现对文件的一些操作,在com.robod.utils包下新建一个类FastDFSUtils

    public class FastDFSUtils {
    
        private static StorageClient storageClient;
        private static TrackerClient trackerClient;
        private static TrackerServer trackerServer;
    
        static {
            try {
                String path = new ClassPathResource("fdfs_client.conf").getPath();
                //加载Tracker连接信息
                ClientGlobal.init(path);
                //创建一个Tracker的客户端对象
                trackerClient = new TrackerClient();
                //通过TrackerClient访问TrackerServer服务,获取连接对象
                trackerServer = trackerClient.getConnection();
                //通过TrackerServer的连接信息去获取Storage的连接信息,存储进StorageClient对象中
                storageClient = new StorageClient(trackerServer, null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 上传文件
         *
         * @param file
         */
        public static String[] upload(FastDFSFile file) throws Exception {
            //上传文件
            return storageClient.upload_file(file.getContent(), file.getExt(), null);
        }
    
        /**
         * 下载文件
         * @param groupName         文件所在的组名     group1
         * @param remoteFileName    文件的路径及名字   M00/00/00/wKgfyF8AjpSADeBLABXmlvc7iOY701.jpg
         */
        public static InputStream downloadFile(String groupName, String remoteFileName) throws Exception {
            byte[] bytes = storageClient.download_file(groupName, remoteFileName);
            return new ByteArrayInputStream(bytes);
        }
    
        /**
         * 删除文件
         * @param groupName         文件所在的组名     group1
         * @param remoteFileName    文件的路径及名字   M00/00/00/wKgfyF8AjpSADeBLABXmlvc7iOY701.jpg
         */
        public static int deleteFile(String groupName, String remoteFileName) throws Exception {
            return storageClient.delete_file(groupName, remoteFileName);
        }
    
        /**
         * 获取文件信息
         * @param groupName         文件所在的组名     group1
         * @param remoteFileName    文件的路径及名字   M00/00/00/wKgfyF8AjpSADeBLABXmlvc7iOY701.jpg
         */
        public static FileInfo getFileInfo(String groupName, String remoteFileName) throws Exception {
            return storageClient.get_file_info(groupName,remoteFileName);
        }
    
        /**
         * 获取storage信息
         * @return  store_path_index
         */
        public static StorageServer getStorage() throws IOException {
            return trackerClient.getStoreStorage(trackerServer);
        }
    
        /**
         * 获取Storage的IP和端口信息
         * @param groupName
         * @param fileName
         * @return ServerInfo:ip_addr,port
         * @throws IOException
         */
        public static ServerInfo[] getStorageInfo(String groupName, String fileName) throws IOException {
            return trackerClient.getFetchStorages(trackerServer,groupName,fileName);
        }
    
        /**
         * 获取Tracker信息
         * @return
         */
        public static String getTrackerInfo() {
            String ip = trackerServer.getInetSocketAddress().getHostString();
            int port = ClientGlobal.getG_tracker_http_port();
            return new StringBuilder(ip).append(":").append(port).toString();
        }
    
    }
    

    在这个类中我们封装了对文件的一些列操作,现在就来写个入口,在com.robod.controller包下新建一个类FileController

    @RestController
    @CrossOrigin
    @RequestMapping("/file")
    public class FileController {
    
        @PostMapping("/upload")
        public Result<String> upload(@RequestParam("file") MultipartFile multipartFile) throws Exception{
            FastDFSFile fastDFSFile = new FastDFSFile(
                    multipartFile.getOriginalFilename(),
                    multipartFile.getBytes(),
                    StringUtils.getFilenameExtension(multipartFile.getOriginalFilename()));
            System.out.println(fastDFSFile.toString() );
            String[] upload = FastDFSUtils.upload(fastDFSFile);
            String groupName = upload[0];
            String fileName = upload[1];
            System.out.println(groupName);
            System.out.println(fileName);
            System.out.println("------------------");
            System.out.println("获取文件信息");
            FileInfo fileInfo = FastDFSUtils.getFileInfo(groupName, fileName);
            System.out.println(fileInfo.getSourceIpAddr());
            System.out.println(fileInfo.getFileSize());
            System.out.println(fileInfo.getCreateTimestamp());
            System.out.println(fileInfo.getCrc32());
            System.out.println("----------------------");
            System.out.println("获取Storage信息");
            StorageServer storage = FastDFSUtils.getStorage();
            System.out.println(storage.getStorePathIndex());
            System.out.println("-----------------");
            System.out.println("获取Storage的IP和端口信息");
            ServerInfo[] storageInfo = FastDFSUtils.getStorageInfo(groupName, fileName);
            for (ServerInfo serverInfo : storageInfo) {
                System.out.println(serverInfo.getIpAddr());
                System.out.println(serverInfo.getPort());
            }
            System.out.println("--------------------");
            System.out.println("获取Tracker信息");
            String trackerInfo = FastDFSUtils.getTrackerInfo();
            System.out.println(trackerInfo);
            return new Result<>(true, StatusCode.OK,"文件上传成功","---");
        }
    }
    

    将项目启动起来,用postman发送请求。


    从控制台的打印情况中可以看出,文件成功上传到了服务器中,而且前面写的一些获取信息的方法也正常运行了。

    写在最后

    这篇文章先是介绍了FastDFS的一些知识,接着就是讲了FastDFS的安装以及文件路径和配置的内容,最后搭建了文件微服务并实现了对于文件的操作功能。如果我的文章对你有些帮助,不要忘了点赞,收藏,转发,关注。要是有什么好的意见欢迎在下方留言。让我们下期再见!

    微信公众号

  • 相关阅读:
    A1066 Root of AVL Tree (25 分)
    A1099 Build A Binary Search Tree (30 分)
    A1043 Is It a Binary Search Tree (25 分) ——PA, 24/25, 先记录思路
    A1079; A1090; A1004:一般树遍历
    A1053 Path of Equal Weight (30 分)
    A1086 Tree Traversals Again (25 分)
    A1020 Tree Traversals (25 分)
    A1091 Acute Stroke (30 分)
    A1103 Integer Factorization (30 分)
    A1032 Sharing (25 分)
  • 原文地址:https://www.cnblogs.com/robod/p/13378441.html
Copyright © 2011-2022 走看看