FastDFS是一個開源的輕量級分佈式文件系統,它對文件進行管理,功能包括:文件存儲、文件同步、文件訪問(文件上傳、文件下載)等,解決了大容量存儲和負載均衡的問題。特別適合以文件爲載體的在線服務,如相冊網站、視頻網站等等。
FastDFS爲互聯網量身定製,充分考慮了冗餘備份、負載均衡、線性擴容等機制,並注重高可用、高性能等指標,使用FastDFS很容易搭建一套高性能的文件服務器集羣提供文件上傳、下載等服務。
官方網站:https://github.com/happyfish100/
相關概念
fastDFS:
FastDFS是一款開源的輕量級分佈式文件系統純C實現,支持Linux、FreeBSD等UNIX系統類google FS,不是通用的文件系統,只能通過專有API訪問,目前提供了C、Java和PHP API爲互聯網應用量身定做,解決大容量文件存儲問題,追求高性能和高擴展性FastDFS可以看做是基於文件的key value pair存儲系統,稱作分佈式文件存儲服務更爲合適。
------ 來自官網介紹
tracker-server:
跟蹤服務器, 主要做調度工作, 起負載均衡的作用。 在內存中記錄集羣中所有存儲組和存儲服務器的狀態信息, 是客戶端和數據服務器交互的樞紐。 相比GFS中的master更爲精簡, 不記錄文件索引信息, 佔用的內存量很少。
storage-server:
存儲服務器( 又稱:存儲節點或數據服務器) , 文件和文件屬性( metadata) 都保存到存儲服務器上。 Storage server直接利用OS的文件系統調用管理文件。
group:
組, 也可稱爲卷。 同組內服務器上的文件是完全相同的 ,同一組內的storage server之間是對等的, 文件上傳、 刪除等操作可以在任意一臺storage server上進行 。
meta data:
meta data:文件相關屬性,鍵值對( Key Value Pair) 方式,如:width=1024,heigth=768 。
單機文件系統的對比
文件系統 | 高可用 | 擴展 | 部署複雜程度 | 性能 |
---|---|---|---|---|
單機文件系統 | 低,依賴於單機服務器,只要服務器崩潰,完全不可用。 | 低,要擴容只能停機增加硬盤。 | 低 | 當文件數量多到一定的程度,磁盤IO尋址操作將會成爲瓶頸 |
分佈式文件系統 | 高,一個group內的服務器崩潰後,group內的其他storage將接管服務。 | 高,可以不停機增加group機器。 | 高,部署較複雜 | 高,通過集羣或者分佈式的方式分擔服務器的壓力。 |
其他文件系統的對比
指標 | 適合類型 | 文件分佈 | 系統性能 | 複雜度 | FUSE | POSIX | 備份機制 | 通訊協議接口 | 社區支持 | 開發語言 |
---|---|---|---|---|---|---|---|---|---|---|
FastDFS | 4KB~500MB | 小文件合併存儲不分片處理 | 很高 | 簡單 | 不支持 | 不支持 | 組內冗餘備份 | Api HTTP | 國內用戶羣 | C語言 |
TFS | 所有文件 | 小文件合併,以block組織分片 | 複雜 | 不支持 | Block存儲多份,主輔災備 | API http | 少 | C++ | ||
MFS | 大於64K | 分片存儲 | Master佔內存多 | 支持 | 支持 | 多點備份動態冗餘 | 使用fuse掛在 | 較多 | Perl | |
HDFS | 大文件 | 大文件分片分塊存儲 | 簡單 | 支持 | 支持 | 多副本 | 原生api | 較多 | Java | |
Ceph | 對象文件塊 | OSD一主多從 | 複雜 | 支持 | 支持 | 多副本 | 原生api | 較少 | C++ | |
MogileFS | 海量小圖片 | 高 | 複雜 | 可以支持 | 不支持 | 動態冗餘 | 原生api | 文檔少 | Perl | |
ClusterFS | 大文件 | 簡單 | 支持 | 支持 | 多 | C |
適用場景
特別適合以中小文件( 建議範圍: 4KB 到 500MB ) 爲載體的在線服務, 如相冊網站、 視頻網站等等。
部署結構
最小化部署圖
192.168.1.177安裝fastdfs的tracker節點,以及nginx反向代理服務器用於下載服務。
192.168.1.188,192.168.1.189安裝fastdfs的storage節點,默認分一組,一組內兩臺機器互爲備份.
注意:爲了做到高可用,一個group建議分爲兩臺以上的機器。
工程實踐
在src/main/resource下面加入fdfs_client.conf配置文件。
connect_timeout=3
network_timeout = 60
charset = ISO8859-1
http.tracker_http_port = 80
http.anti_steal_token = no
http.secret_key = FastDFS1234567890
tracker_server = svr.io:22122
訪問FastDFS文件系統的代碼如下:
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* @author Johny
* @Title: FastDFSClient
* @Description: TODO(用一句話描述該文件做什麼)
*/
public class FastDFSClient {
private static final Logger logger = LoggerFactory.getLogger(FastDFSClient.class);
private static final String CONFIG_FILENAME = "fdfs_client.conf";
private static StorageClient1 storageClient1 = null;
// 初始化FastDFS Client
static {
// System.out.println(Thread.currentThread().getContextClassLoader().getResource(""));
// System.out.println(ClassLoader.getSystemResource(""));
Resource configLocation = new ClassPathResource(CONFIG_FILENAME);
// URL resource = FastDFSClient.class.getClassLoader().getResource("").;
// System.out.println(resource);
try {
ClientGlobal.init(configLocation.getFile().getPath());
TrackerClient trackerClient = new TrackerClient(ClientGlobal.g_tracker_group);
TrackerServer trackerServer = trackerClient.getConnection();
if (trackerServer == null) {
throw new IllegalStateException("getConnection return null");
}
StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
if (storageServer == null) {
throw new IllegalStateException("getStoreStorage return null");
}
storageClient1 = new StorageClient1(trackerServer, storageServer);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
/**
* 上傳文件
*
* @param file 文件對象
* @param fileName 文件名
* @return
*/
public static String uploadFile(File file, String fileName) {
return uploadFile(file, fileName, null);
}
/**
* 上傳文件
*
* @param file 文件對象
* @param fileName 文件名
* @param metaList 文件元數據
* @return
*/
public static String uploadFile(File file, String fileName, Map<String, String> metaList) {
byte[] buff = null;
try {
buff = IOUtils.toByteArray(new FileInputStream(file));
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
return uploadFile(buff, fileName, metaList);
}
public static String uploadFile(byte[] fileBytes, String fileName, Map<String, String> metaList) {
if (fileBytes == null) {
return null;
}
try {
NameValuePair[] nameValuePairs = null;
if (metaList != null && !metaList.isEmpty()) {
nameValuePairs = new NameValuePair[metaList.size()];
int index = 0;
for (Iterator<Map.Entry<String, String>> iterator = metaList.entrySet().iterator(); iterator
.hasNext(); ) {
Map.Entry<String, String> entry = iterator.next();
String name = entry.getKey();
String value = entry.getValue();
nameValuePairs[index++] = new NameValuePair(name, value);
}
}
return storageClient1.upload_file1("group0", fileBytes, FilenameUtils.getExtension(fileName),
nameValuePairs);
// storageClient1.upload_file();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
/**
* 獲取文件元數據
*
* @param fileId 文件ID
* @return
*/
public static Map<String, String> getFileMetadata(String fileId) {
try {
NameValuePair[] metaList = storageClient1.get_metadata1(fileId);
if (metaList != null) {
HashMap<String, String> map = new HashMap<String, String>();
for (NameValuePair metaItem : metaList) {
map.put(metaItem.getName(), metaItem.getValue());
}
return map;
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
/**
* 刪除文件
*
* @param fileId 文件ID
* @return 刪除失敗返回-1,否則返回0
*/
public static int deleteFile(String fileId) {
try {
return storageClient1.delete_file1(fileId);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return -1;
}
/**
* 下載文件
*
* @param fileId 文件ID(上傳文件成功後返回的ID)
* @param outFile 文件下載保存位置
* @return
*/
public static int downloadFile(String fileId, File outFile) {
FileOutputStream fos = null;
ByteArrayInputStream in = null;
try {
byte[] content = storageClient1.download_file1(fileId);
fos = new FileOutputStream(outFile);
in = new ByteArrayInputStream(content);
IOUtils.copy(in, fos);
return 0;
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
if (in != null) {
try {
fos.close();
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
}
return -1;
}
public static void main(String[] args) throws IOException {
// File f = new File("/Users/ligeit/Documents/49870901000.jpg");
// String aa = uploadFile(f, "aa.jpg");
// System.out.println(aa);
// downloadFile("group0/M00/08/2F/wKgCGV6xQB-AUm5-AAGF7QEQ4to56.jpeg",f);
/*
* String[] aa = FastDFSClientUtils.uploadFile(null, f, "aa.jpg", f.length());
* for (int i = 0; i < aa.length; i++) { System.out.println(aa[i]); }
*/
}
}