一、简介
解决了大容量的文件存储和高并发访问的问题,文件存取时实现了负载均衡。
FastDFS服务端只有两个角色,tracker server和storage server。
所有同角色服务器集群节点都是平等的,不存在主从关系(Master-Slave)。
存储服务器采用分组方式,同组内存储服务器上的文件完全相同(备份);不同分组的存储服务器管理不同的文件(扩容 RAID)。
不同组的storage server之间不会相互通信。
由storage server主动向tracker server报告状态信息,tracker server之间不会相互通信。
二、搭建环境
(1)拉取镜像
docker pull delron/fastdfs
(2)创建tracker service宿主机目录
mkdir -p /opt/fdfs/tracker
(3)创建tracker service容器,默认端口号22122
docker run -d --network=host --name tracker -v /opt/fdfs/tracker:/var/fdfs delron/fastdfs tracker
(4)创建storage service宿主机目录
mkdir -p /opt/fdfs/storage
(5)创建storage service并启动容器,默认端口号23000
docker run -d --network=host --name storage -e TRACKER_SERVER=192.168.146.130:22122 -v /opt/fdfs/storage:/var/fdfs -e GROUP_NAME=group1 delron/fastdfs storage
(6)重启问题解决
rm -rf /opt/fdfs/tracker/data/*.pid
rm -rf /opt/fdfs/storage/data/*.pid
三、文件上传
(1)导入依赖
<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>
(2)在src/main/resources目录中创建properties类型配置文件,命名不限。如:fdfs.properties
fastdfs.connect_timeout_in_seconds=10
fastdfs.network_timeout_in_seconds=30
fastdfs.charset=UTF-8
# tracker服务器地址,多个服务器,逗号分隔
fastdfs.tracker_servers=192.168.130.146:22122
# tracker服务器中配置文件tracker.conf中的http端口配置。必须相同。
fastdfs.http_tracker_http_port=8080
(3)创建工具类com.bjsxt.utils.FastDFSUtils
/**
* FastDFS Java客户端工具
*/
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);
}
}
/**
* 删除文件
* int delete_file(String 卷名, String 路径及文件名);
* 返回值: 0代表成功,其他数字代表错误编码
*/
public static int remote(String group, String remote){
try {
return STORAGE_CLIENT.delete_file(group, remote);
}catch (Exception e){
e.printStackTrace();
return -1;
}
}
/**
* 查询某文件的元数据
* @param group 卷名
* @param remote 路径及文件名
* @return 返回文件的元数据数组。发生错误返回null
*/
public static NameValuePair[] getMetaData(String group, String remote){
try{
return STORAGE_CLIENT.get_metadata(group, remote);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 下载文件工具方法
* 下载方法
* byte[] download_file(String 卷名, String 路径及文件名)
* 返回要下载的文件内容
* @param group 卷名
* @param remote 路径及文件名
* @return 返回下载的文件内容,发生错误返回null
*/
public static byte[] download(String group, String remote){
try {
return STORAGE_CLIENT.download_file(group, remote);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 上传文件的工具方法
* 一定保存文件到FastDFS,一定保存至少一个元数据(文件原始名称)
* @param inputStream 要上传的文件的输入流
* @param fileName 上传文件的原始名称
* @param metaProperties 上传文件的元数据,成对提供,如: 名,值,名,值
* @return
*/
public static String[] uploadFile(InputStream inputStream, String fileName, String... metaProperties){
try {
int length = inputStream.available();
byte[] datas = new byte[length];
inputStream.read(datas, 0, length);
// 处理元数据
NameValuePair[] nameValuePairs = null;
if (metaProperties.length % 2 == 0) {
// 参数数量满足要求,开始处理
nameValuePairs = new NameValuePair[metaProperties.length / 2 + 1];
for (int i = 0; i < nameValuePairs.length; i = i + 2) {
nameValuePairs[i / 2] = new NameValuePair(metaProperties[i], metaProperties[i + 1]);
}
} else {
nameValuePairs = new NameValuePair[1];
}
nameValuePairs[nameValuePairs.length - 1] = new NameValuePair("fileName", fileName);
// 获取文件后缀
String extName = getExtName(fileName);
// 上传文件到FastDFS
String[] result = STORAGE_CLIENT.upload_file(datas, extName, nameValuePairs);
return result;
}catch (Exception e){
// 发生任何异常,上传文件失败。返回null
e.printStackTrace();
return null;
}
}
/**
* 截取文件后缀
* @param fileName
* @return
*/
private static String getExtName(String fileName){
if(fileName.lastIndexOf(".") > -1){
// 文件名称中包含字符 .
return fileName.substring(fileName.lastIndexOf(".") + 1);
}else{
// 文件名称中不包含字符 .
return "";
}
}
/**
* 提供获取Storage客户端对象的工具方法
*/
public static StorageClient getStorageClient(){
return STORAGE_CLIENT;
}
private FastDFSUtils(){}
}
(4)service层
@Override
public boolean upload(MultipartFile file) {
try {
//将文件存入到Storage service中,返回值为长度为2的数组,一个为存储的仓库,一个为存储的文件路径
String[] strings = FastDFSUtils.uploadFile(file.getInputStream(), file.getOriginalFilename(), "owner", "admin");
if(strings==null){
return false;
}
//将文件信息存储到数据库中
MyFile myFile = new MyFile();
myFile.setFileName(file.getOriginalFilename());
myFile.setGroupName(strings[0]);
myFile.setRemoteName(strings[1]);
myFile.setLength(file.getSize());
myFile.setCreateTime(new Date());
int insert = uploadMapper.insert(myFile);
if(insert != 1){
return false;
}
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
四、文件下载
(1)service层
/**
* 文件上传service
* @param groupName 存储的主机
* @param remoteName 存储的文件路径及文件名
* @return
*/
@Override
public Map<String, Object> download(String groupName, String remoteName) {
HashMap<String, Object> map = new HashMap<>();
//获取文件二进制数组
byte[] download = FastDFSUtils.download(groupName, remoteName);
//获取文件元数据
NameValuePair[] metaData = FastDFSUtils.getMetaData(groupName, remoteName);
if(download != null && metaData != null){
map.put("data",download);
for (NameValuePair metaDatum : metaData) {
if(metaDatum.getName().equals("fileName")){
map.put("fileName", metaDatum.getValue());
break;
}
}
}
return map;
}
(2)controller
@RequestMapping("/download")
public void download(String groupName, String remoteName, HttpServletResponse response) throws IOException {
Map<String, Object> download = uploadService.download(groupName, remoteName);
// 设置响应头
response.setContentType("application/octet-stream");
// 设置下载文件的附件名称
response.setHeader("content-disposition", "attachment;filename="+download.get("fileName").toString());
// 输出要下载的文件内容到客户端
byte[] datas = (byte[]) download.get("data");
response.getOutputStream().write(datas, 0, datas.length);
}
五、文件删除
@Override
public boolean delete(MyFile file) {
//删除数据库文件
int delete = uploadMapper.delete(file);
//删除文件
int remote = FastDFSUtils.remote(file.getGroupName(), file.getRemoteName());
return delete>0&&remote==0;
}
六、文件预览
(1)Nginx简介
Nginx (engine x) 是一个高性能的HTTP和反向代理服务。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的,第一个公开版本0.1.0发布于2004年10月4日。
Nginx 是一个很强大的高性能Web和反向代理服务,它具有很多非常优越的特性:在连接高并发的情况下,Nginx是Apache服务不错的替代品:Nginx在美国是做虚拟主机生意的老板们经常选择的软件平台之一。
(2)代理方式
正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。
反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
两者间的区别
位置不同 正向代理,架设在客户机和目标主机之间; 反向代理,架设在服务器端;
代理对象不同 正向代理,代理客户端,服务端不知道实际发起请求的客户端; 反向代理,代理服务端,客户端不知道实际提供服务的服务端;
(3)Nginx常用场景
Http协议代理
只要支持HTTP协议访问的内容,都可以由Nginx进行代理。Nginx只支持HTTP协议的代理,其他协议不支持。
搭建虚拟主机
Nginx可以监听所安装的主机的某个端口,对外支持这个端口的HTTP访问。当接收到外部HTTP请求后把本机中资源返回给客户端。今天的课程内容就是使用Nginx的搭建虚拟主机功能,外部请求图片时,把图片信息响应给请求发。
负载均衡
Nginx可以代理多个主机,内置负载均衡策略。
(4)使用
使用http请求,ip+存储主机+存储路径及文件名