分布式文件存储-FastDFS

1.FastDFS体系结构

FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:未见存储、文件同步、文件访问(文件上传、下载)等,解决了大容量存储和负载均衡的问题,特别适合以文件为载体的在线服务。充分考虑冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标。

FastDFS架构包括Tracker server 和 Storage server。客户端请求 Tracker server进行文件上传、下载,通过Tracker server调度Storage server完成文件上传和下载。

Tracker server 作用是负载均衡和调度,通过Tracker server 在文件上传时可以根据一些策略找到Storage server提供文件上传服务。可以将tracker称为追踪服务器或调度服务器。

Storage server 作用是文件存储,客户端上传的文件最终存储在Storage服务器上,Storage server 没有实现自己的文件系统,而是利用操作系统的文件系统来管理文件。可以将storage称为存储服务器。

2.上传流程


客户端上传文件后存储服务器将文件ID返回给客户端,此文件ID用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。

组名:文件上传后所在的storage组名称,在文件上传成功后由storage服务器返回,需要客户端自行保存。

虚拟磁盘目录:storage配置的虚拟路径,与磁盘选项store_path*对应。如果配置了store_path0则是M00,如果配置了store_path1,则是M01,以此类推。

数据两级目录:storage服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。

文件名:与文件上传时不同。是由储存服务器根据特定信息生成,文件名包括:源存储服务器IP地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。

3.FastDFS搭建

使用Docker搭建FastDFS的开发环境

(1)拉取镜像

docker pull morunchang/fastdfs

(2)运行tracker

docker run -d --name tracker --net=host morunchang/fastdfs sh tracker.sh

(3)运行storage

docker run ‐d ‐‐name storage ‐‐net=host ‐e TRACKER_IP=<your tracker server address>:22122 ‐e GROUP_NAME=<group name> morunchang/fastdfs sh storage.sh

使用的网络模式是–net=host, 替换为你机器的Ip即可

GROUP_NAME是组名,即storage的组

如果想要增加新的storage服务器,再次运行该命令,注意更换 新组名

(4)修改nginx的配置

进入storage的容器内部,修改nginx.conf

docker exec ‐it storage /bin/bash

进入后

vi /data/nginx/conf/nginx.conf

添加以下内容

location /group1/M00 {

proxy_next_upstream http_502 http_504 error timeout invalid_header;

proxy_cache http‐cache;

proxy_cache_valid 200 304 12h;

proxy_cache_key $uri$is_args$args;

proxy_pass http://fdfs_group1;

expires 30d;

}

(5)退出容器

exit

(6)重启storage容器

docker restart storage

4.文件存储微服务

(1)修改pom.xml,引入依赖

<dependencies>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring‐boot‐starter‐web</artifactId>

    </dependency>

    <dependency>

        <groupId>net.oschina.zcx7878</groupId>

        <artifactId>fastdfs‐client‐java</artifactId>

        <version>1.27.0.0</version>

    </dependency>

    <dependency>

        <groupId>com.changgou</groupId>

        <artifactId>changgou_common</artifactId>

        <version>1.0‐SNAPSHOT</version>

    </dependency>

</dependencies>

(2)在resources文件夹下创建fasfDFS的配置文件fdfs_client.conf

connect_timeout = 60

network_timeout = 60

charset = UTF‐8

http.tracker_http_port = 8080

tracker_server = 192.168.200.128:22122

connect_timeout:连接超时时间,单位为秒。

network_timeout:通信超时时间,单位为秒。发送或接收数据时。假设在超时时间后还不能发送或接收数据,则本次网络通信失败

charset: 字符集

http.tracker_http_port :.tracker的http端口

tracker_server: tracker服务器IP和端口设置

(3)在resources文件夹下创建application.yml

spring:

    servlet:

        multipart:

            max‐file‐size: 10MB

            max‐request‐size: 10MB

server:

    port: 9008

eureka:

    client:

        service‐url:

            defaultZone: http://127.0.0.1:6868/eureka

    instance:

        prefer‐ip‐address: true

feign:

    hystrix:

        enabled: true

max-file-size是单个文件大小,max-request-size是设置总上传的数据大小

(4)创建com.changgou.file包,创建启动类FileApplication

@SpringBootApplication

@EnableEurekaClient

public class FileApplication {

    public static void main(String[] args) {

        SpringApplication.run(FileApplication.class);

    }

}

5.文件上传

(1)文件信息封装 (包括文件名字、文件内容、文件扩展名、文件md5值、文件作者等)

public class FastDFSFile {

        //文件名字

        private String name;

        //文件内容

        private byte[] content;

        //文件扩展名

        private String ext;

        //文件MD5摘要值

        private String md5;

        //文件创建作者

        private String author;

        public FastDFSFile(String name, byte[] content, String ext, String height,String width, String author) {

            super();

            this.name = name;

            this.content = content;

            this.ext = ext;

            this.author = author;

       }

    public FastDFSFile(String name, byte[] content, String ext) {

        super();

        this.name = name;

        this.content = content;

        this.ext = ext;

    }

        // getter and setter ...

}

(2)文件操作(util)

public class FastDFSClient {

private static org.slf4j.Logger logger = LoggerFactory.getLogger(FastDFSClient.class);

    /***

    * 初始化加载FastDFS的TrackerServer配置

    */

    static {

        try {

            String filePath = new

            ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();

            ClientGlobal.init(filePath);

        } catch (Exception e) {

            logger.error("FastDFS Client Init Fail!",e);

        }

    }

    /***

    * 文件上传

    * @param file

    * @return

    */

