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 返回的格式
后记
- 感谢 sjqzhang 将这么好用的中间件开源。
- git地址 sjqzhang/go-fastdfs