Java:82-FastDFS详解

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 #一般需要先安装libfastcommon(包含FastDFS运行需要的基础库),才可进行编译,否则会提示报错
./make.sh install
安装成功后,将安装目录下的conf下的文件拷贝到/etc/fdfs/下:
cp /opt/FastDFS/conf/* /etc/fdfs/ 
#其中/opt/FastDFS是FastDFS_v5.05.tar.gz解压后的目录,/etc/fdfs/是安装时创建的
配置:
Tracker配置:
vim /etc/fdfs/tracker.conf #部分改变(看看有没有不一样的对应配置)
#端口号
port=22122   
#基础目录(不是文件),Tracker运行时会向此目录存储storage的管理数据,基础目录不存在的话,需要自行创建 
#mkdir /home/fastdfs
base_path=/home/fastdfs 
Storage配置(安装了Tracker时,也顺便自带Storage):
vim /etc/fdfs/storage.conf #部分改变(看看有没有不一样的对应配置)
#配置组名
group_name=group1
#端口
port=23000
#向tracker心跳间隔(秒)
heart_beat_interval=30
#storage基础目录
#目录不存在,需要自行创建,好像是给的日志
base_path=/home/fastdfs
#store存放文件的位置(store_path)
#可以理解一个磁盘一个path,多个磁盘,多个store_path,当然这只是对于属性(store_path0看成盘)来说的
#实际上一般都是指向一个根目录
#若fdfs_storage目录不存在,需要自行创建
#mkdir /home/fastdfs/fdfs_storage
store_path0=/home/fastdfs/fdfs_storage #记得看看是否有该目录(不是文件),没有就创建
#如果有多个挂载磁盘则定义多个store_path,如下
#store_path1=.....   (M01)
#store_path2=.....   (M02)
#配置tracker服务器:IP
tracker_server=192.168.164.128:22122 
#指定对应的操作者,即定时对他发送消息,使得他返回消息给客户端,然后客户端来操作自己(Storage)
#为什么不直接操作客户端呢,因为客户端不是一直运行的,且非常多,那么会非常浪费心跳
#即需要一个地方来统一给出好的ip,使得被访问,而不是每个客户端都进行心跳,然后得到好的ip
#如果有多个,则配置多个tracker
#tracker_server=192.168.164.x:22122
启动服务:
启动tracker:
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart 
#会在/home/fastdfs/目录下创建两个目录,分别是data和logs(有日志文件)
#可以start(启动),stop(关闭),这里的restart是重启,没有status
启动storage:
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart 
#一般需要先启动tracker,才可启动这个,否则一般会停止不动,ctrl+c退出
#启动后,会在存放文件的目录/home/fastdfs/fdfs_storage/下,创建data目录,里面包含了数据的两级目录
#可以start(启动),stop(关闭),这里的restart是重启,没有status
查看所有运行的端口:
netstat -ntlp
记得关闭防火墙:
systemctl stop firewalld.service
搭建 Java工程:
使用IDEA创建maven工程:
<!--fastdfs的java客户端-->
<dependency>
    <!--
有读取文件的操作,如ClientGlobal.initByProperties("config/fastdfs-client.properties");
基本是必须的
-->
    <groupId>net.oschina.zcx7878</groupId>
    <artifactId>fastdfs-client-java</artifactId>
    <version>1.27.0.0</version>
</dependency>
<dependency>
 <!--
有对应的文件操作,比如
使用IOUtils完成文件的复制,这个类直接封装了对输入流和输出流的读取写入操作
注意:输出流会对路径(目录)进行判断,即必须是存在的目录,否则不会执行读取写入操作,即报错
但文件不会,会自动创建文件
IOUtils.copy(InputStream input, OutputStream output)
-->
    <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.*;

//通常来说fastdfs也可以设置文件大小的上限,但这里并没有设置,那么一般认为没有上限,那这时就看对方服务器的空间了,若他没有这么多,自然可能会使得连接断开,或者终止传输(通常服务器在只有固定的空间后,就会使得终止,这是对于连接来说的),在满之前,通常只能手动补充满,但实际上连接也会(可能不会)
/**
 *
 */
