GoFastDFS 极速体验

go-fastdfs简介

  • go-fastdfs是一个基于http协议的分布式文件系统,它基于大道至简的设计理念,一切从简设计,使得它的运维及扩展变得更加简单,它具有高性能、高可靠、无中心、免维护等优点。
  • 此文件系统搭建十分简单,并且性能极佳,相对于fastdfs,go-fastdfs部署运维十分简单,中小型公司使用完全足够。且项目本身自带性能测试与压测脚本,测试也十分简单。

特性

  • 支持curl命令上传
  • 支持浏览器上传
  • 支持HTTP下载
  • 支持多机自动同步
  • 支持断点下载
  • 支持配置自动生成
  • 支持小文件自动合并(减少inode占用)
  • 支持秒传
  • 支持跨域访问
  • 支持一键迁移
  • 支持并行体验
  • 支持断点续传(tus)
  • 支持docker部署
  • 支持自监控告警
  • 支持图片缩放
  • 支持google认证码
  • 支持自定义认证
  • 支持集群文件信息查看
  • 使用通用HTTP协议
  • 无需专用客户端(支持wget,curl等工具)
  • 类fastdfs
  • 高性能 (使用leveldb作为kv库)
  • 高可靠(设计极其简单,使用成熟组件)
  • 无中心设计(所有节点都可以同时读写)

优点

  • 无依赖(单一文件)
  • 自动同步
  • 失败自动修复
  • 按天分目录方便维护
  • 支持不同的场景
  • 文件自动去重
  • 支持目录自定义
  • 支持保留原文件名
  • 支持自动生成唯一文件名
  • 支持浏览器上传
  • 支持查看集群文件信息
  • 支持集群监控邮件告警
  • 支持小文件自动合并(减少inode占用)
  • 支持秒传
  • 支持图片缩放
  • 支持google认证码
  • 支持自定义认证
  • 支持跨域访问
  • 极低资源开销
  • 支持断点续传(tus)
  • 支持docker部署
  • 支持一键迁移(从其他系统文件系统迁移过来)
  • 支持并行体验(与现有的文件系统并行体验,确认OK再一键迁移)
  • 支持token下载 token=md5(file_md5+timestamp)
  • 运维简单,只有一个角色(不像fastdfs有三个角色Tracker Server,Storage Server,Client),配置自动生成
  • 每个节点对等(简化运维)
  • 所有节点都可以同时读写

急速体验

  • 适用于开发,或验证功能阶段。

linux

wget --no-check-certificate  https://github.com/sjqzhang/go-fastdfs/releases/download/v1.3.4/fileserver -O fileserver && chmod +x fileserver && ./fileserver

后台运行方式:

  • chmod +x control
  • ./control start|stop|status #对和序进行启动,停止,查看状态等。请确保control与fileserver在同一个目录

Docker体验

 docker run --name fastdfs -v ./data:/data -p 8080:8080 -e GO_FASTDFS_DIR=/data sjqzhang/go-fastdfs

docker-compose

version: '3'
services:
  fastdfs:
    container_name: fastdfs
    image: sjqzhang/go-fastdfs
    environment:
      - GO_FASTDFS_DIR=/data
    restart: always
    ports:
      - 22122:8080
    volumes:
      - ./data:/data

编译安装

git clone https://github.com/sjqzhang/go-fastdfs.git
cd go-fastdfs
mv vendor src
pwd=`pwd`
GOPATH=$pwd go build -o fileserver fileserver.go
./fileserver

配置说明

安装完成后data/conf 目录下会有一个cfg.json文件

