一、简介

FastDFS是一个轻量级的开源分布式文件系统。主要解决了大容量文件存储和高并发访问的问题,文件存取时实现了负载均衡。实现了软件方式的磁盘阵列,可以使用廉价的IDE硬盘进行存储,并且支持存储服务器在线扩容。支持相同内容的文件只保存一份,节约磁盘空间。
FastDFS只能通过Client API访问,不支持POSIX访问方式。
FastDFS特别适合大中型网站使用,用来存储资源文件,如:图片、文档、音频、视频等。
磁盘阵列:技术是加州大学伯克利分校1987年提出,最初是为了组合小的廉价磁盘来代替大的昂贵磁盘,同时希望磁盘失效时不会使对数据的访问受损 失而开发出一定水平的数据保护技术。RAID就是一种由多块廉价磁盘构成的冗余阵列,在操作系统下是作为一个独立的大型存储设备出现。RAID可以充分发 挥出多块硬盘的优势,可以提升硬盘速度,增大容量,提供容错功能够确保数据安全性,易于管理的优点,在任何一块硬盘出现问题的情况下都可以继续工作,不会 受到损坏硬盘的影响RAID(Redundant Array of Independent Disk 独立冗余磁盘阵列)技术是加州大学伯克利分校1987年提出,最初是为了组合小的廉价磁盘来代替大的昂贵磁盘,同时希望磁盘失效时不会使对数据的访问受损 失而开发出一定水平的数据保护技术。RAID就是一种由多块廉价磁盘构成的冗余阵列,在操作系统下是作为一个独立的大型存储设备出现。RAID可以充分发 挥出多块硬盘的优势,可以提升硬盘速度,增大容量,提供容错功能够确保数据安全性,易于管理的优点,在任何一块硬盘出现问题的情况下都可以继续工作,不会 受到损坏硬盘的影响
下载网址:FastDFS没有官网。但是作者余庆(happy_fish100)担任chinaunix中FastDFS板块版主。并且会不定期更新板块中内容。http://bbs.chinaunix.net/
FastDFS软件可以在sourceforge中进行下载,最新版本为5.08
https://sourceforge.net/projects/fastdfs/files/

二、FastDFS架构(面试题)

1、架构图

image.png

2、角色

client:客户端,使用java语言编写的项目属于客户端。
Tracker Server:跟踪服务器,主要做调度工作,在访问上起负载均衡的作用。在内存中记录集群中group和storage server状态信息,是连接Client和Storage和Storage server的枢纽。
Storage Server:存储服务器,文件和文件属性(meta data)都保存到存储服务器中。

3、架构解读

  • 只有两个角色,tracker server和storage server,不需要存储文件索引信息。
  • 所有服务器都是对等的,不存在Master-Slave关系。
  • 存储服务器采用分组方式,同组内存储服务器上的文件完全相同(RAID 1).
  • 不同组的Storage Server之间不会相互通信。
  • 由Storage server主动向tracker server报告状态信息,tracker server之间不会相互通信。

    三、FastDFS安装

    1、拉取镜像

    docker中fastdfs没有官方镜像
    1. docker pull delron/fastdfs

    2、创建并启动tracker容器

    —network表示与宿主共用网络
    1. docker run -d --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs delron/fastdfs tracker

    3、创建并启动storage容器

    1. docker run -d --network=host --name storage -e TRACKER_SERVER=192.168.80.128:22122 -v /var/fdfs/storage:/var/fdfs -e GROUP_NAME=group1 delron/fastdfs storage
    注意:第一次启动storage是成功的,但是如果希望重新启动成功,必须要先删除Linux中
    /var/fdfs/storage/data/fdfs_.pid
    然后再启动。否则查看docker容器日志会出现如下的情况。
    image.png

    四、操作文件流程

    1、文件上传

    1.1 时序图

    image.png

    1.2 流程说明

  1. 客户端访问Tacker
  2. Tracker返回Storage的IP和端口号
  3. 客户端直接访问Storage,把文件内容和元数据发送过去。
  4. Storage返回文件存储id。包含组名和文件名。

image.png

2、文件下载

2.1 时序图

image.png

2.2 流程说明

  1. client询问tracker下载的文件的storage,参数为文件标识(组名和文件名);
  2. tracker返回一台可用的storage
  3. client直接和storage通讯完成文件下载。

    2.3 注意事项

  4. 直接使用工具方法完成下载即可

  5. 使用java原生API实现输入流和输出流的转换
  6. downloadFile的第二个参数要写成自己上传后的图片路径。

    3、文件删除

    3.1 注意事项

  • 删除成功的返回结果为0。
  • 图片路径要写自己上传后的图片路径。

    五、FastDFS-java-client代码实现

  1. 添加依赖

    1. <dependencies>
    2. <dependency>
    3. <groupId>cn.bestwu</groupId>
    4. <artifactId>fastdfs-client-java</artifactId>
    5. <version>1.27</version>
    6. </dependency>
    7. <dependency>
    8. <groupId>org.apache.commons</groupId>
    9. <artifactId>commons-lang3</artifactId>
    10. <version>3.4</version>
    11. </dependency>
    12. </dependencies>
  2. 编写配置文件

  • 在resources下新建fastdfs的配置文件
  • 文件名:fdfs_client.conf
  • 修改成自己的tracker服务器ip
    1. connect_timeout = 10
    2. network_timeout = 30
    3. charset = UTF-8
    4. http.tracker_http_port = 8080
    5. tracker_server = 192.168.80.128:22122
  1. 导入工具类