public class TestUpload {
    public static void main(String[] args){

        try {
            //加载配置文件
            ClientGlobal.initByProperties("config/fastdfs-client.properties");

            //创建tracker客户端,该客户端会使用ClientGlobal类,即操作使用了上面的加载
            TrackerClient trackerClient = new TrackerClient();

            //通过trackerClient客户端获取tracker连接服务并返回
            //即根据配置文件来获取服务器的tracker来进行操作
            TrackerServer trackerServer = trackerClient.getConnection();

            //声明storage服务
            StorageServer storageServer = null;

            //定义storage客户端,传递对应的tracker连接,和storage服务的声明
            StorageClient1 client1 = new StorageClient1(trackerServer, storageServer);

            //定义文件元信息(使用org.csource.common包下的该类),一般上传时需要
            NameValuePair[] list = new NameValuePair[1];
            list[0] = new NameValuePair("fileName","1.jpg"); //对应元信息的两个数据,相当于键值对

            //使用StorageClient1时,会根据trackerServer
            //即tracker连接来得到storageServer的信息(内部连接,基本是一体,即结合的)
            //然后返回对应的storage地址信息
            //再操作这个地址信息进行上传(返回的信息使得storageServer有值了),然后返回具体特殊的文件ID
            //其中会顺便关闭storageServer对应连接(释放资源,堆中),可以在执行后,打印即可,发现的确是null
            //且将storageServer设置为null
            
            /*
            参数1:当前地址文件
            参数2:改变该文件的后缀名
            参数3:也上传元信息
            */
            String fileID = client1.upload_file1("E:\\img\\back.jpg", "jpg", list); //jpg是后缀名
            //操作完后,里面会将图片名称改成对应的fileID,使得基本不会出现重复的文件,防止覆盖

            System.out.println(fileID);
            //group1/M00/00/00/wKikgGK69xSAPPdaAANjV8eqsnI410.jpg
            /*
            在前面的配置中,应该说明了文件的位置,即store_path0=/home/fastdfs/fdfs_storage
            其中现在对应的
            group1:因为只有一台服务器,所以一般就是一个组,配置文件指定服务器,服务器配置文件配置了组名
            store_path0=/home/fastdfs/fdfs_storage
            由于是store_path0,那么就代表M00
            若是store_path1,那么就代表M01
            若是store_path2,那么就代表M02
            以此类推
            M00:代表者/home/fastdfs/fdfs_storage/data,即这个目录
            00/00,代表data里面的两级目录下面
            wKikgGK69xSAPPdaAANjV8eqsnI410.jpg,文件名称
            当你启动时,可以去/home/fastdfs/fdfs_storage/data/00/00里面去看一看
            发现的确有wKikgGK69xSAPPdaAANjV8eqsnI410.jpg文件
            还多一个wKikgGK69xSAPPdaAANjV8eqsnI410.jpg-m文件
            与元信息有关的,一般保存了对应设置的数据,比如文件里有fileName^B1.jpg
            相当于键值对,^B好像是编码问题吧,可能是=(不确定)
            实际上可以看成一个唯一(根据名称和内容整体来看)
            注意:若你把00目录(无论是一级还是两级)删掉
            即找不到对应目录,就会返回null,即没有存放的地方(一般默认00/00目录里面)
            当多次运行时,由于受ip,创建时间戳,大小,后缀名
            这里看起来主要就是受时间戳关系,使得文件名不会一致
            使得没有进行覆盖,那么会多出很多的文件信息,自行测试一下即可
             */

            trackerServer.close(); //下面的自动进行关闭了,所以只需要关闭这一个
            System.out.println(storageServer); //null
        }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");

            //创建tracker客户端,该客户端会使用ClientGlobal类,即操作使用了上面的加载
            TrackerClient trackerClient = new TrackerClient();

            //通过trackerClient客户端获取tracker连接服务并返回
            //即根据配置文件来获取服务器的tracker来进行操作
            TrackerServer trackerServer = trackerClient.getConnection();

            //声明storage服务
            StorageServer storageServer = null;

            //定义storage客户端,传递对应的tracker连接,和storage服务的声明
            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);
                //source_ip_addr = 192.168.164.128, file_size = 222039, 
                //create_timestamp = 2022-06-29 09:58:03, crc32 = -945114510
                //文件信息,比如服务器地址,大小,更新时间(一开始自然是创建时间)等等
                //后面的crc32好像是唯一的值,具体可以百度看看是什么意思,实际上可以不必理会
                //如果没有找到,那么返回null
            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");

