草庐IT

fastdfs详解

y-y-_-h-h- 2023-12-18 原文

概述

分布式文件系统

  • 分布式文件系统(Distributed File System)是一个软件/软件服务器 此软件可以用来管理在多个服务器节点中的文件(这些服务器节点通过网络相连并构成一个庞大的文件存储服务器集群 即这些服务器节点都是用来存储文件资源的 且用dfs来管理这些文件)

  • 传统文件系统与分布式文件系统的对比

  • 传统文件系统的缺点

    • 若用户数量多 则io操作会很频繁 则对磁盘的访问压力会较大
    • 若磁盘故障 则可能会造成数据的丢失
    • 一个磁盘的存储容量有限

FastDFS

  • fastdfs是一个开源的轻量级分布式文件系统 由c语言开发

  • fastdfs对文件进行管理的功能有存储、同步、访问(上传、下载、删除)等

  • fastdfs有冗余备份、线性扩容等机制 高可用、高性能 且解决了较大文件的存储问题 特别适合应用于如相册网站、文档网站、图片网站、视频网站等以文件为载体的在线服务的网站

  • 架构

    • 客户端
      • 即使用java去连接/操作fastdfs的代码
    • 服务端
      • 跟踪器(tracker)
        • 负责调度且在内存中记录集群中storage的状态信息 是客户端与storage的中间枢纽
        • 因为信息都保存在内存中 因此性能高 一般的 一个较大的集群有3台tracker就够了
      • 存储节点(storage)
        • 负责存储文件与文件属性等信息到服务器磁盘中 文件的存储、同步、访问都由storage来完成

在linux-minimal上安装fastdfs

  • linux-mini与linux的区别

    • linux-mini没有图形界面 但占用磁盘资源小 且公司里一般使用的都是linux-mini

    • 由于linux-mini较linux来说缺少一些常用的工具库 因此推荐先安装下面的工具库 否则使用起来可能会不太方便

      #net-tools为使用ifconfig命令等的工具库
      yum install git lrzsz wget vim unzip net-tools -y
      
#先安装第三方库(gcc编译器、libevent库) 否则配置/编译时可能会报错
yum install gcc libevent libevent-devel -y

#从github中下载(若显示连不上github则需要自己手动下载 手动下载后用tar -zxvf命令解压缩)、编译且安装libfastcommon库
#libfastcommon库是从fastdfs中分离出来的c语言公用函数库
git clone https://github.com/happyfish100/libfastcommon.git --depth 1
cd libfastcommon
./make.sh
./make.sh install

#从github中下载、编译且安装libserverframe库
#注意 若不安装libserverframe库则在编译fastdfs时会出现错误:../common/fdfs_global.h:15:26: fatal error: sf/sf_global.h: No such file or directory
git clone https://github.com/happyfish100/libserverframe.git --depth 1
cd libserverframe
./make.sh
./make.sh install

#从github中下载、编译且安装fastdfs
git clone https://github.com/happyfish100/fastdfs.git --depth 1
cd fastdfs
./make.sh
./make.sh install

#fastdfs的编译出来的文件在/usr/bin目录下(以fdfs开头)
#fastdfs的配置文件在/etc/fdfs目录下

#复制fastdfs的conf目录下的两个文件到/etc/fdfs目录下 否则实际应用时可能会出现奇奇怪怪的问题
cp http.conf /etc/fdfs
cp mime.types /etc/fdfs

#备份配置文件
cd /etc/fdfs
cp client.conf client.conf.sample
cp storage.conf storage.conf.sample
cp storage_ids.conf storage_ids.conf.sample
cp tracker.conf tracker.conf.sample

#修改tracker.conf文件
#base_path为存储tracker的数据与日志文件的目录路径
base_path = /opt/fastdfs/tracker

#修改storage.conf文件
#base_path为存储storage的数据与日志文件的目录路径
base_path = /opt/fastdfs/storage
#store_path为存储真正文件的目录 可以写多个 结尾为0、1、..的数字 若写多个 则要改store_path_count
store_path0 = /opt/fastdfs/storage/files
#store_path1 = /home/yuqing/fastdfs1
store_path_count = 1
#tracker_server表示将此storage注册到哪个tracker下
tracker_server = 192.168.190.131:22122
#测试时使用一个tracker即可 剩余的要么注释掉要么删除掉
#tracker_server = 192.168.209.122:22122
#创建上述目录
mkdir -p /opt/fastdfs/tracker
mkdir -p /opt/fastdfs/storage/files

#启动fastdfs
#启动tracker(在任意目录下)
fdfs_trackerd /etc/fdfs/tracker.conf
#启动storage(在任意目录下)
#首次启动storage后会在$store_path$/data目录下新建256^2个存储文件的目录
fdfs_storaged /etc/fdfs/storage.conf
#查看storage是否已经注册到了tracker下
fdfs_monitor /etc/fdfs/storage.conf
#若有此信息说明注册成功
tracker server is 192.168.190.131:22122

#重启tracker
fdfs_trackerd /etc/fdfs/tracker.conf restart
#重启storage
fdfs_storaged /etc/fdfs/storage.conf restart