在com.bjsxt.utils.FastDFSClient下创建工具类。
如果新建的是聚合的项目,完成FastDFS文件上传操作需要修改成:
private static final String CONF_FILENAME = “fdfs_client.conf”;
FastDFSClient工具类文件(点击即可预览或下载):FastDFSClient.java

  1. 新建测试类,完成上传、下载、删除、修改的测试

    1. package com.bjsxt.utils;
    2. import java.io.*;
    3. import java.util.Arrays;
    4. public class TestA {
    5. public static void main(String[] args) {
    6. //up();
    7. //down();
    8. //remove();
    9. modify();
    10. }
    11. //文件上传实现
    12. //注意事项:参数A:需要上传的文件
    13. // 参数B是文件的类型 文件名称无所谓但是必须保证文件类型一致,因为FastDFS需要对文件进行重命名
    14. //String[] strings = FastDFSClient.uploadFile(new File("D:/1.png"), "xxxxx.png");
    15. //[group1, M00/00/00/wKgIgWGEoK2APyjKAAE8sEFdJRc658.png]
    16. // System.out.println(Arrays.toString(strings));
    17. /*文件的上传*/
    18. public static void up(){
    19. File file = new File("E:/aa.png");
    20. String[] result = FastDFSClient.uploadFile(file, "xxxx.png");
    21. System.out.println("length:"+result.length);
    22. System.out.println(result[0]);
    23. System.out.println(result[1]);
    24. }
    25. /*文件的下载*/
    26. public static void down(){
    27. try {
    28. InputStream is = FastDFSClient.downloadFile("group1", "M00/00/00/wKhQgGGE2-iAFuIOAAiDton9kwo378.png");
    29. FileOutputStream os = new FileOutputStream(new File("E:/jqk.png"));
    30. int i=0;
    31. while ((i=is.read())!=-1){
    32. os.write(i);
    33. }
    34. os.flush();
    35. os.close();
    36. } catch (IOException e) {
    37. e.printStackTrace();
    38. }
    39. }
    40. //文件删除 :注意 删除成功返回值是0
    41. /*文件的删除*/
    42. public static void remove(){
    43. int index = FastDFSClient.deleteFile("group1", "M00/00/00/wKhQgGGE3s-Aam5eAAiDton9kwo634.png");
    44. System.out.println("result:"+index);
    45. }
    46. /*文件的修改*/
    47. public static void modify(){
    48. String[] group1s = FastDFSClient.modifyFile("group1", "M00/00/00/wKhQgGGE2-iAFuIOAAiDton9kwo378.png", new File("E:/aa.png"), "xx.png");
    49. System.out.println(Arrays.toString(group1s));
    50. }
    51. }

    六、Nginx

    1、简介

    FastDFS是没有HTTP协议文件访问功能的,需要借助其他工具实现图片HTTP访问的。Nginx就具备了代理虚拟机主机功能。
    Nginx是一个高性能的HTTP和反向代理服务。它具有很多非常优越的特性:在连接高并发的情况下,Nginx是Apache服务不错的替代品。

    2、代理方式-正向代理

    正向代理:一个位于客户端和原始服务器之间的服务器,为了从原始服务器中获取内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端,客户端才能使用正向代理。
    image.png

    3、代理方式-反向代理

    反向代理(Reverse Proxy)方式是值以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
    image.png

    4、正反向代理区别

    正向代理即是客户端代理, 代理客户端, 服务端不知道实际发起请求的客户端.
    反向代理即是服务端代理, 代理服务端, 客户端不知道实际提供服务的服务端
    image.png

    5、Nginx作用

    5.1 HTTP协议代理(反向代理)

    只要支持HTTP协议访问的内容,都可以由Nginx进行代理。Nginx只支持HTTP协议的代理,其他协议不支持。

    5.2 搭建虚拟主机(暴露主机端口,外部可以通过http访问)

    Nginx可以监听所安装的主机的某个端口,对外支持这个端口的HTTP访问。当接收到外部HTTP请求后把本机中资源返回给客户端。今天的课程内容就是使用Nginx的搭建虚拟主机功能,外部请求图片时,把图片信息响应给请求发。

    5.3 负载均衡(反向代理)

    Nginx可以代理多个主机,内置负载均衡策略。

    6、Nginx安装配置

  2. 修改Nginx配置

进入到storage容器后修改nginx配置文件

docker exec -it storage /bin/bash
vi /tmp/nginx/nginx-1.12.2/conf/nginx.conf

在配置文件的http里面添加下面内容

