分布式文件系统如何做?终于有篇文章分布式文件上传讲清楚了

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 本篇文章详细讲述了分布式项目中文件系统的使用问题,主要通过FastDFS实现分布式系统中的文件管理功能,包括文件存储,文件同步和文件访问。分析了FastDFS的文件系统架构以及上传交互过程和下载交互过程。通过基于DOCKER安装FastDFS的实例讲解了文件系统的相关配置和使用方式。通过这篇文章的讲解,可以学会分布式文件系统FastDFS的执行原理以及具体的使用方式,可以很方便地对分布式系统中各个模块的文件进行统一的管理。

FastDFS概念

  • FastDFS是开源的轻量级分布式文件系统,实现文件管理, 主要功能:

    • 文件存储
    • 文件同步
    • 文件访问(文件上传,文件下载)
  • 解决了大容量存储和负载均衡的问题,特别适合以文件为载体的在线服务:相册网站,视频网站
  • FastDFS为互联网量身定制,充分考虑了冗余备份,负载均衡,线性扩容等机制,并注重高可用,高性能等指标,使用FastDFS可以很方便地搭建一套高性能的文件服务器集群提供文件上传,下载等服务

FastDFS文件系统架构

  • FastDFS服务端有两个角色:

    • 跟踪器(tracker): 主要做调度工作,在访问上起负载均衡作用

      • 跟踪器和存储节点都可以由一台服务器或多台服务器构成,跟踪器和存储节点中的服务器可以随时增加或下线而不会影响下线服务.
      • 跟踪器中所有服务都是对等的,可以根据服务器的压力情况随时增加或减少
    • 存储节点(storage): 存储文件,完成文件管理的所有功能

      • 就是这样的存储
      • 同步存储接口
      • 提供存储接口
      • FastDFS同时对文件metadata进行管理,文件metadata是文件属性列表,可以包含多个键值对

        • 文件metadata: 文件的相关属性,以键值对方式表示
        • 为了支持大容量,存储节点采用分卷的组织方式

          • 存储系统由一个卷或多个卷组成,卷与卷之间的文件是相互独立的,所有卷的文件容量累加就是整个存储系统的文件容量
          • 一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中文件都是相同的,卷中的多台服务器起到了冗余备份和负载均衡作用
          • 在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务
          • 当存储空间不足或即将耗尽时,可以动态添加卷,只需要增加一台或多台服务器,配置一个新的卷,这样扩大存储系统的容量
          • FastDFS 文件标识分为两部分:

            • 卷名
            • 文件名
高可用要有崩溃恢复的能力
服务集群要有同步的功能
否则就要有负载均衡

上传交互过程

  • client询问tracker上传到的storage,不需要附加参数
  • tracker返回一台可用的storage
  • client直接和storage通讯完成文件上传
client为使用FastDFS的调用方,client也是一台服务器,对tracker和对storage的调用均为服务器间的调用

下载交互过程

  • client询问tracker下载文件的storage,参数为文件标识(卷名和文件名)
  • tracker返回一台可用的storage
  • client直接和storage通讯完成文件下载
client为使用FastDFS的调用方,client也是一台服务器,对tracker和对storage的调用均为服务器间的调用

FastDFS结合Nginx

  • 使用FastDFS部署分布式文件系统时,通过FastDFS的客户端API进行文件的上传,下载,删除等操作,同时通过FastDFS和HTTP服务器来提供HTTP服务.但是FastDFS的HTTP服务较为简单,无法提供负载均衡等高性能的服务.需要使用FastDFS的Nginx模块弥补这一缺陷
  • FastDFS通过tracker服务器,将文件放在storage服务器存储,但是同组之间的服务器需要复制文件,有延迟的问题,可以通过fastdfs-nginx-module可以重定向连接到源服务器取文件,避免客户端由于复制延迟的问题,出现错误

基于Docker安装FastDFS

  • 环境准备:

    • libfastcommon: FastDFS分离出的一些公用函数包
    • FastDFS: FastDFS本体
    • fastdfs-nginx-module: FastDFS和nginx的关联模块
    • nginx: nginx1.15.4
  • 创建工作目录:

    • 在Linux中创建
    /usr/local/docker/fastdfs/environment
    
    /usr/local/docker/fastdfs:用于存放docker-compose.yml配置文件及FastDFS数据卷
    /usr/local/docker/fastdfs/environment:用于存放Dockerfile镜像配置文件及FastDFS所需环境
  • 在 /usr/local/docker/fastdfs/environment目录中创建Dockerfile