#关闭tracker
fdfs_trackerd /etc/fdfs/tracker.conf stop
#关闭storage
fdfs_storaged /etc/fdfs/storage.conf stop
#或kill命令关闭
#但不建议使用kill -9命令强制关闭 因为可能会产生文件信息不同步的问题

本地上传本地访问

#启动/重启fastdfs
fdfs_trackerd /etc/fdfs/tracker.conf (restart)
fdfs_storaged /etc/fdfs/storage.conf (restart)

#修改client.conf文件以测试文件上传
#base_path为存储client的日志文件的目录路径
base_path = /opt/fastdfs/client
#tracker_server表示将此client注册到哪个tracker下
tracker_server = 192.168.190.131:22122
#创建上述目录
mkdir -p /opt/fastdfs/client

#测试文件上传
fdfs_test /etc/fdfs/client.conf upload /opt/fastdfs-6.09.tar.gz
-----------------------------------------------------------------------------------
#tracker查询到有一个storage注册到它的名下
tracker_query_storage_store_list_without_group: 
	server 1. group_name=, ip_addr=192.168.190.131, port=23000

group_name=group1, ip_addr=192.168.190.131, port=23000

#此为上传的文件的信息且fastdfs会对上传的文件进行重新命名
storage_upload_by_filename
group_name=group1, remote_filename=M00/00/00/wKi-g2NE2l-AGCKaAAw1cLkA25I.tar.gz
source ip address: 192.168.190.131
file timestamp=2022-10-11 10:52:15
file size=800112
file crc32=3103841170
#/group1为组名(非cluster下默认只有一个组group1)
#/M00为linux的虚拟磁盘名
#/00/00为目录名
#/wKi-g2NE2l-AGCKaAAw1cLkA25I.tar.gz为文件名
example file url: http://192.168.190.131/group1/M00/00/00/wKi-g2NE2l-AGCKaAAw1cLkA25I.tar.gz

#此为上传的文件的从文件的信息
#一般的 上传的文件只需要存一份主文件即可
#在存放图片时需要存放它的大图与小图时可以存放从文件
storage_upload_slave_by_filename
group_name=group1, remote_filename=M00/00/00/wKi-g2NE2l-AGCKaAAw1cLkA25I_big.tar.gz
source ip address: 192.168.190.131
file timestamp=2022-10-11 10:52:15
file size=800112
file crc32=3103841170
example file url: http://192.168.190.131/group1/M00/00/00/wKi-g2NE2l-AGCKaAAw1cLkA25I_big.tar.gz
-----------------------------------------------------------------------------------
#查看上传的文件情况
cd /opt/fastdfs/storage/files/data/00/00
ll
#此为从文件
wKi-g2NE2l-AGCKaAAw1cLkA25I_big.tar.gz
#此为从文件的属性信息(m为meta)
wKi-g2NE2l-AGCKaAAw1cLkA25I_big.tar.gz-m
#此为主文件
wKi-g2NE2l-AGCKaAAw1cLkA25I.tar.gz
#此为主文件的属性信息(m为meta)
wKi-g2NE2l-AGCKaAAw1cLkA25I.tar.gz-m

#删除测试文件
#删除主文件及其属性信息
fdfs_delete_file /etc/fdfs/client.conf group1/M00/00/00/wKi-g2NE2l-AGCKaAAw1cLkA25I.tar.gz
#删除从文件及其属性信息
fdfs_delete_file /etc/fdfs/client.conf group1/M00/00/00/wKi-g2NE2l-AGCKaAAw1cLkA25I_big.tar.gz

本地上传远程访问

#通过nginx访问已经上传到fastdfs中的文件

#从github中下载fastdfs-nginx模块
#注意 在nginx的安装过程中才能配置fastdfs-nginx模块 因此得重新安装一遍nginx
git clone https://github.com/happyfish100/fastdfs-nginx-module.git --depth 1

#下载并解压nginx
wget https://nginx.org/download/nginx-1.22.0.tar.gz
tar -zxvf nginx-1.22.0.tar.gz

#先安装第三方库(gcc编译器、openssl库、pcre库、zlib库) 否则配置/编译nginx时可能会报错
yum install gcc openssl openssl-devel pcre pcre-devel zlib zlib-devel -y

#配置nginx的安装路径以及要添加的模块的源代码路径
cd nginx-1.22.0
./configure --prefix=/opt/nginx --add-module=/opt/fastdfs-nginx-module/src

#编译且安装nginx
make
make install

#拷贝文件 否则可能不能正常启动nginx
cp /opt/fastdfs-nginx-module/src/mod_fastdfs.conf /etc/fdfs

#修改mod_fastdfs.conf文件
#base_path为存储fastdfs-nginx模块的日志文件的目录路径
base_path=/opt/fastdfs/fastdfs-nginx
#tracker_server表示将此fastdfs-nginx模块注册到哪个tracker下
tracker_server=192.168.190.131:22122
#若url中带有组名则设为true 默认为false
url_have_group_name = true
#store_path为storage存储真正文件的目录 可以写多个 若写多个 则要改store_path_count
store_path0=/opt/fastdfs/storage/files
#store_path1=/home/yuqing/fastdfs1
store_path_count=1
#创建上述目录
mkdir -p /opt/fastdfs/fastdfs-nginx