    public static String[] upload(FastDFSFile file) {

        //获取文件的作者

        NameValuePair[] meta_list = new NameValuePair[1];

        meta_list[0] = new NameValuePair("author", file.getAuthor());

        //接收返回数据

        String[] uploadResults = null;

        StorageClient storageClient=null;

        try {

            //创建StorageClient客户端对象

            storageClient = getTrackerClient();

    /***

    * 文件上传

    * 1)文件字节数组

    * 2)文件扩展名

    * 3)文件作者

    */

    uploadResults = storageClient.upload_file(file.getContent(),

    file.getExt(), meta_list);

    } catch (Exception e) {

        logger.error("Exception when uploadind the file:" +

        file.getName(), e);

}

    if (uploadResults == null && storageClient!=null) {

        logger.error("upload file fail, error code:" +

        storageClient.getErrorCode());

    }

    //获取组名

    String groupName = uploadResults[0];

    //获取文件存储路径

    String remoteFileName = uploadResults[1];

    return uploadResults;

}

    /***

    * 获取文件信息

    * @param groupName:组名

    * @param remoteFileName:文件存储完整名

    * @return

    */

    public static FileInfo getFile(String groupName, String

        remoteFileName) {

            try {

                StorageClient storageClient = getTrackerClient();

                return storageClient.get_file_info(groupName,

                remoteFileName);

                } catch (Exception e) {

                logger.error("Exception: Get File from Fast DFS failed", e);

            }

            return null;

        }

    /***

    * 文件下载

    * @param groupName

    * @param remoteFileName

    * @return

    */

    public static InputStream downFile(String groupName, String

        remoteFileName) {

            try {

            //创建StorageClient

            StorageClient storageClient = getTrackerClient();

            //下载文件

            byte[] fileByte = storageClient.download_file(groupName,remoteFileName);

            InputStream ins = new ByteArrayInputStream(fileByte);

            return ins;

            } catch (Exception e) {

             logger.error("Exception: Get File from Fast DFS failed", e);

        }

        return null;

    }

    /***

    * 文件删除

    * @param groupName

    * @param remoteFileName

    * @throws Exception

    */

    public static void deleteFile(String groupName, String remoteFileName) throws Exception {

        //创建StorageClient

        StorageClient storageClient = getTrackerClient();

        //删除文件

        int i = storageClient.delete_file(groupName, remoteFileName);

    }

    /***

    * 获取Storage组

    * @param groupName

    * @return

    * @throws IOException

    */

    public static StorageServer[] getStoreStorages(String groupName)  throws IOException {

        //创建TrackerClient

        TrackerClient trackerClient = new TrackerClient();

        //获取TrackerServer

        TrackerServer trackerServer = trackerClient.getConnection();

        //获取Storage组

        return trackerClient.getStoreStorages(trackerServer, groupName);

    }

    /***

    * 获取Storage信息,IP和端口

    * @param groupName

    * @param remoteFileName

    * @return

    * @throws IOException

    */

    public static ServerInfo[] getFetchStorages(String groupName,String remoteFileName) throws IOException {

        TrackerClient trackerClient = new TrackerClient();

        TrackerServer trackerServer = trackerClient.getConnection();

        return trackerClient.getFetchStorages(trackerServer, groupName,remoteFileName);

    }

    /***

    * 获取Tracker服务地址

    * @return

    * @throws IOException

    */

    public static String getTrackerUrl() throws IOException {

        return "http://"+getTrackerServer().getInetSocketAddress().getHostString()+":"+ClientGlobal.getG_tracker_http_port()+"/";

    }

    /***

    * 获取Storage客户端

    * @return

    * @throws IOException

    */

    private static StorageClient getTrackerClient() throws IOException {

        TrackerServer trackerServer = getTrackerServer();

        StorageClient storageClient = new StorageClient(trackerServer,null);

        return storageClient;

    }

    /***

    * 获取Tracker

    * @return

    * @throws IOException

    */

    private static TrackerServer getTrackerServer() throws IOException {

        TrackerClient trackerClient = new TrackerClient();

        TrackerServer trackerServer = trackerClient.getConnection();

        return trackerServer;

    }

}

(3)文件上传(Controller)

@RestController

@RequestMapping("/file")

public class FileController {

    @PostMapping("/upload")

    public Result uploadFile(MultipartFile file){

        try{

            //判断文件是否存在

            if (file == null){

                throw new RuntimeException("文件不存在");

            }

            //获取文件的完整名称

            String originalFilename = file.getOriginalFilename();

            if (StringUtils.isEmpty(originalFilename)){

                throw new RuntimeException("文件不存在");

            }

        //获取文件的扩展名称 abc.jpg jpg

        String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);

        //获取文件内容

        byte[] content = file.getBytes();

        //创建文件上传的封装实体类

        FastDFSFile fastDFSFile = new FastDFSFile(originalFilename,content,extName);

        //基于工具类进行文件上传,并接受返回参数 String[]

        String[] uploadResult = FastDFSClient.upload(fastDFSFile);

        //封装返回结果

        String url = FastDFSClient.getTrackerUrl()+uploadResult[0]+"/"+uploadResult[1];

        return new Result(true,StatusCode.OK,"文件上传成功",url);

        }catch (Exception e){

            e.printStackTrace();

        }

    return new Result(false, StatusCode.ERROR,"文件上传失败");

}

6.Postman测试文件上传

步骤:

(1)选择post请求方式,输入请求地址 http://localhost:9008/upload

(2)填写Headers

Key:Content‐Type

Value:multipart/form‐data

(3)填写body

选择form-data 然后选择文件file 点击添加文件,最后发送即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容