分布式文件系统之FastDFS
一、FastDFS概述
FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。
FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
FastDFS服务端有两个角色:跟踪器(tracker server)和存储节点(storage server)。客户端请求 Tracker server 进行文件上传、下载,通过 Tracker server 调度最终由 Storage server 完成文件上传和下载。
Tracker server 作用是负载均衡和调度,通过 Tracker server 在文件上传时可以根据一些策略找到 Storage server 提供文件上传服务。可以将 tracker 称为追踪服务器或调度服务器。
Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上,Storageserver 没有实现自己的文件系统而是利用操作系统 的文件系统来管理文件。可以将storage称为存储服务器。
存储节点存储文件,完成文件管理的所有功能:存储、同步和提供存取接口,FastDFS同时对文件的meta data进行管理。所谓文件的meta data就是文件的相关属性,以键值对(key value pair)方式表示,如:width=1024,其中的key为width,value为1024。文件meta data是文件属性列表,可以包含多个键值对。
FastDFS系统结构:
跟踪器和存储节点都可以由一台或多台服务器构成。跟踪器和存储节点中的服务器均可以随时增加或下线而不会影响线上服务。其中跟踪器中的所有服务器都是对等的,可以根据服务器的压力情况随时增加或减少。
为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起到了冗余备份和负载均衡的作用。
在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。
当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。
二、文件上传及下载的流程
文件上传流程
客户端上传文件后存储服务器将文件 ID 返回给客户端,文件 ID 用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。
组名:
文件上传后所在的 storage 组名称,在文件上传成功后有 storage 服务器返回,需要客户端自行保存。
虚拟磁盘路径:
storage 配置的虚拟路径,与磁盘选项 store_path*对应。如果配置了store_path0 则是 M00,如果配置了 store_path1 则是 M01,以此类推。
数据两级目录:
storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。
文件名:
与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储服务器 IP 地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。
示例:
group1/MM/00/00/rBEABV-MFbaAGT8DAAA1oGMcX2I500.jpg
文件下载流程
三、安装FastDFS
四、FastDFS的入门使用
引入依赖
<dependency>
<groupId>cn.aghost</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.29.0</version>
</dependency>
配置tracker.conf文件
connect_timeout = 60 # 连接超时时间,单位为秒
network_timeout = 60 # 通信超时时间,单位为秒。
charset = UTF-8 # 字符集
http.tracker_http_port = 8080 # tracker的http端口
tracker_server = ip:22122 # tracker服务器IP和端口设置
封装UploadUtil类
public class UploadUtil {
/***
* 远程文件上传
* @param trackerserver tracker的配置文件全路径
* @param buffer 上传文件的字节数组
* @param suffix 上传文件的后缀
* @return
* @throws Exception
*/
public static String[] upload(String trackerserver,byte[] buffer,String suffix) throws Exception{
//解析类路径
trackerserver = getConfig(trackerserver);
//初始化tracker服务
ClientGlobal.init(trackerserver);
//创建Tracker客户端
TrackerClient trackerClient = new TrackerClient();
//链接Tracker获得TrackeServer
TrackerServer trackerServer = trackerClient.getTrackerServer();
//通过TrackerServer来创建一个Storage客户端
StorageClient storageClient = new StorageClient(trackerServer,null);
//通过Storage客户端实现文件上传
String[] strings = storageClient.upload_file(buffer,suffix,null);
return strings;
}
/**
* 本地文件上传
* @param trackerserver tracker的配置文件全路径
* @param filename 上传文件全路径
* @return
* @throws Exception
*/
public static String[] upload(String trackerserver,String filename) throws Exception{
//解析类路径
trackerserver = getConfig(trackerserver);
filename = getConfig(filename);
//初始化
ClientGlobal.init(trackerserver);
//创建Tracker客户端
TrackerClient trackerClient = new TrackerClient();
//链接Tracker获得TrackeServer
TrackerServer trackerServer = trackerClient.getTrackerServer();
//通过TrackerServer来创建一个Storage客户端
StorageClient storageClient = new StorageClient(trackerServer,null);
//通过Storage客户端实现文件上传
String[] strings = storageClient.upload_file(filename, null, null);
return strings;
}
/***
* 获取类路径文件
* @param path
* @return
*/
public static String getConfig(String path) throws Exception{
//类路径下的文件
if(path.startsWith("classpath:")){
//URLDecoder.decode()处理中文路径造成乱码
path = path.replace("classpath:", URLDecoder.decode(UploadUtil.class.getClassLoader().getResource("").getPath(),"UTF-8"));
}
return path;
}
public static void main(String[] args) throws Exception{
String tracker="classpath:tracker.conf";
String filename="classpath:fastdfs.jpg";
String[] uploads = upload(tracker, filename);
for (String upload : uploads) {
System.out.println(upload);
}
}
}
执行测试
五、Spring环境中的使用
引入依赖
<!--文件上传组件-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
springmvc.xml配置文件解析器
<!--文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
<!-- 设定文件上传的最大值5MB,5*1024*1024 -->
<property name="maxUploadSize" value="5242880"></property>
</bean>
配置sysconfig.properties
#tracker.conf位置
TRACKER_PATH=classpath:/config/tracker.conf
#图片访问域名
FASTDFS_IP=http://IP/
文件上传控制器UploadController
//注入tracker文件路径配置
@Value("${TRACKER_PATH}")
private String trackerPath;
//注入图片访问域名
@Value("${FASTDFS_IP}")
private String ip;
@RequestMapping(value = "/upload")
public Result upload(MultipartFile file) throws Exception{ //file前台传的文件必须也为file
try {
//获取文件名字
String originalFilename = file.getOriginalFilename();
//获取文件扩展名
String suffix = StringUtils.getFilenameExtension(originalFilename);
//文件的字节数组
byte[] bytes = file.getBytes();
//文件上传
String url = UploadUtil.upload(trackerPath,file.getBytes(),subfix,ip);
return new Result(true,url);
} catch (Exception e) {
e.printStackTrace();
}
return new Result(false,"上传文件失败!");
}
六、SpringBoot集成FastDFS
引入依赖
<!-- https://mvnrepository.com/artifact/net.oschina.zcx7878/fastdfs-client-java -->
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.0.0</version>
</dependency>
配置tracker.conf文件
connect_timeout = 60 # 连接超时时间,单位为秒
network_timeout = 60 # 通信超时时间,单位为秒。
charset = UTF-8 # 字符集
http.tracker_http_port = 8080 # tracker的http端口
tracker_server = ip:22122 # tracker服务器IP和端口设置
文件信息封装
@Data
public class FastDFSFile {
//文件名称
private String name;
//文件内容
private byte[] content;
//文件扩展名
private String ext;
//文件MD5摘要值
private String md5;
//文件创建作者
private String author;
public FastDFSFile(String name, byte[] content, String ext, String height, String width, String author) {
super();
this.name = name;
this.content = content;
this.ext = ext;
this.author = author;
}
public FastDFSFile(String name, byte[] content, String ext) {
super();
this.name = name;
this.content = content;
this.ext = ext;
}
public FastDFSFile(String name, byte[] content, String ext, String md5, String author) {
this.name = name;
this.content = content;
this.ext = ext;
this.md5 = md5;
this.author = author;
}
}
文件操作工具类
@Slf4j
public class FastDFSClient {
/***
* 初始化加载FastDFS的TrackerServer配置
*/
static {
try {
String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();
ClientGlobal.init(filePath);
} catch (Exception e) {
log.error("FastDFS Client Init Fail!", e);
}
}
/***
* 文件上传
* @param file
* @return 1.文件的组名 2.文件的路径信息
*/
public static String[] upload(FastDFSFile file) {
//获取文件的作者
NameValuePair[] meta_list = new NameValuePair[1];
meta_list[0] = new NameValuePair("author", file.getAuthor());
//接收返回数据
String[] uploadResults = null;
StorageClient storageClient = null;
try {
//创建StorageClient客户端对象
storageClient = getTrackerClient();
/***
* 文件上传
* 1)文件字节数组
* 2)文件扩展名
* 3)文件作者
*/
uploadResults = storageClient.upload_file(file.getContent(), file.getExt(), meta_list);
} catch (Exception e) {
log.error("Exception when uploadind the file:" + file.getName(), e);
}
if (uploadResults == null && storageClient != null) {
log.error("upload file fail, error code:" + storageClient.getErrorCode());
}
//获取组名
String groupName = uploadResults[0];
//获取文件存储路径
String remoteFileName = uploadResults[1];
return uploadResults;
}
/***
* 获取文件信息
* @param groupName:组名
* @param remoteFileName:文件存储完整名
* @return
*/
public static FileInfo getFile(String groupName, String remoteFileName) {
try {
StorageClient storageClient = getTrackerClient();
return storageClient.get_file_info(groupName, remoteFileName);
} catch (Exception e) {
log.error("Exception: Get File from Fast DFS failed", e);
}
return null;
}
/***
* 文件下载
* @param groupName
* @param remoteFileName
* @return
*/
public static InputStream downFile(String groupName, String remoteFileName) {
try {
//创建StorageClient
StorageClient storageClient = getTrackerClient();
//下载文件
byte[] fileByte = storageClient.download_file(groupName, remoteFileName);
InputStream ins = new ByteArrayInputStream(fileByte);
return ins;
} catch (Exception e) {
log.error("Exception: Get File from Fast DFS failed", e);
}
return null;
}
/***
* 文件删除
* @param groupName
* @param remoteFileName
* @throws Exception
*/
public static void deleteFile(String groupName, String remoteFileName)
throws Exception {
//创建StorageClient
StorageClient storageClient = getTrackerClient();
//删除文件
int i = storageClient.delete_file(groupName, remoteFileName);
}
/***
* 获取Storage组
* @param groupName
* @return
* @throws IOException
*/
public static StorageServer[] getStoreStorages(String groupName)
throws IOException {
//创建TrackerClient
TrackerClient trackerClient = new TrackerClient();
//获取TrackerServer
TrackerServer trackerServer = trackerClient.getConnection();
//获取Storage组
return trackerClient.getStoreStorages(trackerServer, groupName);
}
/***
* 获取Storage信息,IP和端口
* @param groupName
* @param remoteFileName
* @return
* @throws IOException
*/
public static ServerInfo[] getFetchStorages(String groupName,
String remoteFileName) throws IOException {
TrackerClient trackerClient = new TrackerClient();
TrackerServer trackerServer = trackerClient.getConnection();
return trackerClient.getFetchStorages(trackerServer, groupName, remoteFileName);
}
/***
* 获取Tracker服务地址
* @return
* @throws IOException
*/
public static String getTrackerUrl() throws IOException {
return "http://" + getTrackerServer().getInetSocketAddress().getHostString() + ":" + ClientGlobal.getG_tracker_http_port() + "/";
}
/***
* 获取Storage客户端
* @return
* @throws IOException
*/
private static StorageClient getTrackerClient() throws IOException {
TrackerServer trackerServer = getTrackerServer();
StorageClient storageClient = new StorageClient(trackerServer, null);
return storageClient;
}
/***
* 获取Tracker
* @return
* @throws IOException
*/
private static TrackerServer getTrackerServer() throws IOException {
TrackerClient trackerClient = new TrackerClient();
TrackerServer trackerServer = trackerClient.getConnection();
return trackerServer;
}
}
文件上传
@RestController
@RequestMapping("/file")
public class FileController {
@PostMapping("/upload")
public Result uploadFile(MultipartFile file) {
try {
//判断文件是否存在
if (file == null) {
throw new BusinessException("文件不存在");
}
//获取文件的完整名称
String originalFilename = file.getOriginalFilename();
if (StringUtils.isEmpty(originalFilename)) {
throw new BusinessException("文件不存在");
}
//获取文件的扩展名称 abc.jpg jpg
String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
//获取文件内容
byte[] content = file.getBytes();
//创建文件上传的封装实体类
FastDFSFile fastDFSFile = new FastDFSFile(originalFilename, content, extName);
//基于工具类进行文件上传,并接受返回参数 String[]
String[] uploadResult = FastDFSClient.upload(fastDFSFile);
//封装返回结果
String url = FastDFSClient.getTrackerUrl() + uploadResult[0] + "/" + uploadResult[1];
return new Result(true, StatusCode.OK, "文件上传成功", url);
} catch (Exception e) {
e.printStackTrace();
}
return new Result(false, StatusCode.ERROR, "文件上传失败");
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/137099.html