分布式文件管理系统(Distributed File System,DFS)

是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点(可简单的理解为一台计算机)
DFS可以看作是一个接口:具体实现有OSS,GlusterFS、GoogleFS、FastDFS、TFS

FastDFS分布式文件管理系统

搭建环境和基本配置

FastDFS特别适合大中型网站使用,用来存储资源文件(如:图片、文档、音频、视频等等)
FastDFS没有官网,http://bbs.chinaunix.net/
下载: https://sourceforge.net/projects/fastdfs/files/
文件大小一般都是在500MB以下的文件,使用fastDFS最合适了。

java分布式缓存技术 java分布式文件存储_运维

角色:
1.Client: 客户端。使用Java语言编写的项目属于客户端。
2.Tracker Server: 跟踪服务器,主要做调度工作,在访问上起负载均衡的作用。在内存中记录集群中group和storage server的状态信息,是连接Client和Storage server的枢纽。相当于酒店前台
3.Storage Server: 存储服务器,文件和文件属性(meta data)都保存到存储服务器上,相当于酒店房间

架构:
1.FastDFS服务端只有两个角色,tracker server(酒店前台)和storage server。(酒店房间)
2.所有同角色服务器集群节点都是平等的,不存在主从关系(Master-Slave)。
3.存储服务器采用分组方式,同组内存储服务器上的文件完全相同(备份);不同分组的存储服务器管理不同的文件(扩容 RAID)。
不同组的storage server之间不会相互通信。

4.由storage server主动向tracker server报告状态信息,tracker server之间不会相互通信。

Docker安装FastDFS

1.创建跟踪器容器

拉取镜像
docker pull delron/fastdfs

创建映射宿主机映射文件目录:
mkdir -p /opt/fdfs/tracker

创建并启动容器: 	跟踪器默认服务端口是:22122
docker run -d --network=host --name tracker -v /opt/fdfs/tracker:/var/fdfs delron/fastdfs tracker

2.创建存储器容器

创建宿主机映射目录:
mkdir -p /opt/fdfs/storage

存储器默认服务端口是:23000
Storage存储器启动后,需要连接Tracker跟踪器并提交元信息,必须配置容器环境 -e TRACKER_SERVER=跟踪器所在宿主机IP:22122
创建并启动容器:
docker run -d --network=host --name storage -e TRACKER_SERVER=192.168.6.100:22122 -v /opt/fdfs/storage:/var/fdfs -e GROUP_NAME=group1 delron/fastdfs storage

3.重启容器错误
关闭容器后,再次启动会报错.
原因: 存储器启动时,需要重新创建日志文件和进程描述文件(xxx.pid),第二次启动时,进程信息和第一次启动时不一样,但还是读取的起一次启动时创建的文件.所以报错.
解决: 删掉对应文件