#配置nginx
#表示拦截此请求路径并用fastdfs的nginx模块进行转发
#ngx_fastdfs_module:此指令非nginx提供 而是由fastdfs-nginx模块提供的 而nginx根据此指令会找到mod_fastdfs.conf文件 进而找到tracker与storage
location ~ /group[1-9]/M0[0-9] {
	ngx_fastdfs_module;
}

#启动nginx
./nginx -c /opt/nginx/conf/nginx.conf

#启动/重启fastdfs
fdfs_trackerd /etc/fdfs/tracker.conf (restart)
fdfs_storaged /etc/fdfs/storage.conf (restart)

#测试文件上传
cp /opt/fastdfs-6.09/conf/http.conf /opt/a.txt
fdfs_test /etc/fdfs/client.conf upload /opt/a.txt

#访问http://192.168.190.131/group1/M00/00/00/wKi-g2NFIHaAE1jiAAADxfUPwyI976.txt

远程上传远程/本地访问

  • 即使用java代码来连接与操作fastdfs并在浏览器中访问fastdfs

  • 先在github上下载fastdfs-client-java-master压缩包(此压缩包实则为java代码) 解压后将此压缩包打成jar包并放到maven仓库中

    #进入源码包中打开cmd并输入maven命令
    (../fastdfs-client-java-master>)mvn clean install
    

fastdfs-java

  • 使用maven项目即可

添加依赖

<!-- fastdfs依赖 -->
<!-- 此gav可在源码包的pom文件中copy -->
<dependency>
    <groupId>org.csource</groupId>
    <artifactId>fastdfs-client-java</artifactId>
    <version>1.29-SNAPSHOT</version>
</dependency>

拷贝源码包的fdfs_client.conf文件到resources目录中并修改

tracker_server = 192.168.190.131:22122

编写文件上传、下载、删除的代码

public class FastDFS {
    public static void main(String[] args) {
        fileUpload();
    }

    /**
     * 文件上传
     */
    public static void fileUpload() {
        try {
            //获取StorageClient对象
            StorageClient sc = getStorageClient();

            //local_filename为需要上传的文件的绝对路径
            //file_ext_name为需要上传的文件的扩展名
            //meta_list为需要上传的文件的属性信息 通常为null 即不用上传
            //返回值为String[] strings[0]为组名(如group1) strings[1]为远程文件名(如M00/00/00/wKi-g2NFYm6AYYnwAAEXDUO_9AI243.png)
            //此返回值很重要 建议存入数据库
            String[] result = sc.upload_file("D:/001.png", "png", null);

            for (String s : result) {
                System.out.println(s);
            }
        } catch (IOException | MyException e) {
            e.printStackTrace();
        }
    }

    /**
     * 文件下载
     */
    public static void fileDownload() {
        try {
            //获取StorageClient对象
            StorageClient sc = getStorageClient();

            //group_name为需要下载的文件的组名
            //remote_filename为需要下载的文件的远程文件名
            //local_filename为需要下载的文件保存到哪个位置的绝对路径名
            //返回值为int 若为0则表示下载成功 其它值都表示下载失败
            int result = sc.download_file("group1",
                    "M00/00/00/wKi-g2NFYm6AYYnwAAEXDUO_9AI243.png",
                    "D:/002.png");

            System.out.println(result);
        } catch (IOException | MyException e) {
            e.printStackTrace();
        }
    }

