共计 13102 个字符,预计需要花费 33 分钟才能阅读完成。
theme: hydrogen
highlight: agate
头图来自:https://www.zcool.com.cn/u/14…
FastDFS
一、FastDFS 根本理解
1、FastDFS 概念
FastDFS 是一个开源的 轻量级分布式 文件系统,它对文件进行治理,性能包含:文件存储、文件同步、文件拜访(文件上传、文件下载)等,解决了 大容量存储 和负载平衡 的问题。特地适宜以文件为载体的在线服务,如相册网站、视频网站等等。
FastDFS 为互联网量身定制,充分考虑了冗余备份、负载平衡、在线扩容等机制,并重视 高可用 、 高性能 等指标,应用 FastDFS 很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
2、FastDFS 的作用
适宜用来存储用户图片、视频、文档等文件。
出于简洁思考,FastDFS 没有对文件做分块存储,因而不太适宜分布式计算场景。
3、FastDFS 的优缺点
长处:
- 适宜中小文件存储(倡议范畴:
4KB<file_size<500MB
) - 主备 Tracker 服务,加强零碎的可用性
- 零碎不须要反对 POSIX,这样的话就升高了零碎的复杂度,使得解决的速度会更高
- 反对主从文件,反对自定义扩展名
- 反对在线扩容机制,加强了零碎的可扩展性
- 实现了软 RAID,加强了零碎的并发解决能力和数据容错恢复能力
毛病:
- 间接按文件存储,可间接读取文件内容,不足文件安全性
- 通过 API 下载,存在单点的性能瓶颈
- 不反对断点续传,对大文件将是噩梦
- 同步机制不反对文件正确性校验,升高了零碎的可用性
- 不反对 POSIX 通用接口拜访,通用性比拟的低
- 对跨公网的文件同步,存在着比拟大的提早,须要利用做相应的容错策略
4、相干概念
FastDFS 服务端有三个角色:跟踪服务器(tracker server)、存储服务器(storage server)和客户端(client)。
tracker server:跟踪服务器,次要做 调度工作 ,起 负载平衡 的作用。Tracker 是 FastDFS 的协调者,负责管理所有的 storage server 和 group,每个 storage 在启动后会连贯 Tracker,告知本人所属的 group 等信息,并放弃 周期性的心跳 ,tracker 依据 storage 的心跳信息,建设 group==>[storage server list] 的映射表。
storage server:存储服务器(又称:存储节点或数据服务器),文件和文件属性(meta data)都保留到存储服务器上。Storage server 间接利用 OS 的文件系统调用管理文件。
client:客户端,作为业务申请的发起方,通过专有接口,应用 TCP/IP 协定与 跟踪器服务器 或存储节点 进行数据交互。FastDFS 向使用者提供根本文件拜访接口,比方 upload、download、append、delete 等,以客户端库的形式提供给用户应用。
二、FastDFS 原理
1、FastDFS 零碎结构图
- FastDFS 分为 Tracker、Storage,其中 Storage 负责存储文件,Tracker 负责存储文件所在地址,次要作用是负载平衡和资源调度。
- Tracker、Storage 都能够实现集群部署,Tracker 的每个节点位置平等,而 Storage 能够分为多个组,每个组之间保留的文件是不同的,组外部分为多个成员,每个成员保留的内容是一样,组成员位置统一,没有主从概念。
- 应用 FastDFS 存储文件长处:能够应答互联网的海量文件存储,一旦文件较多,能够随时横向扩大,且集群的实现也使零碎不存在单点故障问题,用户不会因为服务器宕机而无法访问文件资源。
2、FastDFS 工作流程
文件上传
• 1. client 询问 tracker 上传到的 storage,不须要附加参数;
• 2. tracker 返回一台可用的 storage;
• 3. client 间接和 storage 通信实现文件上传。
文件下载
- client 询问 tracker 下载文件的 storage,参数为文件标识(组名和文件名);
- tracker 返回一台可用的 storage;
- client 间接和 storage 通信实现文件下载。
三、Linux 下部署 FastDFS
1、筹备工作
装置 gcc(编译时须要)
yum install -y gcc gcc-c++
装置 libevent(运行)
yum -y install libevent
创立文件夹并上传文件
mkdir -p /fileservice/fast
cd /fileservice/fast
留神:须要将相干文件上传值该文件夹中。
2、装置 libfastcommon
# 解压文件
tar -zxvf libfastcommon-1.0.35.tar.gz
# 进入目录
cd libfastcommon-1.0.35
# 编译
./make.sh
# 装置
./make.sh install
应用 make
指令
装置实现后
目录构造
3、装置 fastdfs
装置相干依赖
yum install perl pcre pcre-devel zlib zlib-devel openssl openssl-devel -y
装置 fastdfs
# 解压 fastdfs
tar zxvf fastdfs-5.11.tar.gz
# 进入目录
cd fastdfs-5.11
# 执行编译
./make.sh
# 装置
./make.sh install
装置胜利之后
查看 tracker 和 storage 的可执行脚本
ll /etc/init.d/ | grep fdfs
筹备配置文件
默认在 /etc/fdfs/ 上面
cd /etc/fdfs/
复制配置文件
cp client.conf.sample client.conf
cp storage.conf.sample storage.conf
cp storage_ids.conf.sample storage_ids.conf
cp tracker.conf.sample tracker.conf
创立批改 tracker 默认的存放数据和日志的须要的目录
mkdir -p /home/fastdfs/tracker
创立批改 storage 默认的存放数据和日志的须要的目录
mkdir -p /home/fastdfs/storage
配置和启动 tracker
配置
# 切换到 /etc/fdfs/ 下
cd /etc/fdfs/
# 批改 tracker.conf
vim tracker.conf
批改默认的 base_path
为新创建的文件,即/home/fastdfs/tracker
文件 tracker.conf
的批改内容
# 批改默认的 base_path,留神:根文件夹必须存在,子文件夹会主动创立
base_path=/home/fastdfs/tracker
启动 tracker
service fdfs_trackerd start
留神:服务启动胜利后,会在 base_path
指定的目录下生成 data
和logs
。
配置和启动 storage
配置
# 切换目录
cd /etc/fdfs/
# 批改 storage.conf
vim storage.conf
文件 tracker.conf
的配置
# 配置组名(可选)group_name=group1
# 配置 base_path,留神:根文件夹必须存在,子文件夹会主动创立
base_path=/home/fastdfs/storage
#store 寄存文件的地位(store_path),store_path0=/home/fastdfs/storage
# 留神:如果挂载多个磁盘,如下配置
# store_path1=.....
# store_path2=......
#。。。#配置 tracker 服务器:IP
tracker_server=192.168.10.100:22122
#如果有多个则配置多个 tracker
#tracker_server= xxx.xxx.xxx.xxx:22122
#。。。
启动 storage
service fdfs_storaged start
启动实现后进入 /home/fastdfs/storage/data
目录下,显示目录如下:
cd /home/fastdfs/storage/data/
4、应用 FastDFS 自带工具测试
配置
# 切换目录到
cd /etc/fdfs/
# 批改 client.conf
vim client.conf
批改根本门路和 tracker_server 如下
# storage 的根目录
base_path=/home/fastdfs/storage
# tracker 的 ip 和地址
tracker_server=192.168.10.100:22122
# 留神:若 tracker 有多个,能够配置多个
# tracker_server= ...
# tracker_server= ...
上传一张图片到 Linux 的根目录/root/
进行测试
/usr/bin/fdfs_upload_file /etc/fdfs/client.conf /root/image-20200708110242109.jpg
如此,示意 FastDFS 搭建胜利。
上图中文件的地址:http://192.168.10.100:80/group1/M00/00/00/wKgKZF8FdfiAIvwVAAETxoCDaLI533.jpg, 对应 storage 服务器上的 /home/fastdfs/storage/data/00/00/wKgKZF8FdfiAIvwVAAETxoCDaLI533.jpg 文件
留神:此时该图片无奈应用 http 拜访下载,能够应用内置 Web Server,也能够和其余 Web Server 配合应用
5、FastDFS 为什么要联合 Nginx?
通过 FastDFS 的 HTTP 服务器来提供 HTTP 服务。然而 FastDFS 的 HTTP 服务 无奈提供 负载平衡等高性能的服务,所以 FastDFS 的开发者—淘宝的架构师余庆为咱们提供了 Nginx 上应用的 FastDFS 模块(FastDFS 的 Nginx 模块)。详情。
6、装置 fastdfs-nginx-module
配置
# 解压 fastdfs-nginx-module
tar -zxvf fastdfs-nginx-module-1.20.tar.gz
# 切换目录
cd fastdfs-nginx-module-1.20/src
# 批改 config
vim config
批改内容
ngx_module_incs="/usr/include/fastdfs /usr/include/fastcommon/"
CORE_INCS="$CORE_INCS /usr/include/fastdfs /usr/include/fastcommon/"
拷贝配置 mod_fastdfs.conf
并批改
# 将 fastdfs-nginx-module/src 下的 mod_fastdfs.conf 拷贝至 /etc/fdfs/ 下
cp mod_fastdfs.conf /etc/fdfs/
# 批改 /etc/fdfs/mod_fastdfs.conf 的内容
vim /etc/fdfs/mod_fastdfs.conf
批改内容如下:
# 配置跟踪服务器 tracker 的 ip 和端口
tracker_server=192.168.10.100:22122
#url 中蕴含 group 名称
url_have_group_name=true
#指定文件存储门路(为配置的 store 门路)store_path0=/home/fdfs_storage
拷贝配置文件
cp http.conf mime.types /etc/fdfs/
7、nginx 的装置
配置
cd /fileservice/fast/
# 解压 nginx
tar -zxvf nginx-1.15.2.tar.gz
# 进入 nginx 解压的目录下
cd nginx-1.15.2/
# 退出模块命令配置
./configure --prefix=/opt/nginx --sbin-path=/usr/bin/nginx --add-module=/fileservice/fast/fastdfs-nginx-module-1.20/src
# 编译并装置
make && make install
# 批改 nginx 配置
cd /opt/nginx/conf
vim nginx.conf
批改 nginx 配置的内容
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
# root html;
# index index.html index.htm;
ngx_fastdfs_module;
}
启动 ngnix
cd /usr/bin/
# 启动
./nginx
四、Docker 搭建 FastDFS
1、拉取镜像并启动
# 启动 docker
systemctl start docker
# -v ${HOME}/fastdfs:/var/local/fdfs 是指:将 ${HOME}/fastdfs 这个目录挂载到容器里的 /var/local/fdfs 这个目录里。#所以上传的文件将被长久化到 ${HOME}/fastdfs/storage/data 里
#IP 前面是本人的服务器公网 ip 或者虚拟机 ip,#-e WEB_PORT=80 指定 nginx 默认端口
docker run -d --restart=always --privileged=true --net=host --name=fastdfs -e IP=192.168.10.100 -e WEB_PORT=80 -v ${HOME}/fastdfs:/var/local/fdfs registry.cn-beijing.aliyuncs.com/tianzuo/fastdfs
2、测试上传
# 进入容器
docker exec -it fastdfs /bin/bash
# 创立文件
echo "Hello FastDFS!">index.html
# 测试文件上传
fdfs_test /etc/fdfs/client.conf upload index.html
3、测试拜访
五、Java 实现文件上传
1、创立 Maven 我的项目
2、引入依赖
<!-- fastdfs 客户端 jar -->
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.0.0</version>
</dependency>
<!-- spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.25.RELEASE</version>
</dependency>
3、创立 fdfs_client.conf
配置文件
# 连贯超时工夫
connect_timeout=30
# 网络超时工夫
network_timeout=60
# tracker 根目录
base_path=/home/fastdfs/tracker
#改为本人服务器的 ip
tracker_server=192.168.10.100:22122
log_level=info
use_connection_pool = false
connection_pool_max_idle_time = 3600
load_fdfs_parameters_from_tracker=false
use_storage_id = false
storage_ids_filename = storage_ids.conf
http.tracker_server_port=80
4、编写测试类
public static void main(String[] args) throws Exception {
// 文件的门路
String testFilePath = "C:/Users/hxxiapgy/Desktop/fastdfs 架构.png";
// 获取 fdfs_client.conf 的
String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();
// 1. 加载配置文件
ClientGlobal.init(filePath);
// 2. 创立一个 TrackerClient 对象
TrackerClient trackerClient = new TrackerClient();
// 3. 应用 TrackerClient 对象创立连贯,取得一个 TrackerServer 对象。TrackerServer trackerServer = trackerClient.getConnection();
// 4. 申明 StorageServer
StorageServer storageServer = null;
// 5. 创立一个 StorageClient 对象,须要两个参数 TrackerServer 对象、StorageServer 的援用
StorageClient storageClient = new StorageClient(trackerServer, storageServer);
// 6. 通过 storangeClient 上传文件,返回组名和文件名
String[] strings = storageClient.upload_file(testFilePath, "png", null);
// 打印组名与文件名
for (String string : strings) {System.out.println(string);
}
System.out.println("文件上传胜利!");
}
六、SpringBoot 上传文件
1、形式一:
创立我的项目
引入依赖
<!-- fastdfs 依赖 -->
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.0.0</version>
</dependency>
<!-- lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
批改配置文件
fastdfs:
connect_timeout_in_seconds: 120
network_timeout_in_seconds: 120
charset: UTF-8
tracker_servers: 192.168.10.100:22122
编写UploadService
/**
* @description:文件上传
* @author: Hxxiapgy
* @date: 2020/7/9 20:02
*/
@Service
public class UploadService {@Value("${fastdfs.connect_timeout_in_seconds}")
private Integer connectTimeout;
@Value("${fastdfs.network_timeout_in_seconds}")
private Integer networkTimeout;
@Value("${fastdfs.charset}")
private String charset;
@Value("${fastdfs.tracker_servers}")
private String trackerServers;
public Map<String,Object> upload(MultipartFile multipartFile){
// 判断文件是否存在
if(multipartFile == null){throw new RuntimeException("文件不能为空!");
}
// 将文件上传到 fastdfs
String fileId = fdfsUpload(multipartFile);
// 判断 fileId 是否为空
if (fileId == null){throw new RuntimeException("文件上传失败!");
}
Map<String,Object> map = new HashMap<>();
map.put("code", 1);
map.put("msg", "文件上传胜利");
map.put("fileId", fileId);
return map;
}
/**
* 性能形容:
* @param
* @return {@link String}
* @author hxxiapgy
* @data 2020/7/9 20:08
*/
private String fdfsUpload(MultipartFile multipartFile){
// 1. 初始化 FastDFS 的环境
initFdfsConfid();
// 1. 创立 trackerClient 对象
TrackerClient trackerClient = new TrackerClient();
try {
// 2. 获取 trackerService
TrackerServer trackerServer = trackerClient.getConnection();
// 3. 申明 StorageService
StorageServer storageServer = null;
// 4. 获取 StorageClient
StorageClient1 storageClient1 = new StorageClient1(trackerServer,storageServer);
// 5. 获取文件后缀
String originalFilename = multipartFile.getOriginalFilename();
// 文件异样
if (StringUtils.isBlank(originalFilename)) {throw new RuntimeException("文件读取异样!");
}
String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
// 6. 通过 StorageService 上传文件,返回组名和文件名
String fileId = storageClient1.upload_file1(multipartFile.getBytes(), extName , null);
return fileId;
} catch (Exception e) {e.printStackTrace();
return null;
}
}
/**
* 性能形容: 加载 fastDFS 环境
* @param
* @return
* @author hxxiapgy
* @data 2020/7/9 20:35
*/
private void initFdfsConfid() {
try {
// 设置连贯超时工夫
ClientGlobal.setG_connect_timeout(connectTimeout);
// 设置网络超时工夫
ClientGlobal.setG_network_timeout(networkTimeout);
// 设置字符编码
ClientGlobal.setG_charset(charset);
// 设置 trackerService
ClientGlobal.initByTrackers(trackerServers);
} catch (Exception e) {e.printStackTrace();
}
}
}
编写UploadController
/**
* @description:文件上传
* @author: Hxxiapgy
* @date: 2020/7/9 20:40
*/
@Controller
public class UploadController {
@Resource
private UploadService uploadService;
@PostMapping("/upload")
@ResponseBody
public Map<String,Object> upload(MultipartFile multipartFile){Map<String, Object> map = uploadService.upload(multipartFile);
return map;
}
@RequestMapping("/index")
public String index(){return "index.html";}
}
编写测试入口index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> 测试文件上传 </title>
</head>
<body>
<h3> 文件上传 </h3>
<hr/>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="multipartFile"/>
<input type="submit" value="上传"/>
</form>
</body>
</html>
测试
形式二
创立我的项目
引入依赖
<!-- fastdfs client -->
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>1.26.7</version>
</dependency>
批改配置文件
fdfs:
so-timeout: 2500 # 读取工夫
connect-timeout: 600 # 连贯超时工夫
thumb-image: # 缩略图
width: 100
height: 100
tracker-list: # tracker 服务配置地址列表
- 192.168.10.100:22122
upload:
base-url: http://192.168.10.100/
allow-types:
- image/jpeg
- image/png
- image/bmp
- image/gif
编写 fastdfs 的属性类
/**
* @description:配置 fastdfs 的属性类
* @author: Hxxiapgy
* @date: 2020/7/9 21:58
*/
@ConfigurationProperties(prefix = "upload")
@Data
public class UploadProperties {
private String baseUrl;
private List<String> allowTypes;
public String getBaseUrl() {return baseUrl;}
public void setBaseUrl(String baseUrl) {this.baseUrl = baseUrl;}
public List<String> getAllowTypes() {return allowTypes;}
public void setAllowTypes(List<String> allowTypes) {this.allowTypes = allowTypes;}
}
编写UploadService
/**
* @description:文件上传
* @author: Hxxiapgy
* @date: 2020/7/9 22:01
*/
@Service
@EnableConfigurationProperties(UploadProperties.class)
public class UploadService {private Log log= LogFactory.getLog(UploadService.class);
@Resource
private FastFileStorageClient storageClient;
@Resource
private UploadProperties uploadProperties;
/**
* 性能形容: 文件上传
* @param multipartFile 要上传的文件
* @return {@link String} 返回文件门路
* @author hxxiapgy
* @data 2020/7/9 22:05
*/
public String upload(MultipartFile multipartFile){
// 1. 校验上传文件是否为空
if (multipartFile == null){log.error("文件不存在");
throw new RuntimeException("文件为空!");
}
// 1. 校验文件类型
// 获取上传文件的类型
String contentType = multipartFile.getContentType();
if (!uploadProperties.getAllowTypes().contains(contentType)){log.info("文件类型不反对!");
throw new RuntimeException("文件类型不反对!");
}
try {
// 2. 校验文件内容
// 读取文件
BufferedImage bufferedImage = ImageIO.read(multipartFile.getInputStream());
if (bufferedImage == null || bufferedImage.getWidth() == 0 || bufferedImage.getHeight() == 0){log.error("上传文件有问题");
throw new RuntimeException("上传文件有问题!");
}
} catch (IOException e) {log.error("测验文件内容失败...{}",e);
throw new RuntimeException("测验文件内容失败...{}" + e.getMessage());
}
// 3. 获取扩展名
String extension = StringUtils.substringAfterLast(multipartFile.getOriginalFilename(),".");
try {
// 4. 上传文件
StorePath storePath = storageClient.uploadFile(multipartFile.getInputStream(), multipartFile.getSize(), extension, null);
// 5. 返回门路
return uploadProperties.getBaseUrl() + storePath.getFullPath();
} catch (Exception e) {log.error("文件上传失败!...{}",e);
throw new RuntimeException("文件上传失败...{}" + e.getMessage());
}
}
}
编写UploadController
/**
* @description:文件上传
* @author: Hxxiapgy
* @date: 2020/7/9 22:25
*/
@Controller
public class UploadController {
@Resource
private UploadService uploadService;
/**
* 性能形容: 文件上传
* @param multipartFile 要删除的文件
* @return {@link Map< String, Object>}
* @author hxxiapgy
* @data 2020/7/9 22:27
*/
@PostMapping("/upload")
@ResponseBody
public Map<String,Object> upload(MultipartFile multipartFile){String filePath = uploadService.upload(multipartFile);
Map<String,Object> map = new HashMap<>();
map.put("code","200");
map.put("msg", "文件上传胜利");
map.put("filePath", filePath);
return map;
}
}
编写测试入口
同办法一。
测试