# 更新数据源
WORKDIR /etc/apt
RUN echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse' > sources.list
RUN echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse' >> sources.list
RUN echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse' > sources.list
RUN echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse' > sources.list
RUN apt-get update

# 安装依赖
RUN apt-get install make gcc libpcre3-dev zliblg-dev --assume-yes

# 复制工具包
ADD fastdfs-5.11.tar.gz /usr/local/src
ADD fastdfs-nginx-module_v1.16.tar.gz /usr/local/src
ADD libfastcommon.tar.gz /usr/local/src
ADD nginx-1.15.4.tar.gz /usr/local/src

# 安装libfastcommon
WORKDIR /usr/local/src/libfastcommon
RUN ./make.sh && ./make.sh install

# 安装 FastDFS
WORKDIR /usr/local/src/fastdfs-5.11
RUN ./make.sh && ./make.sh install

# 配置FastDFS tracker
ADD tracker.conf /etc/fdfs
RUN mkdir -p /fastdfs/tracker

# 配置FastDFS storage
ADD storage.conf /etc/fdfs
RUN mkdir -p /fastdfs/storage

# 配置FastDFS客户端
ADD client.conf /etc/fdfs

# 配置fastdfs-nginx-module
ADD config /usr/local/src/fastdfs-nginx-modules/src

# FastDFS与Nginx集成
WORKDIR /usr/local/src/nginx-1.13.6
RUN ./configure --add-module=/usr/local/src/fastdfs-nginx-module/src
RUN make && make install
ADD mod_fastdfs.conf /etc/fdfs

WORKDIR /usr/local/src/fastdfs-5.11/conf
RUN cp http.conf mime.types /etc/fdfs/

# 配置Nginx
ADD nginx.conf /usr/local/nginx/conf

COPY entrypoint.sh /usr/local/bin/
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

WORKDIR /
EXPOSE 8888
CMD ["/bin/bash"]
  • 在/usr/local/docker/fastdfs/environment创建entrypoint.sh,创建完成的后要执行 chmod +x entrypoint.sh命令文件才可以使用
# !/bin/sh
/etc/init.d/fdfs_trackerd start
/etc/init.d/fdfs_storaged start
/usr/local/nginx/sbin/nginx -g 'daemon off;'

相关配置文件

  • tracker.conf: FastDFS跟踪器配置,容器路径为:/etc/fdfs,修改:
base_path=/fastdfs/tracker
  • storage.conf: FastDFS存储节点配置,容器路径为:/etc/fdfs,修改:
base_path=/fastdfs/storage
store_path0=/fastdfs/storage
tracker_server=192.168.32.255:22122
http.server_port=8888
  • client.conf: FastDFS客户端配置,容器中路径为:/etc/fdfs,修改:
base_path=/fastdfs/tracker
tracker_server=192.168.32.255:22122
  • config: fastdfs-nginx-module配置文件,容器中路径为:/usr/local/src/fastdfs-nginx-module/src,修改:
# 修改前
CORE_INCS="$CORE_INCS /usr/local/include/fastdfs /usr/local/include/fastcommon/"
CORE_LIBS="$CORE_LIBS -L/usr/local/lib -lfastcommon -lfdfsclient"

# 修改后
CORE_INCS="$CORE_INCS /usr/include/fastdfs /usr/include/fastcommon/"
CORE_LIBS="$CORE_LIBS -L/usr/lib -lfastcommon -lfdfsclient"
  • mod_fastdfs.conf: fastdfs-nginx-module配置文件,容器中路径:/usr/local/src/fastdfs-nginx-module/src,修改:
connect_timeout=10
tracker_server=192.168.75.128:22122
url_have_group_name=true
store_path0=/fastdfs/storage
  • nginx.conf: Nginx配置文件,容器中的路径为:/usr/local/src/nginx-1.15.4/conf,修改:
user root;
worker_processes 1;

events {
    worker_connections 1024;
}

