FastDFS详解
场景概述:
天猫,淘宝等购物网站,大量的图片和视频,文件太多,如何存储
用户访问量大又如何保证下载速度或者加载速度:分布式文件系统就是解决这些问题的
什么是文件系统:
文件数据是如何存储的:
分布式文件系统(这个文件包括图片,视频等等,只要是文件,基本就可以操作,无视文件类型):
一台电脑存储量有限,并且并发吞吐量也有限,如何提高性能:
一吨货物,我要运送到吐鲁番:
1个人运,不敢想象
50个人运,太难了
500个人运,每个人都很轻松
这就是分布式吗?
答:这里面有集群的概念(货物来说),也有分布式的概念(整体来说),二者不要混淆,面试常问的经典题目
分布式:不同的业务模块部署在不同的服务器上或者同一个业务模块分拆多个子业务,部署不同的服务器上
解决高并发的问题(分开进行,使得不集中)
集群:同一个业务部署在多台服务器上,提高系统的高可用(防止对应服务器宕机)
例如:
小饭馆原来只有一个厨师,切菜洗菜备料一手抓,客人越来越多,一个厨师忙不过来,只能再请一个厨师
两个厨师都能炒菜,也就是两个厨师的作用是一样的,这样,两个厨师的关系就是"集群"
为了让厨师专心炒菜,把菜炒到极致,又请了配菜师负责切菜,备料等工作,厨师和备菜师的关系是"分布式"
一个备菜师忙不过来,要提供两份食材给两个厨师,又请了一个备菜师,两个备菜师的关系又是"集群"
流的分布式文件系统:
HDFS:
(Hadoop Distributed File System)Hadoop 分布式文件系统
高容错的系统,适合部署到廉价的机器上
能提供高吞吐量的数据访问,非常适合大规模数据应用
HDFS采用主从结构,一个HDFS是由一个name节点和N个data节点组成
name节点储存元数据,一个文件分割成N份存储在不同的data节点上
GFS:
Google File System
可扩展的分布式文件系统,用于大型的,分布式的,对大量数据进行访问的应用
运行于廉价的普通硬件上,可以提供容错功能
它可以给大量的用户提供总体性能较高的服务
GFS采用主从结构,一个GFS集群由一个master和大量的chunkserver(分块服务器)组成
一个文件被分割若干块,分散储存到多个分块server中
FastDFS:
由淘宝资深架构师余庆编写并开源
专为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标
使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务
HDFS,GFS等都是通用的文件系统,他们的优点是开发体验好,但是系统的复杂度较高,性能也一般
相比之下,专用的分布式文件系统体验差,但是复杂度低,性能也高,尤其fastDFS特别适合图片,小视频等小文件
因为fastDFS对文件是不分割的,所以没有文件合并的开销,但也导致不适合操作大的文件(如大视频文件等等),而特别适合图片,小视频等小文件
网络通信用socket,速度快
工作原理:
fastDFS包含Tracker Server和Storage Server
客户端请求Tracker Server进行文件的上传与下载
Tracker Server调度Storage Server最终完成上传与下载
Tracker (译:追踪者):
作用是负载均衡和调度,它管理着存储服务(Storage Server),可以理解为:“大管家,追踪者,调度员”
告诉你哪个Storage Server可以操作(返回信息给客户端)
Tracker Server可以集群,实现高可用,策略为"轮询"
Storage (译:仓库,贮存器):
作用是文件存储,客户端上传的文件最终存储到storage服务器上
storage集群采用分组的方式,同组内的每台服务器是平等关系,数据同步,目的是实现数据备份
从而高可用,而不同组的服务器之间是不通信的
同组内的每台服务器的存储量不一致的情况下,会选取容量最小的那个(防止同步不了)
所以同组内的服务器之间软硬件最好保持一致
Storage Server会连接集群中的所有Tracker Server,定时向他们汇报自己的状态(通常用来给Tracker Server来返回信息给客户端的)
例如:剩余空间,文件同步情况,文件上传下载次数等信息
上传/下载 原理:
客户端上传文件后,storage会将文件id返回给客户端
比如:group1/M00/02/11/aJxAeF21O5wAAAAAAAAGaEIOA12345.sh,这个id用来决定对应文件(可以找到)
通常情况下:上传时返回上传成功(包含对应的文件信息,用来下载用的)
下载时,给出文件信息,找到并返回文件,使得下载成功
其中group1就是组名:文件上传后,在storage组的名称,文件上传成功后,由storage返回,一般是需要客户端自行保存
M00是虚拟磁盘路径(包括data,相对于属性storage_path来说,实际上也就是目录):
storage配置的虚拟路径,在磁盘选项storage_path对应
storage_path0对应M00
storage_path1对应M01
02/11是数据两级目录:
storage在虚拟磁盘下自行创建的目录,一般启动storage时就会出现data文件夹
里面包含两级目录,两级目录,就是目录的目录,有两层,如02/11,即02目录下面的11目录下,这样就是两级目录
aJxAeF21O5wAAAAAAAAGaEIOA12345.sh是文件名:
与上传时不同(比如上传a.sh,对应名称一般不是a,这里好像就是上传a.sh),是用storage根据特定信息生成的
里面包含:storage服务器的ip,创建时间戳,大小,后缀名等信息
FastDFS的上传与下载 :
安装:
安装gcc(编译时需要)
yum install -y gcc gcc-c++
安装libevent(运行时需求) :
yum -y install libevent
对应需要的文件下载地址(后面需要的):
链接:https://pan.baidu.com/s/1sYGezUM13XCeDtPHAZJcAQ
提取码:alsk
安装 libfastcommon:
libfastcommon是FastDFS官方提供的,libfastcommon包含了FastDFS运行所需要的一些基础库
上传 libfastcommon-master.zip 到 /opt
安装解压zip包的命令: yum install -y unzip
解压包: unzip libfastcommon.zip
进入目录: cd libfastcommon-master
编译:
./make.sh
如果make.sh的权限不够,则需要授权(可执行的权利):
chmod 777 make.sh
安装:
./make.sh install
libfastcommon安装好后会在/usr/lib64 目录下生成 libfastcommon.so 库文件
拷贝库文件:
cd /usr/lib64
cp libfastcommon.so /usr/lib
安装Tracker:
下载 FastDFS_v5.05.tar.gz,并上传到 /opt
tar -zxvf FastDFS_v5.05.tar.gz
cd FastDFS
./make.sh
./make.sh install
安装成功后,将安装目录下的conf下的文件拷贝到/etc/fdfs/下:
cp /opt/FastDFS/conf/* /etc/fdfs/
配置:
Tracker配置:
vim /etc/fdfs/tracker.conf
port=22122
base_path=/home/fastdfs
Storage配置(安装了Tracker时,也顺便自带Storage):
vim /etc/fdfs/storage.conf
group_name=group1
port=23000
heart_beat_interval=30
base_path=/home/fastdfs
store_path0=/home/fastdfs/fdfs_storage
tracker_server=192.168.164.128:22122
启动服务:
启动tracker:
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart
启动storage:
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart
查看所有运行的端口:
netstat -ntlp
记得关闭防火墙:
systemctl stop firewalld.service
搭建 Java工程:
使用IDEA创建maven工程:
<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>
创建配置文件 :
在resources下创建config目录,在config目录下创建 fastdfs-client.properties,内容如下:
##fastdfs-client.properties
fastdfs.connect_timeout_in_seconds = 5
fastdfs.network_timeout_in_seconds = 30
fastdfs.charset = UTF-8
fastdfs.http_anti_steal_token = false
fastdfs.http_secret_key = FastDFS1234567890
fastdfs.http_tracker_http_port = 80
#记得改成自己的端口,实际上可以说是去tracker里获得好的storage组的服务器地址
fastdfs.tracker_servers = 192.168.164.128:22122
文件上传:
package test;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;
public class TestUpload {
public static void main(String[] args){
try {
ClientGlobal.initByProperties("config/fastdfs-client.properties");
TrackerClient trackerClient = new TrackerClient();
TrackerServer trackerServer = trackerClient.getConnection();
StorageServer storageServer = null;
StorageClient1 client1 = new StorageClient1(trackerServer, storageServer);
NameValuePair[] list = new NameValuePair[1];
list[0] = new NameValuePair("fileName","1.jpg");
String fileID = client1.upload_file1("E:\\img\\back.jpg", "jpg", list);
System.out.println(fileID);
trackerServer.close();
System.out.println(storageServer);
}catch (Exception e){
e.printStackTrace();
}
}
}
文件查询:
package test;
import org.csource.fastdfs.*;
public class TestQuery {
public static void main(String[] args) {
try {
ClientGlobal.initByProperties("config/fastdfs-client.properties");
TrackerClient trackerClient = new TrackerClient();
TrackerServer trackerServer = trackerClient.getConnection();
StorageServer storageServer = null;
StorageClient1 client1 = new StorageClient1(trackerServer, storageServer);
FileInfo fileInfo =
client1.query_file_info1("group1/M00/00/00/wKikgGK7sauAQspmAANjV8eqsnI139.jpg");
if(fileInfo!=null)
System.out.println(fileInfo);
else
System.out.println("没有该文件");
trackerServer.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
文件下载:
package test;
import org.csource.fastdfs.*;
import java.io.File;
import java.io.FileOutputStream;
public class TestDownload {
public static void main(String[] args) {
try {
ClientGlobal.initByProperties("config/fastdfs-client.properties");
TrackerClient trackerClient = new TrackerClient();
TrackerServer trackerServer = trackerClient.getConnection();
StorageServer storageServer = null;
StorageClient1 client1 = new StorageClient1(trackerServer, storageServer);
byte[] bytes =
client1.download_file1("group1/M00/00/00/wKikgGK7sauAQspmAANjV8eqsnI139.jpg");
FileOutputStream fileOutputStream = new FileOutputStream(new File("E:/xxxxxx.jpg"));
fileOutputStream.write(bytes);
fileOutputStream.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
到此,文件的操作完毕,接下来我们操作实战
项目实战:
搭建图片服务器:
Nginx模块安装 (Storage) :
上传 fastdfs-nginx-module_v1.16.tar.gz 到 /opt
解压nginx模块
tar -zxvf fastdfs-nginx-module_v1.16.tar.gz
修改 config 文件,将文件中的 /usr/local/ 路径改为 /usr/ :
cd /opt/fastdfs-nginx-module/src
vim config
原来的文件:
修改后的文件:
我们发现 /usr/local/ 的确变成了 /usr/ ,自己根据图片进行对比
将 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
base_path=/home/fastdfs
tracker_server=192.168.164.128:22122
url_have_group_name=true
store_path0=/home/fastdfs/fdfs_storage
将 libfdfsclient.so 拷贝至 /usr/lib 下:
cp /usr/lib64/libfdfsclient.so /usr/lib/
创建nginx/client目录:
mkdir -p /var/temp/nginx/client
Nginx安装 (Tracker) :
将 nginx-1.14.0.tar.gz上传到/opt(安装过nginx,此步省略)
解压:tar -zxvf nginx-1.14.0.tar.gz(安装过nginx,此步省略)
安装依赖库(安装过nginx,此步省略)
yum install pcre
yum install pcre-devel
yum install zlib
yum install zlib-devel
yum install openssl
yum install openssl-devel
进入nginx解压的目录下 cd /opt/nginx-1.14.0:
安装:
./configure \
--prefix=/usr/local/nginx \
--pid-path=/var/run/nginx/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--with-http_gzip_static_module \
--http-client-body-temp-path=/var/temp/nginx/client \
--http-proxy-temp-path=/var/temp/nginx/proxy \
--http-fastcgi-temp-path=/var/temp/nginx/fastcgi \
--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
--http-scgi-temp-path=/var/temp/nginx/scgi \
--add-module=/opt/FastDFS/fastdfs-nginx-module/src
注意:上边将临时文件目录指定为 /var/temp/nginx
需要在 /var 下创建 temp 及 nginx 目录:mkdir /var/temp/nginx(好像会自动创建)
编译:make
安装:make install
拷贝配置文件
cd /opt/FastDFS/conf
cp http.conf mime.types /etc/fdfs/
是否覆盖:yes
修改nginx配置文件,可以使用上面的nginx,也可以使用自己的nginx,这里我使用自己的,所以与上面的目录不同
之所以可以不同,是因为nginx只负责操作路径,自然与其他框架是分开的
主要是因为安装后出现的nginx目录文件不会覆盖,只会添加或者删除,比如上面的模块,自己目录的那个
虽然是添加,但是如果再次的操作没有他的,还是会删除,也就是说,到那时,下面的nginx启动,就启动不了了:
cd /usr/local/nginx/conf/
vim nginx.conf
server {
listen 80;
server_name 192.168.164.128;
location /group1/M00 {
root /home/fastdfs/fdfs_storage/data;
ngx_fastdfs_module;
}
关闭nginx,并启动nginx:
pkill -9 nginx
/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
访问nginx并查看图片:
http://192.168.164.128
看到上面的图片,那么就是启动成功
接下来我们访问如下:
http://192.168.164.128/group1/M00/00/00/wKikgGK6-paAVi7gAANjV8eqsnI871.jpg
这样,我们就通过nginx来访问到了服务器的资源,通常来说,fastdfs需要nginx来指定到对应的文件返回
因为单独的指定(即对应端口没有操作数据流通),并没有给数据流通
所以通常需要借助在端口上执行的可以进行数据流通的应用程序,比如nginx就可以操作http协议,将文件返回给对应的前端(也可以说成浏览器,基本上只要是操作http协议的都可以)
当然了,是需要可以访问的静态资源,假如就是一个文件,一般是访问不了的,一般会下载
若是目录,若没有指定里面的文件,那么默认指定index.html,若没有这个文件,那么返回对应错误界面
在这里要注意一点,当你重启服务器后,再次启动nginx,一般会出现异常(ctrl+c或者回车会退出异常),如下图:
一般我们可以直接创建/var/run/nginx/nginx.pid这个文件
一般需要在/var/run/下创建nginx目录,nginx.pid文件可以创建也可以不用创建
因为启动时会自动创建,且会进行覆盖(有对应文件的话)
那么也可以操作(启动nginx)
但是下次的重启(虚拟机的重启)却需要再次创建了,非常的麻烦
因为/var/run一般会删除我们创建的文件(每次的启动虚拟机,挂起不会,因为并不是重新启动,是保留状态的)
为什么会出现这种情况呢:
这是因为我们的每次操作都需要日志的存储(如启动,更新,停止等等),所以没有对应文件就会报错
启动会创建对应的nginx.pid,其他操作基本不会
他们每次的操作一般都会查看是否有该文件并读取里面的信息
所以中途删除再创建的话,由于信息不同(空信息和有值,但不对的信息,这两个报错)
那么更新和停止也是不会执行的(对应信息是启动时出现的pid的数字加1)
从而报错,如果加上这个信息,那么会执行,具体自己操作
且没有对应文件的话,也会报错,一般停止后就会删除该nginx.pid文件,重启虚拟机自然也会使得停止,即也会删除
一般nginx会默认存放在/var/run/nginx/nginx.pid里面,我们也可以设置这个位置
在nginx.conf里面加上如下(部分配置):
worker_processes 1;
pid /usr/local/nginx/logs/nginx.pid;
events {
worker_connections 1024;
}
我们要上传文件,那么就需要多部件的表单,格式如下:
<%--上传文件,文件与文字相比较起来,属于内容较大,必须使用post方式提交--%>
<%--上传文件,和普通文件有区别,action接收参数也会区别对待,所以声明带文件提交的表单为"多部件表单"--%>
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="fname">
<br>
<button>提交</button>
</form>
搭建web服务:
对应目录:
pom.xml依赖:
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<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>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8001</port>
<path>/</path>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
web.xml :
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
spring-mvc.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="controller"/>
<mvc:annotation-driven/>
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="2048000000"/>
</bean>
</beans>
创建前端页面(index.jsp):
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--上传文件,文件与文字相比较起来,属于内容较大,必须使用post方式提交--%>
<%--上传文件,和普通文件有区别,action接收参数也会区别对待,所以声明带文件提交的表单为"多部件表单"--%>
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="fname">
<br>
<button>提交</button>
</form>
</body>
</html>
文件实体类:
package entity;
import java.io.Serializable;
public class FileSystem implements Serializable {
private String fileId;
private String filePath;
private String fileName;
@Override
public String toString() {
return "FileSystem{" +
"fileId='" + fileId + '\'' +
", filePath='" + filePath + '\'' +
", fileName='" + fileName + '\'' +
'}';
}
public FileSystem() {
}
public FileSystem(String fileId, String filePath, String fileName) {
this.fileId = fileId;
this.filePath = filePath;
this.fileName = fileName;
}
public String getFileId() {
return fileId;
}
public void setFileId(String fileId) {
this.fileId = fileId;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
}
控制层:
package controller;
import entity.FileSystem;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import java.io.File;
import java.util.UUID;
@Controller
public class FileAction {
@RequestMapping("upload")
public @ResponseBody FileSystem upload(MultipartHttpServletRequest request) throws Exception{
FileSystem fileSystem = new FileSystem();
MultipartFile file = request.getFile("fname");
String originalFilename = file.getOriginalFilename();
String hou = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
String FileName = UUID.randomUUID().toString() + "." + hou;
File file1 = new File("E:/upload/" + FileName);
file.transferTo(file1);
String absolutePath = file1.getAbsolutePath();
System.out.println("文件的绝对路径信息是" +absolutePath);
ClientGlobal.initByProperties("config/fastdfs-client.properties");
TrackerClient trackerClient = new TrackerClient();
TrackerServer trackerServer = trackerClient.getConnection();
StorageServer storageServer = null;
StorageClient1 client1 = new StorageClient1(trackerServer,storageServer);
NameValuePair[] nameValuePairs = new NameValuePair[1];
nameValuePairs[0] = new NameValuePair("fileName",originalFilename);
String fileId = client1.upload_file1(absolutePath, hou, nameValuePairs);
System.out.println(fileId);
fileSystem.setFileId(fileId);
fileSystem.setFileName(originalFilename);
fileSystem.setFilePath(fileId);
return fileSystem;
}
}
添加fastDFS的配置文件(fastdfs-client.properties):
##fastdfs-client.properties
fastdfs.connect_timeout_in_seconds = 5
fastdfs.network_timeout_in_seconds = 30
fastdfs.charset = UTF-8
fastdfs.http_anti_steal_token = false
fastdfs.http_secret_key = FastDFS1234567890
fastdfs.http_tracker_http_port = 80
fastdfs.tracker_servers = 192.168.164.128:22122
接下来进行启动,测试,若浏览器返回数据,那么则操作完毕,至此FastDFS操作完成
最后,若要使用JMeter来操作文件,那么操作如下:
进行操作文件,最后要说明一下:跨域的问题,一般是浏览器中对应的操作标签的问题,如ajax,其他情况下,一般没有
即通常都可以跨域,如直接访问,java的连接等等