FastDFS基本介绍
FastDFS 系统三个角色
Tracker Server
Tracker Server: 跟踪服务器,主要做调度工作,起到均衡的作用;负责管理所有的 storage server
和 group,每个 storage 在启动后会连接 Tracker,告知自己所属 group 等信息,并保持周期性心跳。
Storage Server
Storage Server:存储服务器,主要提供容量和备份服务;以 group 为单位,每个 group 内可以有多台 storage server,数据互为备份。
Client
Client:客户端,上传下载数据的服务器,也就是我们自己的项目所部署在的服务器。
fastDFS优点:1.高可靠性:无单点故障 2.高吞吐性:只要 Group 足够多,数据流量将足够分散。
搭建fastDFS(阿里服务器)
安装 libfastcommon 和 FastDFS
一、安装 libfastcommon
这里是通过wget下载:
wget https://github.com/happyfish100/libfastcommon/archive/V1.0.7.tar.gz
解压 libfastcommon,命令:
tar -zxvf V1.0.7.tar.gz
移动解压目录到/usr/local下:
mv libfastcommon-1.0.7 /usr/local/
进入libfastcommon-1.0.7目录,命令:
cd /usr/local/libfastcommon-1.0.7
编译
./make.sh
安装:
./make.sh install
安装 libfastcommon 成功:
配置
配置 Tracker 服务
上述安装成功后,在/etc/目录下会有一个fdfs的目录,进入它。会看到三个.sample后缀的文件,这是作者给我们的示例文件,我们需要把其中的tracker.conf.sample文件改为tracker.conf配置文件并修改它。参照以下命令:
cd /etc/fdfs/
cp tracker.conf.sample tracker.conf
vim tracker.conf
// 编辑tracker.conf文件,找到你需要修改的两个参数就可以了,我这里不做修改,用默认的
# the base path to store data and log files // 数据和日志的存放目录
base_path=/home/yuqing/fastdfs
# HTTP port on this tracker server // http服务端口
http.server_port=8070
修改完成后我们需要建立tracker的工作目录,不然启动报错(上面base_path配置的目录)
mkdir -p /home/yuqing/fastdfs
启动tracker服务:
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf start
查看监听:
ps -ef|grep fdfs
#或
netstat -lnpt|grep fdfs
Tracker服务安装成功,启动成功:
pat(disk or mount point) count, default value is 1 // storage在存储文件时支持多路径,默认只设置一个(下面store_path0配置的数量个数)
store_path_count=1
store_path#, based 0, if store_path0 not exists, it’s value is base_path // 配置多个store_path路径,从0开始,如果store_path0不存在,则base_path必须存在 the paths must be exist // 此处可以配置多个路径,如:store_path0=xx, store_path1=xx,store_path2=xx
// 多个项目的文件按照文件夹分割,通过/usr/bin/fdfs_upload_file[storage_ip:port] [store_path_index] 选择storage服务器和store_path角标上传到相应的项目下
store_path0=/home/yuqing/fastdfs tracker_server can ocur more than once, and tracker_server format is // 设置tracker_server换成自己的IP端口 “host:port”, host can be hostname or ip address tracker_server=192.168.2.121:22122 修改完成后我们需要建立tracker的工作目录,不然启动报错(上面base_path配置的目录和store_path0配置的目录)mkdir -p /home/yuqing/fastdfs
启动storage服务:
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf start
查看监听:
ps -ef|grep fdfs 或 netstat -lnpt|grep fdfs
22122 和 23000端口都在监听:(/home/yuqing/fastdfs文件夹下看的话,会出现一大堆文件夹)
到这里,我们安装配置并启动了 Tracker 和 Storage 服务,也没有报错了。那他俩是不是在通信呢?我们可以监视一下:
/usr/bin/fdfs_monitor /etc/fdfs/storage.conf
红线处ACTIVE成功:
配置 fdfs_upload_file 上传文件
依然是进入/etc/fdfs的目录操作,首先进入它。会看到三个.sample后缀的文件,我们需要把其中的client.conf.sample文件改为client.conf配置文件并修改它。参照以下命令:
cd /etc/fdfs/
cp client.conf.sample client.conf
vim client.conf
进行修改:
# the base path to store log files // 日志的存放目录
base_path=/home/yuqing/fastdfs
# tracker_server can ocur more than once, and tracker_server format is // tracker_server服务的地址和端口号
# "host:port", host can be hostname or ip address
tracker_server=192.168.2.121:22122
修改完成后我们就可以通过/usr/bin/fdfs_upload_file命令上传文件了,/usr/bin/fdfs_upload_file 命令用法:
[root@localhost fdfs]# /usr/bin/fdfs_upload_file
Usage: /usr/bin/fdfs_upload_file <config_file> <local_filename> [storage_ip:port] [store_path_index]
// 命令 配置文件 上传的文件 storage_ip和端口 store_path角标
上传成功:
// 配置tracker.conf这个配置文件是否生效,因为在启动fastdfs服务端进程时需要指定配置文件,所以需要使次配置文件生效。false是生效,true是屏蔽。
disabled=false
// 程序的监听地址,如果不设定则监听所有地址
bind_addr=
// tracker监听的端口
port=22122
// 链接超时设定
connect_timeout=30
// tracker在通过网络发送接收数据的超时时间
network_timeout=60
// 数据和日志的存放地点
base_path=/opt/fdfs
// 服务所支持的最大链接数
max_connections=256
// 工作线程数一般为cpu个数
work_threads=4
// 在存储文件时选择group的策略,0:轮训策略 1:指定某一个组 2:负载均衡,选择空闲空间最大的group
store_lookup=2
// 如果上面的store_lookup选择了1,则这里需要指定一个group
// store_group=group2
// 在group中的哪台storage做主storage,当一个文件上传到主storage后,就由这台机器同步文件到group内的其他storage上,0:轮训策略 1:根据ip地址排序,第一个 2:根据优先级排序,第一个
store_server=0
// 选择那个storage作为主下载服务器,0:轮训策略 1:主上传storage作为主下载服务器
download_server=0
// 选择文件上传到storage中的哪个(目录/挂载点),storage可以有多个存放文件的base path 0:轮训策略 2:负载均衡,选择空闲空间最大的
store_path=0
// 系统预留空间,当一个group中的任何storage的剩余空间小于定义的值,整个group就不能上传文件了
reserved_storage_space = 4GB
// 日志信息级别
log_level=info
// 进程以那个用户/用户组运行,不指定默认是当前用户
run_by_group=
run_by_user=
// 允许那些机器连接tracker默认是所有机器
allow_hosts=*
// 设置日志信息刷新到disk的频率,默认10s
sync_log_buff_interval = 10
// 检测storage服务器的间隔时间,storage定期主动向tracker发送心跳,如果在指定的时间没收到信号,tracker人为storage故障,默认120s
check_active_interval = 120
// 线程栈的大小,最小64K
thread_stack_size = 64KB
// storage的ip改变后服务端是否自动调整,storage进程重启时才自动调整
storage_ip_changed_auto_adjust = true
// storage之间同步文件的最大延迟,默认1天
storage_sync_file_max_delay = 86400
// 同步一个文件所花费的最大时间
storage_sync_file_max_time = 300
// 是否用一个trunk文件存储多个小文件
use_trunk_file = false
// 最小的solt大小,应该小于4KB,默认256bytes
slot_min_size = 256
// 最大的solt大小,如果上传的文件小于默认值,则上传文件被放入trunk文件中
slot_max_size = 16MB
// trunk文件的默认大小,应该大于4M
trunk_file_size = 64MB
// http服务是否生效,默认不生效
http.disabled=false
// http服务端口
http.server_port=8080
// 检测storage上http服务的时间间隔,<=0表示不检测
http.check_alive_interval=30
// 检测storage上http服务时所用请求的类型,tcp只检测是否可以连接,http必须返回200
http.check_alive_type=tcp
// 通过url检测storage http服务状态
http.check_alive_uri=/status.html
// if need find content type from file extension name
http.need_find_content_type=true
// 用include包含进http的其他设置
// include http.conf
## storage.conf配置文件
```java
storage.conf配置文件分析:
// 同tracker.conf
disabled=false
// 这个storage服务器属于那个group
group_name=group1
// 同tracker.conf
bind_addr=
// 连接其他服务器时是否绑定地址,bind_addr配置时本参数才有效
client_bind=true
// 同tracker.conf
port=23000
connect_timeout=30
network_timeout=60
// 主动向tracker发送心跳检测的时间间隔
heart_beat_interval=30
// 主动向tracker发送磁盘使用率的时间间隔
stat_report_interval=60
// 同tracker.conf
base_path=/opt/fdfs
max_connections=256
// 接收/发送数据的buff大小,必须大于8KB
buff_size = 256KB
// 同tracker.conf
work_threads=4
// 磁盘IO是否读写分离
disk_rw_separated = true
// 是否直接读写文件,默认关闭
disk_rw_direct = false
// 混合读写时的读写线程数
disk_reader_threads = 1
disk_writer_threads = 1
// 同步文件时如果binlog没有要同步的文件,则延迟多少毫秒后重新读取,0表示不延迟
sync_wait_msec=50
// 同步完一个文件后间隔多少毫秒同步下一个文件,0表示不休息直接同步
sync_interval=0
// 表示这段时间内同步文件
sync_start_time=00:00
sync_end_time=23:59
// 同步完多少文件后写mark标记
write_mark_file_freq=500
// storage在存储文件时支持多路径,默认只设置一个
store_path_count=1
// 配置多个store_path路径,从0开始,如果store_path0不存在,则base_path必须存在
store_path0=/opt/fdfs
// store_path1=/opt/fastdfs2
// subdir_count * subdir_count个目录会在store_path下创建,采用两级存储
subdir_count_per_path=256
// 设置tracker_server
tracker_server=x.x.x.x:22122
// 同tracker.conf
log_level=info
run_by_group=
run_by_user=
allow_hosts=*
// 文件在数据目录下的存放策略,0:轮训 1:随机
file_distribute_path_mode=0
// 当问及是轮训存放时,一个目录下可存放的文件数目
file_distribute_rotate_count=100
// 写入多少字节后就开始同步,0表示不同步
fsync_after_written_bytes=0
// 刷新日志信息到disk的间隔
sync_log_buff_interval=10
// 同步storage的状态信息到disk的间隔
sync_stat_file_interval=300
// 线程栈大小
thread_stack_size=512KB
// 设置文件上传服务器的优先级,值越小越高
upload_priority=10
// 是否检测文件重复存在,1:检测 0:不检测
check_file_duplicate=0
// 当check_file_duplicate设置为1时,次值必须设置
key_namespace=FastDFS
// 与FastDHT建立连接的方式 0:短连接 1:长连接
keep_alive=0
// 同tracker.conf
http.disabled=false
http.domain_name=
http.server_port=8888
http.trunk_size=256KB
http.need_find_content_type=true
// include http.conf
nginx简单整合fastDFS
这里小编只是简单的整合,只是为了访问方便,
配置nginx的配置文件,nginx.conf文件:
server {
listen 89;#监听89端口
server_name localhost;#ip
location /group1/M00 {## 当访问89端口并且/group1/M00开头的请求,都有映射到fastDFS的存储数据区
alias /usr/local/fastdfs-5.05/files_music/data;# 该地址就是文件存储的数据文件夹
}
}
FastDFS项目中实现文件上传下载
了解FastDFS的工作流程
一、pom文件中加jar包
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
二、配置FastDFS基本信息:
在项目的resource目录下面新建fdfs_client.conf配置文件,配置信息如下:
connect_timeout = 60
network_timeout = 60
charset = UTF-8
http.tracker_http_port = 8070//搭建时的fastDFS的根据配置
tracker_server = 192.168.200.128:22122
二、工具类
封装文件的实体类
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 String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public byte[] getContent() {
return content;
}
public void setContent(byte[] content) {
this.content = content;
}
public String getExt() {
return ext;
}
public void setExt(String ext) {
this.ext = ext;
}
public String getMd5() {
return md5;
}
public void setMd5(String md5) {
this.md5 = md5;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
上传工具类(上传、下载和删除等等操作)
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @author Administrator
* @date 2020/12/9 10:58
**/
public class FastDFSClient {
private static org.slf4j.Logger logger = LoggerFactory.getLogger(FastDFSClient.class);
/***
* 初始化加载FastDFS的TrackerServer配置
*/
static {
try {
String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();
ClientGlobal.init(filePath);
} catch (Exception e) {
logger.error("FastDFS Client Init Fail!",e);
}
}
/***
* 文件上传
* @param file
* @return 1.文件的组名 2.文件的路径信息
*/
public static String[] upload(FastDFSFile file) {
logger.info("File Name: " + file.getName() + "File Length:" + file.getContent().length);
//获取文件的作者
NameValuePair[] meta_list = new NameValuePair[1];
meta_list[0] = new NameValuePair("author", file.getAuthor());
long startTime = System.currentTimeMillis();
//接收返回数据
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) {
logger.error("Exception when uploadind the file:" + file.getName(), e);
}
if (uploadResults == null && storageClient!=null) {
logger.error("upload file fail, error code:" + storageClient.getErrorCode());
}
logger.info("upload_file time used:" + (System.currentTimeMillis() - startTime) + " ms");
if (uploadResults == null && storageClient!=null) {
logger.error("upload file fail, error code:" + storageClient.getErrorCode());
}
//获取组名
String groupName = uploadResults[0];
//获取文件存储路径
String remoteFileName = uploadResults[1];
logger.info("upload file successfully!!!" + "group_name:" + groupName + ", remoteFileName:" + " " + remoteFileName);
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) {
logger.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) {
logger.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);
logger.info("delete file successfully!!!" + i);
}
/***
* 获取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()+"/";
return "http://"+getTrackerServer().getInetSocketAddress().getHostString()+":89"+"/";//89为nginx监听的端口,有nginx代理监听映射到文件,进行访问
}
/***
* 获取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;
}
}
测试前端页面,提交表上
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="上传">
</form>
测试后端控制台,上传文件
@Controller
public class UploadController {
private static Logger logger = LoggerFactory.getLogger(UploadController.class);
@PostMapping("/upload")
public String singleFileUpload(@RequestParam("file") MultipartFile file,
RedirectAttributes redirectAttributes) {
if (file.isEmpty()) {
redirectAttributes.addFlashAttribute("message", "Please select a file to upload");
return "redirect:uploadStatus";
}
try {
// Get the file and save it somewhere
String path=saveFile(file);
redirectAttributes.addFlashAttribute("message",
"You successfully uploaded '" + file.getOriginalFilename() + "'");
redirectAttributes.addFlashAttribute("path",
"file path url '" + path + "'");
} catch (Exception e) {
logger.error("upload file failed",e);
}
return "上传成功";
}
/*
* fastdfs上传到服务器
*/
public String saveFile(MultipartFile multipartFile) throws IOException {
String[] fileAbsolutePath={};
String fileName=multipartFile.getOriginalFilename();
String ext = fileName.substring(fileName.lastIndexOf(".") + 1);
byte[] file_buff = null;
InputStream inputStream=multipartFile.getInputStream();
if(inputStream!=null){
int len1 = inputStream.available();
file_buff = new byte[len1];
inputStream.read(file_buff);
}
inputStream.close();
FastDFSFile file = new FastDFSFile(fileName, file_buff, ext);
try {
fileAbsolutePath = FastDFSClient.upload(file); //upload to fastdfs
} catch (Exception e) {
logger.error("upload file Exception!",e);
}
if (fileAbsolutePath==null) {
logger.error("upload file failed,please upload again!");
}
String path=FastDFSClient.getTrackerUrl()+fileAbsolutePath[0]+ "/"+fileAbsolutePath[1];
logger.info("文件上传成功,地址:"+path);
return path;
}
}
访问:
访问成功:
结束语
至此我们的文件存储系统就搭建完成啦。