为什么需要文件存储系统
回想传统项目中,文件存储的方式是上传到tomcat服务器中,经过分布式、集群的学习,大家眼界提升,知道tomcat等服务器就用来跑java项目,我们还会觉得它扛不住压力,而采用tomcat集群。居然还用来存储文件,显然不太合适。而且tomcat能存储多少文件?
针对中大型项目,需要一种专业文件服务器来完成对文件存储、访问等功能
主流的文件存储一般两种方式
- 采用第三方的文件服务器,比如七牛云等,简单方便,不用自己搭建,但是贵,而且文件在别人手中
- 自己搭建一套服务器,比如fastdfs、hbase等。
什么是fastdfs
百度百科:
Fastdfs:存储、访问、上传、下载文件
1.2fastdfs详细介绍
前面已经介绍过,项目大了,将以前的单系统拆分,如图:
问题:这么大的系统,一台服务器hold得住吗?所以,如图:
然后fastdfs给文件服务器改名为Storage Server:存储服务器
问题:因为一台storage存的数据量有限,所以用了上图的多台,每台存储的数据不相同,如果其中一台挂了,所存储的部分数据就会丢失,所以,如图:
问题:这么多组,现在的大家很容易就能想到负载均衡,所以如图,复杂的图直接借了:
Tracker server:跟踪服务器,主要做调度工作,起负载均衡的作用。在内存中记录集群中所有存储组和存储服务器的状态信息,是客户端和数据服务器交互的枢纽
每个storage在启动后会连接Tracker,告知自己所属的group等信息,并保持周期性的心跳,tracker根据storage的心跳信息,建立group==>[storage server list]的映射表。
理解了上面的关系,整个文件上传流程就很简单了:
流程:首先每个组存储服务器storage会向tracker定时上传状态信息,比如剩余空间,是否可用等,客户端(java、php等)上传文件时,先请求tracker,tracker根据storage的情况,将某一台storage的ip、端口等告知client,client调用API开始上传,storage存储好了后,将文件存储的具体位置形成相对路径file_id返回给客户端,客户端存储文件路径到mysql等
- tracker有多个,客户端如何选择:采用nginx来完成负载均衡
- group有多个tracker如何选择(了解)
通过参数store_lookup = 0 配置
0:所有的Group以轮询方式进行选择
1:指定Group,该Group名称由store_group配置指定
2:负载均衡,表示选择空余容量最大的Group - 每个group中很多个服务器如何选择(了解)
通过参数store_server = 0 配置
0:Group中的所有Storage进行轮询
1:选择IP地址最大的Storage
2:根据优先级配置(在每个storage.conf、upload_priority配置) - 选择了某台服务器,如何选择服务器中的磁盘及目录(了解)
通过参数store_path = 0配置
0:对所有的Store_path进行轮询
1:负载均衡,选择空闲空间最大的Store_path
好像有点麻烦,其实这些你都不用管,主要是通过上面可用总结出,storage存储文件,返回file_id应该包含,group信息,磁盘信息,目录信息,文件名信息(具体机器不用返回,每台都会同步),例如如图:
文件下载,网上的图,不用解释了,有点小问题就是 client 在发送下载请求给某个 tracker,必须带上文件名信息,tracker 从文件名中解析出文件的 group、大小、创建时间等信息,然后再返回合适的storage的ip与端口
1.3fastdfs环境搭建(自行百度)
Java配置fastdfs文件上传下载
需要的文件在附件中
1.4springboot整合fastdfs
1.添加依赖
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>1.26.2</version>
</dependency>
2.配置文件
分布式文件系统FDFS配置
#读取数据超时时间
fdfs.soTimeout=1500
#链接超时时间
fdfs.connectTimeout=600
#缩略图宽高 压缩后的
fdfs.thumbImage.width=150
fdfs.thumbImage.height=150
#跟踪调度器 集群就加在后面就可用了
fdfs.trackerList[0]=172.18.34.9:22122
#fdfs.trackerList[1]=192.168.0.202:22122
package com.xmcc.redis01.fastdfs;
import com.github.tobato.fastdfs.FdfsClientConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.context.annotation.Import;
import org.springframework.jmx.support.RegistrationPolicy;
/**
* 导入FastDFS-Client组件
*/
@Configuration
//表示将FdfsClientConfig类 的对象交给spring管理 @Import非常简单 需要掌握的注解 自己看
@Import(FdfsClientConfig.class)
// 解决jmx重复注册bean的问题 jmx是一个监控与管理的框架 以后有机会在去学习吧
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class FastDFSConfig {
}
测试:
package com.xmcc.redis01;
import com.github.tobato.fastdfs.domain.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class Redis01ApplicationTests {
@Autowired
private FastFileStorageClient fastFileStorageClient;
@Test
public void upload() throws FileNotFoundException {
File file = new File("E://timg.jpg");
FileInputStream fileInputStream = new FileInputStream(file);
//文件输入流 文件长度 文件格式 第四个不管
StorePath storePath = fastFileStorageClient.uploadFile(fileInputStream, file.length(), "jpg", null);
log.info("文件上传返回路径为:{}",storePath.getFullPath());
}
}
效果:
OK的
那么以后上传文件只需要保存这个返回的路径,因为是nginx放在前端就可以访问了
工具类:
package com.xmcc.redis01.fastdfs;
import com.github.tobato.fastdfs.domain.StorePath;
import com.github.tobato.fastdfs.exception.FdfsUnsupportStorePathException;
import com.github.tobato.fastdfs.proto.storage.DownloadByteArray;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
@Component
@Slf4j
public class FastDFSUtils {
@Autowired
private FastFileStorageClient storageClient;
/**
* 上传文件
*
* @param file 文件对象
* @return 文件访问地址
* @throws IOException
*/
public String uploadFile(MultipartFile file) throws IOException {
StorePath storePath = storageClient.uploadFile(file.getInputStream(), file.getSize(), FilenameUtils.getExtension(file.getOriginalFilename()), null);
return getAccessUrl(storePath);
}
/**
* 普通文件上传
* @param file
* @return
* @throws IOException
*/
public String uploadFile(File file) throws IOException {
FileInputStream fileInputStream = new FileInputStream(file);
//获取后缀名
String fileName = file.getName();
String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
StorePath storePath = storageClient.uploadFile(fileInputStream, file.length(), suffix, null);
return getAccessUrl(storePath);
}
/**
* 将一段字符串生成一个文件上传
*
* @param content 文件内容
* @param fileExtension
* @return
*/
public String uploadFile(String content, String fileExtension) {
byte[] buff = content.getBytes(Charset.forName("UTF-8"));
ByteArrayInputStream stream = new ByteArrayInputStream(buff);
StorePath storePath = storageClient.uploadFile(stream, buff.length, fileExtension, null);
return getAccessUrl(storePath);
}
private String getAccessUrl(StorePath storePath) {
String fileUrl = storePath.getFullPath();
return fileUrl;
}
/**
* 删除文件
*
* @param fileUrl 文件访问地址
* @return
*/
public void deleteFile(String fileUrl) {
if (StringUtils.isEmpty(fileUrl)) {
return;
}
try {
StorePath storePath = StorePath.praseFromUrl(fileUrl);
storageClient.deleteFile(storePath.getGroup(), storePath.getPath());
} catch (FdfsUnsupportStorePathException e) {
log.warn(e.getMessage());
}
}
/**
* 文件名下载文件
* @param fileUrl
* @return
* @throws IOException
*/
public byte[] downloadFile(String fileUrl) throws IOException {
String group = fileUrl.substring(0, fileUrl.indexOf("/"));
String path = fileUrl.substring(fileUrl.indexOf("/") + 1);
DownloadByteArray downloadByteArray = new DownloadByteArray();
byte[] bytes = storageClient.downloadFile(group, path, downloadByteArray);
return bytes;
}
}