Triển khai và Sử dụng FastDFS Phân tán trên Máy Đơn

Trong công việc, chúng ta thường cần tải lên và tải xuống các tệp. Trong quá khứ, tệp thường được lưu trữ trên máy chủ web. Tuy nhiên, với việc triển khai nhiều máy chủ, một máy chủ tệp độc lập là cần thiết. Trước đây, NFS được sử dụng cho chia sẻ tệp giữa nhiều máy chủ web, và rsync được sử dụng để đồng bộ hóa tệp giữa các máy chủ tệp. Các giải pháp này đã trở nên lỗi thời do độ phức tạp cao. Hiện nay, FastDFS, một hệ thống tệp phân tán nhẹ, đang được ưa chuộng vì dễ dàng triển khai và duy trì.

FastDFS, phát triển bởi Yu Qing từ Alibaba, là một hệ thống tệp phân tán mã nguồn mở, quản lý tệp (lưu trữ, đồng bộ, tải lên/xuống/xóa), đặc biệt phù hợp cho dịch vụ trực tuyến dựa trên tệp như trang web hình ảnh và video. Bài viết này sẽ hướng dẫn cách triển khai FastDFS đơn giản bằng Docker Compose, và cung cấp mã demo để kết nối với FastDFS, thực hiện các thao tác tải lên, tải xuống, và xóa tệp.

Giới thiệu về Kiến trúc FastDFS

FastDFS bao gồm TrackerServer và StorageServer. TrackerServer chịu trách nhiệm cân bằng tải và điều phối, trong khi StorageServer lưu trữ tệp. Mỗi TrackerServer có vị trí ngang hàng, thu thập trạng thái của StorageServer. StorageServer có thể được chia thành nhiều nhóm, mỗi nhóm lưu trữ tệp khác nhau, và các thành viên trong cùng một nhóm sẽ đồng bộ tệp.

Triển khai FastDFS trên Máy Đơn

Giả sử IP máy ảo của bạn là 192.168.136.128, đã cài đặt docker và docker-compose. Đầu tiên, tạo hai thư mục để lưu trữ dữ liệu của TrackerServer và StorageServer:

mkdir -p /app/fastdfs/tracker
mkdir -p /app/fastdfs/storage

Tiếp theo, vào thư mục /app/fastdfs và tạo file docker-compose.yml với nội dung sau:

version: '3.5'
services:
  tracker:
    image: delron/fastdfs
    container_name: tracker
    network_mode: host
    volumes:
      - /app/fastdfs/tracker:/var/fdfs
    command: tracker
  storage:
    image: delron/fastdfs
    container_name: storage
    network_mode: host
    volumes:
      - /app/fastdfs/storage:/var/fdfs
    environment:
      TRACKER_SERVER: 192.168.136.128:22122
      GROUP_NAME: group1
    command: storage
    depends_on:
      - tracker

Chạy lệnh sau để khởi động các container:

cd /app/fastdfs
docker-compose up -d

Sau khi khởi động, bạn có thể truy cập FastDFS thông qua các địa chỉ sau:

Xây dựng Dự án

Tạo một dự án Spring Boot với cấu trúc sau:

  • WebMvcConfig để cấu hình Knife4j.
  • FastDfsController cung cấp các API để thao tác với FastDFS.
  • FastDfsService chứa các phương thức thao tác với FastDFS.

File pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jobs</groupId>
    <artifactId>springboot_fastdfs</artifactId>
    <version>1.0</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
    </parent>

    <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.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.4.5</version>
            </plugin>
        </plugins>
    </build>
</project>

File application.yml:

server:
  port: 8090

knife4j:
  enable: true
  production: false

fdfs:
  so-timeout: 2000
  connect-timeout: 1000
  tracker-list:
    - 192.168.136.128:22122
  web-server-url: http://192.168.136.128:8888/

Spring:
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB

Chi tiết Mã Nguồn

Lớp FastDfsService:

package com.jobs.service;

import com.github.tobato.fastdfs.domain.conn.FdfsWebServer;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.proto.storage.DownloadByteArray;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;