{
	"绑定端号": "端口",
	"addr": ":8080",
	"PeerID": "集群内唯一,请使用0-9的单字符,默认自动生成",
	"peer_id": "1",
	"本主机地址": "本机http地址,默认自动生成(注意端口必须与addr中的端口一致),必段为内网,自动生成不为内网请自行修改,下同",
	"host": "http://192.168.1.234:8080",
	"集群": "集群列表,注意为了高可用,IP必须不能是同一个,同一不会自动备份,且不能为127.0.0.1,且必须为内网IP,默认自动生成",
	"peers": ["http://192.168.1.234:8080"],
	"组号": "用于区别不同的集群(上传或下载)与support_group_manage配合使用,带在下载路径中",
	"group": "group1",
	"是否支持按组(集群)管理,主要用途是Nginx支持多集群": "默认支持,不支持时路径为http://10.1.5.4:8080/action,支持时为http://10.1.5.4:8080/group(配置中的group参数)/action,action为动作名,如status,delete,sync等",
	"support_group_manage": true,
	"是否合并小文件": "默认不合并,合并可以解决inode不够用的情况(当前对于小于1M文件)进行合并",
	"enable_merge_small_file": false,
    "允许后缀名": "允许可以上传的文件后缀名,如jpg,jpeg,png等。留空允许所有。",
	"extensions": [],
	"重试同步失败文件的时间": "单位秒",
	"refresh_interval": 1800,
	"是否自动重命名": "默认不自动重命名,使用原文件名",
	"rename_file": false,
	"是否支持web上传,方便调试": "默认支持web上传",
	"enable_web_upload": true,
	"是否支持非日期路径": "默认支持非日期路径,也即支持自定义路径,需要上传文件时指定path",
	"enable_custom_path": true,
	"下载域名": "用于外网下载文件的域名,不包含http://",
	"download_domain": "",
	"场景列表": "当设定后,用户指的场景必项在列表中,默认不做限制(注意:如果想开启场景认功能,格式如下:'场景名:googleauth_secret' 如 default:N7IET373HB2C5M6D ",
	"scenes": [],
	"默认场景": "默认default",
	"default_scene": "default",
	"是否显示目录": "默认显示,方便调试用,上线时请关闭",
	"show_dir": true,
	"邮件配置": "",
	"mail": {
		"user": "abc@163.com",
		"password": "abc",
		"host": "smtp.163.com:25"
	},
	"告警接收邮件列表": "接收人数组",
	"alarm_receivers": [],
	"告警接收URL": "方法post,参数:subject,message",
	"alarm_url": "",
	"下载是否需带token": "真假",
	"download_use_token": false,
	"下载token过期时间": "单位秒",
	"download_token_expire": 600,
	"是否自动修复": "在超过1亿文件时出现性能问题,取消此选项,请手动按天同步,请查看FAQ",
	"auto_repair": true,
	"文件去重算法md5可能存在冲突,默认md5": "sha1|md5",
	"file_sum_arithmetic": "md5",
	"管理ip列表": "用于管理集的ip白名单,",
	"admin_ips": ["127.0.0.1","192.168.1.234","172.19.0.1"],
	"是否启用迁移": "默认不启用",
	"enable_migrate": false,
	"文件是否去重": "默认去重",
	"enable_distinct_file": true,
	"是否开启跨站访问": "默认开启",
	"enable_cross_origin": true,
	"是否开启Google认证,实现安全的上传、下载": "默认不开启",
	"enable_google_auth": false,
	"认证url": "当url不为空时生效,注意:普通上传中使用http参数 auth_token 作为认证参数, 在断点续传中通过HTTP头Upload-Metadata中的auth_token作为认证参数,认证流程参考认证架构图",
	"auth_url": "",
	"下载是否认证": "默认不认证(注意此选项是在auth_url不为空的情况下生效)",
	"enable_download_auth": false,
	"默认是否下载": "默认下载",
	"default_download": true,
	"本机是否只读": "默认可读可写",
	"read_only": false,
	"是否开启断点续传": "默认开启",
	"enable_tus": true,
	"同步单一文件超时时间(单位秒)": "默认为0,程序自动计算,在特殊情况下,自已设定",
	"sync_timeout": 0
}

集群部署图

小集群安装

  • 适用于单一的集群,扩展性相对差一点。
worker_processes  1;
events {
        worker_connections  1024;
}
http {
        include       mime.types;
        default_type  application/html;
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
        access_log  /var/log/nginx/access.log  main;
        error_log  /var/log/nginx/error.log  error;
        sendfile        on;
        keepalive_timeout  65;
        client_max_body_size 0; 
        proxy_redirect ~/big/upload/(.*) /big/upload/$1;  #继点续传一定要设置(注意)
        upstream go-fastdfs {
                server 10.1.14.36:8080;
                server 10.1.14.37:8080;
                ip_hash;     #notice:very important(注意)
        }
        server {
                listen       80;
                server_name  localhost;
                location / {
                    proxy_set_header Host $host; #notice:very important(注意)
                    proxy_set_header X-Real-IP $remote_addr; #notice:very important(注意)
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #notice:very important(注意)
                    proxy_pass http://go-fastdfs;
                }

        }
}

海量集群安装(推荐)

  • 适用于海量集群,扩展性较好。
