FastDFS概念
- FastDFS是開源的輕量級分散式檔案系統,實現檔案管理, 主要功能:
- 檔案儲存
- 檔案同步
- 檔案訪問(檔案上傳,檔案下載)
- 解決了大容量儲存和負載均衡的問題,特別適合以檔案為載體的線上服務:相簿網站,視訊網站
- FastDFS為網際網路量身定製,充分考慮了冗餘備份,負載均衡,線性擴容等機制,並注重高可用,高效能等指標,使用FastDFS可以很方便地搭建一套高效能的檔案伺服器叢集提供檔案上傳,下載等服務
FastDFS檔案系統架構
- FastDFS服務端有兩個角色:
- 跟蹤器(tracker): 主要做排程工作,在訪問上起負載均衡作用
- 跟蹤器和儲存節點都可以由一臺伺服器或多臺伺服器構成,跟蹤器和儲存節點中的伺服器可以隨時增加或下線而不會影響下線服務.
- 跟蹤器中所有服務都是對等的,可以根據伺服器的壓力情況隨時增加或減少
- 儲存節點(storage): 儲存檔案,完成檔案管理的所有功能
- 就是這樣的儲存
- 同步儲存介面
- 提供儲存介面
- FastDFS同時對檔案metadata進行管理,檔案metadata是檔案屬性列表,可以包含多個鍵值對
- 檔案metadata: 檔案的相關屬性,以鍵值對方式表示
- 為了支援大容量,儲存節點採用分卷的組織方式
- 儲存系統由一個卷或多個卷組成,卷與卷之間的檔案是相互獨立的,所有卷的檔案容量累加就是整個儲存系統的檔案容量
- 一個卷可以由一臺或多臺儲存伺服器組成,一個卷下的儲存伺服器中檔案都是相同的,卷中的多臺伺服器起到了冗餘備份和負載均衡作用
- 在卷中增加伺服器時,同步已有的檔案由系統自動完成,同步完成後,系統自動將新增伺服器切換到線上提供服務
- 當儲存空間不足或即將耗盡時,可以動態新增捲,只需要增加一臺或多臺伺服器,配置一個新的卷,這樣擴大儲存系統的容量
- FastDFS 檔案標識分為兩部分:
高可用要有崩潰恢復的能力
服務叢集要有同步的功能
否則就要有負載均衡
上傳互動過程
- client詢問tracker上傳到的storage,不需要附加引數
- tracker返回一臺可用的storage
- client直接和storage通訊完成檔案上傳
client為使用FastDFS的呼叫方,client也是一臺伺服器,對tracker和對storage的呼叫均為伺服器間的呼叫
下載互動過程
- client詢問tracker下載檔案的storage,引數為檔案標識(卷名和檔名)
- tracker返回一臺可用的storage
- client直接和storage通訊完成檔案下載
client為使用FastDFS的呼叫方,client也是一臺伺服器,對tracker和對storage的呼叫均為伺服器間的呼叫
FastDFS結合Nginx
- 使用FastDFS部署分散式檔案系統時,通過FastDFS的客戶端API進行檔案的上傳,下載,刪除等操作,同時通過FastDFS和HTTP伺服器來提供HTTP服務.但是FastDFS的HTTP服務較為簡單,無法提供負載均衡等高效能的服務.需要使用FastDFS的Nginx模組彌補這一缺陷
- FastDFS通過tracker伺服器,將檔案放在storage伺服器儲存,但是同組之間的伺服器需要複製檔案,有延遲的問題,可以通過fastdfs-nginx-module可以重定向連線到源伺服器取檔案,避免客戶端由於複製延遲的問題,出現錯誤
基於Docker安裝FastDFS
# 更新資料來源
WORKDIR /etc/apt
RUN echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse' > sources.list
RUN echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse' >> sources.list
RUN echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse' > sources.list
RUN echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse' > sources.list
RUN apt-get update
# 安裝依賴
RUN apt-get install make gcc libpcre3-dev zliblg-dev --assume-yes
# 複製工具包
ADD fastdfs-5.11.tar.gz /usr/local/src
ADD fastdfs-nginx-module_v1.16.tar.gz /usr/local/src
ADD libfastcommon.tar.gz /usr/local/src
ADD nginx-1.15.4.tar.gz /usr/local/src
# 安裝libfastcommon
WORKDIR /usr/local/src/libfastcommon
RUN ./make.sh && ./make.sh install
# 安裝 FastDFS
WORKDIR /usr/local/src/fastdfs-5.11
RUN ./make.sh && ./make.sh install
# 配置FastDFS tracker
ADD tracker.conf /etc/fdfs
RUN mkdir -p /fastdfs/tracker
# 配置FastDFS storage
ADD storage.conf /etc/fdfs
RUN mkdir -p /fastdfs/storage
# 配置FastDFS客戶端
ADD client.conf /etc/fdfs
# 配置fastdfs-nginx-module
ADD config /usr/local/src/fastdfs-nginx-modules/src
# FastDFS與Nginx整合
WORKDIR /usr/local/src/nginx-1.13.6
RUN ./configure --add-module=/usr/local/src/fastdfs-nginx-module/src
RUN make && make install
ADD mod_fastdfs.conf /etc/fdfs
WORKDIR /usr/local/src/fastdfs-5.11/conf
RUN cp http.conf mime.types /etc/fdfs/
# 配置Nginx
ADD nginx.conf /usr/local/nginx/conf
COPY entrypoint.sh /usr/local/bin/
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
WORKDIR /
EXPOSE 8888
CMD ["/bin/bash"]
- 在/usr/local/docker/fastdfs/environment建立entrypoint.sh,建立完成的後要執行 chmod +x entrypoint.sh命令檔案才可以使用
# !/bin/sh
/etc/init.d/fdfs_trackerd start
/etc/init.d/fdfs_storaged start
/usr/local/nginx/sbin/nginx -g 'daemon off;'
相關配置檔案
- tracker.conf: FastDFS跟蹤器配置,容器路徑為:/etc/fdfs,修改:
base_path=/fastdfs/tracker
- storage.conf: FastDFS儲存節點配置,容器路徑為:/etc/fdfs,修改:
base_path=/fastdfs/storage
store_path0=/fastdfs/storage
tracker_server=192.168.32.255:22122
http.server_port=8888
- client.conf: FastDFS客戶端配置,容器中路徑為:/etc/fdfs,修改:
base_path=/fastdfs/tracker
tracker_server=192.168.32.255:22122
- config: fastdfs-nginx-module配置檔案,容器中路徑為:/usr/local/src/fastdfs-nginx-module/src,修改:
# 修改前
CORE_INCS="$CORE_INCS /usr/local/include/fastdfs /usr/local/include/fastcommon/"
CORE_LIBS="$CORE_LIBS -L/usr/local/lib -lfastcommon -lfdfsclient"
# 修改後
CORE_INCS="$CORE_INCS /usr/include/fastdfs /usr/include/fastcommon/"
CORE_LIBS="$CORE_LIBS -L/usr/lib -lfastcommon -lfdfsclient"
- mod_fastdfs.conf: fastdfs-nginx-module配置檔案,容器中路徑:/usr/local/src/fastdfs-nginx-module/src,修改:
connect_timeout=10
tracker_server=192.168.75.128:22122
url_have_group_name=true
store_path0=/fastdfs/storage
- nginx.conf: Nginx配置檔案,容器中的路徑為:/usr/local/src/nginx-1.15.4/conf,修改:
user root;
worker_processes 1;
events {
worker_connections 1024;
}
http{
include mime.types;
defaulte_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server{
listen 8888;
server_name localhost;
location ~/group([0-9])/M00{
ngx_fastddfs_module;
}
error_page 500 502 503 504 /50x.html
location = /50x.html {
root html;
}
}
}
啟動容器
- docker-compose.yml: 在/usr/local/docker/fastdfs資料夾中建立docker-compose.yml
version: '3.1'
services:
fastdfs:
build: environment
restart: always
container_name: fastdfs
volumes:
- ./storage:/fastdfs/storage
network_mode: host # 網路模式:主機模式--將所有埠對映到主機,Docker容器與宿主機共享埠,即埠一致
docker-compose up -d
測試上傳
docker exec -it fastdfs /bin/bash
- 測試檔案上傳: 在/usr/bin目錄中執行(第1個是二進位制可執行檔案客戶端,第2個是客戶端的客戶端配置檔案,第3個是需要上傳的檔案)
/usr/bin/fdfs_upload_file /etc/fdfs/client.conf /usr/local/src/fastdfs-5.11/INSTALL
- 伺服器反饋上傳地址: 檔案的上傳路徑(非地址),通過在瀏覽器輸入Ngnix的訪問地址+檔案上傳路徑即可訪問伺服器上的檔案
group1/M00/00/00/wKliyyfhHHkjsio986777
- 測試Nginx訪問: 通過在瀏覽器輸入Ngnix的訪問地址+檔案上傳路徑即可訪問伺服器上的檔案
http://192.168.32.255:8888/group1/M00/00/00/wKliyyfhHHkjsio986777
配置 FastDFS Java客戶端
- 建立專案: 建立專案名為myshop-service-upload的服務提供者專案
安裝FastDFS Java客戶端
- 從github上git clone FastDFS專案程式碼:
git clone https://github.com/happyfish100/fastdfs-client-java.git
- 配置到本地倉庫: 在專案目錄的target包下有專案的jar檔案
mvn clean install
- 將專案jar檔案上傳到Nexus中
- 在專案中新增依賴:
<!--FastDFS Begin-->
<dependency>
<groupId>org.csource</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27-SNAPSHOT</version>
</dependency>
建立FastDFS工具類
package com.oxford.myshop.service.upload.fastdfs;
public interface StorageService{
/**
*上傳檔案
*
*@param data 檔案的二進位制符
*@param extName 副檔名
*@return 上傳成功後返回生成檔案的id,失敗則返回null
*/
public String upload(byte[] data,String extName);
/**
*刪除檔案
*
*@param fileId 被刪除的檔案id
*@return 刪除成功後返回0,失敗後返回錯誤程式碼
*/
public int delete(String fileId);
}
public class FastDFSStorageService implements StorageService,InitializingBean{
private static final Logger logger=LoggerFactory.getLogger(FastDFSStorageService.class);
private TrackerClient trackerClient;
@Value("${storage.fastdfs.tracker_server}")
@Override
public String upload(byte[] data,String extName){
TrackerServer trackerServer=null;
StorageServer storageServer=null;
StorageClient storageClient=null;
try{
NameValuePair[] meta_list=null; // new NameValuePair[0]
trackerServer=trackerClient.getConnection();
if(trackerServer==null){
logger.error("getConnection return null");
}
storageServer=trackerClient.getStoreStorage(trackerServer);
storageClient1=new StorageClient1(trackerServer,storageServer);
String fileId=storageClient1.upload_file1(data,extName,meta_list);
logger.debug("uploaded file <{}>",fileId);
return fileId;
}catch(Exception ex){
logger.error("Uploaded fail",ex);
return null;
}finally{
if(storageServer!=null){
try{
storageServer.close();
}catch(IOException e){
e.printStackTrace();
}
}
if(trackeServer!=null){
try{
trackeServer.close();
}catch(IOException e){
e.printStackTrace();
}
}
storageClient1=null;
}
}
@Override
public int delete(String fileId){
TrackerServer trackerServer=null;
StorageServer storageServer=null;
StorageClient storageClient=null;
int index=fileId.indexOf('/');
String groupName=fileId.substring(0,index);
try{
trackerServer=trackerClient.getConnection();
if(trackerServer==null){
logger.error("getConnection return null");
}
storageServer=trackerClient.getStoreStorage(trackerServer,groupName);
storageClient1=new StorageClient1(trackerServer,storageServer);
int result=storageClient1.delete_file1(fileId);
return result;
}catch(Exception ex){
logger.error("Delete fail",ex);
return 1;
}finally{
ifreturn fileId;
}catch(Exception ex){
logger.error("Uploaded fail",ex);
return null;
}finally{
if(storageServer!=null){
try{
storageServer.close();
}catch(IOException e){
e.printStackTrace();
}
}
if(trackeServer!=null){
try{
trackeServer.close();
}catch(IOException e){
e.printStackTrace();
}
}
storageClient1=null;
}
}
@Override
public void afterPropertiesSet() throws Exxception{
File confFile=File.createTempFile("fastdfs",".conf");
PrintWriter confWriter=new PrintWriter(new FileWriter(confFile));
confWriter.println("tracker_server="+trackerServer);
confWriter.close();
ClientGlobal.init(confFile.getAbsolutePath());
confFile.delete();
TrackerGroup trackerGroup=ClientGlobal.g_tracker_group;
trackerClient=new TrackerClient(trackerGroup)
logger.info("Init FastDFS with tracker_server : {}",trackerServer);
}
}
public class StorageFactory implements FactoryBean<StorageService>{
@Autowired
private AutowireCapableBeanFactory acbf;
/**
* 儲存服務的型別,僅支援FastDFS
*/
@Value("${storage.type}")
private String type;
private Map<String,Class<? extends StorageService>> classMap;
public StorageFactory(){
classMap=new HashMap<>();
classMap.put("fastdfs",FastDFSStorageService.class);
}
@Override
public StorageService getObject() throws Exception{
Class<? extends StorageService> clazz=classMap.get(type);
if(class==null){
throw new RuntimeException("Unsupported storage type ["+type+"],valid are"+ classMap.keySet());
}
StorageService bean=clazz.newInstance();
acbf.autowireBean(bean);
acbf.initializeBean(bean,bean.getClass().getSimpleName());
return bean;
}
@Override
public Class<?> getObjectType(){
return StorageService.class;
}
@Override
public boolean isSingleton(){
return true;
}
}
/**
* Java配置方式定義StorageFactory的bean,使其可以被依賴注入
*/
@Configuration
public classs FastDFSConfiguration{
@Bean
public StorageFactory storageFactory(){
return new StorageFactory();
}
}
建立FastDFS控制器
# SpringBoot Application
spring:
application:
name: myshop-service-upload
# FastDFS Configuration
fastdfs.base.url: htttp//192.168.32.255:8888/
storage:
type: fastdfs
fastdfs:
tracker_server: 192.168.32.255:22122
@CrossOrigin(origins="*",maxAge=3600)
@RestController
public class UploadController{
@Value("${fastdfs.base.url}")
private String FASTDFS_BASE_URL;
@Autowired
private StorageService storageService;
@RequestMapping(value="upload",method=RequestMethod.POST)
public Map<String,Object> upload(MultipartFile dropFile,MultipartFile[] editorFiles){
Map<String,Object> result=new HashMap<>();
//DropZone上傳
if(dropFile!=null){
result.put("fileName",writeFile(dropFile));
}
//wangEditor上傳
if(editorFiles != null && editorFiles.length > 0){
List<String> fileNames=new ArrayList<>();
for(MultipartFile editorFile:editorFiles){
fileNames.add(writeFile(editorFile));
}
result.put("error",0);
result.put("data",fileNames);
}
return result;
}
/**
*將圖片寫入指定目錄
*/
private String writeFile(MultipartFile multipartFile){
// 獲取檔案字尾
String oName=multipartFile.getOriginalFilename();
String exName=oName.substring(oName.lastIndexOf(".")+1);
// 檔案存放路徑
String url=null;
try{
String uploadUrl=storageService.upload(multipartFile.getBytes(),exName);
url=FASTDFS_BASE_URL+uploadUrl;
}catch(IOException e){
e.printStackTrace();
}
// 返回檔案完整路徑
return url;
}
}
- 建立SpringBoot Application,執行執行分散式檔案上傳專案