第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 上傳流程
- Storage服務器定時向Tracker服務器發送上傳信息
- 客服端向Tracker發送上傳請求
- Tracker服務器查詢可用的Storage
- Tracker服務器向客戶端返回Storage服務器的ip和端口
- 客戶端向Storage服務器上傳文件
- Storage服務器生成file_id
- Storage服務器將上傳內容寫入磁盤
- Storage服務器向客戶端返回file_id
- 客戶端存儲文件信息
其中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的上傳功能已經完成。至於其他功能在後續使用時會進行開發