seaweedfs架构

SeaweedFS是一个分布式存储系统,用于存储blob、对象、文件和数据仓库。具有可预测的低延迟,具有O(1)磁盘寻道和灵活的数据。

  • Blob Storage 由主服务器、卷服务器和云层组成。
  • File Storage 由Blob存储和文件服务器组成。
  • Object Storage 由文件存储和S3服务器组成。
  • 由File Storage和Hadoop兼容的库组成,由HDFS、Hadoop、Spark、Flink、Presto、HBA等使用。
  • FUSE Mount由装载到客户机上用户空间的文件存储组成,用于普通FUSE Mount、Kubernetes持久卷等。

Master

它代表一个由 1 个(或 3 个或更多服务器)组成的集群,这些服务器拥有整个 SeaweedFS 集群的一致视图,并通过 Raft 协议选出的 Leader 将其传达给所有参与节点。

主服务中的服务器数量必须始终为奇数,以确保可以形成多数共识。您最好保持这个数字不变,少量稳定的服务器比大量的 flakey 盒要好。1 或 3 是典型的。

通过定期的 raft 选举,从所有可用的主服务器中任意选择领导者。它分配文件 ID,指定存储对象的卷,并决定哪些节点是集群的一部分。

SeaweedFS 中的所有其他卷服务器都会向领导者发送心跳,领导者使用它们来决定将流量路由到哪里以及如何处理复制。

Volume

它将许多对象(文件和文件块)有效地打包到更大的单个卷中,这些卷可以是磁盘上的任意大块。数据的冗余和复制在卷级别进行管理,而不是在每个对象级别进行管理。

每个卷服务器通过主服务器将带有状态和卷信息的周期性心跳发送回领导者。

Filer

它通过 HTTP 或 UNIX FUSE 挂载将 SeaweedFS 卷和对象组织到用户可见的路径(如 URL 或文件系统)中。

Filer 提供了一种方便且通用的抽象,可用于向现有应用程序提供正常外观的文件系统或用于下载/上传的 Web API,无需修改。

S3

此可选服务提供 AWS 风格的 S3 存储桶,类似于文件管理器服务。它可以单独启动,也可以与文件管理器一起启动。

搭建服务

这里还是使用docker,另外,如果要使用它官方自带的compose文件,需要再拉取一个普罗米修斯服务。不用的可以不拉取

docker pull chrislusf/seaweedfs
docker pull prom/prometheus

然后,新建一个docker-compose.yml文件

version: '2'

services:
  master:
    image: chrislusf/seaweedfs # use a remote image
    ports:
      - 9333:9333
      - 19333:19333
    command: "master -ip=master"
  volume:
    image: chrislusf/seaweedfs # use a remote image
    ports:
      - 8080:8080
      - 18080:18080
      - 9325:9325
    command: 'volume -mserver="master:9333" -port=8080  -metricsPort=9325'
    depends_on:
      - master
  filer:
    image: chrislusf/seaweedfs # use a remote image
    ports:
      - 8888:8888
      - 18888:18888
      - 9326:9326
    command: 'filer -master="master:9333"  -metricsPort=9326'
    tty: true
    stdin_open: true
    depends_on:
      - master
      - volume
  cronjob:
    image: chrislusf/seaweedfs # use a remote image
    command: 'cronjob'
    environment:
      # Run re-replication every 2 minutes
      CRON_SCHEDULE: '*/2 * * * * *' # Default: '*/5 * * * * *'
      WEED_MASTER: master:9333 # Default: localhost:9333
    depends_on:
      - master
      - volume
  s3:
    image: chrislusf/seaweedfs # use a remote image
    ports:
      - 8333:8333
      - 9327:9327
    command: 's3 -filer="filer:8888" -metricsPort=9327'
    depends_on:
      - master
      - volume
      - filer
  webdav:
    image: chrislusf/seaweedfs # use a remote image
    ports:
      - 7333:7333
    command: 'webdav -filer="filer:8888"'
    depends_on:
      - master
      - volume
      - filer
  prometheus:
    image: prom/prometheus
    ports:
      - 9000:9090
    volumes:
      - ./prometheus:/etc/prometheus
    command: --web.enable-lifecycle  --config.file=/etc/prometheus/prometheus.yml
    depends_on:
      - s3

启动命令:

docker-compose -p seaweedfs up -d

访问localhost:9333,就可以看到你的集群了,不过这样做的话,感觉还是在搞事情,文件没有持久化储存。emmmm….那我们还是挂载一下吧

docker run --name weed -d -p 9333:9333 -p 8080:8080 -p 18080:8080 -v /自己持久化文件路径/data:/data chrislusf/seaweedfs server -dir="/data"

注意,此时是绝对路径。