http{
    include                    mime.types;
    defaulte_type        application/octet-stream;

    sendfile                on;

    keepalive_timeout    65;

    server{
        listen                8888;
        server_name     localhost;

        location ~/group([0-9])/M00{
            ngx_fastddfs_module;
        }

        error_page    500 502 503 504 /50x.html
        location = /50x.html {
            root html;
        }
    }
}

启动容器

  • docker-compose.yml: 在/usr/local/docker/fastdfs文件夹中创建docker-compose.yml
version: '3.1'
services:
 fastdfs:
  build: environment
  restart: always
  container_name: fastdfs
  volumes:
   - ./storage:/fastdfs/storage
  network_mode: host            # 网络模式:主机模式--将所有端口映射到主机,Docker容器与宿主机共享端口,即端口一致
  • 执行命令,使文件编译生效
docker-compose up -d

测试上传

  • 交互式进入容器:
docker exec -it fastdfs /bin/bash
  • 测试文件上传: 在/usr/bin目录中执行(第1个是二进制可执行文件客户端,第2个是客户端的客户端配置文件,第3个是需要上传的文件)
/usr/bin/fdfs_upload_file /etc/fdfs/client.conf /usr/local/src/fastdfs-5.11/INSTALL
  • 服务器反馈上传地址: 文件的上传路径(非地址),通过在浏览器输入Ngnix的访问地址+文件上传路径即可访问服务器上的文件
group1/M00/00/00/wKliyyfhHHkjsio986777
  • 测试Nginx访问: 通过在浏览器输入Ngnix的访问地址+文件上传路径即可访问服务器上的文件
http://192.168.32.255:8888/group1/M00/00/00/wKliyyfhHHkjsio986777

配置 FastDFS Java客户端

  • 创建项目: 创建项目名为myshop-service-upload的服务提供者项目

安装FastDFS Java客户端

  • 从github上git clone FastDFS项目代码:
git clone https://github.com/happyfish100/fastdfs-client-java.git
  • 配置到本地仓库: 在项目目录的target包下有项目的jar文件
mvn clean install
  • 将项目jar文件上传到Nexus中
  • 在项目中添加依赖:
<!--FastDFS Begin-->
<dependency>
    <groupId>org.csource</groupId>
    <artifactId>fastdfs-client-java</artifactId>
    <version>1.27-SNAPSHOT</version>
</dependency>

创建FastDFS工具类

  • 定义文件存储服务接口:
package com.oxford.myshop.service.upload.fastdfs;

public interface StorageService{
    /**
     *上传文件
     *
     *@param data    文件的二进制符
     *@param extName 扩展名
     *@return          上传成功后返回生成文件的id,失败则返回null
     */
     public String upload(byte[] data,String extName);
     
     /**
     *删除文件
     *
     *@param fileId    被删除的文件id
     *@return            删除成功后返回0,失败后返回错误代码
     */
     public int delete(String fileId);
}
  • 实现文件存储服务接口:
public class FastDFSStorageService implements StorageService,InitializingBean{
    private static final Logger logger=LoggerFactory.getLogger(FastDFSStorageService.class);

    private TrackerClient trackerClient;

    @Value("${storage.fastdfs.tracker_server}")