//追踪器
rm -rf /opt/fdfs/tracker/data/*.pid
//存储器
rm -rf /opt/fdfs/storage/data/*.pid

4.Storage服务器磁盘空间不足不能上传文件

进入Tracker容器:
docker exec -it tracker bash
进入配置文件目录:
cd /etc/fdfs
编辑配置文件:
vi /etc/fdfs/tracker.conf
按照具体环境修改限制即可。默认磁盘限制是,剩余磁盘空间不足10%时,关闭上传文件功能。如:reserved_storage_space = 10K
退出容器:exit
关闭容器:docker stop tracker
删除宿主机中映射tracker容器的目录中的内容:
rm -rf /opt/fdfs/tracker/*
启动容器
docker start tracker
观察日志:
docker logs -f tracker

准备

导入依赖:

<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>

新建工具类:

public final class FastDFSUtils {
    /**
     * 定义静态属性,Properties和StorageClient
     */
    private final static Properties PROPERTIES;
    private final static StorageClient STORAGE_CLIENT;

    /**
     * 静态初始化代码块,初始化静态属性
     * 静态初始化代码块有异常如何处理?
     * 处理的时候,try。。catch。。 抛出一个Error,终止虚拟机。
     */
    static{
        try {
            PROPERTIES = new Properties();
            // 读取配置文件
            PROPERTIES.load(
                    FastDFSUtils.class
                            .getClassLoader()
                            .getResourceAsStream("fdfs.properties")
            );
            // 使用ClientGlobal初始化FastDFS客户端配置
            ClientGlobal.initByProperties(PROPERTIES);
            // 创建Tracker客户端对象
            TrackerClient trackerClient = new TrackerClient();
            // 基于Tracker客户端对象,获取Tracker服务器对象
            TrackerServer trackerServer = trackerClient.getConnection();
            // 基于Tracker服务器和客户端对象,获取Storage服务器对象
            StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
            // 创建Storage客户端对象
            STORAGE_CLIENT = new StorageClient(trackerServer, storageServer);
        }catch (Exception e){
            throw new ExceptionInInitializerError(e);
        }
    }


    /**
     *
     * @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 = STORAGE_CLIENT.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 = STORAGE_CLIENT.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 = STORAGE_CLIENT.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 = STORAGE_CLIENT.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 = STORAGE_CLIENT.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); // 不带最后的点
        }
    }

    /**
     * 提供获取Storage客户端对象的工具方法
     */
    public static StorageClient getStorageClient(){
        return STORAGE_CLIENT;
    }

    private FastDFSUtils(){}
}

文件上传

@Test
    public void test(){
    	//普通上传
        String[] strings = FastDFSUtils.uploadFile(new File("E:/UDP.png"), "UDP.png");
        System.out.println(Arrays.toString(strings));//[group1, M00/00/00/wKgGZGQs7ZqABc58AAC4jNgFKNE689.png]
		//上传并保存元信息,同时会生成一个.png-m 文件,用于保存上传文件的元信息
		String[] strings2 = FastDFSUtils.uploadFile(new FileInputStream("E:/UDP.png"), "UDP.png");
        System.out.println(Arrays.toString(strings2));//[group1, M00/00/00/wKgGZGQs7ZqASMNfAAC4jNgFKNE630.png]
        //获取元信息
        NameValuePair[] group1 = FastDFSUtils.getMetaDate("group1", "M00/00/00/wKgGZGQs542Ac7bhAAC4jNgFKNE132.png");
        System.out.println(group1[0].getName()+ "---" + group1[0].getValue());//file length---47244
        System.out.println(group1[1].getName()+ "---" + group1[1].getValue());//file name---UDP.png
    }

文件下载

@Test
    public void download() throws IOException {
        //下载文件
        InputStream inputStream = FastDFSUtils.downloadFile("group1", "M00/00/00/wKgGZGQs542Ac7bhAAC4jNgFKNE132.png");
        //指定下载位置及文件名,注意文件格式一致
        FileOutputStream fileOutputStream = new FileOutputStream("E:/abc.png");
        IOUtils.copy(inputStream,fileOutputStream);
    }

文件删除

@Test
    public void delete(){
        //返回结果0,删除成功,如果存在改文件的元信息,会一并删除
        int result = FastDFSUtils.deleteFile("group1", "M00/00/00/wKgGZGQs542Ac7bhAAC4jNgFKNE132.png");
        System.out.println(result);
    }

在线预览FastDFS中的文件

“http://192.168.137.128:8888/” + groupName + “/” + remoteName;
例: http://192.168.6.100:8888/group1/M00/00/00/wKgGZGQs7ZqASMNfAAC4jNgFKNE630.png

既然FastDFS不支持在线文件预览,那么我们系统页面中的预览图片从何而来?
这是Storage容器中附带的Nginx软件给我们提供的在线预览能力。
查看Storage存储器中Nginx配置

docker exec -it storage
查看配置文件/usr/local/nginx/conf/nginx.conf
cat /usr/local/nginx/conf/nginx.conf
如下:
server{
    listen        8888; 
    server_name  localhost;
    location ~/group([0-9])/M00{
        ngx_fastdfs_module;
    }
}