这样一来,可以直接上生产了,compose是太过于完整了,实际上用的也无非就是上传文件和读取文件。emmmm。。。纠结怪,不知道该用compose还是直接用命令挂载,哎,都放出来吧。

version: '2'

services:
  master:
    image: chrislusf/seaweedfs # use a remote image
    ports:
      - 9333:9333
      - 19333:19333
    command: "master -ip=master "
  volume:
    image: chrislusf/seaweedfs # use a remote image
    ports:
      - 8080:8080
      - 18080:18080
      - 9325:9325
    volumes:
    - /挂载文件的地址/data:/data
    command: 'volume -mserver="master:9333" -dir="./data" -port=8080  -metricsPort=9325'
    depends_on:
      - master
docker rm $(sudo docker ps -a -q)
把无用的容器清理一下

ok,那么,剩下的就是客户端来连接了,不过seaweed是用go写的,那我们也使用go来连接。首先在config.ini下添加一个配置,如法炮制将其初始化。

SeaweedAddress = http://127.0.0.1:9333

新建文件夹goseaweed,新建interface.goseaweed.go

seaweed.go

type seaweedFS struct {
	serverURL string
	timeout   time.Duration
}

type Res struct {
	Fid string `json:"fid"`
}


func NewSeaweedFs(serverURL string, timeout time.Duration) Seaweed {
	if timeout <= 100 {
		timeout = time.Second * 10
	}
	return &seaweedFS{serverURL: serverURL, timeout: timeout}
}


func (s *seaweedFS) UploadFile(objectName string, content []byte) (string, error) {
	// 拼接url
	url := fmt.Sprintf("%s/%s", s.serverURL, objectName)
	method := "POST"
	payload := &bytes.Buffer{}
	writer := multipart.NewWriter(payload)
	err := writer.WriteField("file", bytes.NewBuffer(content).String())
	if err != nil {
		return "",errors.WithStack(err)
	}
	err = writer.Close()
	if err != nil {
		return "",errors.WithStack(err)
	}
	client := &http.Client{Timeout: s.timeout}
	req, err := http.NewRequest(method, url, payload)
	if err != nil {
		return "",errors.WithStack(err)
	}

	req.Header.Set("Content-Type", writer.FormDataContentType())
	// 返回值
	res, _ := client.Do(req)
	// 将数据转换为json
	body, err := ioutil.ReadAll(res.Body)
	result := string(body)
	var fid Res
	_ = json.Unmarshal([]byte(result),&fid)
	if err != nil {
		return "",errors.WithStack(err)
	}
	if res.StatusCode != http.StatusCreated {
		defer res.Body.Close()
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return "",errors.WithStack(err)
		}
		return "",fmt.Errorf("create failed, response:%s", string(body))
	}
	return fid.Fid,nil
}

func (s *seaweedFS) GetFile(objectName string) ([]byte, error) {
	body, err := http.Get(fmt.Sprintf("%s/%s", s.serverURL, objectName))
	if err != nil {
		return nil, errors.WithStack(err)
	}
	defer body.Body.Close()
	return ioutil.ReadAll(body.Body)
}

func (s *seaweedFS) RemoveFile(objectName string) error {
	url := fmt.Sprintf("%s/%s", s.serverURL, objectName)
	method := "DELETE"

	client := &http.Client{Timeout: s.timeout}
	req, err := http.NewRequest(method, url, nil)
	if err != nil {
		return errors.WithStack(err)
	}

	res, err := client.Do(req)
	if err != nil {
		return errors.WithStack(err)
	}
	if res.StatusCode != http.StatusNoContent {
		defer res.Body.Close()
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return errors.WithStack(err)
		}
		return fmt.Errorf("delete failed, response:%s", string(body))
	}
	return nil
}

interface.go

package goseaweed

type Seaweed interface {
	UploadFile(objectName string, content []byte)(string, error)
	GetFile(objectName string) ([]byte, error)
	RemoveFile(objectName string) error
}

可以用本地读文件的方式

func TestSeaweed() {
	log.SetFlags(log.Llongfile | log.LstdFlags)
	
	fs := NewSeaweedFs(utils.SeaweedAddress, time.Second*10)
	file, err := ioutil.ReadFile("/Users/salmon/demo/themes/hexo-theme-matery-develop/source/medias/logo.png")
	if err != nil {
		log.Fatalln(err)
	}
	if fid,err := fs.UploadFile("submit", file); err != nil {
		log.Fatalln(err)
	}else {
		fmt.Println(fid)
	}

测试一下,ok,没有问题。文件服务器,没有必要非得用网络云盘等存储,自己搭建也是一个很好的方案,不仅可以磨练心态,更可以增进自己的技术。