美文网首页后端砖头分布式系统分布式
分布式--分布式文件系统FastDFS

分布式--分布式文件系统FastDFS

作者: aruba | 来源:发表于2022-06-18 10:51 被阅读0次

    大型项目中,文件服务器是很重要的角色,如果只有一台文件服务器,一旦当机,会产生很大影响,和业务服务器不同,文件服务器主要还是处理存放文件,和读取文件的功能

    专用分布式文件系统是基于google File System的思想,文件上传后不能修改。需要专门的api对文件进行访问,也可称作分布式文件存储服务。典型代表:MogileFS、FastDFS、TFS

    FastDFS由国人余庆开发,在chinaunix中担任FastDFS版主。FastDFS的架构如下:

    三个角色:

    角色 描述
    Client 客户端,调用方,就是我们的业务服务器的代码
    Tracker 跟踪服务器(索引服务器),调度中心,作负载均衡作用
    Storage 存储服务器,文件真实的存储服务器,同组内使用RAID1

    由图可知,Tracker和Storage都支持集群,Storage内部以组来区分

    一、FastDFS安装

    1. 工具安装

    FastDFS是由c/c++编写,使用了cmake进行编译,所以需要安装cmake,至于make、gcc、g++编译器,一般linux都自带

    yum install cmake
    

    2. FastDFS下载

    地址(墙):https://sourceforge.net/projects/fastdfs/files/
    可以从我代码仓库中下载:https://gitee.com/aruba/fast-dfs.git

    传到服务器中:

    3. 解压编译libfastcommon-master

    解压:

    unzip libfastcommon-master.zip
    cd libfastcommon-master
    

    使用make.sh编译:

    ./make.sh
    

    编译完后安装:

    ./make.sh install
    

    创建软连接:

    ln -s /user/lib64/libfastcommon.so /usr/local/lib/libfastcommon.so
    ln -s /usr/local/lib64/libfdfsclient.so /usr/local/lib/libfdfsclient.so
    

    4. 解压编译FastDFS

    解压:

    tar zxf FastDFS_v5.08.tar.gz 
    cd FastDFS
    

    编译安装:

    ./make.sh
    ./make.sh install
    

    安装后 FastDFS主程序所在的位置:

    目录 描述
    /usr/bin 可执行文件所在的位置、主程序代码所在位置
    /etc/fdfs 配置文件所在的位置
    /usr/include/fastdfs 包含一些插件组所在的位置

    5. 配置和启动Tracker

    创建存放tracker数据的目录:

    mkdir -p /usr/local/fastdfs/tracker
    

    进入配置目录:

    cd /etc/fdfs
    

    复制sample配置文件:

    cp tracker.conf.sample  tracker.conf
    

    修改配置文件:

    vi tracker.conf
    

    指定为上面创建的tracker目录路径:

    启动服务和查看服务启动状态命令:

    service fdfs_trackerd start
    service fdfs_trackerd status
    

    6. 配置和启动Storage

    Tracker和Storage可以在不同服务器上

    创建两个目录, 把base用于存储基础数据和日志,store用于存储上传数据:

    mkdir -p /usr/local/fastdfs/storage/base
    mkdir -p /usr/local/fastdfs/storage/store
    

    复制sample配置文件:

    cp storage.conf.sample storage.conf
    

    修改配置文件:

    vi storage.conf
    

    指定上面创建的base和store目录路径,以及Tracker服务器地址

    • base_path:


    • store_path0:


    • tracker_server:


    启动和查看服务:

    service fdfs_storaged start
    service fdfs_storaged status
    

    最后记得关闭防火墙

    二、FastDFS文件上传和下载

    FastDFS文件操作需要专门的API,不过都已经封装成工具类了

    1. 依赖

            <dependency>
                <groupId>cn.bestwu</groupId>
                <artifactId>fastdfs-client-java</artifactId>
                <version>1.27</version>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>3.4</version>
            </dependency>
    

    2. fdfs_client.conf

    在resources目录下新建配置文件fdfs_client.conf:

    connect_timeout = 10
    network_timeout = 30
    charset = UTF-8
    http.tracker_http_port = 8080
    tracker_server = 192.168.42.4:22122
    

    tracker服务器ip改为自己的

    3. 工具类

    /**
     * FastDFS分布式文件系统操作客户端.
     */
    public class FastDFSClient {
    
        private static final String CONF_FILENAME = Thread.currentThread().getContextClassLoader().getResource("").getPath() + "fdfs_client.conf";
    
        private static StorageClient storageClient = null;
    
        /**
         * 只加载一次.
         */
        static {
            try {
                ClientGlobal.init(CONF_FILENAME);
                TrackerClient trackerClient = new TrackerClient(ClientGlobal.g_tracker_group);
                TrackerServer trackerServer = trackerClient.getConnection();
                StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
                storageClient = new StorageClient(trackerServer, storageServer);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * @param inputStream 上传的文件输入流
         * @param fileName    上传的文件原始名
         * @return
         */
        public static String[] uploadFile(InputStream inputStream, String fileName) {
            try {
                // 文件的元数据
                NameValuePair[] meta_list = new NameValuePair[2];
                // 第一组元数据,文件的原始名称
                meta_list[0] = new NameValuePair("file name", fileName);
                // 第二组元数据
                meta_list[1] = new NameValuePair("file length", inputStream.available() + "");
                // 准备字节数组
                byte[] file_buff = null;
                if (inputStream != null) {
                    // 查看文件的长度
                    int len = inputStream.available();
                    // 创建对应长度的字节数组
                    file_buff = new byte[len];
                    // 将输入流中的字节内容,读到字节数组中。
                    inputStream.read(file_buff);
                }
                // 上传文件。参数含义:要上传的文件的内容(使用字节数组传递),上传的文件的类型(扩展名),元数据
                String[] fileids = storageClient.upload_file(file_buff, getFileExt(fileName), meta_list);
                return fileids;
            } catch (Exception ex) {
                ex.printStackTrace();
                return null;
            }
        }
    
        /**
         * @param file     文件
         * @param fileName 文件名
         * @return 返回Null则为失败
         */
        public static String[] uploadFile(File file, String fileName) {
            FileInputStream fis = null;
            try {
                NameValuePair[] meta_list = null; // new NameValuePair[0];
                fis = new FileInputStream(file);
                byte[] file_buff = null;
                if (fis != null) {
                    int len = fis.available();
                    file_buff = new byte[len];
                    fis.read(file_buff);
                }
    
                String[] fileids = storageClient.upload_file(file_buff, getFileExt(fileName), meta_list);
                return fileids;
            } catch (Exception ex) {
                return null;
            } finally {
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        /**
         * 根据组名和远程文件名来删除一个文件
         *
         * @param groupName      例如 "group1" 如果不指定该值,默认为group1
         * @param remoteFileName 例如"M00/00/00/wKgxgk5HbLvfP86RAAAAChd9X1Y736.jpg"
         * @return 0为成功,非0为失败,具体为错误代码
         */
        public static int deleteFile(String groupName, String remoteFileName) {
            try {
                int result = storageClient.delete_file(groupName == null ? "group1" : groupName, remoteFileName);
                return result;
            } catch (Exception ex) {
                return 0;
            }
        }
    
        /**
         * 修改一个已经存在的文件
         *
         * @param oldGroupName 旧的组名
         * @param oldFileName  旧的文件名
         * @param file         新文件
         * @param fileName     新文件名
         * @return 返回空则为失败
         */
        public static String[] modifyFile(String oldGroupName, String oldFileName, File file, String fileName) {
            String[] fileids = null;
            try {
                // 先上传
                fileids = uploadFile(file, fileName);
                if (fileids == null) {
                    return null;
                }
                // 再删除
                int delResult = deleteFile(oldGroupName, oldFileName);
                if (delResult != 0) {
                    return null;
                }
            } catch (Exception ex) {
                return null;
            }
            return fileids;
        }
    
        /**
         * 文件下载
         *
         * @param groupName      卷名
         * @param remoteFileName 文件名
         * @return 返回一个流
         */
        public static InputStream downloadFile(String groupName, String remoteFileName) {
            try {
                byte[] bytes = storageClient.download_file(groupName, remoteFileName);
                InputStream inputStream = new ByteArrayInputStream(bytes);
                return inputStream;
            } catch (Exception ex) {
                return null;
            }
        }
    
        public static NameValuePair[] getMetaDate(String groupName, String remoteFileName) {
            try {
                NameValuePair[] nvp = storageClient.get_metadata(groupName, remoteFileName);
                return nvp;
            } catch (Exception ex) {
                ex.printStackTrace();
                return null;
            }
        }
    
        /**
         * 获取文件后缀名(不带点).
         *
         * @return 如:"jpg" or "".
         */
        private static String getFileExt(String fileName) {
            if (StringUtils.isBlank(fileName) || !fileName.contains(".")) {
                return "";
            } else {
                return fileName.substring(fileName.lastIndexOf(".") + 1); // 不带最后的点
            }
        }
    
    }
    

    4. 文件上传

    public class Upload {
    
        public static void main(String[] args) {
            InputStream is = null;
            try {
                is = new FileInputStream(new File("C:\\Users\\tyqhc\\Pictures\\苹果.png"));
                String[] ret = FastDFSClient.uploadFile(is, "苹果.png");
                System.out.println(Arrays.toString(ret));
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    }
    

    执行结果:

    5. 文件下载

    public class DownLoad {
    
        public static void main(String[] args) {
            InputStream inputStream = null;
            OutputStream outputStream = null;
            try {
                inputStream = FastDFSClient.downloadFile("group1", "M00/00/00/wKgqBGKsFF2AToPoAAAnvQxtles494.png");
                outputStream = new FileOutputStream(new File("D:\\a.jpg"));
                int index = 0;
                while ((index = inputStream.read()) != -1) {
                    outputStream.write(index);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (outputStream != null) {
                    try {
                        outputStream.flush();
                        outputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    }
    

    执行结果:

    三、Nginx反向代理

    由于FastDFS不支持http,上传后就无法通过http访问了,所以使用Nginx反向代理,来支持使用http进行访问

    Nginx安装前,可以使用yum下载工具,就不用手动配置openssl等了:

    yum install -y automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl openssl-devel
    

    Nginx下载可以参考以前的文章:Nginx流媒体服务器搭建,只需要下载nginx,并解压

    1. 解压fastdfs-nginx-module_v1.16.tar.gz

    tar zxf fastdfs-nginx-module_v1.16.tar.gz
    

    2. 修改module配置

    cd fastdfs-nginx-module
    cd src
    vi config
    

    路径中local去掉,修改后:

    3. 编译和安装nginx

    编译nginx,指定编译安装到当前目录的bin目录下,并添加FastDFS的module:

    cd /root/nginx/nginx-1.12.1
    ./configure --prefix=`pwd`/bin --add-module=/root/nginx/fastdfs-nginx-module/src
    

    安装nginx:

    make install
    

    4. 配置FastDFS

    4.1 将fastdfs-nginx-module模块中的配置文件mod_fastdfs.conf,复制到FastDFS配置文件目录/etc/fdfs
    cp /root/nginx/fastdfs-nginx-module/src/mod_fastdfs.conf /etc/fdfs
    

    并进行编辑:

    vi /etc/fdfs/mod_fastdfs.conf
    

    修改内容如下:

    方便复制:
    connect_timeout=10
    tracker_server=192.168.42.4:22122
    url_have_group_name=true
    store_path0=/usr/local/fastdfs/storage/store

    4.2 复制FastDFS解压后的conf文件至/etc/fdfs下:
    cp /root/fastdfs/FastDFS/conf/http.conf /etc/fdfs
    cp /root/fastdfs/FastDFS/conf/mime.types /etc/fdfs
    
    4.3 创建软连接

    M00是FastDFS保存数据时使用的虚拟目录,需要当它链接到真实目录上:

    ln -s /usr/local/fastdfs/storage/store/data/ /usr/local/fastdfs/storage/store/data/M00
    

    5. 配置nginx

    接下来就到了配置nginx的环节,配置nginx只需要修改它的nginx.conf文件

    5.1 打开nginx.conf文件

    定位到nginx安装的目录,通过vi编辑器打开:

    cd /root/nginx/nginx-1.12.1/bin/conf
    
    vi nginx.conf
    
    5.2 权限使用root用户

    nginx需要访问文件权限,所以给它最大的用户权限:

    5.3 配置反向代理

    FastDFS为http预留的是哪个端口呢?查看/etc/fdfs/storage.conf 文件:

    vi /etc/fdfs/storage.conf
    

    可以看到默认使用的是8888:


    所以我们需要反向代理8888端口,匹配group0到group9的/M00下的文件请求

    5.4 启动nginx
    cd ../sbin
    ./nginx
    

    使用浏览器访问我们之前上传的图片:
    ip:8888/group1/M00/00/00/wKgqBGKsFF2AToPoAAAnvQxtles494.png

    四、SpringBoot中使用

    服务器配置好后,后台开发上传功能,无非就是在service层使用FastDFS工具类上传,使用之前SpringBoot文件上传的项目:SpringBoot--文件上传

    将上面文件上传的工具类和依赖进行相同配置:

    1. service层

    定义上传接口:

    public interface UploadService {
    
        String upload(MultipartFile file) throws IOException;
    
    }
    

    实现接口:

    @Service
    public class UploadServiceImpl implements UploadService {
    
        @Override
        public String upload(MultipartFile file) throws IOException {
            String[] ret = FastDFSClient.uploadFile(file.getInputStream(), file.getName());
            StringBuilder stringBuilder = new StringBuilder();
            for (String item : ret) {
                stringBuilder.append(File.separator).append(item);
            }
            return stringBuilder.toString();
        }
    
    }
    

    2. controller层

    注入UploadService并上传:

    @Controller
    public class PlayerController {
        // 文件存储位置
        private final static String FILESERVER = "http://192.168.42.4:8888/";
        @Autowired
        private UploadService uploadService;
    
        @RequestMapping("uploadImg.do")
        @ResponseBody
        public Map<String, Object> uploadImg(MultipartFile img, HttpServletRequest req) throws IOException {
            Map<String, Object> model = new HashMap<>();
    
            System.out.println("OriginalFilename:" + img.getOriginalFilename());
            String suffix = img.getOriginalFilename().substring(img.getOriginalFilename().lastIndexOf('.'));
            System.out.println("suffix:" + suffix);
            if (!suffix.equalsIgnoreCase(".jpg")) {
                model.put("msg", "文件类型必须为jpg");
                return model;
            }
    
            String path = uploadService.upload(img);
    
            model.put("msg", "上传成功");
            model.put("filepath", FILESERVER + path);
            model.put("filetype", img.getContentType());
    
            System.out.println("返回结果:" + model);
    
            return model;
        }
    }
    

    启动SpringBoot后,浏览器访问并上传:

    后台控制台也打印了日志:

    项目地址:

    https://gitee.com/aruba/fast-dfs.git

    相关文章

      网友评论

        本文标题:分布式--分布式文件系统FastDFS

        本文链接:https://www.haomeiwen.com/subject/utxhvrtx.html