    /**
     * 文件删除
     */
    public static void fileDelete() {
        try {
            //获取StorageClient对象
            StorageClient sc = getStorageClient();

            //group_name为需要删除的文件的组名
            //remote_filename为需要删除的文件的远程文件名
            //返回值为int 若为0则表示删除成功 其它值都表示删除失败
            int result = sc.delete_file("group1",
                    "M00/00/00/wKi-g2NFYm6AYYnwAAEXDUO_9AI243.png");

            System.out.println(result);
        } catch (IOException | MyException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取StorageClient对象
     * @return StorageClient对象
     */
    public static StorageClient getStorageClient() {
        StorageClient sc = null;
        try {
            //读取fastdfs的配置文件以将所有的tracker_server地址读取到内存中
            //默认从classpath中读取
            ClientGlobal.init("fdfs_client.conf");

            //创建TrackerClient对象以获取TrackerServer对象与StorageServer对象
            TrackerClient tc = new TrackerClient();
            TrackerServer ts = tc.getTrackerServer();
            StorageServer ss = tc.getStoreStorage(ts);

            //通过TrackerServer对象与StorageServer对象来获取StorageClient对象
            //使用StorageClient对象以实现对文件的操作
            sc = new StorageClient(ts, ss);
        } catch (IOException | MyException e) {
            e.printStackTrace();
        }
        return sc;
    }
}

启动nginx、fastdfs以进行测试

#永久关闭防火墙
systemctl stop firewalld
systemctl disable firewalld

#启动nginx
./nginx -c /opt/nginx/conf/nginx.conf

#启动fastdfs
fdfs_trackerd /etc/fdfs/tracker.conf
fdfs_storaged /etc/fdfs/storage.conf

#访问http://192.168.190.131/group1/M00/00/00/wKi-g2NFYm6AYYnwAAEXDUO_9AI243.png或查看fastdfs的存储文件的目录

fastdfs-web

  • 使用springboot项目

配置pom

添加依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.2</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- fastdfs依赖 -->
    <dependency>
        <groupId>org.csource</groupId>
        <artifactId>fastdfs-client-java</artifactId>
        <version>1.29-SNAPSHOT</version>
    </dependency>
</dependencies>

配置build

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludes>
                    <exclude>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                    </exclude>
                </excludes>
            </configuration>
        </plugin>

        <!-- mybatis代码自动生成插件 -->
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.4.1</version>
            <configuration>
                <!-- 指定GeneratorMapper.xml文件的位置 -->
                <!-- 此位置在模块下 即此文件与src目录、pom文件同级 -->
                <configurationFile>GeneratorMapper.xml</configurationFile>
                <verbose>true</verbose>
                <overwrite>true</overwrite>
            </configuration>
        </plugin>
    </plugins>

    <resources>
        <resource>
            <directory>src/main/java</directory>

            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>

            <filtering>false</filtering>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.*</include>
            </includes>
        </resource>
    </resources>
</build>

创建数据库

创建GeneratorMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>

    <!-- 指定jdbc的jar包的位置 -->
    <classPathEntry location="D:\maven\maven_repository\mysql\mysql-connector-java\8.0.30\mysql-connector-java-8.0.30.jar"/>

    <!-- 配置内容 -->
    <!-- targetRuntime指定采用MyBatis3的版本 -->
    <context id="tables" targetRuntime="MyBatis3">

        <!-- 抑制生成注释 -->
        <!-- 由于生成的注释都是英文的 因此可以不生成注释 -->
        <commentGenerator>
            <property name="suppressAllComments" value="true" />
        </commentGenerator>

        <!-- 配置datasource -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/fastdfs?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false"
                        userId="root"
                        password="123456">
        </jdbcConnection>

        <!-- 生成实体类 -->
        <!-- targetPackage指定实体类的包名 targetProject指定实体类包的位置 -->
        <javaModelGenerator targetPackage="com.model" targetProject="src/main/java">
            <property name="enableSubPackages" value="false" />
            <property name="trimStrings" value="false" />
        </javaModelGenerator>

        <!-- 生成mapper文件 -->
        <!-- targetPackage指定mapper文件的包名 targetProject指定mapper包的位置 -->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>

        <!-- 生成dao接口 -->
        <!-- targetPackage指定dao接口的包名 targetProject指定dao接口包的位置 -->
        <javaClientGenerator type="XMLMAPPER"
                             targetPackage="com.dao" targetProject="src/main/java">
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>

        <!-- db的表名以及对应的实体类名 -->
        <table tableName="t_fastdfs" domainObjectName="User"
               enableCountByExample="false"
               enableUpdateByExample="false"
               enableDeleteByExample="false"
               enableSelectByExample="false"
               selectByExampleQueryId="false"/>
    </context>
</generatorConfiguration>

双击mybatis反向工程插件

  • 记得先clean

配置application.yml

spring:
  #配置数据库
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/fastdfs?useUnicode=true&characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
  #配置thymeleaf
  thymeleaf:
    #关闭模板缓存以让修改立即生效
    #开发阶段设为false 上线阶段设为true
    cache: false
  servlet:
    multipart:
      #设置上传的单个文件的大小的最大值 默认为1MB
      max-file-size: 1MB
      #设置上传的总数据的大小的最大值 默认为10MB
      max-request-size: 10MB

#配置mybatis
mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

修改UserMapper

@Mapper
public interface UserMapper {
    int deleteByPrimaryKey(Integer id);

    int insert(User row);

    int insertSelective(User row);

    List<User> selectAll();

    User selectByPrimaryKey(Integer id);

    int updateByPrimaryKeySelective(User row);

    int updateByPrimaryKey(User row);
}

修改UserMapper.xml

<select id="selectAll" resultMap="BaseResultMap">
	select
	<include refid="Base_Column_List"/>
	from t_fastdfs
</select>

创建service接口及其实现类

public interface UserService {

    List<User> selectAll();

    User selectById(Integer id);

    int updateUser(User user);

    int deleteUserById(Integer id);
}
@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public List<User> selectAll() {
        return userMapper.selectAll();
    }

    @Override
    public User selectById(Integer id) {
        return userMapper.selectByPrimaryKey(id);
    }

    @Override
    public int updateUser(User user) {
        return userMapper.updateByPrimaryKeySelective(user);
    }

    @Override
    public int deleteUserById(Integer id) {
        return userMapper.deleteByPrimaryKey(id);
    }
}

创建FastDFSUtil工具类

public class FastDFSUtil {

