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


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

专用分布式文件系统是基于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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值