写点什么

分布式文件系统 FastDFS 实战

用户头像
程序员Fox
关注
发布于: 44 分钟前
分布式文件系统FastDFS实战

1. 分布式文件系统应用场景

互联网海量非结构化数据的存储需求

  • 电商网站:海量商品图片

  • 视频网站:海量视频文件

  • 网盘 : 海量文件

  • 社交网站:海量图片


2.FastDFS 介绍

https://github.com/happyfish100/fastdfs

2.1 简介

​ FastDFS 是一个开源的高性能分布式文件系统(DFS)。 它的主要功能包括:文件存储,文件同步和文件访问,以及高容量和负载平衡。主要解决了海量数据存储问题,特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体的在线服务。


​ FastDFS 设计是用来存储小文件的,过大的文件处理方案是拆分为小文件,可跟踪小文件的上传情况。 如果应用场景都是处理大文件,可能选择其他分布式文件系统方案会更合适。

2.2 特性

FastDFS 为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用 FastDFS 很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。


优点:


  • 文件不分块存储,文件和系统中的文件一一对应。

  • 对文件内容做 hash 处理,避免出现重复文件,节约磁盘空间。

  • 下载文件支持 HTTP 协议,可基于内置 Web Server 或外部 Web Server。

  • 支持在线扩容,动态添加卷。

  • 支持文件冗余备份和负载均衡。

  • 存储服务器上可以保存文件属性(meta-data)

  • V2.0 网络通信采用 libevent,支持大并发访问,整体性能更好。


缺点:


  • 直接按文件存储,可直接查看文件内容,缺乏文件安全性。

  • 数据同步无校验,存在静默 IO 问题,降低系统可用性。

  • 单线程数据同步,仅适合存储小文件(1)。

  • 备份数根据存储分卷(分组)决定,缺乏文件备份数设置灵活性。

  • 单个挂载点异常会导致整个存储节点下线。

  • 缺乏多机房容灾支持。

  • 静态的负载均衡机制。


优点与缺点并存,但针对中小型系统已经完全足够使用了。

2.3 角色

  • Tracker Server:跟踪服务器,主要做调度工作,起到均衡的作用;负责管理所有的 storage server 和 group,每个 storage 在启动后会连接 Tracker,告知自己所属 group 等信息,并保持周期性心跳。

  • Storage Server:存储服务器,主要提供容量和备份服务;以 group 为单位,每个 group 内可以有多台 storage server,数据互为备份。

  • Client:客户端,上传下载数据的服务器,也就是我们自己的项目所部署在的服务器。

Tracker 相当于一个调度中心,上传和下载都通过它来进行分配指定。

Storage cluster 部分,由 Volume1、Volume2……VolumeK 组成,它们称为卷(或者叫做组),卷与卷之间是平行的关系,可以根据资源的使用情况随时增加,卷内服务器文件相互同步备份,以达到容灾的目的。

2.4 存储策略

为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起到了冗余备份和负载均衡的作用。

在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。

2.5 上传过程

当服务启动之后,Storage Server 会定期的向 Tracker Server 发送存储信息。如果 Tracker Server 是集群形式,则每个 Tracker 之间的关系是对等的,客户端上传时选择任意一个 Tracker 即可。

整体流程:当客户端请求 Tracker 进行上传操作时,会获取存储服务器相关信息,主要包括 IP 和端口。根据返回信息上传文件,通过存储服务器写入磁盘,并返回给客户端 file_id、路径信息、文件名等信息。

对应流程图如下:

其中,当 Tracker 收到客户端上传文件的请求时,会为该文件分配一个可以存储文件的 group,当选定了 group 后就要决定给客户端分配 group 中的哪一个 storage server。

当分配好 storage server 后,客户端向 storage 发送写文件请求,storage 将会为文件分配一个数据存储目录。然后为文件分配一个 fileid,最后根据以上的信息生成文件名存储文件。

生成的文件名基本格式如下:

  • 组名:文件上传后所在的 storage 组名称,在文件上传成功后有 storage 服务器返回, 需要客户端自行保存。

  • 虚拟磁盘路径:storage 配置的虚拟路径,与磁盘选项 store_path*对应。如果配置了 store_path0 则是 M00,如果配置了 store_path1 则是 M01,以此类推。

  • 数据两级目录:storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据 文件。

  • 文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储 服务器 IP 地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。

2.6 文件同步