            //创建tracker客户端,该客户端会使用ClientGlobal类,即操作使用了上面的加载
            TrackerClient trackerClient = new TrackerClient();

            //通过trackerClient客户端获取tracker连接服务并返回
            //即根据配置文件来获取服务器的tracker来进行操作
            TrackerServer trackerServer = trackerClient.getConnection();

            //声明storage服务
            StorageServer storageServer = null;

            //定义storage客户端,传递对应的tracker连接,和storage服务的声明
            StorageClient1 client1 = new StorageClient1(trackerServer, storageServer);

            //我们发现,他返回的刚好是byte数组,而正是全部放入数组里面
            //所以若是很大的文件的话,在没有设置使得超过最大长度的情况下
            //一般不能操作很大的文件,这就是为什么FastDFS是适合操作较小文件的主要原因
            //因为没有这么复杂,简单,但是若有超大的文件时
            //内存(虚拟机的内存)的占用很大,因为没有分开获取,而是一次性获取
            //好像默认情况下byte数组最大长度是61858764字节,即61858764B
            //也就是将近60000kb,将近60mb(是将近不是等于)
            //1b=8字节,即一个byte是8字节,byte数组的一个数也就是1b
            //当然这是因为虚拟机内存的缘故,实际上若增大虚拟机内存
            //可以增加上限长度,但还是不会超过内存(计算机的内存)
            //只是更长的数组并没有意义,不会牺牲其他的内存而成全byte数组
            //所以在没有特殊情况下,FastDFS一般只用来操作小的文件(虽然也可以操作大文件,得到更多的内存即可)
            byte[] bytes = 
                client1.download_file1("group1/M00/00/00/wKikgGK7sauAQspmAANjV8eqsnI139.jpg");

            //没有找到对应图片的话,返回就是null,数组可以被赋值为null
            FileOutputStream fileOutputStream = new FileOutputStream(new File("E:/xxxxxx.jpg"));
            fileOutputStream.write(bytes);
            //若参数是null,那么会返回空指针,里面的代码需要这个bytes参数执行
            //如b.length(是null的话,会报空指针异常)
            fileOutputStream.close();
            //接下来去找E:/xxxxxx.jpg,发现的确有,且的确是上传的图片
        }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 #解压后的文件里面的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 
#(n个tracker配置n行)
#tracker_server=192.168.164.x:22122
#url中包含group名称
url_have_group_name=true       
#指定文件存储路径(上面配置的store路径) 
store_path0=/home/fastdfs/fdfs_storage 
将 libfdfsclient.so 拷贝至 /usr/lib 下:
cp /usr/lib64/libfdfsclient.so /usr/lib/ #libfdfsclient.so好像是前面安装或者启动时创建了,可能也不是
创建nginx/client目录:
mkdir -p /var/temp/nginx/client #-p,若对应目录不存在,就创建,否则不创建
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
#前面安装过的可以省略,比如操作过第75章博客,可能版本会不同,但75章博客是高版本,所以并不会有太大的影响
进入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 #自己解压的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;
   #charset koi8-r;
   #access_log logs/host.access.log main;
   location /group1/M00 { #没有匹配,默认/
       root   /home/fastdfs/fdfs_storage/data; #这里是/开头的,那么就是从服务器的/开始,而不是nginx开始
       ngx_fastdfs_module; 
       #一般的,其他文件不会放在nginx服务器里进行访问
       #而这个配置,前提是需要前面的Nginx模块安装 (Storage)
       #实际上就是需要添加--add-module=/opt/fastdfs-nginx-module/src
       #否则不添加的话,启动会报错(不识别)
       #使得Storage组里面的文件加载到nginx服务器里,那么就可以通过路径访问了(整个服务器的路径)
       #且使得匹配的进行替换掉root的值,而不是加在root后面
   }
   #虽然没有/,但实际上/就是默认html下的index.html的(虽然不写/,但也是默认/)
   
   #注意:虽然加上ngx_fastdfs_module;可以访问其他文件,但是他也只能操作Storage最里面的(两级目录下面)文件
   #其他目录的文件不可以加载,可以自己进行测试
