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/aptRUN echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse' > sources.listRUN echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse' >> sources.listRUN echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse' > sources.listRUN echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse' > sources.listRUN apt-get update# 装置依赖RUN apt-get install make gcc libpcre3-dev zliblg-dev --assume-yes# 复制工具包ADD fastdfs-5.11.tar.gz /usr/local/srcADD fastdfs-nginx-module_v1.16.tar.gz /usr/local/srcADD libfastcommon.tar.gz /usr/local/srcADD nginx-1.15.4.tar.gz /usr/local/src# 装置libfastcommonWORKDIR /usr/local/src/libfastcommonRUN ./make.sh && ./make.sh install# 装置 FastDFSWORKDIR /usr/local/src/fastdfs-5.11RUN ./make.sh && ./make.sh install# 配置FastDFS trackerADD tracker.conf /etc/fdfsRUN mkdir -p /fastdfs/tracker# 配置FastDFS storageADD storage.conf /etc/fdfsRUN mkdir -p /fastdfs/storage# 配置FastDFS客户端ADD client.conf /etc/fdfs# 配置fastdfs-nginx-moduleADD config /usr/local/src/fastdfs-nginx-modules/src# FastDFS与Nginx集成WORKDIR /usr/local/src/nginx-1.13.6RUN ./configure --add-module=/usr/local/src/fastdfs-nginx-module/srcRUN make && make installADD mod_fastdfs.conf /etc/fdfsWORKDIR /usr/local/src/fastdfs-5.11/confRUN cp http.conf mime.types /etc/fdfs/# 配置NginxADD nginx.conf /usr/local/nginx/confCOPY entrypoint.sh /usr/local/bin/ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]WORKDIR /EXPOSE 8888CMD ["/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/storagestore_path0=/fastdfs/storagetracker_server=192.168.32.255:22122http.server_port=8888
  • client.conf: FastDFS客户端配置,容器中门路为:/etc/fdfs,批改:

    base_path=/fastdfs/trackertracker_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=10tracker_server=192.168.75.128:22122url_have_group_name=truestore_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: environmentrestart: alwayscontainer_name: fastdfsvolumes: - ./storage:/fastdfs/storagenetwork_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 Applicationspring: application:name: myshop-service-upload# FastDFS Configurationfastdfs.base.url: htttp//192.168.32.255:8888/storage: type: fastdfs fastdfs:tracker_server: 192.168.32.255:22122
  • 控制器代码

    @CrossOrigin(origins="*",maxAge=3600)@RestControllerpublic 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,运行执行分布式文件上传我的项目