分布式文件管理系统(Distributed File System,DFS)
是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点(可简单的理解为一台计算机)
DFS可以看作是一个接口:具体实现有OSS,GlusterFS、GoogleFS、FastDFS、TFS
FastDFS分布式文件管理系统
搭建环境和基本配置
FastDFS特别适合大中型网站使用,用来存储资源文件(如:图片、文档、音频、视频等等)
FastDFS没有官网,http://bbs.chinaunix.net/
下载: https://sourceforge.net/projects/fastdfs/files/
文件大小一般都是在500MB以下的文件,使用fastDFS最合适了。
角色:
1.Client: 客户端。使用Java语言编写的项目属于客户端。
2.Tracker Server: 跟踪服务器,主要做调度工作,在访问上起负载均衡的作用。在内存中记录集群中group和storage server的状态信息,是连接Client和Storage server的枢纽。相当于酒店前台
3.Storage Server: 存储服务器,文件和文件属性(meta data)都保存到存储服务器上,相当于酒店房间
架构:
1.FastDFS服务端只有两个角色,tracker server(酒店前台)和storage server。(酒店房间)
2.所有同角色服务器集群节点都是平等的,不存在主从关系(Master-Slave)。
3.存储服务器采用分组方式,同组内存储服务器上的文件完全相同(备份);不同分组的存储服务器管理不同的文件(扩容 RAID)。
不同组的storage server之间不会相互通信。
4.由storage server主动向tracker server报告状态信息,tracker server之间不会相互通信。
Docker安装FastDFS
1.创建跟踪器容器
拉取镜像
docker pull delron/fastdfs
创建映射宿主机映射文件目录:
mkdir -p /opt/fdfs/tracker
创建并启动容器: 跟踪器默认服务端口是:22122
docker run -d --network=host --name tracker -v /opt/fdfs/tracker:/var/fdfs delron/fastdfs tracker
2.创建存储器容器
创建宿主机映射目录:
mkdir -p /opt/fdfs/storage
存储器默认服务端口是:23000
Storage存储器启动后,需要连接Tracker跟踪器并提交元信息,必须配置容器环境 -e TRACKER_SERVER=跟踪器所在宿主机IP:22122
创建并启动容器:
docker run -d --network=host --name storage -e TRACKER_SERVER=192.168.6.100:22122 -v /opt/fdfs/storage:/var/fdfs -e GROUP_NAME=group1 delron/fastdfs storage
3.重启容器错误
关闭容器后,再次启动会报错.
原因: 存储器启动时,需要重新创建日志文件和进程描述文件(xxx.pid),第二次启动时,进程信息和第一次启动时不一样,但还是读取的起一次启动时创建的文件.所以报错.
解决: 删掉对应文件
//追踪器
rm -rf /opt/fdfs/tracker/data/*.pid
//存储器
rm -rf /opt/fdfs/storage/data/*.pid
4.Storage服务器磁盘空间不足不能上传文件
进入Tracker容器:
docker exec -it tracker bash
进入配置文件目录:
cd /etc/fdfs
编辑配置文件:
vi /etc/fdfs/tracker.conf
按照具体环境修改限制即可。默认磁盘限制是,剩余磁盘空间不足10%时,关闭上传文件功能。如:reserved_storage_space = 10K
退出容器:exit
关闭容器:docker stop tracker
删除宿主机中映射tracker容器的目录中的内容:
rm -rf /opt/fdfs/tracker/*
启动容器
docker start tracker
观察日志:
docker logs -f tracker
准备
导入依赖:
<dependency>
<groupId>cn.bestwu</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
新建工具类:
public final class FastDFSUtils {
/**
* 定义静态属性,Properties和StorageClient
*/
private final static Properties PROPERTIES;
private final static StorageClient STORAGE_CLIENT;
/**
* 静态初始化代码块,初始化静态属性
* 静态初始化代码块有异常如何处理?
* 处理的时候,try。。catch。。 抛出一个Error,终止虚拟机。
*/
static{
try {
PROPERTIES = new Properties();
// 读取配置文件
PROPERTIES.load(
FastDFSUtils.class
.getClassLoader()
.getResourceAsStream("fdfs.properties")
);
// 使用ClientGlobal初始化FastDFS客户端配置
ClientGlobal.initByProperties(PROPERTIES);
// 创建Tracker客户端对象
TrackerClient trackerClient = new TrackerClient();
// 基于Tracker客户端对象,获取Tracker服务器对象
TrackerServer trackerServer = trackerClient.getConnection();
// 基于Tracker服务器和客户端对象,获取Storage服务器对象
StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
// 创建Storage客户端对象
STORAGE_CLIENT = new StorageClient(trackerServer, storageServer);
}catch (Exception e){
throw new ExceptionInInitializerError(e);
}
}
/**
*
* @param inputStream
* 上传的文件输入流
* @param fileName
* 上传的文件原始名
* @return
*/
public static String[] uploadFile(InputStream inputStream, String fileName) {
try {
// 文件的元数据
NameValuePair[] meta_list = new NameValuePair[2];
// 第一组元数据,文件的原始名称
meta_list[0] = new NameValuePair("file name", fileName);
// 第二组元数据
meta_list[1] = new NameValuePair("file length", inputStream.available()+"");
// 准备字节数组
byte[] file_buff = null;
if (inputStream != null) {
// 查看文件的长度
int len = inputStream.available();
// 创建对应长度的字节数组
file_buff = new byte[len];
// 将输入流中的字节内容,读到字节数组中。
inputStream.read(file_buff);
}
// 上传文件。参数含义:要上传的文件的内容(使用字节数组传递),上传的文件的类型(扩展名),元数据
String[] fileids = STORAGE_CLIENT.upload_file(file_buff, getFileExt(fileName), meta_list);
return fileids;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
/**
*
* @param file
* 文件
* @param fileName
* 文件名
* @return 返回Null则为失败
*/
public static String[] uploadFile(File file, String fileName) {
FileInputStream fis = null;
try {
NameValuePair[] meta_list = null; // new NameValuePair[0];
fis = new FileInputStream(file);
byte[] file_buff = null;
if (fis != null) {
int len = fis.available();
file_buff = new byte[len];
fis.read(file_buff);
}
String[] fileids = STORAGE_CLIENT.upload_file(file_buff, getFileExt(fileName), meta_list);
return fileids;
} catch (Exception ex) {
return null;
}finally{
if (fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 根据组名和远程文件名来删除一个文件
*
* @param groupName
* 例如 "group1" 如果不指定该值,默认为group1
* @param remoteFileName
* 例如"M00/00/00/wKgxgk5HbLvfP86RAAAAChd9X1Y736.jpg"
* @return 0为成功,非0为失败,具体为错误代码
*/
public static int deleteFile(String groupName, String remoteFileName) {
try {
int result = STORAGE_CLIENT.delete_file(groupName == null ? "group1" : groupName, remoteFileName);
return result;
} catch (Exception ex) {
return 0;
}
}
/**
* 修改一个已经存在的文件
*
* @param oldGroupName
* 旧的组名
* @param oldFileName
* 旧的文件名
* @param file
* 新文件
* @param fileName
* 新文件名
* @return 返回空则为失败
*/
public static String[] modifyFile(String oldGroupName, String oldFileName, File file, String fileName) {
String[] fileids = null;
try {
// 先上传
fileids = uploadFile(file, fileName);
if (fileids == null) {
return null;
}
// 再删除
int delResult = deleteFile(oldGroupName, oldFileName);
if (delResult != 0) {
return null;
}
} catch (Exception ex) {
return null;
}
return fileids;
}
/**
* 文件下载
*
* @param groupName 卷名
* @param remoteFileName 文件名
* @return 返回一个流
*/
public static InputStream downloadFile(String groupName, String remoteFileName) {
try {
byte[] bytes = STORAGE_CLIENT.download_file(groupName, remoteFileName);
InputStream inputStream = new ByteArrayInputStream(bytes);
return inputStream;
} catch (Exception ex) {
return null;
}
}
public static NameValuePair[] getMetaDate(String groupName, String remoteFileName){
try{
NameValuePair[] nvp = STORAGE_CLIENT.get_metadata(groupName, remoteFileName);
return nvp;
}catch(Exception ex){
ex.printStackTrace();
return null;
}
}
/**
* 获取文件后缀名(不带点).
*
* @return 如:"jpg" or "".
*/
private static String getFileExt(String fileName) {
if (StringUtils.isBlank(fileName) || !fileName.contains(".")) {
return "";
} else {
return fileName.substring(fileName.lastIndexOf(".") + 1); // 不带最后的点
}
}
/**
* 提供获取Storage客户端对象的工具方法
*/
public static StorageClient getStorageClient(){
return STORAGE_CLIENT;
}
private FastDFSUtils(){}
}
文件上传
@Test
public void test(){
//普通上传
String[] strings = FastDFSUtils.uploadFile(new File("E:/UDP.png"), "UDP.png");
System.out.println(Arrays.toString(strings));//[group1, M00/00/00/wKgGZGQs7ZqABc58AAC4jNgFKNE689.png]
//上传并保存元信息,同时会生成一个.png-m 文件,用于保存上传文件的元信息
String[] strings2 = FastDFSUtils.uploadFile(new FileInputStream("E:/UDP.png"), "UDP.png");
System.out.println(Arrays.toString(strings2));//[group1, M00/00/00/wKgGZGQs7ZqASMNfAAC4jNgFKNE630.png]
//获取元信息
NameValuePair[] group1 = FastDFSUtils.getMetaDate("group1", "M00/00/00/wKgGZGQs542Ac7bhAAC4jNgFKNE132.png");
System.out.println(group1[0].getName()+ "---" + group1[0].getValue());//file length---47244
System.out.println(group1[1].getName()+ "---" + group1[1].getValue());//file name---UDP.png
}
文件下载
@Test
public void download() throws IOException {
//下载文件
InputStream inputStream = FastDFSUtils.downloadFile("group1", "M00/00/00/wKgGZGQs542Ac7bhAAC4jNgFKNE132.png");
//指定下载位置及文件名,注意文件格式一致
FileOutputStream fileOutputStream = new FileOutputStream("E:/abc.png");
IOUtils.copy(inputStream,fileOutputStream);
}
文件删除
@Test
public void delete(){
//返回结果0,删除成功,如果存在改文件的元信息,会一并删除
int result = FastDFSUtils.deleteFile("group1", "M00/00/00/wKgGZGQs542Ac7bhAAC4jNgFKNE132.png");
System.out.println(result);
}
在线预览FastDFS中的文件
“http://192.168.137.128:8888/” + groupName + “/” + remoteName;
例: http://192.168.6.100:8888/group1/M00/00/00/wKgGZGQs7ZqASMNfAAC4jNgFKNE630.png
既然FastDFS不支持在线文件预览,那么我们系统页面中的预览图片从何而来?
这是Storage容器中附带的Nginx软件给我们提供的在线预览能力。
查看Storage存储器中Nginx配置
docker exec -it storage
查看配置文件/usr/local/nginx/conf/nginx.conf
cat /usr/local/nginx/conf/nginx.conf
如下:
server{
listen 8888;
server_name localhost;
location ~/group([0-9])/M00{
ngx_fastdfs_module;
}
}