    /**
     * 文件上传
     */
    public static String[] fileUpload(byte[] file_buff, String file_ext_name, NameValuePair[] meta_list) {
        try {
            //获取StorageClient对象
            StorageClient sc = getStorageClient();

            //file_buff为需要上传的文件的字节数组
            //file_ext_name为需要上传的文件的扩展名
            //meta_list为需要上传的文件的属性信息 通常为null 即不用上传
            //返回值为String[] strings[0]为组名(如group1) strings[1]为远程文件名(如M00/00/00/wKi-g2NFYm6AYYnwAAEXDUO_9AI243.png)
            //此返回值很重要 建议存入数据库
            String[] result = sc.upload_file(file_buff, file_ext_name, meta_list);

            return result;
        } catch (IOException | MyException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 文件下载
     */
    public static byte[] fileDownload(String group_name, String remote_filename) {
        try {
            //获取StorageClient对象
            StorageClient sc = getStorageClient();

            //group_name为需要下载的文件的组名
            //remote_filename为需要下载的文件的远程文件名
            //返回值为int 若为0则表示下载成功 其它值都表示下载失败
            byte[] result = sc.download_file(group_name, remote_filename);

            return result;
        } catch (IOException | MyException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 文件删除
     */
    public static Integer fileDelete(String group_name, String remote_filename) {
        try {
            //获取StorageClient对象
            StorageClient sc = getStorageClient();

            //group_name为需要删除的文件的组名
            //remote_filename为需要删除的文件的远程文件名
            //返回值为int 若为0则表示删除成功 其它值都表示删除失败
            int result = sc.delete_file(group_name, remote_filename);

            return result;
        } catch (IOException | MyException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取StorageClient对象
     * @return StorageClient对象
     */
    public static StorageClient getStorageClient() {
        StorageClient sc = null;
        try {
            //读取fastdfs的配置文件以将所有的tracker_server地址读取到内存中
            //默认从classpath中读取
            ClientGlobal.init("fdfs_client.conf");

            //创建TrackerClient对象以获取TrackerServer对象与StorageServer对象
            TrackerClient tc = new TrackerClient();
            TrackerServer ts = tc.getTrackerServer();
            StorageServer ss = tc.getStoreStorage(ts);

            //通过TrackerServer对象与StorageServer对象来获取StorageClient对象
            //使用StorageClient对象以实现对文件的操作
            sc = new StorageClient(ts, ss);
        } catch (IOException | MyException e) {
            e.printStackTrace();
        }
        return sc;
    }
}

在resources下生成fdfs_client.conf文件

connect_timeout = 2
network_timeout = 30
charset = UTF-8
http.tracker_http_port = 8080
http.anti_steal_token = no
http.secret_key = FastDFS1234567890

tracker_server = 192.168.190.131: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

创建controller

@Controller
public class UserController {

    @Resource
    private UserService userService;

    @GetMapping("/")
    public String selectAll(Model model) {
        List<User> list = userService.selectAll();
        model.addAttribute("list", list);
        return "index";
    }

    @GetMapping("/toUpload/{id}")
    public String toUpload(Model model, @PathVariable Integer id) {
        User user = userService.selectById(id);
        model.addAttribute("user", user);
        return "upload";
    }

    //MultipartFile为spring提供的一个接口 专门用来获取文件的数据
    //注意 MultipartFile的参数名必须与表单中的文件的name属性保持一致 否则获取不到MultipartFile
    @PostMapping("/upload")
    public String upload(Model model, Integer id, MultipartFile myFile) {
        try {
            System.out.println(myFile.getOriginalFilename());
            //myFile.getName()获取的是表单中的文件的name属性
            System.out.println(myFile.getName());
            System.out.println(myFile.getContentType());
            System.out.println(myFile.getSize());
            System.out.println(myFile.isEmpty());

            byte[] file_buff = myFile.getBytes();
            String filename = myFile.getOriginalFilename();
            //注意 有些文件是没有扩展名的 因此此处并不能这样写 而要做逻辑控制 但此处为测试 因此不做处理
            String file_ext_name = filename.substring(filename.lastIndexOf(".") + 1);

            String[] result = FastDFSUtil.fileUpload(file_buff, file_ext_name, null);

            User user = new User();
            user.setId(id);
            user.setFilename(myFile.getOriginalFilename());
            user.setFilesize(myFile.getSize());
            user.setGroupname(result[0]);
            user.setRemotefilepath(result[1]);

            int ret = userService.updateUser(user);

            if (ret == 1) {
                model.addAttribute("message", "文件上传成功,点击 确定 返回主页面!");
            } else {
                model.addAttribute("message", "文件上传失败,点击 确定 返回主页面!");
            }
            model.addAttribute("url", "/");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "result";
    }

    //ResponseEntity为spring提供的一个类
    @GetMapping("/download/{id}")
    public ResponseEntity<byte[]> download(@PathVariable Integer id) {
        User user = userService.selectById(id);
        byte[] result = FastDFSUtil.fileDownload(user.getGroupname(), user.getRemotefilepath());

        //创建响应头对象以设置其属性
        HttpHeaders httpHeaders = new HttpHeaders();
        //设置响应头的类型为流类型
        httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        //设置响应头的文件大小 用来提供下载器的属性(如下载速度)显示
        httpHeaders.setContentLength(user.getFilesize());
        //设置下载文件时的文件名为user.getFilename() 也可以使用时间戳+扩展名的方式
        httpHeaders.setContentDispositionFormData("attachment",
                //处理下载的文件的文件名的中文乱码问题
                new String(user.getFilename().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));

        //创建响应体对象
        //body为响应数据 此数据可以为html代码/js代码/字符串/一个文件的流/..
        //headers为响应头的信息
        //status为响应后想要显示的状态码
        ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(result, httpHeaders, HttpStatus.OK);
        return responseEntity;
    }

    @GetMapping("/delete/{id}")
    public String delete(Model model, @PathVariable Integer id) {
        User user = userService.selectById(id);

        //此处应添加事务控制 但此处为测试 因此不做处理
        int ret = userService.deleteUserById(id);
        Integer result = FastDFSUtil.fileDelete(user.getGroupname(), user.getRemotefilepath());

        if (result == 0 && ret == 1) {
            model.addAttribute("message", "文件删除成功,点击 确定 返回主页面!");
        } else {
            model.addAttribute("message", "文件删除失败,点击 确定 返回主页面!");
        }
        model.addAttribute("url", "/");
        return "result";
    }
}

在templates下创建对应的页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
<center>
    <table>
        <tr>
            <td>序号</td>
            <td>姓名</td>
            <td>文件名称</td>
            <td>文件大小</td>
            <td>操作</td>
        </tr>
        <tr th:each="user:${list}">
            <td th:text="${userStat.count}">序号</td>
            <td th:text="${user.name}">姓名</td>
            <!-- "${user.filename} ?: '无'"用于非null判断 即当user.filename为null时显示"无" 否则显示user.filename -->
            <!-- 但不能用于非""判断 -->
            <td th:text="${user.filename == null || user.filename == '' ? '' : user.filename}">文件名称</td>
            <td th:text="${user.filesize == null || user.filename == '' ? '' : user.filesize}">文件大小</td>
            <td>
                <span th:if="${user.remotefilepath == null || user.remotefilepath == ''}">
                    <a th:href="@{|/toUpload/${user.id}|}">上传</a>
                </span>
                <span th:if="${user.remotefilepath != null && user.remotefilepath != ''}">
                    <a th:href="@{|/download/${user.id}|}">下载</a>
                    <a th:href="@{|/delete/${user.id}|}">删除</a>
                </span>
            </td>
        </tr>
    </table>
</center>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>upload</title>
</head>
<body>
<center>
    <form th:action="@{|/upload|}" method="post" enctype="multipart/form-data">
        姓名:<span th:text="${user.name}"></span><br>
        文件:<input type="file" name="myFile"/><br>
        <input type="hidden" name="id" th:value="${user.id}"/>
        <input type="submit" value="上传文件"/>
    </form>
</center>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>result</title>
</head>
<body>
<center>
  <h3 th:text="${message}">result</h3>
  <a th:href="@{${url}}">确定</a>
</center>
</body>
</html>

测试

fastdfs-cluster

  • 架构图

环境搭建

安装7个linux-minimal

安装fastdfs与nginx

#先安装linux-minimal缺少的但常用的工具库
yum install git lrzsz wget vim unzip net-tools -y

#再安装fastdfs与nginx所需要的第三方库 否则配置/编译时可能会报错
yum install gcc openssl openssl-devel pcre pcre-devel zlib zlib-devel libevent libevent-devel -y
#下载、编译且安装libfastcommon库
git clone https://github.com/happyfish100/libfastcommon.git --depth 1
cd /opt/libfastcommon-1.0.62
./make.sh
./make.sh install
#下载、编译且安装libserverframe库
git clone https://github.com/happyfish100/libserverframe.git --depth 1
cd /opt/libserverframe-1.1.21
./make.sh
./make.sh install

#从github中下载、编译且安装fastdfs、nginx、fastdfs-nginx模块
#下载、编译且安装fastdfs
git clone https://github.com/happyfish100/fastdfs.git --depth 1
cd /opt/fastdfs-6.09
./make.sh
./make.sh install
#下载并解压nginx
wget https://nginx.org/download/nginx-1.22.0.tar.gz
tar -zxvf nginx-1.22.0.tar.gz
#下载fastdfs-nginx模块
git clone https://github.com/happyfish100/fastdfs-nginx-module.git --depth 1
#配置nginx的安装路径以及要添加的模块的源代码路径
cd /opt/nginx-1.22.0
#对tracker与入口即192.168.190.131的nginx的配置
./configure --prefix=/opt/nginx
#对storage的配置
./configure --prefix=/opt/nginx --add-module=/opt/fastdfs-nginx-module/src
#编译且安装nginx
make
make install

修改配置文件

#先拷贝/备份文件
cp /opt/fastdfs-6.09/conf/http.conf /etc/fdfs
cp /opt/fastdfs-6.09/conf/mime.types /etc/fdfs
cd /etc/fdfs
cp client.conf client.conf.sample
cp storage.conf storage.conf.sample
cp storage_ids.conf storage_ids.conf.sample
cp tracker.conf tracker.conf.sample
#tracker不需要 storage才需要
cp /opt/fastdfs-nginx-module/src/mod_fastdfs.conf /etc/fdfs

#修改tracker.conf文件
base_path = /opt/fastdfs/tracker
#修改fastdfs的负载均衡策略
#0为轮询组 1为指定组(若为1 则还要配置store_group来指定哪个组) 2为选择剩余空间最大的组(默认值)
store_lookup=0
#创建上述目录
mkdir -p /opt/fastdfs/tracker

#修改storage.conf文件
#X修改成具体的值
group_name=groupX
base_path = /opt/fastdfs/storage
store_path0 = /opt/fastdfs/storage/files
tracker_server = 192.168.190.132:22122
tracker_server = 192.168.190.133:22122
#创建上述目录
mkdir -p /opt/fastdfs/storage/files

#修改mod_fastdfs.conf文件
#tracker不需要 storage才需要
base_path=/opt/fastdfs/fastdfs-nginx
tracker_server=192.168.190.132:22122
tracker_server=192.168.190.133:22122
#X修改成具体的值
group_name=groupX
url_have_group_name = true
store_path0=/opt/fastdfs/storage/files
group_count=2
#在末尾添加
[group1]
group_name=group1
storage_server_port=23000
store_path_count=1
store_path0=/opt/fastdfs/storage/files
[group2]
group_name=group2
storage_server_port=23000
store_path_count=1
store_path0=/opt/fastdfs/storage/files
#创建上述目录
mkdir -p /opt/fastdfs/fastdfs-nginx

配置nginx

#对tracker的配置
location ~ /group[1-9]/M0[0-9] {
	proxy_pass http://fastdfs_server;
}
upstream fastdfs_server {
	server 192.168.190.134:80;
	server 192.168.190.135:80;
	server 192.168.190.136:80;
	server 192.168.190.137:80;
}

#对storage的配置
location ~ /group[1-9]/M0[0-9] {
	ngx_fastdfs_module;
}

#对入口的配置
location ~ /group[1-9]/M0[0-9] {
	proxy_pass http://fastdfs_server;
}
upstream fastdfs_server {
	server 192.168.190.132:80;
	server 192.168.190.133:80;
}

启动fastdfs与nginx

#永久关闭防火墙
systemctl stop firewalld
systemctl disable firewalld

#启动fastdfs
#对tracker
fdfs_trackerd /etc/fdfs/tracker.conf
#对storage
fdfs_storaged /etc/fdfs/storage.conf

#启动nginx
cd /opt/nginx/sbin
./nginx -c /opt/nginx/conf/nginx.conf -t
./nginx -c /opt/nginx/conf/nginx.conf

测试文件上传、下载、删除

  • 使用之前的java程序测试文件上传、下载、删除操作
  • 为了保证高可用 通常会在入口nginx处再添加一个备用的nginx 并使用keepalive(通常由运维人员使用 作用为当主nginx出现故障后会自动切换到备用nginx)连接两者

有关fastdfs详解的更多相关文章

  1. 物联网MQTT协议详解 - 2

    一、什么是MQTT协议MessageQueuingTelemetryTransport:消息队列遥测传输协议。是一种基于客户端-服务端的发布/订阅模式。与HTTP一样,基于TCP/IP协议之上的通讯协议,提供有序、无损、双向连接,由IBM(蓝色巨人)发布。原理:(1)MQTT协议身份和消息格式有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。MQTT传输的消息分为:主题(Topic)和负载(payload)两部分Topic,可以理解为消息的类型,订阅者订阅(Su

  2. Tcl脚本入门笔记详解(一) - 2

    TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是

  3. 【详解】Docker安装Elasticsearch7.16.1集群 - 2

    开门见山|拉取镜像dockerpullelasticsearch:7.16.1|配置存放的目录#存放配置文件的文件夹mkdir-p/opt/docker/elasticsearch/node-1/config#存放数据的文件夹mkdir-p/opt/docker/elasticsearch/node-1/data#存放运行日志的文件夹mkdir-p/opt/docker/elasticsearch/node-1/log#存放IK分词插件的文件夹mkdir-p/opt/docker/elasticsearch/node-1/plugins若你使用了moba,直接右键新建即可如上图所示依次类推创建

  4. 【Elasticsearch基础】Elasticsearch索引、文档以及映射操作详解 - 2

    文章目录概念索引相关操作创建索引更新副本查看索引删除索引索引的打开与关闭收缩索引索引别名查询索引别名文档相关操作新建文档查询文档更新文档删除文档映射相关操作查询文档映射创建静态映射创建索引并添加映射概念es中有三个概念要清楚,分别为索引、映射和文档(不用死记硬背,大概有个印象就可以)索引可理解为MySQL数据库;映射可理解为MySQL的表结构;文档可理解为MySQL表中的每行数据静态映射和动态映射上面已经介绍了,映射可理解为MySQL的表结构,在MySQL中,向表中插入数据是需要先创建表结构的;但在es中不必这样,可以直接插入文档,es可以根据插入的文档(数据),动态的创建映射(表结构),这就

  5. 最强Http缓存策略之强缓存和协商缓存的详解与应用实例 - 2

    HTTP缓存是指浏览器或者代理服务器将已经请求过的资源保存到本地,以便下次请求时能够直接从缓存中获取资源,从而减少网络请求次数,提高网页的加载速度和用户体验。缓存分为强缓存和协商缓存两种模式。一.强缓存强缓存是指浏览器直接从本地缓存中获取资源,而不需要向web服务器发出网络请求。这是因为浏览器在第一次请求资源时,服务器会在响应头中添加相关缓存的响应头,以表明该资源的缓存策略。常见的强缓存响应头如下所述:Cache-ControlCache-Control响应头是用于控制强制缓存和协商缓存的缓存策略。该响应头中的指令如下:max-age:指定该资源在本地缓存的最长有效时间,以秒为单位。例如:Ca

  6. IDEA 2022 创建 Spring Boot 项目详解 - 2

    如何用IDEA2022创建并初始化一个SpringBoot项目?目录如何用IDEA2022创建并初始化一个SpringBoot项目?0. 环境说明1.  创建SpringBoot项目 2.编写初始化代码0. 环境说明IDEA2022.3.1JDK1.8SpringBoot1.  创建SpringBoot项目        打开IDEA,选择NewProject创建项目。        填写项目名称、项目构建方式、jdk版本,按需要修改项目文件路径等信息。        选择springboot版本以及需要的包,此处只选择了springweb。        此处需特别注意,若你使用的是jdk1

  7. 详解Unity中的粒子系统Particle System (二) - 2

    前言上一篇我们简要讲述了粒子系统是什么,如何添加,以及基本模块的介绍,以及对于曲线和颜色编辑器的讲解。从本篇开始,我们将按照模块结构讲解下去,本篇主要讲粒子系统的主模块,该模块主要是控制粒子的初始状态和全局属性的,以下是关于该模块的介绍,请大家指正。目录前言本系列提要一、粒子系统主模块1.阅读前注意事项2.参考图3.参数讲解DurationLoopingPrewarmStartDelayStartLifetimeStartSpeed3DStartSizeStartSize3DStartRotationStartRotationFlipRotationStartColorGravityModif

  8. VMware虚拟机与本地主机进行磁盘共享(详解) - 2

    VMware虚拟机与本地主机进行磁盘共享前提虚拟机版本为Windows10(专业版,不是可能有问题)本地主机为家庭版或学生版(此版本会有问题,但有替代方式)最好是专业版VMware操作1.关闭防火墙,全部关闭。2.打开电脑属性3.点击共享-》高级共享-》权限4.如果没有everyone,就添加权限选择完全控制,然后应用确定。5.打开cmd输入lusrmgr.msc(只有专业版可以打开)如果不是专业版,可以跳过这一步。点击用户-》administrator密码要复杂密码,否则不行。推荐admaiN@1234类型的密码。设置完密码,点击属性,将禁用解开。6.如果虚拟机的windows不是专业版,可

  9. ElasticSearch之 ik分词器详解 - 2

    IK分词器本文分为简介、安装、使用三个角度进行讲解。简介倒排索引众所周知,ES是一个及其强大的搜索引擎,那么它为什么搜索效率极高呢,当然和他的存储方式脱离不了关系,ES采取的是倒排索引,就是反向索引;常见索引结构几乎都是通过key找value,例如Map;倒排索引的优势就是有效利用Value,将多个含有相同Value的值存储至同一位置。分词器为了配合倒排索引,分词器也就诞生了,只有合理的利用Value,才会让倒排索引更加高效,如果一整个Value不进行任何操作直接进行存储,那么Value和key毫无区别。分词器Analyzer通常会对Value进行操作:一、字符过滤,过滤掉html标签;二、分

  10. Educational Codeforces Round 146 (Rated for Div. 2)(B,E详解) - 2

    题外话:抑郁场,开局一小时只出A,死活想不来B,最后因为D题出锅ura才保住可怜的分。但咱本来就写不到DB-LongLegs(数论)本题题解法一学自同样抑郁的知乎作者幽血魅影的题解,有讲解原理。法二来着知乎巨佬cup-pyy(大佬说《不难发现》呜呜)题意三种操作:向上走mmm步向右走mmm步给自己一次走的步数加111,即使得m=m+1m=m+1m=m+1问从(0,0)(0,0)(0,0)走到(a,b)(a,b)(a,b)的最小操作次数,值得注意的是操作三不可逆。解析假设我们最终一步的大小增长到mmm,那么在这个过程中我能以[1,m][1,m][1,m](当步数增长到该数时)之间的任何数字向上或

随机推荐