第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的上傳功能已經完成。至于其他功能在後續使用時會進行開發
- Github位址:https://github.com/Thankson2020/SpringCloud-ChangGou
- 碼雲位址:https://gitee.com/thankson2020/SpringCloud-ChangGou