写文件时,客户端将文件写至 group 内一个 storage server 即认为写文件成功,storage server 写完文件后,会由后台线程将文件同步至同 group 内其他的 storage server。

每个 storage 写文件后,同时会写一份 binlog,binlog 里不包含文件数据,只包含文件名等元信息,这份 binlog 用于后台同步,storage 会记录向 group 内其他 storage 同步的进度,以便重启后能接上次的进度继续同步;进度以时间戳的方式进行记录,所以最好能保证集群内所有 server 的时钟保持同步。

storage 的同步进度会作为元数据的一部分汇报到 tracker 上,tracker 在选择读 storage 的时候会以同步进度作为参考。

2.7 下载过程

跟上传一样,在下载时客户端可以选择任意 Tracker server。

客户端带文件名信息请求 Tracker,Tracker 从文件名中解析出文件的 group、大小、创建时间等信息,然后选择一个 storage 用来服务处理请求,返回对应文件。

对应流程图如下:

如果是基于 Web 的 http 请求,此处的 Client 可以是 Nginx 代理服务。下面这张图更加形象的描述了相关的流程。


3.FastDFS 环境搭建

环境搭建参考:FastDFS环境搭建


4. Java 应用整合 FastDFS

fastdfs-client-java 使用

https://github.com/happyfish100/fastdfs-client-java

引入依赖

<dependency>  <groupId>com.taoyuanx</groupId>  <artifactId>fastdfs-client-java</artifactId>  <version>1.29-SNAPSHOT</version></dependency>
复制代码


上传文件

public class UploadDemo {    public static void main(String[] args) throws IOException, MyException {        //1、加载配置文件        ClientGlobal.init("fdfs_client.conf");        //2、创建一个TrackerClient对象。        TrackerClient trackerClient = new TrackerClient();        //3、使用TrackerClient对象获得trackerserver对象。        TrackerServer trackerServer = trackerClient.getTrackerServer();        //4、创建一个StorageClient对象。trackerserver、StorageServer两个参数。        StorageClient storageClient = new StorageClient(trackerServer, null);        String path = System.getProperty("user.dir")+ File.separator+"1.jpg";        //5、使用StorageClient对象上传文件。        String[] strings = storageClient.upload_file(path, "jpg", null);        //返回的数组包含两个元素分别类似于:        //group1        //M00/00/00/wKgDCmDtis2AAsj9AAW3ED-O1WQ151.jpg        for (String string : strings) {            System.out.println(string);        }    }}
复制代码


下载文件

public class DownloadDemo {    public static void main(String[] args)  {
try { //1、加载配置文件 ClientGlobal.init("fdfs_client.conf"); //2、创建一个TrackerClient对象。 TrackerClient trackerClient = new TrackerClient(); //3、使用TrackerClient对象获得trackerserver对象。 TrackerServer trackerServer = trackerClient.getTrackerServer(); //4、创建一个StorageClient对象。trackerserver、StorageServer两个参数。 StorageClient storageClient = new StorageClient(trackerServer, null); String path = System.getProperty("user.dir")+ File.separator+"fox.jpg"; //5、使用StorageClient对象下载文件。 storageClient.download_file("group1", "M00/00/00/wKgDDWDtRu6AMPhBARBlpcz7xUs146.jpg",path);
} catch (IOException e) { e.printStackTrace(); } catch (MyException e) { e.printStackTrace(); }
}}
复制代码

fastDFS_Client 使用

在原作者 YuQing 与 yuqih 发布的 java 客户端基础上进行了大量重构工作,便于 Java 工作者学习与阅读。


主要特性


  1. 对关键部分代码加入了单元测试,便于理解与服务端的接口交易,提高接口质量

  2. 将以前对 byte 硬解析风格重构为使用 对象+注解 的形式,尽量增强了代码的可读性

  3. 支持对服务端的连接池管理(commons-pool2)

  4. 支持上传图片时候检查图片格式,并且自动生成缩略图

