前言
提示:以下是本篇文章正文内容,下面案例可供参考
一、FastDFS是什么?
1.1 思考
- 在分布式集群环境中上传文件,我们想要上传到节点A,但是通过负载均衡的算法,上传时,将文件传到了节点B;这种情况下,我们能否精准的访问到我们上产的文件?
- 同时考虑如何为文件做冗余备份、负载均衡、线性扩容?这些能力,单节点是否具备?
1.2 FastDFS介绍
- FastDFS是一个开源的、轻量级的 分布式文件系统 ,它对文件的管理包括:文件存储、文件同步、文件访问(上传、下载)等等,解决了大容量存储和负载均衡的问题。特别适合以文件为猜题的在线服务(如相册网站、视频网站等);
- FastDFS为互联网量身定做,充分考虑了冗余备份、负载均衡、线性扩容等机制,
- 注重高可用、高性能指标,可以很容易的搭建一套高性能的文件还能服务器集群并提供服务;
- FastDFS包括 Tracker server 和 Storage server 。客户端请通过求Tracker server进行文件操作,而 Tracker server 调度最终由 Storage server完成文件的实际操作(如上传、下载等);
- Tracker Server:追踪服务器或调度服务器 作用是负载均衡和调度,Tracker server 在文件上传时,可以根据以下策略找到 Storage server 提供文件上传服务;
- Storgae server:存储服务器 作用是文件存储,客户端上传的文件,最终存储在 Storage server 上, Storage server没有实现自己的文件系统,而是利用操作系统的文件系统来管理文件。
1.3 上传流程
- 客户端上传文件后,存储服务器将文件ID返回给客户端,此文件ID用于以后访问该文件的索引信息;
- 文件索引信息包括:组名(group)、虚拟磁盘路径、数据存储的两级目录和文件名(新生成的)。
- 组名:文件上传后所在的 storage 组名称,文件上传成功后,由 storgae 服务器返回,需要我们自己进行保存;
- 虚拟磁盘名:Storage 配置的虚拟路径,与磁盘选项 store_path*对应 。如果配置了 stroe_path0 则是M00,如果配置了 store_path1 就是M01,以此类推。
- 数据两级目录:storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件;
- 文件名:与文件上传时不同。是由存储服务器根据特定信息生成的,文件名包括:源存储服务器IP地址、文件上传的时间戳、文件大小、随机数和文件扩展名等信息。
二、FastDFS使用
2.1 安装
在 centOS 7 环境下使用Docker 安装FastDFS
- 拉取镜像
docker pull morunchang/fastdfs
2. 创建并运行 tracker
sh tracker.sh:进入容器是执行的初始化指令,进入tracker之后,执行Tracker.sh脚本。
docker run -d --name tracker --net=host morunchang/fastdfs sh tracker.sh
3. 创建并运行storage
使用时 < your tracker server address > 要替换称自己的IP,
< group name > 要替换成自己的组名;
docker run -d --name storage --net=host -e TRACKER_IP=<your tracker server address>:22122 -e
GROUP_NAME=<group name> morunchang/fastdfs sh storage.sh
eg:
docker run -d --name storage --net=host -e TRACKER_IP=192.168.119.130:22122 -e
GROUP_NAME=group1 morunchang/fastdfs sh storage.sh
说明:
- –net=host 是使用的网络模式,这种模式下,可以不用映射容器和宿主机的ip和端口,容器的ip和端口会暴露出来,直接通过宿主机的ip和端口就可以使用;
- 组名,是 storage 的组名
- 新增 storage 容器,只需要重复第 3 步的命令,但是要注意更换 组名;
2.2 修改配置
- 进入 storage 的内部,修改nginx的配置文件 nginx.conf;
docker exec -it storage /bin/bash
进入后
vi /etc/nginx/conf/nginx.conf
找到以下内容
location ~ /M00 {
root /data/fast_data/data;
ngx_fastdfs_module;
}
添加配置项:
add_header Cache-Control no-store;
这项配置的作用是,禁止使用缓存。
修改之后,记得重启storage容器;
2.3 文件上传微服务
2.3.1 父工程pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kaikeba</groupId>
<artifactId>fastDFS</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>eureka-server</module>
<module>upload-service</module>
<module>bill</module>
</modules>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.1.18.RELEASE</version>
</parent>
<!-- 版本管理 -->
<properties>
<java.version>1.8</java.version>
<spring-cloud-version>Greenwich.SR1</spring-cloud-version>
<tk-mybatis.version>2.1.5</tk-mybatis.version>
<mysql-connection-version>8.0.25</mysql-connection-version>
<pagehelper.version>5.1.2</pagehelper.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- tk mybatis -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connection-version}</version>
</dependency>
<!-- 分页 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.3.2 注册中心eureka-server
2.3.2.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>fastDFS</artifactId>
<groupId>com.kaikeba</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
2.3.2.2 application.yml
server:
port: 8080
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://localhost:8080/eureka
register-with-eureka: false
server:
enable-self-preservation: false # 关闭自我保护
eviction-interval-timer-in-ms: 6000 #失效服务扫描的时间间隔
2.3.2.3 EurekaServerApplication.java
package com.kaikeba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class);
}
}
2.3.3 文件上传服务
2.3.3.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>fastDFS</artifactId>
<groupId>com.kaikeba</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>upload-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- FastDFS依赖 -->
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>1.26.7</version>
</dependency>
</dependencies>
</project>
2.3.3.2 application.yml
这里要特别注意的是fdfs配置内容:
- connect-timeout: 连接超时时间
- so-timeout: 文件处理时间
- thumb-image: 缩略图的配置
- tracker-list: 调度服务器的ip列表
server:
port: 8000
spring:
application:
name: uoload-service
servlet:
multipart:
enabled: true
max-file-size: 10MB #单个文件上传的大小限制
max-request-size: 20MB #上传文件的大小的总限制
logging:
pattern:
console: "%d - %msg%n"
level:
org.springframework.web: debug
com.kaikeba: debug
# fastDFS设置
fdfs:
connect-timeout: 60 # 连接超时时间
so-timeout: 60 # 读取时间(也就是上传的处理时间),超过这个时间也认为是失败
thumb-image: # 缩略图片的尺寸设置
width: 150
height: 150
tracker-list:
- 192.168.119.130:22122 # 地址
eureka:
client:
service-url:
defaultZone: http://localhost:8080/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
lease-renewal-interval-in-seconds: 30 # 续约时间
lease-expiration-duration-in-seconds: 90 # 服务失效时间
2.3.3.2 FileDfsUtil.java
文件上传的工具类,其实FastDFS的依赖中已经有相当健全的上传方法,我们只要直接调用即可,这里的工具类是做一些参数处理
package com.kaikeba.util;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
/**
* 调用fastdfs-client工具方法实现文件上传和删除
*/
@Component
public class FileDfsUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(FileDfsUtil.class);
@Resource
private FastFileStorageClient storageClient;
/**
* 上传文件
*/
public String upload(MultipartFile multipartFile) throws IOException {
// 截取文件扩展名
String originalFilename = multipartFile.getOriginalFilename().substring(multipartFile
.getOriginalFilename().lastIndexOf(".") + 1);
// 上传图片并创建缩略版
StorePath storePath = storageClient.uploadImageAndCrtThumbImage(multipartFile.getInputStream(), multipartFile.getSize(),
originalFilename, null);
return storePath.getFullPath();
}
/**
* 删除文件
* @param url
*/
public void deleteFile(String url) {
if(StringUtils.isEmpty(url)) {
LOGGER.info("url ===> 文件路径不能为空");
return;
}
try {
StorePath storePath = StorePath.parseFromUrl(url);
storageClient.deleteFile(storePath.getGroup(), storePath.getPath());
} catch (Exception e) {
LOGGER.error(e.getMessage());
}
}
}
2.3.3.3 FastDFSConfigure.java配置类
配置类中我们只要引入 FdfsClientConfig.class 即可完成配置;
package com.kaikeba.configure;
import com.github.tobato.fastdfs.FdfsClientConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* FastDFS 配置类
*/
@Configuration
@Import(FdfsClientConfig.class)
public class FastDFSConfigure {
}
2.3.3.4 FileController.java
文件的操作Controller
package com.kaikeba.controller;
import com.kaikeba.util.FileDfsUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
/**
* 文件上传
*/
@RestController
@RequestMapping("/fu")
public class FileController {
@Resource
private FileDfsUtil fileDfsUtil;
@RequestMapping(value = "/upload", method = RequestMethod.POST, headers = "content-type=multipart/form-data")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile multipartFile) {
try {
String upload = fileDfsUtil.upload(multipartFile);
if(StringUtils.isEmpty(upload)) {
return ResponseEntity.ok("上传失败");
}
return ResponseEntity.ok(upload);
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.ok("服务异常");
}
}
@RequestMapping(value = "/delete", method = RequestMethod.GET)
public ResponseEntity<String> deleteFile(String path) {
fileDfsUtil.deleteFile(path);
return ResponseEntity.ok("SUCCESS") ;
}
}
2.3.4 测试结果
我们通过postman进行测试
结果
浏览器访问:
storage 容器:
2.3.5 自己总结
- FastDFS是为了解决分布式的文件上传的问题,产生的一个分布式文件系统;
- 他会经过两次nginx的负载均衡和转发,第一次是我们发送请求到调度服务器(Tracker server), 此时会帮我们选择一个文件存储服务器(Storage server);当调度服务器(Tracker server)的请求转发到存储服务器(Storage server)时,会产生二次的负载均衡,选择实际的文件存储的位置;
- storage server 会根据我们上传的文件信息,计算文件存储的位置,也就是文件在二级目录中的位置