关闭nginx,并启动nginx:
pkill -9 nginx
/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
#-c使得指定对应文件的启动,而不是默认的地址,虽然与对应地址相同
访问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;

#之所以会起作用,是因为nginx启动时,会使用这个文件,即使用了这个配置
pid    /usr/local/nginx/logs/nginx.pid; 
#设置pid位置(这个pid一般是进程的pid),远离/var/run/,这样,就不会出现重启虚拟机使得文件删除了
#当然,第一次没有logs/nginx.pid;,所以需要自己创建
#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>
    <!-- 因为有jsp页面,所以引用servlet依赖,有对应类,如HttpServletRequest,需要这个依赖-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <scope>provided</scope>
        <version>2.5</version>
    </dependency>
    <!-- 页面提交过来的请求,使用springmvc来处理,比如前端控制器-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.7.RELEASE</version>
    </dependency>
    <!-- 
java连接fastDFS的客户端工具
有读取文件的操作,如ClientGlobal.initByProperties("config/fastdfs-client.properties");
基本是必须的
-->
    <dependency>
        <groupId>net.oschina.zcx7878</groupId>
        <artifactId>fastdfs-client-java</artifactId>
        <version>1.27.0.0</version>
    </dependency>
    <!-- 
图片上传到FastDFS可以用到的IO工具
有对应的文件操作,比如
使用IOUtils完成文件的复制,这个类直接封装了对输入流和输出流的读取写入操作
注意:输出流会对路径(目录)进行判断,即必须是存在的目录,否则不会执行读取写入操作,即报错
但文件不会,会自动创建文件
IOUtils.copy(InputStream input, OutputStream output)
-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-io</artifactId>
        <version>1.3.2</version>
    </dependency>
    <!-- 
			图片保存到web服务器可以用到的IO工具
			比如创建磁盘文件工厂对象,临时的操作,必要的
            DiskFileItemFactory factory = new DiskFileItemFactory();
			创建文件上传核心类,有操作文件的方法
            ServletFileUpload upload = new ServletFileUpload(factory);
		DiskFileItemFactory类和ServletFileUpload类需要这个依赖
-->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
    </dependency>
    <!--用来转换java对象和json字符串,注意,2.7以上版本必须搭配spring5.0以上
