1 Fast DFS文件系统
跟踪器(tracker)和存储节点(storage)。跟踪器主要做调度工作,在访问上起负载均衡的作用。
存储节点存储文件,完成文件管理的所有功能:就是这样的存储、同步和提供存取接口,Fast DFS同时对文件的元数据(Meta Data)进行管理。所谓文件的Meta Data就是文件的相关属性,以键值对(key value)方式表示,如:width=1024,其中的key为width,value为1024。文件Meta Data是文件属性列表,可以包含多个键值对。
跟踪器和存储节点都可以由一台或多台服务器构成。跟踪器和存储节点中的服务器均可以随时增加或下线而不会影响线上服务。其中跟踪器中的所有服务器都是对等的,可以根据服务器的压力情况随时增加或减少。
为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷(一个卷对应一个组名,如:group1,相当于windows系统中的一块磁盘分区)组成,卷与卷之间的文件是相互独立的,所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起到了冗余备份和负载均衡的作用。
在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。
当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。
Fast DFS中的文件标识分为两个部分:卷名和文件名,二者缺一不可。
FastDFS架构图如下图所示:
2 基本操作(CRUD)
将基本操作封装到一个类中,方便service层调用。
- pom.xml
<dependency>
<groupId>org.csource</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27-RELEASE</version>
</dependency>
- FastDfsUtil
import org.apache.commons.lang.StringUtils;
import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
public class FastDfsUtil {
private static final String CONF_FILENAME
= Thread.currentThread().getContextClassLoader().getResource("fastdfsConfig.properties").getPath();
Logger logger = LoggerFactory.getLogger(FastDfsUtil.class);
private static TrackerClient trackerClient;
static {
try{
ClientGlobal.init(CONF_FILENAME.substring(1, CONF_FILENAME.length()));
TrackerGroup trackerGroup = ClientGlobal.g_tracker_group;
trackerClient = new TrackerClient(trackerGroup);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (MyException e) {
e.printStackTrace();
}
}
/**
* <B>方法名称:</B>上传方法<BR>
* <B>概要说明:</B><BR>
* @param path path 路径
* @return 上传成功返回id, 失败返回""
*/
public String upload(File path){
TrackerServer trackerServer = null;
StorageServer storageServer = null;
StorageClient1 storageClient1 = null;
FileInputStream fis = null;
try{
NameValuePair[] meta_list = null;
fis = new FileInputStream(path);
byte[] file_buff = null;
if(fis != null){
int len = fis.available();
file_buff = new byte[len];
fis.read(file_buff);
}
trackerServer = trackerClient.getConnection();
if(trackerServer == null){
logger.error("getConnection return null");
}
// 获取storageServer服务器
storageServer = trackerClient.getStoreStorage(trackerServer);
// 创建storageClient客户端
storageClient1 = new StorageClient1(trackerServer, storageServer);
String fileid = storageClient1.upload_file1(file_buff, getFileExt(path.getPath()), meta_list);
return fileid;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException | MyException e) {
e.printStackTrace();
} finally {
if (fis != null){
try {
fis.close();
} catch (IOException e) {
logger.error("IO读写异常",e);
}
}
if (storageServer != null){
try {
storageServer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (trackerServer != null){
try {
trackerServer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
storageClient1 = null;
}
return "";
}
/**
* <B>方法名称:</B>下载方法<BR>
* <B>概要说明:</B>通过文件id进行下载<BR>
* @param fileId 文件id
* @return 返回InputStream
*/
public InputStream download(String groupName, String fileId) {
TrackerServer trackerServer = null;
StorageServer storageServer = null;
StorageClient1 storageClient1 = null;
try {
trackerServer = trackerClient.getConnection();
if (trackerServer == null) {
logger.error("getConnection return null");
}
storageServer = trackerClient.getStoreStorage(trackerServer, groupName);
storageClient1 = new StorageClient1(trackerServer, storageServer);
byte[] bytes = storageClient1.download_file1(fileId);
InputStream inputStream = new ByteArrayInputStream(bytes);
return inputStream;
} catch (Exception ex) {
logger.error("Exception:", ex);
return null;
} finally {
if (storageServer != null){
try {
storageServer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (trackerServer != null){
try {
trackerServer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
storageClient1 = null;
}
}
/**
* <B>方法名称:</B>删除方法<BR>
* <B>概要说明:</B>根据id来删除一个文件<BR>
* @param fileId 文件id
* @return 删除成功返回0,非0则操作失败,返回错误代码
*/
public int delete(String groupName, String fileId) {
TrackerServer trackerServer = null;
StorageServer storageServer = null;
StorageClient1 storageClient1 = null;
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("Exception:", ex);
return 0;
} finally {
if (storageServer != null){
try {
storageServer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (trackerServer != null){
try {
trackerServer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
storageClient1 = null;
}
}
/**
* <B>方法名称:</B><BR>
* <B>概要说明:</B><BR>
* @param oldFileId 旧文件id
* @param path 新文件路径
* @return 上传成功返回id,失败返回null
*/
public String modify(String oldGroupName, String oldFileId, File path) {
String fileid = null;
try {
// 先上传
fileid = upload(path);
if (fileid == null) {
return null;
}
// 再删除
int delResult = delete(oldGroupName, oldFileId);
if (delResult != 0) {
return null;
}
} catch (Exception ex) {
logger.error("Exception:",ex);
return null;
}
return fileid;
}
/**
* <B>方法名称:</B>获取文件后缀名<BR>
* <B>概要说明:</B>获取文件后缀名<BR>
* @param fileName
* @return 如:"jpg"、"txt"、"zip" 等
*/
private static String getFileExt(String fileName) {
if (StringUtils.isBlank(fileName) || !fileName.contains(".")) {
return "";
} else {
return fileName.substring(fileName.lastIndexOf(".") + 1);
}
}
public static void main(String[] args) throws IOException {
FastDfsUtil fastDfsUtil = new FastDfsUtil();
String localPath = "D:\\TestPicture\\5.jpg";
File path = new File(localPath);
System.out.println("--------开始上传文件-----------");
String fileId = fastDfsUtil.upload(path);
System.out.println("本地文件:"+localPath);
System.out.println("上传至服务器路径:http://192.168.225.200/"+fileId);
System.out.println("-------开始下载文件---------");
InputStream is = fastDfsUtil.download("group1",fileId);
String dlPath = "D:\\DownloadPic\\5.jpg";
fastDfsUtil.inputStreamToFile(is,dlPath);
System.out.println("下载完成, 路径:"+dlPath);
System.out.println("---------开始替换文件----------");
File targetPath = new File("D:\\TestPicture\\4.jpg");
String newFileId = fastDfsUtil.modify("group1",fileId,targetPath);
System.out.println("本地文件:"+targetPath +"4.jpg");
System.out.println("上传至服务器路径:http://192.168.225.200/"+newFileId);
System.out.println("-------开始下载替换的文件---------");
InputStream isReplace = fastDfsUtil.download("group1",newFileId);
String dlReplacePath = "D:\\DownloadPic\\4.jpg";
fastDfsUtil.inputStreamToFile(isReplace,dlReplacePath);
System.out.println("下载完成, 路径:"+dlReplacePath);
System.out.println("----------开始删除文件-----------");
int returnMess = fastDfsUtil.delete("group1",newFileId);
if(returnMess==0){
System.out.println("成功删除文件!");
}else{
System.out.println("删除失败");
}
}
// public void initConfig() throws Exception{
// ClientGlobal.init("fastdfsConfig.properties");
// System.out.println("ClientGlobal.getG_tracker_group():"+ ClientGlobal.getG_tracker_group());
// }
public void inputStreamToFile(InputStream is,String fileName) throws IOException{
File file = new File(fileName);
File parent = file.getParentFile();
if(!parent.exists()) {
parent.mkdirs();
}
if(!file.exists()) {
file.createNewFile();
System.out.println("文件创建完毕");
}
BufferedInputStream in=null;
BufferedOutputStream out=null;
in=new BufferedInputStream(is);
out=new BufferedOutputStream(new FileOutputStream(fileName));
int len=-1;
byte[] b=new byte[1024];
while((len=in.read(b))!=-1){
out.write(b,0,len);
}
in.close();
out.close();
}
}
- fastdfsConfig.properties
connect_timeout_in_seconds = 5
network_timeout_in_seconds = 30
charset = UTF-8
http_anti_steal_token = false
http_secret_key = FastDFS1234567890
http_tracker_http_port = 80
tracker_server = 192.168.225.200:22122
3 测试
测试方法写到了FastDfsUtil中了,运行程序即可得到如下效果: