FastDFS教程

FastDFS简介

FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。

FastDFS服务端有两个角色:跟踪器(tracker)存储节点(storage)

跟踪器主要负责调度工作,在访问上起负载均衡的作用。

存储节点存储文件,完成文件管理的所有功能,同时FastDFS同时对文件的metaData(文件属性列表)进行管理。所谓文件的metaData就是文件的相关属性,以键值对(key value pair)方式表示,如:width=1024,其中的key为width,value为1024。文件meta data是文件属性列表,可以包含多个键值对。

tracker和storage都可以由一台或多台服务器构成。tracker和storage中的服务器均可以随时增加或下线而不影响线上服务。其中tracker中的所有服务器都是对等的,可以根据服务器的压力情况随时增加或减少。

为了支持大容量,storage采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起到了冗余备份和负载均衡的作用。

在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。

当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将他们配置为一个新的组,这样就扩大了存储的容量。

FastDFS中的文件标识分为两个部分:组名和文件名。二者缺一不可。

FastDFS原理

存储节点采用了分组(group)的方式。存储系统由一个或多个group组成,group与group之间的文件是相互独立的,所有group的文件容量累加就是整个存储系统中的文件容量。

一个group可以由一台或多台存储服务器组成,一个group下的存储服务器中的文件都是相同的,group中的多台存储服务器起到了冗余备份和负载均衡的作用(一个组的存储容量为该组内存储服务器容量最小的那个,不同组的Storage server之间不会相互通信,同组内的Storage server之间会相互连接进行文件同步)。

工作流程

文件上传

Client会先想Tracker询问存储地址,Tracker查询到存储地址后返回给Client,Client拿着地址直接和对应的Storage通信,将文件上传到storage。

文件下载

同样,Client会向Tracker询问地址,并带上要查询的文件名和组名,tracker查询后会将地址返回给Client,client拿到地址和指定Storage通讯并下载文件。

使用docker安装FastDFS

  1. 拉取镜像:docker pull season/fastdfs
  2. 创建tracker容器:docker run -it -d --name tracker -v /home/iie4bu/docker/fastDFS/tracker:/fastdfs/tracker/data --net=host season/fastdfs tracker 默认端口是22122
  3. 创建storage容器:docker run -itd --name storage -v /home/iie4bu/docker/fastDFS/storage:/fastdfs/storage/data -v /home/iie4bu/docker/fastDFS/store_path:/fastdfs/store_path --net=host -e TRACKER_SERVER:192.168.171.25(本机IP):22122 -e GROUP_NAME=group1 season/fastdfs storage
  4. 此时两个服务都以启动,进行服务的配置。
    进入storage容器,到storage的配置文件中配置http访问的端口,配置文件在fdfs_conf目录下的storage.conf。将文件拷贝出来进行修改:docker cp storage:/fdfs_conf/storage.conf ~/,修改storage.conf文件中的tracker_server=你自己的宿主机ip:22122
    将修改后的配置文件拷贝到storage的配置目录下:docker cp ~/storage.conf storage:/fdfs_conf/
    重启storage容器:docker container restart storage
  5. 查看tracker容器和storage容器的关联
[root@localhost ~]# docker exec -it storage bash
root@localhost:/# cd fdfs_conf
root@localhost:/fdfs_conf# fdfs_monitor storage.conf

在这里插入图片描述

在docker模拟客户端上传文件到storage容器

开启一个客户端:docker run -tid --name fdfs_sh --net=host season/fastdfs sh
将之前的storage.conf文件拷贝到容器fdfs_sh:/fdfs_conf/目录下:docker cp ~/storage.conf fdfs_sh:/fdfs_conf/
创建一个txt文件:

[root@localhost 00]# docker exec -it fdfs_sh bash
root@localhost:/# echo hello>a.txt

进入fdfs_conf目录,并将文件上传到storage容器:

