暢購商城(二):分散式檔案系統FastDFS
好好學習,天天向上
本文已收錄至我的Github倉庫DayDayUP:github.com/RobodLee/DayDayUP,歡迎Star,更多文章請前往:目錄導航
- 暢購商城(一):環境搭建
- 暢購商城(三):商品管理
- 暢購商城(四):Lua、OpenResty、Canal實現廣告快取與同步
- 暢購商城(五):Elasticsearch實現商品搜尋
- 暢購商城(六):商品搜尋
FastDFS介紹
1. 簡介
FastDFS是一個開源的輕量級分散式檔案系統,它對檔案進行管理,功能包括:檔案儲存、檔案同步、檔案訪問(檔案上傳、檔案下載)等,解決了大容量儲存和負載均衡的問題。特別適合以檔案為載體的線上服務,如相簿網站、視訊網站等等。FastDFS為網際網路量身定製,充分考慮了冗餘備份、負載均衡、線性擴容等機制,並注重高可用、高效能等指標,使用FastDFS很容易搭建一套高效能的檔案伺服器叢集提供檔案上傳、下載等服務。
2. 架構
FastDFS由跟蹤伺服器(Tracker Server)、儲存伺服器(Storage Server)和客戶端(Client)構成。
Tracker
、Storage
和Client
Tracker不負責檔案的儲存,它主要負責檔案管理負載均衡操作,就像是一箇中介,為Client提供Storage的資訊。而Storage才是負責檔案上傳,修改,刪除等工作。
Client:“嗨,Tracker!你那裡還有沒有正在閒著的Storage呀?我現在有些檔案需要儲存。”
Tracker:“我來看看,嗯.... 找到了,Storage5現在正好閒著,他的地址是192.168.200:8888/group5,你直接去找他吧。”
Client:“OK!”
Client:“嗨,Storage5!我這裡現在有一堆檔案,就放你這裡吧。”
Storage5:“好的,老闆,幫你存好了,放在我這兒你就放心吧,我還讓我家二弟幫你做了備份,不會丟的。”
Client:“好的,謝謝啦!”
......
通過上面的對話,就應該能明白這三者之間的關係了吧。
從圖中可以看出,Storage是一個叢集,叢集裡面分為一個一個的組,每個組裡面有不止一臺Storage(也可以是一臺),這麼設計有什麼好處呢?首先並不是只有一臺Storage為客戶端提供服務,而是由Tracker動態地分配相對空閒的Storage給客戶端提供服務,這樣就做到了負載均衡
容災
,一臺Storage掛了資料也不會丟失,除非全掛了。還有一個好處就是可以很方便地實現線性擴容
,如果哪天Storage的空間不夠用了,就可以直接新增一組Storage,然後註冊到Tracker中。同樣的,Tracker也是叢集。
3. 檔案上傳流程
首先Storage會定時向Tracker上報自己的狀態資訊,這樣Tracker就會知道Storage還是不是活著;當Client給Tracker傳送請求的時候,Tracker會查詢是否有可用的Storage;有的話就將Storage的資訊給Client,Client在得到Storage的資訊後,就將檔案上傳到Storage;Storage先是生成一個file_id,再將檔案寫入磁碟中儲存,最後將file_id返回給Client。
4. file_id的格式
group1是Storage所在的組名,M00是Storage的虛擬路徑,02/04是兩級目錄,後面的就是檔名了,這個檔名是自動生成的。
FastDFS搭建
我們要把FastDFS安裝到docker中,首先要有docker的環境,資料提供的虛擬機器裡面已經安裝好docker了。
1. 下載FastDFS
接下來將FastDFS的映象下載到本地:
docker pull morunchang/fastdfs
下載完成之後看一下有沒有:
docker images
已經下載好了,接下來就開始安裝吧。
2. 安裝Tracker和Storage
安裝Tracker
docker run -d --name tracker --net=host morunchang/fastdfs sh tracker.sh
- docker run:執行容器
- -d:後臺執行
- --name trackr:起個名字,這裡叫tracker,也可以用等號連線 --name=tracker
- --net=host:host網路模式,容器與宿主機共享IP
- morunchang/fastdfs:映象
- sh tracker.sh:執行一個名為tracker.sh的shell指令碼
現在Tracker就安裝好了。
安裝Storage
docker run -d --name storage --net=host -e TRACKER_IP=192.168.31.200:22122 -e GROUP_NAME=group1 morunchang/fastdfs sh storage.sh
- -e:將Storage新增到環境變數
- TRACKER_IP:Tracker的IP以及埠
- GROUP_NAME=group1:當前Storage的組名
Tracker和Storage開機自啟動
現在我們的Tracker和Storage已經安裝好了,但是如果每次開機都要去手動啟動的話還是太麻煩了,所以設定一下開機自啟動:
docker update --restart=always tracker
docker update --restart=always storage
需要注意的是這裡的“tracker”和“storage”是前面--name設定的名字,前面設定的是什麼,這裡就寫什麼。到此為止,FastDFS就已經安裝好了。
3. 配置ngx_fastdfs_module
當我們訪問Storage中的檔案資源的時候,中間是經過了Nginx,然後通過ngx_fastdfs_module去訪問Storage的,ngx_fastdfs_module在安裝Tracker和Storage的時候已經自動幫我們安裝好了,我們來看一下。
docker exec -it storage /bin/bash //進入到storage容器中
vim /etc/nginx/conf/nginx.conf //修改/etc/nginx/conf/目錄下的nginx。conf檔案
裡面會有這麼一段內容
location ~ /M00 {
root /data/fast_data/data;
ngx_fastdfs_module;
}
上面這段內容就說明ngx_fastdfs_module已經配置好了。
4. 設定禁止快取
當瀏覽器訪問過Storage中的資源後,即便將Storage中的資源已經被刪除了,瀏覽器還是會訪問快取中資料,那如果我們不想這樣,就可以配置一下禁止快取。還是上面的步驟,在裡面新增一行內容:
location ~ /M00 {
add_header Cache-Control no-store; #告訴瀏覽器不要快取資料
root /data/fast_data/data;
ngx_fastdfs_module;
}
5. 檔案位置
nginx配置檔案
前面我們說過,訪問Storage先是通過了Nginx,那麼Nginx的配置檔案在哪呢?
docker exec -it storage /bin/bash //進入到storage容器中
cd etc/nginx/conf
可以看到,這裡有個nginx.conf檔案,這個就是nginx的配置檔案。裡面配置了Nginx的埠等資訊。
Tracker和Storage配置檔案
cd etc/fsds //切換到etc/fsds/conf目錄下
storage.conf和tracker.conf分別是Storage和Tracker配置檔案。
6. 儲存的檔案位置
上一節中提到了file_id的格式,其中有個虛擬目錄M00,看配置檔案中的M00,其實就是/data/fast_data/data目錄。切換進來看看:
cd data/fast_data/data
這些都是目錄,前面不是說了file_id的虛擬目錄後面跟著二級目錄麼,再進入00目錄看一下:
還是一堆目錄,再進入00目錄:
這個jpg檔案是我剛才傳上去的。
檔案儲存微服務
1. 建立微服務工程changgou-service-file
在changgou-service下新建一個Module叫做changgou-service-file,因為用到了fastdfs,自然需要新增依賴。
<dependencies>
<!-- FastDFS客戶端程式包-->
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.0.0</version>
</dependency>
</dependencies>
接下來就需要在resource目錄下建立FastDFS的配置檔案fdfs_client.conf:
#連線超時,單位是秒
connect_timeout=60
#通訊超時時間,傳送或接收資料時。假設在超時時間後還不能傳送或接收資料,則本次網路通訊失敗
network_timeout=60
#字符集
charset=UTF-8
#Tracker的http埠
http.tracker_http_port=8080
#Tracker伺服器IP和埠設定
tracker_server=192.168.31.200:22122
註釋的部分請刪掉,我在使用的過程中總是連線失敗,然後把註釋刪了就好了。
微服務工程自然也需要配置,在resource目錄下建立application.yml:
spring:
servlet:
multipart:
max-file-size: 10MB #上傳檔案最大大小
max-request-size: 10MB #請求資料最大大小
application:
name: file #該微服務的名字
server:
port: 18082 #該微服務的埠
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
最後再建立一個啟動類即可,在com.robod包下新建一個類FileApplication.class:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableEurekaClient
public class FileApplication {
public static void main(String[] args) {
SpringApplication.run(FileApplication.class,args);
}
}
這裡有個地方需要強調一下,就是(exclude = {DataSourceAutoConfiguration.class})
,它的作用是取消資料來源自動匯入。SpringBoot會自動從配置檔案中查詢spring.datasource.相關屬性並自動配置單資料來源。因為在這個微服務工程並沒有配置資料庫的相關屬性,所以不加exclude的話就會報錯。
***************************
APPLICATION FAILED TO START
***************************
Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
Action:
Consider the following:
If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
因此我們需要新增這行內容來取消資料來源自動匯入。
接下來我們先啟動Eureka工程後再來啟動一下該專案試試,啟動後訪問http://127.0.0.1:7001
OK! 專案成功啟動,接下來就實現一下檔案上傳,刪除等功能。
2. 檔案操作功能的實現
為了方便管理,我們將檔案資訊封裝成一個JavaBean,在com.robod.file包下新建一個類FastDFSFile:
@Data //不要忘了匯入Lombok的依賴
public class FastDFSFile {
//檔名
private String name;
//檔案內容
private byte[] content;
//副檔名
private String ext;
//檔案MD5摘要
private String md5;
//檔案作者
private String author;
public FastDFSFile(String name, byte[] content, String ext) {
this.name = name;
this.content = content;
this.ext = ext;
}
public FastDFSFile
(String name, byte[] content, String ext, String md5, String author) {
this.name = name;
this.content = content;
this.ext = ext;
this.md5 = md5;
this.author = author;
}
}
現在再建立一個工具類來實現對檔案的一些操作,在com.robod.utils包下新建一個類FastDFSUtils:
public class FastDFSUtils {
private static StorageClient storageClient;
private static TrackerClient trackerClient;
private static TrackerServer trackerServer;
static {
try {
String path = new ClassPathResource("fdfs_client.conf").getPath();
//載入Tracker連線資訊
ClientGlobal.init(path);
//建立一個Tracker的客戶端物件
trackerClient = new TrackerClient();
//通過TrackerClient訪問TrackerServer服務,獲取連線物件
trackerServer = trackerClient.getConnection();
//通過TrackerServer的連線資訊去獲取Storage的連線資訊,儲存進StorageClient物件中
storageClient = new StorageClient(trackerServer, null);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 上傳檔案
*
* @param file
*/
public static String[] upload(FastDFSFile file) throws Exception {
//上傳檔案
return storageClient.upload_file(file.getContent(), file.getExt(), null);
}
/**
* 下載檔案
* @param groupName 檔案所在的組名 group1
* @param remoteFileName 檔案的路徑及名字 M00/00/00/wKgfyF8AjpSADeBLABXmlvc7iOY701.jpg
*/
public static InputStream downloadFile(String groupName, String remoteFileName) throws Exception {
byte[] bytes = storageClient.download_file(groupName, remoteFileName);
return new ByteArrayInputStream(bytes);
}
/**
* 刪除檔案
* @param groupName 檔案所在的組名 group1
* @param remoteFileName 檔案的路徑及名字 M00/00/00/wKgfyF8AjpSADeBLABXmlvc7iOY701.jpg
*/
public static int deleteFile(String groupName, String remoteFileName) throws Exception {
return storageClient.delete_file(groupName, remoteFileName);
}
/**
* 獲取檔案資訊
* @param groupName 檔案所在的組名 group1
* @param remoteFileName 檔案的路徑及名字 M00/00/00/wKgfyF8AjpSADeBLABXmlvc7iOY701.jpg
*/
public static FileInfo getFileInfo(String groupName, String remoteFileName) throws Exception {
return storageClient.get_file_info(groupName,remoteFileName);
}
/**
* 獲取storage資訊
* @return store_path_index
*/
public static StorageServer getStorage() throws IOException {
return trackerClient.getStoreStorage(trackerServer);
}
/**
* 獲取Storage的IP和埠資訊
* @param groupName
* @param fileName
* @return ServerInfo:ip_addr,port
* @throws IOException
*/
public static ServerInfo[] getStorageInfo(String groupName, String fileName) throws IOException {
return trackerClient.getFetchStorages(trackerServer,groupName,fileName);
}
/**
* 獲取Tracker資訊
* @return
*/
public static String getTrackerInfo() {
String ip = trackerServer.getInetSocketAddress().getHostString();
int port = ClientGlobal.getG_tracker_http_port();
return new StringBuilder(ip).append(":").append(port).toString();
}
}
在這個類中我們封裝了對檔案的一些列操作,現在就來寫個入口,在com.robod.controller包下新建一個類FileController
@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {
@PostMapping("/upload")
public Result<String> upload(@RequestParam("file") MultipartFile multipartFile) throws Exception{
FastDFSFile fastDFSFile = new FastDFSFile(
multipartFile.getOriginalFilename(),
multipartFile.getBytes(),
StringUtils.getFilenameExtension(multipartFile.getOriginalFilename()));
System.out.println(fastDFSFile.toString() );
String[] upload = FastDFSUtils.upload(fastDFSFile);
String groupName = upload[0];
String fileName = upload[1];
System.out.println(groupName);
System.out.println(fileName);
System.out.println("------------------");
System.out.println("獲取檔案資訊");
FileInfo fileInfo = FastDFSUtils.getFileInfo(groupName, fileName);
System.out.println(fileInfo.getSourceIpAddr());
System.out.println(fileInfo.getFileSize());
System.out.println(fileInfo.getCreateTimestamp());
System.out.println(fileInfo.getCrc32());
System.out.println("----------------------");
System.out.println("獲取Storage資訊");
StorageServer storage = FastDFSUtils.getStorage();
System.out.println(storage.getStorePathIndex());
System.out.println("-----------------");
System.out.println("獲取Storage的IP和埠資訊");
ServerInfo[] storageInfo = FastDFSUtils.getStorageInfo(groupName, fileName);
for (ServerInfo serverInfo : storageInfo) {
System.out.println(serverInfo.getIpAddr());
System.out.println(serverInfo.getPort());
}
System.out.println("--------------------");
System.out.println("獲取Tracker資訊");
String trackerInfo = FastDFSUtils.getTrackerInfo();
System.out.println(trackerInfo);
return new Result<>(true, StatusCode.OK,"檔案上傳成功","---");
}
}
將專案啟動起來,用postman傳送請求。
從控制檯的列印情況中可以看出,檔案成功上傳到了伺服器中,而且前面寫的一些獲取資訊的方法也正常運行了。
寫在最後
這篇文章先是介紹了FastDFS的一些知識,接著就是講了FastDFS的安裝以及檔案路徑和配置的內容,最後搭建了檔案微服務並實現了對於檔案的操作功能。如果我的文章對你有些幫助,不要忘了點贊,收藏,轉發,關注。要是有什麼好的意見歡迎在下方留言。讓我們下期再見!