@Slf4j
@Service
public class FastDfsService {

    @Autowired
    private FastFileStorageClient client;

    @Autowired
    private FdfsWebServer webServer;

    public String getFileFullUrl(String fdfsPath) {
        return webServer.getWebServerUrl() + fdfsPath;
    }

    public String uploadFile(MultipartFile file) {
        try {
            StorePath storePath = client.uploadFile(file.getInputStream(), file.getSize(),
                    FilenameUtils.getExtension(file.getOriginalFilename()), null);
            return storePath.getFullPath();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public String uploadFile(File file) {
        try {
            if (file.isDirectory()) {
                log.error("Vui lòng tải lên tệp, không phải thư mục");
                return null;
            }

            FileInputStream inputStream = new FileInputStream(file);
            StorePath storePath = client.uploadFile(inputStream, file.length(),
                    FilenameUtils.getExtension(file.getName()), null);
            return webServer.getWebServerUrl() + storePath.getFullPath();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public byte[] downloadFile(String fdfsPath) {
        if (StringUtils.isEmpty(fdfsPath)) {
            return null;
        }

        try {
            StorePath storePath = StorePath.parseFromUrl(fdfsPath);
            return client.downloadFile(storePath.getGroup(), storePath.getPath(), new DownloadByteArray());
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    public Boolean deleteFile(String fdfsPath) {
        if (StringUtils.isEmpty(fdfsPath)) {
            return false;
        }

        try {
            StorePath storePath = StorePath.parseFromUrl(fdfsPath);
            client.deleteFile(storePath.getGroup(), storePath.getPath());
            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }
}

Lớp FastDfsController:

package com.jobs.controller;

import com.jobs.service.FastDfsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

@Api(tags = "Các giao diện thao tác FastDFS")
@RestController
@RequestMapping("/fdfs")
public class FastDfsController {

    @Autowired
    private FastDfsService fastDfsService;

    @ApiOperation("Tải lên tệp")
    @ApiImplicitParam(name = "file", value = "Tệp cần tải lên",
            dataType = "java.io.File", paramType = "query", required = true)
    @PostMapping("/upload")
    public Map uploadFile(@RequestPart("file") MultipartFile file) {
        String fdfsPath = fastDfsService.uploadFile(file);

        Map<String, String> map = new HashMap<>();
        if (StringUtils.isNotBlank(fdfsPath)) {
            map.put("fdfs_path", fdfsPath);
            map.put("file_url", fastDfsService.getFileFullUrl(fdfsPath));
        }

        return map;
    }

    @ApiOperation(value = "Tải xuống tệp", produces = "application/octet-stream")
    @ApiImplicitParam(name = "fdfsPath", value = "Đường dẫn tệp fdfs (không bao gồm tên miền web)", required = true)
    @GetMapping("/download")
    public void downloadFile(String fdfsPath, HttpServletResponse response) {
        String fileName = FilenameUtils.getName(fdfsPath);
        byte[] bytes = fastDfsService.downloadFile(fdfsPath);
        if (bytes != null) {
            try {
                response.setContentType("application/octet-stream;charset=utf-8");
                fileName = new String(fileName.getBytes(), "iso8859-1");
                response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
                IOUtils.write(bytes, response.getOutputStream());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    @ApiOperation("Xóa tệp")
    @ApiImplicitParam(name = "fdfsPath", value = "Đường dẫn tệp fdfs (không bao gồm tên miền web)", required = true)
    @PostMapping("/delete")
    public String deleteFile(String fdfsPath) {
        Boolean flag = fastDfsService.deleteFile(fdfsPath);
        return flag ? "delete success" : "delete fail";
    }
}

Kiểm tra và Xác minh

Khởi động dự án (cổng 8090) và truy cập http://localhost:8090/doc.html để mở giao diện tài liệu Knife4j. Thử nghiệm các chức năng tải lên, tải xuống, và xóa tệp thông qua các API.

Thẻ: fastdfs docker Docker Compose Spring Boot knife4j

Đăng vào ngày 14 tháng 9 lúc 11:49