FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。
FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
FastDFS 架构包括 Tracker server 和 Storage server。客户端请求 Tracker server 进行文件上传、下载,通过Tracker server 调度最终由 Storage server 完成文件上传和下载。
Tracker server 作用是负载均衡和调度,通过 Tracker server 在文件上传时可以根据一些策略找到Storage server 提供文件上传服务。可以将 tracker 称为追踪服务器或调度服务器。Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上,Storageserver 没有实现自己的文件系统而是利用操作系统的文件系统来管理文件。可以将storage称为存储服务器。
首先是,tracker和storage之间,有心跳机制,定时发送信息到tracker, racker根据storage的心跳信息,建立group==>[storage server list]的映射表
客户端Client通过访问Tracker,tracker会通过负载算法,计算出合适的storage,返回给客户端(包含IP和port信息)
客户端Client通过返回的storage信息,访问storage服务器,进行文件的上传或下载
storage存储服务器保存文件之后,会将文件 ID 返回给客户端,此文件 ID 用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名
返回给客户端的文件名,类似:group1/M00/00/00/wKggZV6ImSeAHw8-AAQKFl3eZcU602.jpg
组名: 文件上传后所在的 storage 组名称,在文件上传成功后有storage 服务器返回,需要客户端自行保存
虚拟磁盘路径 :storage 配置的虚拟路径,与磁盘选项store_path*对应。如果配置了
store_path0 则是 M00,如果配置了 store_path1 则是 M01,以此类推
数据两级目录 :storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据
文件。
文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储
服务器 IP 地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息
结论:Tracker只负责调度(老板级别),Storage负责文件的操作(员工)
拉去镜像
docker pull morunchang/fastdfs
运行docker
docker run -d --name tracker --net=host morunchang/fastdfs sh tracker.sh
运行storage
docker run -d --name storage --net=host -e TRACKER_IP=192.168.32.101:22122 -e GROUP_NAME=group1 morunchang/fastdfs sh storage.sh
#查看docker运行中的容器
docker ps
#进入到运行的storage容器实例中
docker exec -it storage bash
#修改Nginx配置文件,添加下面配置,
vi /data/nginx/conf/nginx.conf
location /group1/M00 {
proxy_next_upstream http_502 http_504 error timeout invalid_header;
proxy_cache http-cache;
proxy_cache_valid 200 304 12h;
proxy_cache_key $uri$is_args$args;
proxy_pass http://fdfs_group1;
expires 30d;
}
#退出容器
exit
#重启storage容器
docker restart storage
可能存在问题
由于每个人的服务器设置不同,可能是端口没有开放,或者防火墙原因导致访问不到,可以开放端口或者关闭防火墙
开放端口:
firewall-cmd --zone=public --add-port=8080/tcp --permanent
firewall-cmd --zone=public --add-port=22122/tcp --permanent
firewall-cmd --zone=public --add-port=23000/tcp --permanent
firewall-cmd --complete-reload
firewall-cmd–zone=public --list-all
查看防火墙状态:firewall-cmd --state
关闭防火墙:systemctl stop firewalld.service
禁止防火墙开机启动:systemctl disable firewalld.service
<dependency>
<groupId>net.oschina.zcx7878groupId>
<artifactId>fastdfs-client-javaartifactId>
<version>1.27.0.0version>
dependency>
connect_timeout = 60
network_timeout = 60
charset = UTF-8
http.tracker_http_port = 8080
#配置的tracker的服务器的IP和port
tracker_server=192.168.32.101:22122
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
*/
public static String[] upload(FastDFSFile file) {
//获取文件的作者
NameValuePair[] meta_list = new NameValuePair[1];
meta_list[0] = new NameValuePair("author", file.getAuthor());
//接收返回数据
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());
}
//获取组名
String groupName = uploadResults[0];
//获取文件存储路径
String remoteFileName = uploadResults[1];
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);
}
/***
* 获取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()+":"+ClientGlobal.getG_tracker_http_port()+"/";
}
/***
* 获取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;
}
}
流程总结:
- ClientGlobal.init(conf); //加载配置文件,创建上下文环境
- trackerClient = new TrackerClient();//创建访问tracker的客户端
- trackerServer = trackerClient.getConnection();//通过客户端获取tracker服务
- storageClient = new StorageClient1(trackerServer, null);//通过tracker服务获取访问storage服务的客户端
- 对文件的上传,下载,删除,都是通过storageClient进行的,
@RestController
@RequestMapping("/file")
public class FileController {
@PostMapping("/upload")
public Result uploadFile(MultipartFile file){
try{
//判断文件是否存在
if (file == null){
throw new RuntimeException("文件不存在");
}
//获取文件的完整名称
String originalFilename = file.getOriginalFilename();
if (StringUtils.isEmpty(originalFilename)){
throw new RuntimeException("文件不存在");
}
//获取文件的扩展名称 abc.jpg jpg
String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
//获取文件内容
byte[] content = file.getBytes();
//创建文件上传的封装实体类
FastDFSFile fastDFSFile = new FastDFSFile(originalFilename,content,extName);
//基于工具类进行文件上传,并接受返回参数 String[]
String[] uploadResult = FastDFSClient.upload(fastDFSFile);
//封装返回结果
String url = FastDFSClient.getTrackerUrl()+uploadResult[0]+"/"+uploadResult[1];
return new Result(true,StatusCode.OK,"文件上传成功",url);
}catch (Exception e){
e.printStackTrace();
}
return new Result(false, StatusCode.ERROR,"文件上传失败");
}
}
#进入storage容器实例中
docker exec -it storage bash
#查看下storage的配置文件
cat etc/fdfs/storage.conf
=====================
#组名
group_name=group1
#storage端口
port=23000
# default value is 30s 连接超时时间
connect_timeout=30
# default value is 30s 网络请求时间
network_timeout=60
# heart beat interval in seconds 心跳机制时间
heart_beat_interval=30
# the base path to store data and log files 文件存储基本目录
base_path=/data/fast_data
# more max_connections means more memory will be used 最大连接数,并发量
max_connections=256
# since V2.00 线程数,一般和CPU保持一致
work_threads=4
# must > 0, default value is 200ms 同步等待时间,同步机制
sync_wait_msec=50
# the paths must be exist 第一个虚拟路径 /data/fast_data/data
store_path0=/data/fast_data
# "host:port", host can be hostname or ip address tracker地址
tracker_server=192.168.32.101:22122
=====================
注意:store_path0=/data/fast_data这个的配置对应上面提到的虚拟磁盘路径,M00就是该store_path0/data目录,返回的文件存储位置,与之对应
对比着上面截图上的返回的路径信息group1/M00/00/00/wKggZV6ImSeAHw8-AAQKFl3eZcU602.jpg,查看文件位置,
M00对应路径/data/fast_data/data
文件路径也就是:group1/data/fast_data/data/00/00/wKggZV6ImSeAHw8-AAQKFl3eZcU602.jpg
备注:由于我配置的storage是单机的,只有一个组group1,如果是多个的,进入到相应的group即可