json解析工具,这里不可以删除,虽然我们并没有操作
但是springmvc操作@RequestBody注解使用的json时,通常需要这个,否则报错
其他的json解析工具基本不行,具体可以看68章博客(一开始的介绍)
-->
    <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"/>
    <!--扫描控制器中的注解:@Response-->
    <mvc:annotation-driven/>
    <!--上传文件的解析器(规定上传文件的大小限制)-->
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 上传文件最大限制约等于:2GB=2048MB约等于2048000kb约等于2048000000B-->
        <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 {


    /*
    上传流程:
    1:先把文件保存到web服务器上(MultipartHttpServletRequest)
    2:再从web服务器上将文件上传到FastDFS上
     */
    @RequestMapping("upload")
    public @ResponseBody FileSystem upload(MultipartHttpServletRequest request) throws Exception{

        FileSystem fileSystem = new FileSystem();

        //第一步:先把文件保存到web服务器上(MultipartHttpServletRequest)

        //MultipartHttpServletRequest和前面的MultipartFile差不多都可以操作文件
        //MultipartHttpServletRequest是HttpServletRequest扩展,使得可以操作文件
        //即不仅可以操作文本信息,还可以操作文件信息(比如图片文件)等
        //而MultipartFile是直接可以操作数据,使得操作文件
        //但也基本只能操作文件,其他的文本信息(如type="text"的数据)基本不可以操作
        //他们两个都是操作文件的
        //可以将MultipartHttpServletRequest看成是HttpServletRequest和MultipartFile的结合体
        MultipartFile file = request.getFile("fname"); //根据名称得到文件数据

        //获得文件的原始名称,即上传的文件名称
        String originalFilename = file.getOriginalFilename();

        //通过字符串截取,得到后缀名
        //String substring(int beginIndex)
        //返回字符串中从下标beginIndex(包括)开始到字符串结尾的子字符串
        //比如说1.jpg,那么先找到.,然后加1,即包括j,那么就是jpg了,截取了后缀名
        String hou = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
        //但是若有相同的名称的话,且同一时间的话
        //虽然服务器操作文件名称当成文件名或者FastDFS会操作对应的fid当成文件名
        //但他们还是可能会有相同的文件出现,使得覆盖(因为是异步的,是有可能的)
        //所以需要创建全新的文件名,来操作两者
        String FileName = UUID.randomUUID().toString() + "." + hou;
        //创建web服务器保存文件的目录
        //记得先创建好E:/upload目录,否则我们操作他指定的文件时(不是操作file1),一般会报错,即找不到路径
        File file1 = new File("E:/upload/" + FileName);
        //当然文件也是需要有的,否则操作时,也是会找不到路径的
        file.transferTo(file1);
        //相当于里面有一个输出流,操作该file1,并将我们的的文件信息file给这个参数文件(输入流)
        //输出流会自动看看是否有该参数文件,使得创建的,最后就可以看到对应文件的信息了
        //String getAbsolutePath(),用于获取文件的绝对路径
        String absolutePath = file1.getAbsolutePath();
        System.out.println("文件的绝对路径信息是" +absolutePath);

        //第二步:再从web服务器上将文件上传到FastDFS上
        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对象
        //封装fileId
        fileSystem.setFileId(fileId);
        //上传的文件名称,不是新的名称,即知道上传的文件是什么
        fileSystem.setFileName(originalFilename);
        //保存的是FastDFS的文件路径,那么也就是fileID(因为这个就是全部的路径)
           //即也知道最后的文件变成了什么,当然,你也可以根据需要,设置成用户的图片路径,比如上面的绝对路径absolutePath
        //使得看看自己的选择的路径是多少,这里还是fileId了
        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的连接等等
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
org.csource:fastdfs-client-java:1.29-是一个Java语言的FastDFS客户端,用于访问FastDFS分布式文件系统。FastDFS是一个开源的分布式文件系统,具有高性能、高可靠性、可扩展性和易于管理等特点。FastDFS将文件分成许多小块,然后存储在多台服务器上,提供了快速的文件上传和下载功能。 org.csource:fastdfs-client-java:1.29-是FastDFSJava语言实现,通过该客户端,我们可以轻松地在Java项目中使用FastDFS进行文件的上传和下载。它提供了一组简单易用的API,允许我们通过指定文件路径或字节数组来上传文件,并通过文件的标识符来下载文件。同时,我们还可以获取文件的元信息,例如文件大小、创建时间等。 通过该客户端,我们还可以进行文件的删除、修改和查询等操作。它提供了丰富的接口方法,可以满足不同的业务需求。此外,该客户端还支持文件的断点续传功能,当网络中断或上传下载过程中出现异常时,我们可以恢复中断的操作,避免重新上传或下载整个文件。 org.csource:fastdfs-client-java:1.29-是一个成熟稳定的Java组件,被广泛应用于各种基于Java的项目中。它的源代码是开放的,意味着我们可以根据自己的需求进行修改和定制。此外,它还具有良好的文档和社区支持,我们可以在遇到问题时及时获得帮助和解决方案。 总之,org.csource:fastdfs-client-java:1.29-是一个功能强大、易用的Java客户端,提供了丰富的API和功能,帮助我们轻松地在Java项目中使用FastDFS分布式文件系统。它是一个值得信赖和推荐的工具,可以提高文件操作的效率和可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值