一、简介和应用场景
1.1、简介
FastDFS是一款使用纯C语言实现的应用级别的分布式文件存储服务
1.2、架构
FastDFS系统由client(文件上传下载客户端)、tracker(协调服务器)、storage(存储服务器)三部分组成。
tracker和storage可以进行集群部署,多个tracker之间并无联系,所以tracker彼此间并不存在同步,仅仅是用做容灾,防止一台tracker宕机后无法继续提供存储服务。
storage服务启动后会向配置的tracker server注册自己,向其报告自己的状态信息,包括磁盘剩余空间、文件同步状况、文件上传下载次数等统计信息,这使得tracker可以协调多个storage共同工作。不同组的Storage server之间不会相互通信,同组内的Storage server之间会相互连接进行文件同步。
同步方式
Storage server采用binlog文件记录文件上传、删除等更新操作。binlog中只记录文件名,不记录文件内容。
文件同步只在同组内的Storage server之间进行,采用push方式,即源头服务器同步给目标服务器。
Storage server中由专门的线程根据binlog进行文件同步,Storage server对组内除自己以外的每台服务器都会启动一个线程来进行文件同步。
文件同步采用增量同步方式,系统记录已同步的位置(binlog文件偏移量)到标识文件中。标识文件名格式:{dest storage IP}_{port}.mark,例如:192.168.1.1_23000.mark。
1.3、交互流程
上传:
客户端通过API向 tracker server发起请求,获取当前可用的storage server 地址( 注意:由于多个tracker间并无关联,此处的负载应由客户端去处理。客户端应获取所有可用的tracker server,按照一定的均衡策略从中选取一个可用链接,若此链接在一段时间内都不可用,应将其暂时移除并重新获取,而且要在一定条件下将其重新加入可选列表)。
客户端从tracker server中成功获取到可用的storage server地址,然后向此storage server发起上传文件的请求。
storage server向客户端返回此文件的path。
storage server向组内其他兄弟发起文件同步。
下载:下载一般有两种,图片和PDF等浏览器自身支持的文件类型,可通过nginx代理直接使用path访问。其他关联了业务的附件下载操作应通过应用服务器中转下载。
单机情况下若ng和storage server在同一台服务器,可以通过文件path直接反向映射到本地磁盘文件。
集群情况下需安装fastdfs-nginx-module插件,此插件可以自动寻址到上传文件的源storage server上,防止同步时间差导致请求其他storage server 获取不到此文件。
1.4 、缺点及应用场景
FastDFS以简单、易用作为其设计原则,但这无法避免的产生了一些问题:
数据安全性
1.上传文件到源服务器即成功,若此时源服务器宕机且处于同步时间差,那么此文件数据会丢失。
2.同步未对文件做正确性校验,这种同步方式仅适用单个集群点的局部内部网络,如果在公网上使用,肯定会出现损坏文件的情况,需要自行添加文件校验机制(篡改和硬件损坏,几率极低)。
大文件处理
FastDFS没有对文件做分块存储,因此不太适合分布式计算场景。(不适合存储大文件)
综上所述,fastDFS适用于处理以小文件为载体、文件安全性不是太苛刻的在线存储服务,如相册、视频等。
二、安装及部署
fastdfs
所有服务安装方式都为源码编译安装,提供两种方式下载源码——github和sourceforge,github上有最新的发布版本。
github
sourceforge
nginx 缓存插件
ngx_cache_purge
2.1、环境依赖
操作系统为4台64位CentOS Linux release 7.5.1804
192.168.152.136 nginx tracker
192.168.152.139 tracker
192.168.152.134 storage2 ngx_fastdfs_module
192.168.152.135 storage1 ngx_fastdfs_module
依赖第三方工具:
zlib zlib-devel pcre pcre-devel gcc gcc-c++ openssl openssl-devel libevent libevent-devel perl unzip net-tools wget
2.2、整体架构
线上架构应为:用户——》keepalived(虚拟IP)——》负载nginx(2+)——》tracker负载nginx(2+)——》
storage(2+),架构图如下
本次部署只为模拟,进行了简化,架构图如下
2.2、安装libfastcommon
解压并安装
tar -zxvf V1.0.7.tar.gz
cd libfastcommon-1.0.7
./make.sh
./make.sh isntall
软链接动态链接库到引用路径
ln -s /usr/lib64/libfastcommon.so /usr/local/lib/libfastcommon.so
ln -s /usr/lib64/libfastcommon.so /usr/lib/libfastcommon.so
ln -s /usr/lib64/libfdfsclient.so /usr/local/lib/libfdfsclient.so
ln -s /usr/lib64/libfdfsclient.so /usr/lib/libfdfsclient.so
2.3、安装FastDFS
解压并安装
tar -zxvf V5.05.tar.gz
cd fastdfs-5.05
./make.sh
./make.sh install
软连接到脚本到引用路径
ln -s /usr/bin/fdfs_trackerd /usr/local/bin
ln -s /usr/bin/fdfs_storaged /usr/local/bin
ln -s /usr/bin/stop.sh /usr/local/bin
ln -s /usr/bin/restart.sh /usr/local/bin
2.4、配置tracker和storage
192.168.152.135 192.168.152.134分别创建 tracker及storage目录
mkdir -p /home/fdfs/trackerd
mkdir -p /home/fdfs/storaged
配置tracker
vi /etc/fdfs/tracker.conf
主要配置tracker的元数据和日志存储路径和均衡策略等
#存储路径
base_path=/home/fdfs/fdfs_trackerd
#下载文件如何选择storage server
#0表示轮询,1表示上传的源服务器(避免同步时间差)
download_server=1
配置storage
vi /etc/fdfs/storage.conf
#分组名称
group_name=group1
#数据及日志存储路径
base_path=/home/mandy/fdfs/fdfs_storaged
#数据存储路径,可以有多个(可以挂载多个磁盘)
store_path0=/home/fdfs/fdfs_storaged
#tracker server地址,多个写成列表形式
tracker_server=192.168.152.136:22122
tracker_server = 192.168.152.134:22122
配置完毕启动服务并验证
service fdfs_trackerd start
service fdfs_storaged start
查看服务是否已开启
netstat -unltp | grep fdfs
查看storage是否已经激活到tracker
/usr/bin/fdfs_monitor /etc/fdfs/storage.conf
storage sever状态
# FDFS_STORAGE_STATUS_INIT :初始化,尚未得到同步已有数据的源服务器
# FDFS_STORAGE_STATUS_WAIT_SYNC :等待同步,已得到同步已有数据的源服务器
# FDFS_STORAGE_STATUS_SYNCING :同步中
# FDFS_STORAGE_STATUS_DELETED :已删除,该服务器从本组中摘除(注:本状态的功能尚未实现)
# FDFS_STORAGE_STATUS_OFFLINE :离线
# FDFS_STORAGE_STATUS_ONLINE :在线,尚不能提供服务
# FDFS_STORAGE_STATUS_ACTIVE :在线,可以提供服务
2.5、nginx配置
2.5.1、storage nginx配置
由于fastdfs在4.0.5之后的版本中将内置的http服务器移除,因此想通过http方式访问storage server,需要在每个storage上配置 nginx和fastdfs-nginx-module;
安装fastdfs-nginx-module
tar -zxf fastdfs-nginx-module-1.20.tar.gz
cd nginx-1.12.1
./configure --add-module=/home/download/fastdfs-nginx-module-master/src
make&&make install
安装有可能失败,如报如下错误 /usr/local/include/fastdfs/fdfs_define.h:15:27: 致命错误:common_define.h:没有那个文件或目录
解决方式
vim fastdfs-nginx-module-1.20/src/config
编辑
ngx_module_incs="/usr/include/fastdfs /usr/include/fastcommon/"
CORE_INCS="$CORE_INCS /usr/include/fastdfs /usr/include/fastcommon/"
./nginx -V 查看fastDFS模块是否已经添加成功
配置nginx.conf
ng配置灰常简单,如下:
server {
listen 80;
server_name lcoalhost;
location /M00 {
ngx_fastdfs_module;
}
}
从解压的fastdfs-nginx-module-1.20.tar.gz src下拷贝 mod_fastdfs.conf到/etc/fdfs/下并编辑,目前只针对单个group进行配置
#日志存储路径
base_path=/home/fdfs
#url中是否需要组名,若未分组,可以置为false
url_have_group_name = false
#tracker地址,多个写成列表形式
tracker_server=192.168.152.134:22122
tracker_server = 192.168.152.136:22122,
重启nginx即可.
2.5.2 负载nginx配置
安装nginx插件 ngx_cache_purge
tracker添加一台负载nginx(此处进行了简化,实际线上应多加一层代理)
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#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 logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
sendfile on;
tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server_names_hash_bucket_size 128;
client_header_buffer_size 32k;
large_client_header_buffers 4 32k;
client_max_body_size 300m;
proxy_redirect off;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffer_size 16k;
proxy_buffers 4 64k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 128k;
proxy_cache_path /home/mandy/nginx/proxy_cache levels=1:2
keys_zone=http-cache:500m max_size=10g inactive=30d;
proxy_temp_path /home/mandy/nginx/proxy_cache/tmp;
upstream fdfs_group1 {
server 192.168.152.134:80 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.152.135:80 weight=1 max_fails=2 fail_timeout=30s;
}
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location /M00 {
proxy_next_upstream http_502 http_504 error timeout invalid_header;
proxy_cache http-cache;
proxy_cache_valid 200 304 12h;
proxy_cache_key $uri$is_args$args;
proxy_pass http://fdfs_group1;
expires 30d;
}
location ~/purge(/.*) {
allow all;
proxy_cache_purge http-cache $1$is_args$args;
}
}
}
2.6、防盗链
fastDFS内置防盗链是在服务端开启token验证,客户端根据文件名、当前unix时间戳、秘钥获取token,在地址中带上token参数即可通过http方式访问文件。
服务端开启认证
拷贝fastdfs安装包conf下的anti-steal.jpg mime.types http.conf文件到 /etc/fdfs/
编辑http.conf
#开启token
http.anti_steal.check_token=true
#token有效期,单位秒(意味着客户端时间要与服务器保持在此时间差以内)
http.anti_steal.token_ttl=900
#加密的key
http.anti_steal.secret_key=FastDFS1234567890
同时需要在 mod_fastdfs.conf中配置认证失败后跳转的403页面
客户端token生成
public static void main(String[] args){
#file_path不带分组名,时间为unix时间,key与服务器http.conf配置的key保持一致
getToken("M00/00/00/wKiYhluV1heAAJDLAAnTlA5XnbM950.pdf",(int) Instant.now().getEpochSecond(),"FastDFS1234567890");
}
public static String md5(byte[] source) throws NoSuchAlgorithmException {
char[] hexDigits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(source);
byte[] tmp = md.digest();
char[] str = new char[32];
int k = 0;
for(int i = 0; i < 16; ++i) {
str[k++] = hexDigits[tmp[i] >>> 4 & 15];
str[k++] = hexDigits[tmp[i] & 15];
}
return new String(str);
}
public static String getToken(String remote_filename, int ts, String secret_key) throws UnsupportedEncodingException, NoSuchAlgorithmException {
final String charSet = "UTF-8";
byte[] bsFilename = remote_filename.getBytes(charSet);
byte[] bsKey = secret_key.getBytes(charSet);
byte[] bsTimestamp = (new Integer(ts)).toString().getBytes(charSet);
byte[] buff = new byte[bsFilename.length + bsKey.length + bsTimestamp.length];
System.arraycopy(bsFilename, 0, buff, 0, bsFilename.length);
System.arraycopy(bsKey, 0, buff, bsFilename.length, bsKey.length);
System.arraycopy(bsTimestamp, 0, buff, bsFilename.length + bsKey.length, bsTimestamp.length);
return md5(buff);
}
三、客户端集成
目前客户端使用连接池方式进行调用,首先进行配置:
新建config类并继承GenericKeyedObjectPoolConfig,样例如下:
@Component
@ConfigurationProperties(prefix = "fastdfs.pool")
public class FastdfsPoolConfig extends GenericKeyedObjectPoolConfig {
}
FastDFSConfig添加对FastdfsExecutor的配置,样例如下:
@Configuration
public class FastDFSConfig {
@Resource
private FastdfsPoolConfig fastdfsPoolConfig;
@Bean
public FastdfsExecutor fastdfsExecutor() {
FastdfsExecutor executor = new FastdfsExecutor();
executor.setPoolConfig(fastdfsPoolConfig);
return executor;
}
@Bean
public SimpleFastdfsClient simpleFastdfsClient(FastdfsExecutor fastdfsExecutor, @Value("${fastdfs.tracker.host}") String trackerServerAddr) {
return new SimpleFastdfsClient(fastdfsExecutor, trackerServerAddr);
}
}
默认开启对每次获取的连接的校验testOnBorrow =true,若需改为轮询方式,在spring配置文件中添加如下配置:
fastdfs.pool.testOnBorrow=false
fastdfs.pool.testWhileIdle=true #开启定时任务校验空闲连接
fastdfs.pool.timeBetweenEvictionRunsMillis=30000 #任务间隙,单位为毫秒
fastdfs.pool.maxTotalPerKey=10 #每个key最大连接数
fastdfs.pool.minIdlePerKey=3 #每个key最小空闲连接
fastdfs.pool.numTestsPerEvictionRun=3 #每次检测空闲连接数
上传样例:
simpleFastdfsClient.upload(new File("E://data//1.pdf"));
四、遗留问题
使用nginx做文件下载服务器存在一个问题:
文件被删除后,由于nginx服务器对已经访问过的文件进行了缓存,那么此文件在一定时间内还是可以被成功下载,即便fastDFS服务已经关闭。
此文题已经解决,安装ngx_cache_purge 插件,访问“~/purge/资源”即可删除此文件