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

posted @ 2020-12-04 15:27  大墨垂杨  阅读(3763)  评论(0编辑  收藏  举报