高可用高性能分布式文件系统FastDFS实践Java程序

在前篇高可用高性能分布式文件系统FastDFS进阶keepalived+nginx对多tracker进行高可用热备中已介绍搭建高可用的分布式文件系统架构。

  那怎么在程序中调用,其实网上有很多栗子,这里在他们的基础上作个简单的介绍。

下载源码并加入本地仓库

官网Java客户端源代码:https://github.com/happyfish100/fastdfs-client-java  

打开源码后 执行maven install 将代码打成jar到本地maven仓库(这步可自行 google)

示例源码

然后创建一个Demo工程,这里采用spring mvc模式创建。

在pom中加入引用 maven中依赖jar包

fastdfs-client-java

在resources的properties文件夹中创建配置文件fastdfs-client.properties

fastdfs.connect_timeout_in_seconds = 5

fastdfs.network_timeout_in_seconds = 30

fastdfs.charset = UTF-8

fastdfs.http_anti_steal_token = false

fastdfs.http_secret_key = FastDFS1234567890

fastdfs.http_tracker_http_port = 80

​fastdfs.tracker_servers = 10.0.11.201:22122,10.0.11.202:22122,10.0.11.203:22122

 创建FastDFSClient工具类

package com.james.utils;

import org.csource.common.MyException;

import org.csource.common.NameValuePair;

import org.csource.fastdfs.*;

import java.io.BufferedOutputStream;

import java.io.IOException;

import java.net.URLDecoder;

/**

* Created by James on 2015/11/14.

* FastDFS文件上传

*/

public class FastDFSClientUtils {

private TrackerClient trackerClient = null;

private TrackerServer trackerServer = null;

private StorageServer storageServer = null;

private StorageClient1 storageClient = null;

public FastDFSClientUtils(String conf) throws Exception {

if (conf.contains("classpath:")) {

String path = this.getClass().getResource("/").getPath();

conf = conf.replace("classpath:", URLDecoder.decode(path, "UTF-8"));

}

ClientGlobal.init(conf);

trackerClient = new TrackerClient();

trackerServer = trackerClient.getConnection();

storageServer = null;

storageClient = new StorageClient1(trackerServer, storageServer);

}

/**

* 上传文件方法

*

Title: uploadFile

*

Description:

*

* @param fileName 文件全路径

* @param extName  文件扩展名,不包含(.)

* @param metas    文件扩展信息

* @return

* @throws Exception

*/

public String uploadFile(String fileName, String extName, NameValuePair[] metas) {

String result = null;

try {

result = storageClient.upload_file1(fileName, extName, metas);

} catch (IOException e) {

e.printStackTrace();

} catch (MyException e) {

e.printStackTrace();

}        return result;

}

/**

* 上传文件,传fileName

*

* @param fileName 文件的磁盘路径名称 如:D:/image/aaa.jpg

* @return null为失败

*/

public String uploadFile(String fileName) {

return uploadFile(fileName, null, null);

}

/**

* @param fileName 文件的磁盘路径名称 如:D:/image/aaa.jpg

* @param extName  文件的扩展名 如 txt jpg等

* @return null为失败

*/

public String uploadFile(String fileName, String extName) {

return uploadFile(fileName, extName, null);

}

/**

* 上传文件方法

*

Title: uploadFile

*

Description:

*

* @param fileContent 文件的内容,字节数组

* @param extName    文件扩展名

* @param metas      文件扩展信息

* @return

* @throws Exception

*/

public String uploadFile(byte[] fileContent, String extName, NameValuePair[] metas) {

String result = null;

try {

result = storageClient.upload_file1(fileContent, extName, metas);

} catch (IOException e) {

e.printStackTrace();

} catch (MyException e) {

e.printStackTrace();

}        return result;

}

/**

* 上传文件

*

* @param fileContent 文件的字节数组

* @return null为失败

* @throws Exception

*/

public String uploadFile(byte[] fileContent) throws Exception {

return uploadFile(fileContent, null, null);

}    /**

* 上传文件

*

* @param fileContent 文件的字节数组

* @param extName    文件的扩展名 如 txt  jpg png 等

* @return null为失败

*/

public String uploadFile(byte[] fileContent, String extName) {

return uploadFile(fileContent, extName, null);

}    /**

* 文件下载到磁盘

*

* @param path  图片路径

* @param output 输出流 中包含要输出到磁盘的路径

* @return -1失败,0成功

*/

public int download_file(String path, BufferedOutputStream output) {

//byte[] b = storageClient.download_file(group, path);

int result = -1;

try {

byte[] b = storageClient.download_file1(path);

try {

if (b != null) {

output.write(b);

result = 0;

}

} catch (Exception e) {

} //用户可能取消了下载

finally {

if (output != null)

try {

output.close();

} catch (IOException e) {

e.printStackTrace();

}

}

} catch (Exception e) {

e.printStackTrace();

}        return result;

}

/**

* 获取文件数组

*

* @param path 文件的路径 如group1/M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg

* @return

*/

public byte[] download_bytes(String path) {

byte[] b = null;

try {

b = storageClient.download_file1(path);

} catch (IOException e) {

e.printStackTrace();

} catch (MyException e) {

e.printStackTrace();

}        return b;

}

/**

* 删除文件

*

* @param group      组名 如:group1

* @param storagePath 不带组名的路径名称 如:M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg

* @return -1失败,0成功

*/

public Integer delete_file(String group, String storagePath) {

int result = -1;

try {

result = storageClient.delete_file(group, storagePath);

} catch (IOException e) {

e.printStackTrace();

} catch (MyException e) {

e.printStackTrace();

}        return result;

}

/**

* @param storagePath 文件的全部路径 如:group1/M00/00/00/wKgRsVjtwpSAXGwkAAAweEAzRjw471.jpg

* @return -1失败,0成功

* @throws IOException

* @throws Exception

*/

public Integer delete_file(String storagePath) {

int result = -1;

try {

result = storageClient.delete_file1(storagePath);

} catch (IOException e) {

e.printStackTrace();

} catch (MyException e) {

e.printStackTrace();

}        return result;

}

}