  5. 在 SpringBoot 当中自动导入依赖


https://github.com/tobato/FastDFS_Client

上传图片

引入依赖

<dependency>    <groupId>com.github.tobato</groupId>    <artifactId>fastdfs-client</artifactId>    <version>1.27.2</version></dependency>
复制代码

在 yml 中配置

# 分布式文件系统FDFS配置fdfs:  so-timeout: 1500   # 读取时间  connect-timeout: 600  # 连接超时时间  thumb-image:             #缩略图生成参数    width: 150    height: 150  tracker-list:            #Tracker服务配置地址列表  - 192.168.3.10:22122  - 192.168.3.12:22122  - 192.168.3.13:22122
pool: #从池中借出的对象的最大数目(配置为-1表示不限制) max-total: -1 #获取连接时的最大等待毫秒数(默认配置为5秒) max-wait-millis: 5000 #每个key最大连接数 max-total-per-key: 50 #每个key对应的连接池最大空闲连接数 max-idle-per-key: 10 #每个key对应的连接池最小空闲连接数 min-idle-per-key: 5
upload: base-url: http://fastdfs.com:8888/
复制代码

大文件上传出现异常:


解决方案:添加 Config 配置

@Configurationpublic class FileConfig {
/** * 解决上传文件过大导致异常的问题。 * the request was rejected because its size (170982031) exceeds the configured * @return */ @Bean public MultipartConfigElement multipartConfigElement() { MultipartConfigFactory factory = new MultipartConfigFactory(); factory.setMaxRequestSize(DataSize.ofBytes(200*1048576L)); factory.setMaxFileSize(DataSize.ofBytes(200*1048576L)); return factory.createMultipartConfig(); }
}
复制代码

Controller

@RestControllerpublic class UploadController {
@Autowired private UploadService uploadService;

@RequestMapping("/upload") @ResponseBody public Map<String,Object> upload(MultipartFile file){ Map<String, Object> map =new HashMap<>(); String path = this.uploadService.uploadImage(file); map.put("path",path); return map; }}
复制代码

Service

@Service@Slf4jpublic class UploadService {    @Autowired    private FastFileStorageClient fastFileStorageClient;    @Value("${upload.base-url}")    private String baseUrl;
/** * 上传图片 * @param file * @return */ public String uploadImage(MultipartFile file) { try { //校验文件 BufferedImage image = ImageIO.read(file.getInputStream()); if (image == null || image.getWidth() == 0 || image.getHeight() == 0) { throw new RuntimeException("上传文件不是图片"); } } catch (IOException e) { log.error("校验文件内容失败....{}", e); throw new RuntimeException("校验文件内容失败" + e.getMessage()); } try { // 获取扩展名 String extension = StringUtils.substringAfterLast(file.getOriginalFilename(), "."); // 上传到FastDFS StorePath storePath = fastFileStorageClient.uploadFile(file.getInputStream(), file.getSize(), extension, null);
// 返回路径 return baseUrl+storePath.getFullPath(); } catch (IOException e) { log.error("【文件上传】上传文件失败!....{}", e);
throw new RuntimeException("【文件上传】上传文件失败!" + e.getMessage());
} }

}
复制代码

测试: http://localhost:8080/index.html


上传略缩图

//上传略缩图StorePath storePath = fastFileStorageClient.uploadImageAndCrtThumbImage(file.getInputStream(), file.getSize(), extension, null);
复制代码

缩略图为上传文件名+缩略图后缀(默认_150x150)

如:源图上传后路径为 xxx.jpg,缩略图为 xxx_150x150.jpg

测试:

http://fastdfs.com:8888/group1/M00/00/00/wKgDDGDub22AO4HUAApS0I7eS2U644.jpg

http://fastdfs.com:8888/group1/M00/00/00/wKgDDGDub22AO4HUAApS0I7eS2U644_150x150.jpg


文件下载

/**     * 测试文件下载     */@Testpublic void download() {    try {        byte[] bytes = fastFileStorageClient.downloadFile("group1", "M00/00/00/wKgDDGDub22AO4HUAApS0I7eS2U644_150x150.jpg", new DownloadByteArray());
FileOutputStream stream = new FileOutputStream("3_150x150.jpg");
stream.write(bytes); } catch (IOException e) { e.printStackTrace(); }}
复制代码

测试:


文件删除

/**     * 测试文件删除     */@Testpublic void deleteFile(){    fastFileStorageClient.deleteFile("group1","M00/00/00/wKgDDGDub22AWGB_AApS0I7eS2U026.jpg");}
复制代码



发布于: 44 分钟前阅读数: 3
用户头像

程序员Fox

关注

有道无术,术尚可求也,有术无道,止于术。 2019.03.12 加入

多年中间件,高并发经验,擅长高并发,中间件,微服务架构,源码控,喜欢分享技术

评论

发布
暂无评论
分布式文件系统FastDFS实战