• seaweedfs学习笔记


    ==背景==

    项目是私有化部署,有一些图片及文件需要存储,数量不多,单文件大小也不是很大,

    目前只能放在linux下的绝对路径下面,有必要研究一下文件系统。

    想找一个简单(不需要依赖其他组件,安装容易)易用(使用起来简单)的分布式文件系统的技术,

    朋友介绍了seaweedfs这个结束,过来研究一下,并记录一下笔记。

    ==环境==

    试验了两个环境:

    环境1:Linux CentOS7 (个人虚拟机)

    环境2:阿里云ECS服务器 Centos8

    Seaweedfs版本:2.13

    ==参考博客==

    在整个验证之前,我看了两个比较好的博客。

    https://www.cnblogs.com/townsend/p/11315348.html

    https://blog.wangqi.love/articles/seaweedfs/seaweedfs%E6%90%AD%E5%BB%BA%E4%B8%8E%E4%BD%BF%E7%94%A8.html

    http://www.diyhi.com/seaweedfs.html

    ==简单部署==

    1、下载

    官网:https://github.com/chrislusf/seaweedfs/releases

    我的操作系统选择的是linux centos8,选择了下载linux_amd64.tar.gz

    2、上传并解压缩

    tar -zxvf linux_amd64.tar.gz

    可以先查看一下weed命令的支持参数

    命令:weed -h

    指令    说明
    benchmark    测试seaweedfs的文件读写性能
    backup    将volume备份到本地
    compact    压缩volume文件
    filer.copy    将一个或多个文件复制到filer目录下
    fix    发生崩溃时修复索引文件
    filer.replicate    将文件的修改复制到另一个目标
    server    启动master服务、volume服务、filer和s3服务
    master    启动master服务
    filer    启动filer服务,指向一个或者多个master服务
    s3    启动s3服务,前提是启动filer服务
    upload    上传一个或多个文件
    download    根据文件id下载文件
    scaffold    生成基本的配置文件
    shell    执行可交互的管理指令
    version    打印seaweedfs的版本
    volume    启动volume服务
    export    列出或者输出volume中的文件
    mount    将filer挂载成一个目录
    webdav    启动webdav服务,前提是启动filer服务

    3、启动master

    命令:./weed master

    nohup方式启动:nohup /home/radmin/soft/weed master whiteList=127.0.0.1,192.168.29.100 > /home/radmin/soft/weed.log &

    4、创建并添加存储路径

    创建两个路径:

    /home/radmin/data/seaweedfs/volume1

    /home/radmin/data/seaweedfs/volume2

    添加存储节点目录。命令:

    nohup /home/radmin/soft/weed volume -dir="/home/radmin/data/seaweedfs/volume1" -max=1000 -mserver="vm1:9333" -port=10001 whiteList=127.0.0.1,192.168.20.100 > /home/radmin/soft/volume1.log &

    nohup /home/radmin/soft/weed volume -dir="/home/radmin/data/seaweedfs/volume2" -max=1000 -mserver="vm1:9333" -port=10002 whiteList=127.0.0.1,192.168.20.100 > /home/radmin/soft/volume2.log &

    5、通过浏览器打开页面

    地址:http://192.168.29.100:9333/

    ==文件存储==

    1、获取一个文件id以及volume服务的url

    curl http://192.168.29.100:9333/dir/assign
    {"fid":"3,01c226517c","url":"192.168.29.100:10001","publicUrl":"192.168.29.100:10001","count":1}

    PS:关于文件ID

    如上述的:3,01c226517c。可以通过/dir/assign来获取。

    文件id分为3个部分。

    第一部分:逗号左边的数字3表示volume id。是一个32位无符号整型

    第二部分:逗号右边的01表示file key。是一个64为无符号整型

    第三部分:剩下c226517c表示file cookie。是一个32位无符号整型,用来防止文件url被猜到。

    2、根据url和fid来上传文件

    curl -F file=@/home/radmin/soft/nice.jpg 192.168.29.100:10001/3,01c226517c
    {"name":"nice.jpg","size":224273,"eTag":"96a0c7bbbceafae209d518bb6b192051"}

    ==文件获取==

    1、根据volume id查询访问volume的url

    curl "http://192.168.29.100:9333/dir/lookup?volumeId=3"
    {"volumeId":"3","locations":[{"url":"192.168.29.100:10001","publicUrl":"192.168.29.100:10001"}]}

    2、通过url访问文件

    http://192.168.29.100:10001/3,01c226517c

    怎么样,看到个美女心情还是不错的吧。

    ==filer服务==

    上面的一系列操作之后,文件是可以看到了,不过貌似还的通过文件id什么的搞,对于喜欢“路径及文件名”的人来说,实在是不爽。

    filer服务就是为了解决这个问题而存在的。filer是一个在seaweedfs之上的服务,它保存路径与文件id的映射关系,最终还是使用文件id来访问文件。

    1、filer配置文件样例

    可以先运行以下命令来查看filer配置文件的样例。

    命令:/home/radmin/soft/weed scaffold -config=filer

    filer配置包括recursive_delete(是否递归删除),以及存储方式的选择。

    filer服务支持多种数据库来保存路径与文件id的映射关系,包括:leveldb2、mysql、postgres、cassandra、redis、redis_cluster、etcd、tikv。

    2、创建filer配置文件

    我这里在weed同目录下创建了配置文件,并尝试使用mysql来保存映射关系。

    文件名:filer.toml

    路径:/home/radmin/soft

    关键配置项:

    另外,需要根据提示,在mysql中创建数据库及表。

    数据库名字:seaweedfs

    建表语句:

    CREATE TABLE
    IF
        NOT EXISTS filemeta (
        dirhash BIGINT COMMENT 'first 64 bits of MD5 hash value of directory field',
        NAME VARCHAR ( 1000 ) COMMENT 'directory or file name',
        DIRECTORY TEXT COMMENT 'full path to parent directory',
        meta LONGBLOB,
        PRIMARY KEY ( dirhash, NAME ) 
        ) DEFAULT CHARSET = utf8

    3、启动filer服务

    命令:/home/radmin/soft/weed filer -master="192.168.29.100:9333" -ip=192.168.29.100

    后台启动:nohup /home/radmin/soft/weed filer -master="192.168.29.100:9333" -ip=192.168.29.100 > /home/radmin/soft/filer.log &

    4、尝试通过路径来管理文件

    上传:curl -F file=@/home/radmin/soft/nice2.jpg http://192.168.29.100:8888/beautygirl

    注意:beautygirl是一个文件的名字,而不是文件夹的名字。

    查看:curl "http://192.168.29.100:8888/beautygirl"

    也可以通过浏览器查看:http://192.168.29.100:8888/beautygirl

    还可以加上快读来指定图片大小:http://192.168.29.100:8888/beautygirl?width=500

    删除文件

    命令:curl -X DELETE "http://192.168.29.100:8888/beautygirl"

    递归删除路径下所有的文件以及目录

    命令:curl -X DELETE http://192.168.29.100:8888/path/to/dir?recursive=true

    递归删除所有的文件以及目录,忽略递归错误

    命令:curl -X DELETE http://192.168.29.100:8888/path/to/dir?recursive=true&ignoreRecursiveError=true

    ==目录挂载==

    seaweedfs可以方便地挂载到本地,可以像普通文件一样操作其中的文件。

    命令:/home/radmin/weed mount -filer=192.168.29.100:8888 -dir=/home/radmin/data/seaweedfs/mount(Linux本地的路径)

    【小插曲】

    上面的命令执行的时候一直报错,

    解决办法:

    我一个兄弟说是缺少fuse的包,所以上网上下载了fuse的依赖

    当然,如果服务器能联网,直接yum install fuse就可以了,我的虚拟机可能有点问题,没有下载下来,于是手动下载了fuse的rpm包

    https://rpmfind.net/linux/rpm2html/search.php?query=%2Fusr%2Fbin%2Ffusermount

    安装之后,重新执行mount命令

    发现,数据果然mount下来了。

    感谢建新同学。

    ==副本策略==

    seaweedfs的副本机制是volume层面,而不是文件层面的。这意味着不同的节点存在相同的volume,其中的文件也是相同的。

    volume启动时可以通过-dataCenter和-rack指定数据中心和机架。

    seaweedfs的副本机制就是通过在不同的数据中心和机架创建相同的volume来实现的。

    在master启动时可以指定副本的策略:

    命令:/home/radmin/soft/weed master -defaultReplication=001

    副本策略是xyz形式的数字,其含义如下:

    • x 在其他数据中心的副本数量
    • y 在相同数据中心,其他机架的副本数量
    • z 在相同机架,不同服务器的副本数量

    x,y,z分别可以是0,1,2,因此有9种副本类型组合。每种副本类型都创建了x+y+z+1个文件。

    ==安全==

    (未验证)

    ==线上使用==

    【节点数】

    3个(阿里云ECS服务器)

    【路径规划】

    【部署命令】

    1-1、启动节点1的master

    nohup /home/radmin/seaweedfs-2.13/weed master -ip=rexel-ids001 -port=9333 -mdir=/home/data/seaweedfs/master -defaultReplication=001 > /home/radmin/seaweedfs-2.13/master.log &

    1-2、启动节点1的volume

    nohup /home/radmin/seaweedfs-2.13/weed volume -ip=rexel-ids001 -port=10001 -dir=/home/data/seaweedfs/volume1 -max=10 -mserver=rexel-ids001:9333 -dataCenter=dc1 -rack=rack1> /home/radmin/seaweedfs-2.13/volume1.log &

    nohup /home/radmin/seaweedfs-2.13/weed volume -ip=rexel-ids001 -port=10002 -dir=/home/data/seaweedfs/volume2 -max=10 -mserver=rexel-ids001:9333 -dataCenter=dc1 -rack=rack1> /home/radmin/seaweedfs-2.13/volume2.log &

    1-3、启动节点1的filer

    nohup /home/radmin/seaweedfs-2.13/weed filer -ip=rexel-ids001 -port=8888 -port.readonly=7777 -master=rexel-ids001:9333 > /home/radmin/seaweedfs-2.13/filer.log &

    2-1、启动节点2的volume

    nohup /home/radmin/seaweedfs-2.13/weed volume -ip=rexel-ids002 -port=10001 -dir=/home/data/seaweedfs/volume1 -max=10 -mserver=rexel-ids001:9333 -dataCenter=dc1 -rack=rack1> /home/radmin/seaweedfs-2.13/volume1.log &

    nohup /home/radmin/seaweedfs-2.13/weed volume -ip=rexel-ids002 -port=10002 -dir=/home/data/seaweedfs/volume2 -max=10 -mserver=rexel-ids001:9333 -dataCenter=dc1 -rack=rack1> /home/radmin/seaweedfs-2.13/volume2.log &

    2-2、启动节点2的filer

    nohup /home/radmin/seaweedfs-2.13/weed filer -ip=rexel-ids002 -port=8888 -port.readonly=7777 -master=rexel-ids001:9333 > /home/radmin/seaweedfs-2.13/filer.log &

    3-1、启动节点3的volume

    nohup /home/radmin/seaweedfs-2.13/weed volume -ip=rexel-ids003 -port=10001 -dir=/home/data/seaweedfs/volume1 -max=10 -mserver=rexel-ids001:9333 -dataCenter=dc1 -rack=rack1> /home/radmin/seaweedfs-2.13/volume1.log &

    nohup /home/radmin/seaweedfs-2.13/weed volume -ip=rexel-ids003 -port=10002 -dir=/home/data/seaweedfs/volume2 -max=10 -mserver=rexel-ids001:9333 -dataCenter=dc1 -rack=rack1> /home/radmin/seaweedfs-2.13/volume2.log &

    3-2、启动节点3的filer

    nohup /home/radmin/seaweedfs-2.13/weed filer -ip=rexel-ids003 -port=8888 -port.readonly=7777 -master=rexel-ids001:9333 > /home/radmin/seaweedfs-2.13/filer.log &

    【路径规范】

    规则:http://rexel-ids001:9433/{project}/{type}/{file}

    样例:http://rexel-ids001:9433/ids/pict/nice1.jpg

    【filer配置】

    配置文件存放路径:/etc/seaweedfs/filer.toml

    ==Java客户端调用==

    写了一个简单的Spring Boot的java调用样例,供参考。

    1、配置文件

    在spring boot的resource配置文件中,增加seaweedfs的上传地址

    seaweedfs:
      url: http://rexel-ids001:8888/ids/online

    2、增加pom依赖

    根据实际验证结果,需要增加两个依赖。其中tika是用来解析MediaType的工具类。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
        <version>2.3.5.RELEASE</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-core</artifactId>
        <version>1.25</version>
    </dependency>

    3、Spring Boot代码

    代码结构如下图所示:

    文件:SeaweedfsInfo.java

    package com.rexel.core.compnent.seaweedfs.vo;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.beans.factory.annotation.Value;
    
    @Configuration
    public class SeaweedfsInfo {
        /**
         * Seaweedfs连接地址
         *
         */
        @Value("${seaweedfs.url}")
        public String url;
    }

    文件:CompSeaweedfsController.ajva

    package com.rexel.core.compnent.seaweedfs.controller;
    
    import com.rexel.core.compnent.seaweedfs.service.ICompSeaweedfsService;
    import com.rexel.core.framework.web.domain.AjaxResult;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;
    
    /**
     * Seaweedfs Controller
     *
     * @author admin
     * @date 2020-12-9
     */
    @RestController
    @RequestMapping("/rexel/seaweedfs")
    public class CompSeaweedfsController {
        @Autowired
        ICompSeaweedfsService seaweedfsService;
    
        @PostMapping("/upload")
        public AjaxResult upload(@RequestParam("file") MultipartFile file) {
            return seaweedfsService.upload(file);
        }
    }

    文件:ICompSeaweedfsService.java

    package com.rexel.core.compnent.seaweedfs.service;
    
    import com.rexel.core.framework.web.domain.AjaxResult;
    import org.springframework.web.multipart.MultipartFile;
    
    /**
     * Seaweedfs Service接口
     *
     * @author admin
     * @date 2020-12-9
     */
    public interface ICompSeaweedfsService {
        /**
         * 上传文件到Seaweedfs服务器
         *
         * @param multipartFile 需要上传的文件
         * @return 结果
         */
        AjaxResult upload(MultipartFile multipartFile);
    }

    文件:CompSeaweedfsServiceImpl.java

    package com.rexel.core.compnent.seaweedfs.service.impl;
    
    import com.rexel.core.common.utils.UuidUtils;
    import com.rexel.core.compnent.seaweedfs.service.ICompSeaweedfsService;
    import com.rexel.core.compnent.seaweedfs.vo.SeaweedfsInfo;
    import com.rexel.core.framework.web.domain.AjaxResult;
    import java.io.File;
    import java.io.IOException;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.io.FileUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.tika.Tika;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.io.FileSystemResource;
    import org.springframework.http.HttpEntity;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.MediaType;
    import org.springframework.stereotype.Service;
    import org.springframework.web.multipart.MultipartFile;
    import org.springframework.web.reactive.function.BodyInserters;
    import org.springframework.web.reactive.function.client.WebClient;
    import reactor.core.publisher.Mono;
    
    /**
     * Seaweedfs Service业务层处理
     *
     * @author admin
     * @date 2020-12-9
     */
    @Service
    @Slf4j
    public class CompSeaweedfsServiceImpl implements ICompSeaweedfsService {
        @Autowired
        private SeaweedfsInfo seaweedfsInfo;
    
        /**
         * 文件上传
         *
         * @param multipartFile 需要上传的文件
         * @return 结果
         */
        @Override
        public AjaxResult upload(MultipartFile multipartFile) {
            // 获取上传文件名
            String filename = multipartFile.getOriginalFilename();
            if (StringUtils.isBlank(filename)) {
                return AjaxResult.error("上传文件为空。");
            }
    
            // 获取文件扩展名
            String fileExtensionName = filename.substring(filename.lastIndexOf("."));
    
            // 生成文件唯一识别码
            String fileNewName = UuidUtils.get16Uuid() + fileExtensionName;
    
            // 文件目标位置
            File file = new File(fileNewName);
    
            // 将文件内容输入到file中
            try {
                FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), file);
            } catch (IOException ioe) {
                return AjaxResult.error("上传文件错误。");
            }
    
            // 获取上传地址
            String url = seaweedfsInfo.url + "/" + fileNewName;
    
            // 执行文件上传
            String result = doWebClientPost(file, url);
    
            log.info(result);
            return AjaxResult.success("操作成功", url);
        }
    
        /**
         * 执行文件上传
         *
         * @param file File实例
         * @param url 上传地址
         * @return 结果
         */
        private String doWebClientPost(File file, String url) {
            MediaType mediaType = getMediaType(file);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(mediaType);
            HttpEntity<FileSystemResource> entity = new HttpEntity<>(new FileSystemResource(file), headers);
            Mono<String> response = WebClient.create().post().uri(url).contentType(mediaType)
                .body(BodyInserters.fromMultipartData("filename", entity)).retrieve()
                .bodyToMono(String.class);
            return response.block();
        }
    
        /**
         * 获取MediaType
         *
         * @param file File实例
         * @return MediaType
         */
        private MediaType getMediaType(File file) {
            try {
                Tika tika = new Tika();
                return MediaType.valueOf(tika.detect(file));
            } catch (IOException e) {
                e.printStackTrace();
            }
            return MediaType.MULTIPART_FORM_DATA;
        }
    }

    4、通过postman验证

    发送请求

    查看文件服务器结果

    ==遗留问题==

    1、Master是单点的,存在单点故障的问题。

    2、未配置systemctl守护进程。

    3、程序中未对文件类型及文件大小限制。

    --END--

  • 相关阅读:
    数据库反范式~认识三大范式
    王家林的“云计算分布式大数据Hadoop实战高手之路从零开始”的第五讲Hadoop图文训练课程:解决典型Hadoop分布式集群环境搭建问题
    linux网络编程之System V 信号量(三):基于生产者消费者模型实现先进先出的共享内存段
    html知识点总结
    [置顶] HTML5开源RPG游戏引擎lufylegendRPG 1.0.0发布
    通过Android trace文件分析死锁ANR
    黄金连分数 蓝桥杯
    vxworks 6.9.3.1
    poj 3468 A Simple Problem with Integers(线段树区区)
    关于Java序列化的一些高级用法
  • 原文地址:https://www.cnblogs.com/quchunhui/p/14086075.html
Copyright © 2020-2023  润新知