3. 暢購商城之FastDFS微服務搭建

第3章 分佈式文件存儲FastDFS

1. FastDFS介紹

1.1 FastDFS簡介

FastDFS是一個以C語言開發的一項開源輕量級分佈式文件系統,它對文件進行管理,功能包括:文件存儲、文件同步、文件訪問(文件上傳、文件下載)等,解決了大容量存儲和負載均衡的問題。特別適合以文件爲載體的在線服務,如相冊網站、視頻網站等等。

FastDFS爲互聯網量身定製,充分考慮了冗餘備份、負載均衡、線性擴容等機制,並注重高可用、高性能等指標,使用FastDFS很容易搭建一套高性能的文件服務器集羣提供文件上傳、下載等服務。

1.2 FastDFS體系結構

FastDFS 架構包括Tracker Server和Storage Server。客戶端請求Tracker Server 進行文件上傳、下載,通過Tracker Server調度最終由Storage Server完成文件上傳和下載。

Tracker Server 作用是負載均衡和調度,通過Tracker Server在文件上傳時可以根據一些策略找到Storage Server提供文件上傳服務。可以將Tracker稱爲追蹤服務器或調度服務器。

Storage Server作用是文件存儲,負責文件上傳、下載、修改和刪除等功能。客戶端上傳的文件最終存儲在Storage服務器上,Storage Server沒有實現自己的文件系統而是利用操作系統的文件系統來管理文件。可以將Storage稱爲存儲服務器。在這裏插入圖片描述

1.3 上傳流程

在這裏插入圖片描述

  1. Storage服務器定時向Tracker服務器發送上傳信息
  2. 客服端向Tracker發送上傳請求
  3. Tracker服務器查詢可用的Storage
  4. Tracker服務器向客戶端返回Storage服務器的ip和端口
  5. 客戶端向Storage服務器上傳文件
  6. Storage服務器生成file_id
  7. Storage服務器將上傳內容寫入磁盤
  8. Storage服務器向客戶端返回file_id
  9. 客戶端存儲文件信息

其中file_id用於以後訪問該文件的索引信息。文件索引信息包括:組名,虛擬磁盤路徑,數據兩級目錄,文件名。

1.4 FastDFS路徑說明

group1/M00/00/00/rBGdZl5ak4yAHTNeAAxv6ziU5pA742.png

  • 組名(/group1):文件上傳後所在的Storage組名稱。在文件上傳成功後由Storage 服務器返回,需要客戶端自行保存。

  • 虛擬磁盤路徑(/M00):Storage配置的虛擬路徑,指向Storage組中某個節點的硬盤地址。與磁盤選項store_path*對應。如果配置了store_path0則是M00,如果配置了store_path1 則是M01,以此類推。

  • 數據兩級目錄(/00/00):Storage服務器在每個虛擬磁盤路徑下創建的兩級目錄,用於存儲數據。該數值採用算法計算得出

  • 文件名:與文件上傳時的文件名稱不同。是由存儲服務器根據特定信息生成,文件名包含:源存儲服務器IP地址、文件創建時間戳、文件大小、隨機數和文件拓展名等信息。

2. FastDFS搭建

2.1 安裝FastDFS鏡像

本文采用Docker容器進行FastDFS的環境搭建。

1、查詢FastDFS鏡像

docker search fastdfs

2、拉取鏡像

docker pull morunchang/fastdfs

3、運行tracker

docker run -d --name tracker --net=host morunchang/fastdfs sh tracker.sh

4、運行storage

docker run -d --name storage --net=host -e TRACKER_IP=服務器IP:22122 -e GROUP_NAME=group1 morunchang/fastdfs sh storage.sh

參數說明:

  • -d 後臺運行
  • –name 別名。–name 空格 別名 --name=別名
  • 使用的網絡模式是–net=host, 告訴容器使用主機網絡堆棧
  • -e添加到環境變量中
  • group1是組名,即Storage的組
  • 如果想要增加新的Storage服務器,再次運行該命令,注意更換新組名

2.2 修改配置

1、 進入storage中

docker exec -it storage  /bin/bash

2.、配置nginx.conf

vi /etc/nginx/conf/nginx.conf