server{
listen 8888;
server_name localhost;
location ~/group([0-9])/M00{
ngx_fastdfs_module;
}
}
  1. 重启storage容器

docker stop storage
删除 xxx.pid文件
docker start storage

  1. 测试

所有操作必须在storage容器内部进行操作。
保证在容器命令行中,任意目录下执行命令。
向a.html尾加bjsxt,nihao。如果a.html不存在会新建这个文件

echo “bjsxt,nihao” > a.html

使用fastdfs客户端命令进行上传文件。
fdfs_upload_file 客户端工具,client.conf 客户端配置文件 a.html要上传的文件

/usr/bin/fdfs_upload_file /etc/fdfs/client.conf a.html

执行完成后会输出可访问访问路径。
在浏览器中访问,如果正常访问,说明安装成功。

七、案例

  1. application.yml

    1. spring:
    2. datasource:
    3. driver-class-name: com.mysql.cj.jdbc.Driver
    4. url: jdbc:mysql://localhost:3306/nginx?serverTimezone=Asia/Shanghai&useSSL=false&characterEncoding=utf8
    5. username: root
    6. password: root
    7. #设置文件上传的大小
    8. servlet:
    9. multipart:
    10. max-file-size: 10MB
    11. max-request-size: 10MB
    12. mybatis:
    13. #起别名
    14. type-aliases-package: com.bjsxt.pojo
    15. #扫描mybatis下所有的xml文件
    16. mapper-locations: classpath:mybatis/*.xml
  2. FastDFSClient工具类文件(点击即可预览或下载):FastDFSClient.java

  3. fdfs_client.conf

    1. connect_timeout = 10
    2. network_timeout = 30
    3. charset = UTF-8
    4. http.tracker_http_port = 8080
    5. tracker_server = 192.168.80.128:22122
  4. mappr和service示例

    1. public interface StudentMapper {
    2. int insert(Student student);
    3. @Select("select * from student")
    4. List<Student> selectMore();
    5. @Delete("delete from student where sid=#{sid}")
    6. int delectOne(Student student);
    7. }
    1. public interface StudentService {
    2. int insertStu(Student student, MultipartFile mpf);
    3. List<Student> findAll();
    4. int delOne(Student student);
    5. }

    ```java @Service @Transactional public class StudentServiceImpl implements StudentService { @Autowired private StudentMapper studentMapper;

    /删除/ @Override public int delOne(Student student) {

    1. int flag=0;
    2. try {
    3. int index = FastDFSClient.deleteFile(student.getGroupname(), student.getFilename());
    4. System.out.println(student.getGroupname());
    5. System.out.println(student.getFilename());
    6. System.out.println("index:"+index);
    7. studentMapper.delectOne(student);
    8. flag=1;
    9. } catch (Exception e) {
    10. throw e;
    11. }
    12. return flag;

    }

    /查找/ @Override public List findAll() {

     return studentMapper.selectMore();
    

    }

    /添加/ @Override public int insertStu(Student student , MultipartFile mpf) {

     try {
         String[] strings = FastDFSClient.uploadFile(mpf.getInputStream(), mpf.getOriginalFilename());
         student.setGroupname(strings[0]);
         student.setFilename(strings[1]);
         student.setUrl("http://192.168.80.128:8888/"+strings[0]+"/"+strings[1]);
         student.setFiletype(mpf.getContentType());
    
     } catch (IOException e) {
         e.printStackTrace();
     }
     int insert = studentMapper.insert(student);
    
     return insert;
    

    }

}


5. StudentController
```java
@Controller
public class StudentController {
    @Autowired
    private StudentService studentService;

    @RequestMapping("/{path}")
    public String getPath(String path){
        return path;
    }

    @RequestMapping("/favicon.ico")
    @ResponseBody
    public void favicon(){

    }

    @PostMapping("/add")
    public String add(Student student, MultipartFile mpf){
        System.out.println(student);
        System.out.println(mpf);
        int i = studentService.insertStu(student, mpf);
        if (i>0){
            return "redirect:/list";
        }
            return "forward:/insert";

    }

    @RequestMapping("/list")
    /*model作用域等同于request*/
    public String list(Model model){
        List<Student> list = studentService.findAll();
        model.addAttribute("list",list);
        return "show";
    }

    @RequestMapping("/download")
    public void download(String groupname, String filename, String filetype, HttpServletResponse response) throws IOException {
        //告诉浏览器把下载的文件保存到硬盘上 不在浏览器进行解析
        response.setHeader("Content-Disposition", "attachment;filename="+filename);
        //设置下载文件的类型
        response.setContentType(filetype);

        InputStream inputStream = FastDFSClient.downloadFile(groupname, filename);

        //下载到客户端本地
        ServletOutputStream outputStream = response.getOutputStream();

        IOUtils.copy(inputStream,outputStream);
        outputStream.close();
        inputStream.close();
    }

    @RequestMapping("/remove")
        public String remove(Student student){
            int i = studentService.delOne(student);
            if (i>0){
                return "redirect:/list";
            }
            return "redirect:/delerr";
        }

}
  1. 前台页面

image.png

image.png