root@localhost:/# cd fdfs_conf
root@localhost:/fdfs_conf# fdfs_upload_file storage.conf /a.txt

返回路径:
在这里插入图片描述
然后去我们对应的宿主机挂载文件夹下查看:
在这里插入图片描述

FastDFS配合使用Nginx

使用delron/fastdfs镜像,这个镜像中自带Nginx服务。

  1. 首先拉取镜像:docker pull delron/fastdfs
  2. 开启tracker 服务:docker run -it -d --network=host --name tracker -v ~/docker/fastdfs/tracker/:/var/fdfs delron/fastdfs tracker。将fastDFS tracker运行目录映射到本机的~/docker/fastdfs/tracker/目录中。
  3. 开启storage服务:docker run -it -d --network=host --name storage -e TRACKER_SERVER=192.168.1.5(本机IP):22122 -v ~/docker/fastdfs/storage/:/var/fdfs delron/fastdfs storage
    查看docker服务:
    在这里插入图片描述
    正常启动了两个服务。
  • 端口8888是默认的nginx代理端口
  • 端口23000是storage服务端口
  • 端口22122是tracker服务端口

配置Storage

进入storage容器,到storage的配置文件中配置http访问的端口,配置文件在/etc/fdfs目录下的storage.conf。
在这里插入图片描述
在这里插入图片描述
默认的端口是8888,可以不修改

配置Nginx

/usr/local/nginx目录下,修改nginx.conf文件
在这里插入图片描述
默认配置如下:(这里可以不用修改)
在这里插入图片描述

开启一个客户端容器

docker run -it -d --network=host --name fdfs_sh -e TRACKER_SERVER=192.168.1.5:22122 delron/fastdfs sh
在这里插入图片描述
然后上传一个文件到fdfs_sh容器中:docker cp timg.jpg fdfs_sh:/
然后进入fdfs_sh容器:docker exec -it fdfs_sh bash
上传文件:fdfs_upload_file /etc/fdfs/client.conf timg.jpg
返回信息如下:

[root@manager /]# fdfs_upload_file /etc/fdfs/client.conf timg.jpg 
group1/M00/00/00/wKirGV6yybOAdf9TAABjtCKBG50922.jpg

在浏览器中输入:ip:8088/group1/M00/00/00/wKirGV6yybOAdf9TAABjtCKBG50922.jpg就可以看到这张图片了。

Springboot代码实现上传与下载

添加依赖包pom.xml内容如下:

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.1.4.RELEASE</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.tobato</groupId>
            <artifactId>fastdfs-client</artifactId>
            <version>1.27.2</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68</version>
        </dependency>

    </dependencies>

配置文件application.yaml内容如下:

fdfs:
  soTimeout: 1500 #socket连接超时时长
  connectTimeout: 600 #连接tracker服务器超时时长
  reqHost: manager   #nginx访问地址
  reqPort: 8888              #nginx访问端口
  thumbImage: #缩略图生成参数,可选
    width: 150
    height: 150
  trackerList: #TrackerList参数,支持多个,我这里只有一个,如果有多个在下方加- x.x.x.x:port
    - manager:22122

spring:
  servlet:
    multipart:
      max-request-size: 500MB
      max-file-size: 500MB
server:
  port: 8080

添加配置文件FdfsConfiguration.java

package cn.ac.iie.config;

import com.github.tobato.fastdfs.FdfsClientConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.context.annotation.Import;
import org.springframework.jmx.support.RegistrationPolicy;

@Configuration
@Import(FdfsClientConfig.class) // 导入FastDFS-Client组件
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING) // 解决jmx重复注册bean的问题
public class FdfsConfiguration {
}

工具类FastDFSClientUtil.java

package cn.ac.iie.utils;


import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.fdfs.ThumbImageConfig;
import com.github.tobato.fastdfs.domain.proto.storage.DownloadCallback;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;

@Component
public class FastDFSClientUtil {

    @Value("${fdfs.reqHost}")
    private String reqHost;

