知识概念
分布式文件系统
通过计算机网络将各个物理存储资源连接起来。通过分布式文件系统,将网络上任意资源以逻辑上的树形结构展现,让用户访问网络上的共享文件更见简便。
文件存储的变迁:
- 直连存储:直接连接与存储,扩展性、灵活性差。如Tomcat、nginx。
- 中心化存储:网络互联。
- 分布式存储:资源存储在多个服务器,这个存储资源构成一个虚拟的存储设备。
常见的DFS
- FastDFS :开源的轻量级分布式文件系统;
- HDFS : Hadoop 子项目, Hadoop 的存储系统。
- Taobao FileSystem :高扩展、高可用、高性能、面向互联网服务的分布式文件系统,针对海量非结构化数据,构建在普通linux机器集群上,提供高可靠、高并发的存储访问。
- 为淘宝提供海量小文件存储,通常不超1M。
- 采用HA架构和平滑扩容。
HA架构:通过设计,减少系统不可用的时间。通过 冗余(集群) + 自动故障转移来实现。
- GridFS:mongodb内置功能。文件分成两份:索引和文件内容。它们存储在集合中,文件内容等分成若干块存储在文档中。一般以4M作为分块存储单位。
- Google File System :非开源。
- MogileFS : 由Six Apart开发,广泛应用在 包括LiveJournal等web2.0站点上 。
文件系统 | FastDFS | HDFS | TFS | MogileFS |
---|---|---|---|---|
数据存储 方式 | 文件/块 | 文件 | 文件 | 文件 |
集群通讯 协议 | 私有协议 | 私有协议 | 私有协议 | Http |
扩容 | 支持 | 支持 | 支持 | 支持 |
冗余备份 | 支持 | 支持 | 支持 | 不 支持 |
单点故障 | 不存在 | 存在 | 存在 | 存在 |
跨集群同 步 | 部分支持 | 不支持 | 支持 | 不支持 |
开发语言 | C | Java | C++ | Perl |
适合类型 | 4KB - 500MB | 大文件 | 所有文件 | 海量小图片 |
复杂度 | 简单 | 简单 | 复杂 | 复杂 |
易用性 | 安装简单,社区 活跃 | 安装简单,文档 专业 | 安装复杂,文档 较少 | 安装复杂,文档 较少 |
研发团队 | 国内开发者-余庆 | Apache | Alibaba | Danga Interactive |
FUSE | 不支持 | 支持 | 不支持 | 支持 |
POSIX | 不支持 | 支持 | 无资料 | 不支持 |
常见DFS提供商
- 阿里OSS
- 七牛云存储
- 百度云存储
FastDFS简介
FastDFS是用C语言编写的一款开源的分布式文件系统,它是由淘宝资深架构师余庆编写并开源。 FastDFS专为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高 性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
功能概况
文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡 的问题。特别适合以中小文件( 建议范围: 4KB 到 500MB ) 为载体的在线服务,如:相册网站、视 频网站等等
架构
FastDFS架构包括 Tracker Server和Storage Server 。 Tracker Server负责处理客户端的文件上传、下载请求,通过调度,追踪定位 Storage Server 目标,最后由其完成文件下载、上传。
Tracker Server
作用是负载均衡和调度。在FastDFS集群中,可以有多台Tracker Server 同时提供服务,不存在单点故障。
Storage Server
处理文件存储,使用操作系统的文件系统管理文件。
采用分组存储方式,集群由多个一个或多个分组组成,集群容量是集群内所有组的总和,不同组之间不会互相通信,同组内相互连接同步文件(所以组的容量由组内最小容量的节点决定)。
使用分组的方式,比较灵活易扩展。客户端可以直接指定上传的组,也可以由Tracker进行调度;当访问压力大的时候,也可以通过增加组内存储服务器来提高服务能力;当容量不足时,可以增加分组,提高容量。
Storage 状态收集
Storage会连接所有Tracker,然后汇报自己的状态。,包括磁盘空间、文件同步情况、文件上传下载次数等统计信息。
文件上传流程
客户端拿到file_id之后,存储起来,后续通过file_id拿到文件。
file_id相当于一个索引,其文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。
例如:group1/M00/00/00/wKjIgGNslmOAf5VSAACQjdb7ANw5904822
- 组名:文件上传后所在的分组;
- 虚拟磁盘路径:通过store_path指定的 Storage 虚拟路径。 store_path0(相当于一个组内节点) 是M00 ,了store_path1则是M01,以此类推。
- 数据两级目录 : 在 虚拟磁盘路径下创建的两级目录,用于存储数据文件。
- 文件名:由存储服务器根据特定信息(源存储服务器IP地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息)生成。
文件下载流程
- 通过
组名
, tracker 能快速锁定所在分组,然后 tracker 会选择一个合适的存储节点,并把节点信息返回给客户端。 - 客户端访问存储服务器的时候,存储服务器可以通过
文件虚拟路径
、数据两级目录
来快速定位文件,并根据文件名找到访问的文件。
准备环境
开几个ubuntu18虚拟机:
节点 | IP | 域名 | 端口开放 |
---|---|---|---|
tracker1 | 192.168.204.167 | tracker1 | 22122 |
tracker2 | 192.168.204.168 | tracker2 | 22122 |
storage1 | 192.168.204.169 | storage1 | 23000、8888 |
storage2 | 192.168.204.170 | storage1 | 23000、8888 |
修改一下自己电脑的host文件:
192.168.204.167 tracker1
192.168.204.168 tracker2
192.168.204.169 storage1
192.168.204.170 storage2
下载镜像:
docker pull morunchang/fastdfs
https://hub.docker.com/r/morunchang/fastdfs
tracker
在tracker1、tracker2服务器运行下面命令(运行前检查tracker_data目录是否创建了)。
默认端口:22122。
docker run -d --name tracker -p 22122:22122 -v ~/tracker_data:/data/fast_data --net=host morunchang/fastdfs sh tracker.sh
–net:支持 bridge/host/none/container 四种类型
storage
在storage1、storage2服务器运行下面命令(运行前检查storage_data、store_path目录是否创建了)。
docker run -d --name storage \
-v ~/storage_data:/data/fast_data \
-v ~/conf/nginx.conf:/etc/nginx/conf/nginx.conf \
--net=host -e GROUP_NAME=group1 \
-e TRACKER_IP=tracker1:22122,tracker2:22122 \
morunchang/fastdfs sh storage.sh
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 8888;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
location ~ /M00 {
root /data/fast_data/data;
ngx_fastdfs_module;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
整合SpringBoot
去github拉取源码,然后mvn clean install到本地仓库(官方没有发布到maven中心仓库),最后再引入依赖:
<dependency>
<groupId>org.csource</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.30-SNAPSHOT</version>
</dependency>
# http连接超时时间
connect_timeout = 2
# tracker与storage网络通信超时时间
network_timeout = 30
charset = UTF-8
http.tracker_http_port = 80
http.anti_steal_token = no
http.secret_key = FastDFS1234567890
# tracker服务器地址,可以重复配置多个
tracker_server = tracker1:22122
tracker_server = tracker2:22122
# 连接池配置
connection_pool.enabled = true
connection_pool.max_count_per_entry = 500
connection_pool.max_idle_time = 3600
connection_pool.max_wait_time_in_ms = 1000
package com.example.demofastdfs.test;
import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.*;
import java.util.Arrays;
/**
* @className: FastDFSDemo
* @description: TODO 类描述
* @author: liangshijie
* @date: 2023/3/5
**/
public class FastDFSDemo {
private static final String CONF_NAME = "fdfs_client.conf";
private StorageClient storageClient;
private TrackerServer trackerServer;
@Before
public void initStorageClient() throws Exception {
ClientGlobal.init(CONF_NAME);
System.out.println("network_timeout=" +
ClientGlobal.g_network_timeout + "ms");
System.out.println("charset=" + ClientGlobal.g_charset);
TrackerClient tracker = new TrackerClient();
trackerServer = tracker.getTrackerServer();
StorageServer storageServer = new StorageServer("storage1", 23000, 0);
storageClient = new StorageClient(trackerServer, storageServer);
// storageClient = new StorageClient(trackerServer, storageServer);
}
/**
* 测试上传文件
*/
@Test
public void upload() throws Exception {
// http://storage2:23000/group1/M00/00/00/wKjMqWQHVleAFzK7AAAWWKCNj-E2720984.jpg
NameValuePair[] metaList = new NameValuePair[1];
String local_filename = "dog.png";
metaList[0] = new NameValuePair("fileName", local_filename);
File file = new File("C:\\Users\\admin\\Desktop\\dog.png");
InputStream inputStream = new FileInputStream(file);
int length = inputStream.available();
byte[] bytes = new byte[length];
inputStream.read(bytes);
String[] result = storageClient.upload_file(bytes, "jpg", metaList);
// String[] result = storageClient.upload_file(bytes, null, metaList);
System.out.println("result {}" + Arrays.asList(result));
}
//查询文件
@Test
public void testQueryFile() throws IOException, MyException {
// group1, M00/00/00/wKjMqWQGk_WAesAEAAAWWKCNj-E4269595
FileInfo fileInfo = storageClient.query_file_info("group1", "M00/00/00/wKjMqWQGk_WAesAEAAAWWKCNj-E4269595");
System.out.println(fileInfo);
}
/**
* 测试下载
*/
@Test
public void download() throws Exception {
String[] uploadresult = {"group1",
"M00/00/00/wKjMqWQGk_WAesAEAAAWWKCNj-E4269595"};
byte[] result = storageClient.download_file(uploadresult[0],
uploadresult[1]);
String local_filename = "dog_two.png";
//文件写入磁盘
writeByteToFile(result, local_filename);
File file = new File(local_filename);
System.out.println("file.isFile = " + file.isFile());
}
@After
public void closeClient() {
System.out.println("close connection");
if (storageClient != null) {
try {
storageClient.close();
} catch (Exception e) {
e.printStackTrace();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
public void writeByteToFile(byte[] fbyte, String fileName) throws
IOException {
BufferedOutputStream bos = null;
FileOutputStream fos = null;
File file = new File(fileName);
try {
fos = new FileOutputStream(file);
bos = new BufferedOutputStream(fos);
bos.write(fbyte);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bos != null) {
bos.close();
}
if (fos != null) {
fos.close();
}
}
}
}
测试地址
http://storage1:8888/group1/M00/00/00/wKjMqWQHWfqAGM9mAAAWWKCNj-E813.jpg