Java客户端文件上传、下载、删除和元数据获取测试:

package com.james.fdfs;

import org.junit.Test;

import java.io.File;

import java.util.HashMap;

import java.util.Iterator;

import java.util.Map;

import  com.james.utils.FastDFSClientUtils;

public class FastDFSClientUtilsTest {

/**

* 文件上传测试

*/

@Test

public void testUpload() {

File file = new File("C:\\Users\\James\\Pic\\share.jpg");

Map metaList = new HashMap();

metaList.put("width","1024");

metaList.put("height","768");

String fid = FastDFSClientUtils.uploadFile(file,file.getName(),metaList);

System.out.println("upload local file " + file.getPath() + " ok, fileid=" + fid);

//上传成功返回的文件ID: group1/M00/00/00/wKgAyVgFk9aAB8hwAA-8Q6_7tHw351.jpg

}

/**

* 文件下载测试

*/

@Test    public void testDownload() {

int r = FastDFSClientUtils.download_file("group1/M00/00/00/wKgAyVgFk9aAB8hwAA-8Q6_7tHw351.jpg", new File("DownloadFile_fid.jpg"));

System.out.println(r == 0 ? "下载成功" : "下载失败");

}

/**

* 文件删除测试

*/

@Test

public void testDelete() {

int r = FastDFSClientUtils.delete_file("group1/M00/00/00/wKgAyVgFk9aAB8hwAA-8Q6_7tHw351.jpg");

System.out.println(r == 0 ? "删除成功" : "删除失败");

}

}


如果没有什么问题将会看到打印的日志。

Net版本

net版本可参考另外一位网友代码:

https://github.com/huanzui/fastdfs.client.net

问题

现在分布式文件平台已经完成了搭建和代码测试,但实践过程中还是有几个问题:

1、上传到平台的文件名都是无规律的64base编码过的字符串,因此如果只作为如图片等文件存储是没有问题的,因为我们不关心其文件名,但如果作为要下载的内容,如附件,或安装包,下载时如果还是编码那无法直观的知道此文件是做什么的,是要转换为正确的文件名。

解决:关于这个问题,网上有方法是通过nginx,利用域名和FID拼出url,然后在url后面增加一个参数,指定原始文件名。

例如:http://121.14.161.48:9030/group2/M00/00/89/eQ6h3FKJf_PRl8p4AUz4wO8tqaA688.apk?attname=filename.apk

在Nginx上进行如下配置,这样Nginx就会截获url中的参数attname,在Http响应头里面加上字段 Content-Disposition “attachment;filename=$arg_attname”。

这里只提供了一个方案,具体内容其实还需要一个篇幅来介绍,有时间再写吧。

2、实际用的时候我们其实是想按业务还将不同文件放在不同的文件夹中的,比如聊天文件,文档文件,还有临时文件 有时需要定时清理的,但分布式文件平台是没法指定文件夹的。

解决:最常用的做法是自己实现一个文件对应库,将上传的文件名,时间,对应的业务等信息与最终的文件路径对应起来,这样就可以作任何逻辑了,但缺点是非常麻烦。

FastDFS没有看到保存到指定的2级目录的API,但可以保存到指定的group,可以指定某个group为哪个业务用。但这样会破坏整个FastDFS的分布式结构,造成某个group非常巨大,而且不容易扩容。实际使用时还会有其它业务的内容进入到此group。

3、大文件如何断点续传?

如果文件大于100M,则需要断点续传的功能了,FastDFS对于大文件来说是有点吃力的,但还是可以实现,根据网友提供的方案来看就是需要客户进行切片上传,并且切片字节大小小于等于storage配置的buff_size,默认是256k,这一块需要自己实现。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容