    @Value("${fdfs.reqPort}")
    private String reqPort;

    @Autowired
    private FastFileStorageClient storageClient;

    @Autowired
    private ThumbImageConfig thumbImageConfig; //创建缩略图   , 缩略图访问有问题,暂未解决


    public String uploadFile(MultipartFile file) throws IOException {
        StorePath storePath = storageClient.uploadFile(file.getInputStream(),file.getSize(), FilenameUtils.getExtension(file.getOriginalFilename()),null);

        String path = thumbImageConfig.getThumbImagePath(storePath.getPath()) ;
        System.out.println("thumbImage :" + path);  //   缩略图访问有问题,暂未解决
        System.out.println(storePath);
        return getResAccessUrl(storePath);
    }

    public void delFile(String filePath) {
        storageClient.deleteFile(filePath);

    }


    public InputStream download(String groupName, String path ) {
        InputStream ins =  storageClient.downloadFile(groupName, path, new DownloadCallback<InputStream>(){
            public InputStream recv(InputStream ins) throws IOException {
                // 将此ins返回给上面的ins
                return ins;
            }}) ;
        return ins ;
    }

    /**
     * 封装文件完整URL地址
     * @param storePath
     * @return
     */
    private String getResAccessUrl(StorePath storePath) {
        System.out.println(storePath.getFullPath());
        String fileUrl = "http://" + reqHost + ":" + reqPort + "/" + storePath.getFullPath();
        return fileUrl;
    }

}

controller:

package cn.ac.iie.controller;


import cn.ac.iie.utils.FastDFSClientUtil;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import org.apache.commons.io.IOUtils;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;

@RestController
public class UploadController {
    @Autowired
    private FastDFSClientUtil dfsClient;

    @PostMapping("/upload")
    public JSONObject fdfsUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
        JSONObject jsonObject = new JSONObject();
        try {
            String fileUrl = dfsClient.uploadFile(file);
            jsonObject.put("url", fileUrl);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return jsonObject;
    }

    /*
     * http://localhost:8080/download?filePath=group1/M00/00/00/wKgIZVzZEF2ATP08ABC9j8AnNSs744.jpg
     */
    @RequestMapping("/download")
    public void download(String filePath , HttpServletRequest request , HttpServletResponse response) throws IOException {

        // group1/M00/00/00/wKgIZVzZEF2ATP08ABC9j8AnNSs744.jpg
        String[] paths = filePath.split("/");
        String groupName = null;
        for (String item : paths) {
            if (item.indexOf("group") != -1) {
                groupName = item;
                break ;
            }
        }
        String path = filePath.substring(filePath.indexOf(groupName) + groupName.length() + 1, filePath.length());
        InputStream input = dfsClient.download(groupName, path);

        //根据文件名获取 MIME 类型
        String fileName = paths[paths.length-1] ;
        System.out.println("fileName :" + fileName); // wKgIZVzZEF2ATP08ABC9j8AnNSs744.jpg
        String contentType = request.getServletContext().getMimeType(fileName);
        String contentDisposition = "attachment;filename=" + fileName;

        // 设置头
        response.setHeader("Content-Type",contentType);
        response.setHeader("Content-Disposition",contentDisposition);

        // 获取绑定了客户端的流
        ServletOutputStream output = response.getOutputStream();

        // 把输入流中的数据写入到输出流中
        IOUtils.copy(input,output);
        input.close();
    }

    @RequestMapping("/deleteFile")
    public String delFile(String filePath , HttpServletRequest request ,HttpServletResponse response)  {

        try {
            dfsClient.delFile(filePath);
        } catch(Exception e) {
            // 文件不存在报异常 : com.github.tobato.fastdfs.exception.FdfsServerException: 错误码:2,错误信息:找不到节点或文件
            // e.printStackTrace();
        }
        request.setAttribute("msg", "成功删除,'" + filePath);

        return "index";
    }

}

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值