worker_processes  auto;
events {
        worker_connections  1024;
}
http {
        include       mime.types;
        default_type  application/html;
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
        access_log  /var/log/nginx/access.log  main;
        error_log  /var/log/nginx/error.log  error;
        sendfile        on;
        keepalive_timeout  65;
        rewrite_log on;
        client_max_body_size 0;
        proxy_redirect ~/(\w+)/big/upload/(.*) /$1/big/upload/$2;  #继点续传一定要设置(注意)
        #以下是一下横向扩展的配置,当前统一大集群容量不够时,只要增加一个小集群,也就是增加一个
        #upstream ,一个小群集内按业务需求设定副本数,也就是机器数。
        upstream gofastdfs-group1 {
                server 10.1.51.70:8082;
                #server 10.1.14.37:8082;
                ip_hash;     #notice:very important(注意)
        }
    upstream gofastdfs-group2 {
        server 10.1.51.70:8083;
                #server 10.1.14.36:8083;
                ip_hash;     #notice:very important(注意)
        }

        server {
                listen       8001;
                server_name  localhost;


        if ( $request_uri ~ /godfs/group ) {
                    # 注意group会随组的前缀改变而改变
            rewrite ^/godfs/(.*)$ /$1 last;
                }
                location ~ /group(\d) { 
                    #统一在url前增加godfs,以便统一出入口。
                    proxy_set_header Host $host;
                    proxy_set_header X-Real-IP $remote_addr;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
                    proxy_pass http://gofastdfs-group$1;
                }
                location ~ /godfs/upload { 
                    #这是一个横向扩展配置,前期可能只有一个集群group1,当group1满后,只需将上传指向group2,
                    #也就是将rewrite , proxy_pass 中的group1改为group2即可。
                    proxy_set_header Host $host;
                    proxy_set_header X-Real-IP $remote_addr;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
                    rewrite ^/godfs/upload /group1/upload break;
                    proxy_pass http://gofastdfs-group1;
                }
                location ~ /godfs/big/upload { 
                    #以上上类似。
                    proxy_set_header Host $host;
                    proxy_set_header X-Real-IP $remote_addr;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
                    rewrite ^/godfs/upload /group1/big/upload break;
                    proxy_pass http://gofastdfs-group1;
                }

        }
}

Java客户端

依赖(这里使用了hutool工具包,更简便)

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>4.4.3</version>
</dependency>

上传代码

public static void main(String[] args) {
    //文件地址
    File file = new File("D:\\git\\2.jpg");
    //声明参数集合
    HashMap<String, Object> paramMap = new HashMap<>();
    //文件
    paramMap.put("file", file);
    //输出
    paramMap.put("output","json");
    //自定义路径
    paramMap.put("path","image");
    //场景
    paramMap.put("scene","image");
    //上传
    String result= HttpUtil.post("http://xxxxx:xxxx/group1/upload", paramMap);
    //输出json结果
    System.out.println(result);
}