    @Override
    public String upload(byte[] data,String extName){
        TrackerServer trackerServer=null;
        StorageServer storageServer=null;
        StorageClient storageClient=null;
        try{
            NameValuePair[] meta_list=null;        // new NameValuePair[0]

            trackerServer=trackerClient.getConnection();
            if(trackerServer==null){
                logger.error("getConnection return null");
            }
            storageServer=trackerClient.getStoreStorage(trackerServer);
            storageClient1=new StorageClient1(trackerServer,storageServer);
            String fileId=storageClient1.upload_file1(data,extName,meta_list);
            logger.debug("uploaded file <{}>",fileId);
            return fileId;
        }catch(Exception ex){
            logger.error("Uploaded fail",ex);
            return null;
        }finally{
            if(storageServer!=null){
                try{
                    storageServer.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
            if(trackeServer!=null){
                try{
                    trackeServer.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
            storageClient1=null;
        }
    }

    @Override
    public int delete(String fileId){
        TrackerServer trackerServer=null;
        StorageServer storageServer=null;
        StorageClient storageClient=null;
        int index=fileId.indexOf('/');
        String groupName=fileId.substring(0,index);
        try{
            trackerServer=trackerClient.getConnection();
            if(trackerServer==null){
                logger.error("getConnection return null");
            }
            storageServer=trackerClient.getStoreStorage(trackerServer,groupName);
            storageClient1=new StorageClient1(trackerServer,storageServer);
            int result=storageClient1.delete_file1(fileId);
            return result;
        }catch(Exception ex){
            logger.error("Delete fail",ex);
            return 1;
        }finally{
            ifreturn fileId;
        }catch(Exception ex){
            logger.error("Uploaded fail",ex);
            return null;
        }finally{
            if(storageServer!=null){
                try{
                    storageServer.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
            if(trackeServer!=null){
                try{
                    trackeServer.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
            storageClient1=null;
        }
    }
    @Override
    public void afterPropertiesSet() throws Exxception{
        File confFile=File.createTempFile("fastdfs",".conf");
        PrintWriter confWriter=new PrintWriter(new FileWriter(confFile));
        confWriter.println("tracker_server="+trackerServer);
        confWriter.close();
        ClientGlobal.init(confFile.getAbsolutePath());
        confFile.delete();
        TrackerGroup trackerGroup=ClientGlobal.g_tracker_group;
        trackerClient=new TrackerClient(trackerGroup)

        logger.info("Init FastDFS with tracker_server : {}",trackerServer);
    }
}
  • 文件存储服务工厂
public class StorageFactory implements FactoryBean<StorageService>{
    @Autowired
    private AutowireCapableBeanFactory acbf;

    /**
     * 存储服务的类型,仅支持FastDFS
     */
     @Value("${storage.type}")
     private String type;
     
     private Map<String,Class<? extends StorageService>> classMap;

     public StorageFactory(){
        classMap=new HashMap<>();
        classMap.put("fastdfs",FastDFSStorageService.class);
    } 

    @Override
    public StorageService getObject() throws Exception{
        Class<? extends StorageService> clazz=classMap.get(type);
        if(class==null){
            throw new RuntimeException("Unsupported storage type ["+type+"],valid are"+ classMap.keySet());
        }

        StorageService bean=clazz.newInstance();
        acbf.autowireBean(bean);
        acbf.initializeBean(bean,bean.getClass().getSimpleName());
        return bean;
    }

    @Override
    public Class<?> getObjectType(){
        return StorageService.class;
    }

    @Override
    public boolean isSingleton(){
        return true;
    }
}
  • 配置文件存储服务工厂类
/**
 * Java配置方式定义StorageFactory的bean,使其可以被依赖注入
 */
 @Configuration
 public classs FastDFSConfiguration{
     @Bean
     public StorageFactory storageFactory(){
         return new StorageFactory();
     }
 }

创建FastDFS控制器

  • 增加云配置: application.yml
# SpringBoot Application
spring:
 application:
  name: myshop-service-upload

# FastDFS Configuration
fastdfs.base.url: htttp//192.168.32.255:8888/
storage:
 type: fastdfs
 fastdfs:
  tracker_server: 192.168.32.255:22122
  • 控制器代码
@CrossOrigin(origins="*",maxAge=3600)
@RestController
public class UploadController{
    @Value("${fastdfs.base.url}")
    private String FASTDFS_BASE_URL;

    @Autowired
    private StorageService storageService;

    @RequestMapping(value="upload",method=RequestMethod.POST)
    public Map<String,Object> upload(MultipartFile dropFile,MultipartFile[] editorFiles){
        Map<String,Object> result=new HashMap<>();

        //DropZone上传
        if(dropFile!=null){
            result.put("fileName",writeFile(dropFile));
        }

        //wangEditor上传
        if(editorFiles != null && editorFiles.length > 0){
            List<String> fileNames=new ArrayList<>();

            for(MultipartFile editorFile:editorFiles){
                fileNames.add(writeFile(editorFile));
            }

            result.put("error",0);
            result.put("data",fileNames);
        }
        return result;
    }
    
    /**
     *将图片写入指定目录
     */
     private String writeFile(MultipartFile multipartFile){
        // 获取文件后缀
        String oName=multipartFile.getOriginalFilename();
        String exName=oName.substring(oName.lastIndexOf(".")+1);
        
        // 文件存放路径
        String url=null;
        try{
            String uploadUrl=storageService.upload(multipartFile.getBytes(),exName);
            url=FASTDFS_BASE_URL+uploadUrl;
        }catch(IOException e){
            e.printStackTrace();
        }
        
        // 返回文件完整路径
        return url;
    }
}
  • 创建SpringBoot Application,运行执行分布式文件上传项目
相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
存储 负载均衡 安全
分布式文件系统实战,使用MinIO构建分布式文件系统!
随着文件数据的越来越多,传统的文件存储方式通过tomcat或nginx虚拟化的静态资源文件在单一的服务器节点内已经无法满足系统需求,也不利于文件的管理和维护,这就需要一个系统来管理多台计算机节点上的文件数据,这就是分布式文件系统。
5099 0
分布式文件系统实战,使用MinIO构建分布式文件系统!
|
7月前
|
存储 分布式计算 监控
Hadoop【基础知识 01+02】【分布式文件系统HDFS设计原理+特点+存储原理】(部分图片来源于网络)【分布式计算框架MapReduce核心概念+编程模型+combiner&partitioner+词频统计案例解析与进阶+作业的生命周期】(图片来源于网络)
【4月更文挑战第3天】【分布式文件系统HDFS设计原理+特点+存储原理】(部分图片来源于网络)【分布式计算框架MapReduce核心概念+编程模型+combiner&partitioner+词频统计案例解析与进阶+作业的生命周期】(图片来源于网络)
330 2
|
7月前
|
存储 Windows
DFS(分布式文件系统)与 DFSR(分布式文件系统复制)的区别
DFS(分布式文件系统)提供了一个逻辑上的命名空间,将不同物理位置的文件夹整合在一起便于集中访问;而DFSR(分布式文件系统复制)是DFS的一部分,负责在多台服务器之间同步和复制共享文件夹的内容以实现数据冗余和一致性。
234 1
|
存储 索引
32分布式电商项目 - FastDFS文件上传与下载流程
32分布式电商项目 - FastDFS文件上传与下载流程
70 0
32分布式电商项目 - FastDFS文件上传与下载流程
|
存储 缓存 应用服务中间件
分布式文件系统FastDFS看这一篇就够了(文件上传下载、单机部署及集群部署)(二)
分布式文件系统FastDFS看这一篇就够了(文件上传下载、单机部署及集群部署)
608 0
分布式文件系统FastDFS看这一篇就够了(文件上传下载、单机部署及集群部署)(二)
|
存储 分布式计算 Hadoop
大数据数据存储的分布式文件系统的HDFS的基本概念和架构的概念的Hadoop 分布式文件系统
Hadoop 分布式文件系统 (HDFS) 是一个开源的分布式文件系统,是 HttpFS 的后继者。
153 2
|
存储 前端开发 Linux
NetCore开发的分布式文件上传系统
一个基于.Net Core构建的简单、跨平台分布式文件上传系统,支持分块上传、多个项目同时上传、接口权限控制采用JWT机制。
230 0
NetCore开发的分布式文件上传系统
|
存储 负载均衡 调度
分布式文件服务器FastDFS介绍与FastDFS文件上传下载
FastDFS 是用 c 语言编写的一款开源的分布式文件系统。FastDFS 为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用 FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
215 0
分布式文件服务器FastDFS介绍与FastDFS文件上传下载
|
存储 缓存 Java
分布式文件系统FastDFS看这一篇就够了(文件上传下载、单机部署及集群部署)(三)
分布式文件系统FastDFS看这一篇就够了(文件上传下载、单机部署及集群部署)
730 0
分布式文件系统FastDFS看这一篇就够了(文件上传下载、单机部署及集群部署)(三)
|
存储 容灾 算法
分布式文件系统FastDFS看这一篇就够了(文件上传下载、单机部署及集群部署)(一)
分布式文件系统FastDFS看这一篇就够了(文件上传下载、单机部署及集群部署)
542 0
分布式文件系统FastDFS看这一篇就够了(文件上传下载、单机部署及集群部署)(一)