修改以下內容

    server {
        listen       9000;
        server_name  localhost;

        location / {
            root   html;
            index  index.html index.htm;
        }

        location ~ /M00 {
                    root /data/fast_data/data;
                    ngx_fastdfs_module;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

3、配置storage.conf

vi /etc/fdfs/storage.conf

修改以下內容

# 創建storage時的ip
tracker_server=服務器IP:22122

#推薦與Nignx配置的端口相同
http.server_port=9000

4、退出容器

exit

5、重啓storage容器

docker restart storage

6、查看啓動的容器

docker ps -a

7、開啓啓動設置

docker update --restart=always tracker
docker update --restart=always storage

8、開放端口
使用阿里雲進行部署時,需要在安全組中開放9000、23000、22122端口,否則會造成無法訪問

3. 文件存儲微服務

thanksong-springcloud-provider模塊下創建thankson-provider-fastdfs微服務,該服務主要用於實現文件上傳、刪除等功能。

3.1 pom.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<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">
    <parent>
        <artifactId>thanksong-springcloud-provider</artifactId>
        <groupId>com.thankson.springcloud</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>thankson-provider-fastdfs</artifactId>

    <dependencies>
        <!--FastFDS-->
        <dependency>
            <groupId>com.github.tobato</groupId>
            <artifactId>fastdfs-client</artifactId>
            <version>1.27.2</version>
        </dependency>
    </dependencies>
</project>

3.2 application.yml配置

在resources文件夾下創建application.yml

server:
  port: 9000
spring:
  application:
    name: FastDFS
  servlet:
    multipart:
      max-request-size: 10MB
      max-file-size: 10MB
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://www.xiexun.top:3306/changgou_sys?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: root
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
  instance:
    prefer-ip-address: true

fdfs:
  # 讀取時間
  so-timeout: 1000
  # 連接超時時間
  connect-timeout: 60
  # 縮略圖
  thumbImage:
    # 寬
    width: 150
    # 高
    height: 150
  # tracker列表
  tracker-list:            #TrackerList參數,支持多個
    - www.xiexun.top:22122

com:
  thankson:
    springcloud:
      FastDFS:
        pathPrefix: www.xiexun.top:9000

3.3 啓動類

com.thankson.fastdfs包下創建啓動類FastDFSApplication.java

@SpringBootApplication
@EnableEurekaClient
@MapperScan(basePackages = {"com.thankson.fastdfs.dao"})
@ComponentScan(value = {"com.thankson.common.util","com.thankson.fastdfs"})
public class FastDFSApplication {

    public static void main(String[] args) {
        SpringApplication.run(FastDFSApplication.class, args);
    }
}

3.4 代碼編寫

3.4.1 實體類

com.thankson.fastdfs.pojo包下創建FileMessage實體類

@Table(name = "tb_file_message")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class FileMessage {
    /**
     * 自增ID
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    /**
     * 文件名稱
     */
    @Column(name = "file_name")
    private String fileName;

    /**
     * 文件類型
     */
    @Column(name = "file_type")
    @NotBlank(message = "文件類型不能爲空")
    private String fileType;

    /**
     * 文件擴展名
     */
    @Column(name = "file_extension")
    private String fileExtension;

    /**
     * 文件大小
     */
    @Column(name = "file_size")
    private Long fileSize;

    /**
     * 文件路徑
     */
    @Column(name = "file_path")
    private String filePath;

    /**
     * 文件描述
     */
    @Column(name = "description")
    @NotBlank(message = "文件描述不能爲空")
    private String description;

    /**
     * 歸屬系統標識
     */
    @Column(name = "sys_flag")
    @NotBlank(message = "系統標識不能爲空")
    private String sysFlag;

    /**
     * 上傳時間
     */
    @Column(name = "create_date")
    private Date createDate;

    /**
     * 上傳人
     */
    @Column(name = "uploader")
    private String uploader;

    /**
     * 刪除標識
     */
    @Column(name = "del_flag")
    private String delFlag;
}

3.4.2 數據訪問層

com.thankson.fastdfs.dao包下創建FileMessageDao接口。
該接口需要繼承tk.mybatis.mapper.common.Mapper類

public interface FileMessageDao extends Mapper<FileMessage> {
}

3.4.3 業務層

1、業務層接口
com.thankson.fastdfs.service包下創建FileService接口

public interface FileService {
    
    /**
     * 上傳文件至FastDFS
     *
     * @param fileMessage 文件信息
     * @author Thankson
     * @date 2020年5月5日
     */
    Result<Object> uploadFile(InputStream inputStream, FileMessage fileMessage);
}

2、業務層實現類
com.thankson.fastdfs.service.impl包下創建FileServiceImpl類

@Service
public class FileServiceImpl implements FileService {
    @Autowired
    private FileMessageDao fileMessageDao;

    @Autowired
    private FastFileStorageClient fastFileStorageClient;

    @Value(value = "${com.thankson.springcloud.FastDFS.pathPrefix}")
    private String pathPrefix;

    @Override
    public Result<Object> uploadFile(InputStream inputStream, FileMessage fileMessage) {
        try {
            //上傳文件至FastDFS
            StorePath storePath = fastFileStorageClient.uploadFile(inputStream, fileMessage.getFileSize(), fileMessage.getFileExtension(), null);
            if (storePath == null) {
                return new Result<>(false, 500, "文件上傳失敗");
            }
            //文件訪問路徑
            String path = pathPrefix + "/" + storePath.getFullPath();
            fileMessage.setFilePath(path);
            //保存文件信息
            int i = fileMessageDao.insertSelective(fileMessage);
            if (i < 1) {
                return new Result<>(false, 500, "保存文件信息失敗");
            }
        } catch (Exception e) {
            e.printStackTrace();
            return new Result<>(false, 500, "上傳文件時發生異常");
        }
        return new Result<>(true, 200, "上傳成功", fileMessage);
    }
}

3.4.4 控制層

com.thankson.fastdfs.controller包下創建FileController類

@RestController
@RequestMapping(value = "/file")
public class FileController {

    @Autowired
    private FileService fileService;

    /**
     * 上傳文件
     *
     * @param file        文件
     * @param fileMessage 文件信息
     * @author Thankson
     * @date 2020年5月5日
     */
    @PostMapping(value = "/uploadFile")
    public Result<Object> uploadFile(@RequestPart MultipartFile file, @RequestPart @Validated FileMessage fileMessage) {
        fileMessage.setFileName(FilenameUtils.getBaseName(file.getOriginalFilename()));
        fileMessage.setCreateDate(new Date());
        fileMessage.setFileExtension(FilenameUtils.getExtension(file.getOriginalFilename()));
        fileMessage.setFileSize(file.getSize());
        fileMessage.setUploader("test");
        InputStream inputStream;
        try {
            inputStream = file.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
            return new Result<>(false, 500, "上傳文件流獲取失敗");
        }
        return fileService.uploadFile(inputStream,fileMessage);
    }
}

3.4.5 處理器

thankson-common-util工程com.thankson.common.util.handler包下創建BaseExceptionHandler類

/**
 * 異常處理
 *
 * @author Thankson
 * @date 2020年5月5日
 */
@RestControllerAdvice
public class BaseExceptionHandler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Result<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        logger.error(e.getMessage(), e);
        return new Result<>(false, 500, Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
    }

    @ExceptionHandler(ValidationException.class)
    public Result<Object> handleValidationException(ValidationException e) {
        logger.error(e.getMessage(), e);
        return new Result<>(false, 500, e.getCause().getMessage());
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public Result<Object> handleConstraintViolationException(ConstraintViolationException e) {
        logger.error(e.getMessage(), e);
        return new Result<>(false, 500, e.getMessage());
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public Result<Object> handlerNoFoundException(Exception e) {
        logger.error(e.getMessage(), e);
        return new Result<>(false, 404, "路徑不存在,請檢查路徑是否正確");
    }

    @ExceptionHandler(DuplicateKeyException.class)
    public Result<Object> handleDuplicateKeyException(DuplicateKeyException e) {
        logger.error(e.getMessage(), e);
        return new Result<>(false, 500, "數據重複,請檢查後提交");
    }


    @ExceptionHandler(Exception.class)
    public Result<Object> handleException(Exception e) {
        logger.error(e.getMessage(), e);
        return new Result<>(false, 500, "系統繁忙,請稍後再試");
    }
}

3.5 測試

採用IDEA中自帶的HTTP Client進行測試

3.5.1 創建FastDFS.http

在這裏插入圖片描述

3.5.2 添加請求

### 上傳文件
POST http://localhost:9000/file/uploadFile
Content-Type: multipart/form-data; boundary=WebAppBoundary

--WebAppBoundary
Content-Disposition: form-data; name="file"; filename="新建文本文檔.txt"

< C:\Users\69303\Desktop\新建文本文檔.txt
--WebAppBoundary
Content-Disposition: form-data; name="fileMessage"
Content-Type: application/json

{
"author":"aaa",
"fileType": "1",
"description":"description",
"sysFlag":"test"
}
--WebAppBoundary--

3.5.3運行http後結果如下

在這裏插入圖片描述

3.5.4 訪問鏈接

www.xiexun.top:9000/group1/M00/00/00/rBGdZl6xhfaAWrW6ABjs7y0zlQQ091.png
在這裏插入圖片描述

3.5.5 查看服務器

docker exec -it storage  /bin/bash
ls /data/fast_data/data/00/00

在這裏插入圖片描述

4. 結束語

至此,FastDFS的上傳功能已經完成。至於其他功能在後續使用時會進行開發

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章