java版本流式上传

  • Hutool-http、HttpClient、OkHttp3多种方式流式文件上传 由于有不少人问到上面的问题,现在本人总结了一个常用的几种http客户端文件流式上传的方式,相当于给自己做下记录,同时也给有这方面疑问的朋友一个借鉴。废话不多说,直接上代码吧。代码是基于springboot的maven工程。Hutool-http方式 先在pom中添加hutool的依赖。
 <dependency>
     <groupId>cn.hutool</groupId>
     <artifactId>hutool-all</artifactId>
     <version>4.5.1</version>
 </dependency>
  • 接着在Controller中代码示例
    @RequestMapping("/upload")
    public String  upload(MultipartFile file) {
        String result = "";
        try {
            InputStreamResource isr = new InputStreamResource(file.getInputStream(),
                    file.getOriginalFilename());

            Map<String, Object> params = new HashMap<>();
            params.put("file", isr);
            params.put("path", "86501729");
            params.put("output", "json");
            String resp = HttpUtil.post(UPLOAD_PATH, params);
            Console.log("resp: {}", resp);
            result = resp;
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }
  • HttpClient方式 pom依赖
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpmime</artifactId>
</dependency>
  • 接着在Controller中代码示例
   @RequestMapping("/upload1")
    public String upload1(MultipartFile file) {
        String result = "";
        try {
            CloseableHttpClient httpClient = HttpClientBuilder.create().build();
            CloseableHttpResponse httpResponse = null;
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(200000)
                    .setSocketTimeout(2000000)
                    .build();
            HttpPost httpPost = new HttpPost(UPLOAD_PATH);
            httpPost.setConfig(requestConfig);
            MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create()
                    .setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
                    .setCharset(Charset.forName("UTF-8"))
                    .addTextBody("output", "json")
                    .addBinaryBody("file", file.getInputStream(),
                            ContentType.DEFAULT_BINARY, file.getOriginalFilename());
            httpPost.setEntity(multipartEntityBuilder.build());
            httpResponse = httpClient.execute(httpPost);

            if (httpResponse.getStatusLine().getStatusCode() == 200) {
                String respStr = EntityUtils.toString(httpResponse.getEntity());
                System.out.println(respStr);
                result = respStr;
            }

            httpClient.close();
            httpResponse.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
  • OkHttp3上传示例 pom文件依赖
 <dependency>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>okhttp</artifactId>
     <version>3.9.1</version>
 </dependency>
  • 接着在Controller中代码示例
   @RequestMapping("/upload2")
    public String upload2(MultipartFile file) {
        String result = "";
        try {
            OkHttpClient httpClient = new OkHttpClient();
            MultipartBody multipartBody = new MultipartBody.Builder().
                    setType(MultipartBody.FORM)
                    .addFormDataPart("file", file.getOriginalFilename(),
                            RequestBody.create(MediaType.parse("multipart/form-data;charset=utf-8"),
                                    file.getBytes()))
                    .addFormDataPart("output", "json")
                    .build();

            Request request = new Request.Builder()
                    .url(UPLOAD_PATH)
                    .post(multipartBody)
                    .build();

            Response response = httpClient.newCall(request).execute();
            if (response.isSuccessful()) {
                ResponseBody body = response.body();
                if (body != null) {
                    result = body.string();
                    System.out.println(result);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return result;
    }
  • 总结 上面给出了几个示例,是不是都挺简单的?通过这种方式,就可以在Controller中做中转了,还是挺方便的。顺便提一下,上面几种方式中,我个人觉得Hutool的是最简单的,最方便的,对于HttpClient而言,概念比较多,显得相对复杂,OkHttp也一样,不过比HttpClient显得优雅点。针对一般的并发量,个人觉得hutool的Http已经够用了,底层是基于jdk的HttpUrlConnection实现的。如果对性能有特殊要求的,可以考虑httpclient或者OKHttp,后两者相对而言,更推荐使用OkHttp。

问题汇总

最佳实战

一、如果是海量存储,不要开启文件token认证功能,减少性能开消。
二、尽量用标准上传,上传后业务保存path,在业务用的时候再并接上域名(方便迁移扩展等)。
三、如果使用断点续传,上传后一定要用文件id置换成path存储(如何置换看QA/API文档),为后面访问减少性能开消。
四、尽量使用物理服务器部署,因为主要压力或性能来自于IO
五、线上业务尽量使用nginx+gofastdfs部署架构(均衡算法使用ip_hash),以满足后面的功能扩展性(nginx+lua)。_六、线上环境最好不要使用容器部署,容器适用于测试和功能验证。
总结:业务保存的文件的path,减少后期访问路径转换带来开消,文件访问权限由业务来完成,这样性能最好,通用性强(可直接其它web服务器)。
重要提醒:如果开启小文件合并功能,后期是无法删除小文件的。
上传结果说明:请使用md5,path,scene字段,其它是为了兼容老的线上系统添加的,以后有可能去掉。

集群搭建

一、先下载已编译的可执行文件(用最新版本)
二、运行可执行文件(生成配置)
三、修改配置

  • peers:增加对端的http地址
  • 检查:
  • host:自动生成是否正确
  • peer_id:集群内是否唯一
    四、重新运行服器
    五、验证服务是否OK

缩放图片

在下载url中加入width各height参数。例如:http://127.0.0.1:8080/group1/haystack/5/124,0,27344,.jpg?download=0&width=100&height=100特明说明是:如果要等比例缩放,请将width或height设为0

在浏览器中直接显示图片

在下载url中加入download=0参数。例如:http://127.0.0.1:8080/group1/haystack/5/124,0,27344,.jpg?download=0

秒传文件

通过http get的方式访问上传接口
http://10.0.5.9:8080/upload?md5=filesum&output=json
参数说明:

  • md5=sum(file) 文件的摘要算法要与文件务器的算法一致(算法支持md5|sha1),如果是断点续传,可以使用文件的id,也就是urlolad后的id
  • output=json|text 返回的格式

后记