本文会搭建一个适合低业务访问业务量的高可用的FastDFS集群环境:两个Tracker服务,一个storage group中两个storage服务节点;该方案仅适用于业务访问量较低的环境下。对于大量业务系统的高并发访问,为了保证存储系统正常工作一般的架构思路:安装多个Tracker服务(至少两个,根据业务量调整),搭建多个storage group(至少两个,根据业务量调整),每个storage group中多个storage node(至少两个,做数据的冗余备份,进行容灾机制,而且node必须在不同的机器上)
创新互联致力于互联网品牌建设与网络营销,包括网站建设、网站设计、SEO优化、网络推广、整站优化营销策划推广、电子商务、移动互联网营销等。创新互联为不同类型的客户提供良好的互联网应用定制及解决方案,创新互联核心团队10多年专注互联网开发,积累了丰富的网站经验,为广大企业客户提供一站式企业网站建设服务,在网站建设行业内树立了良好口碑。
一、FastDFS简介
FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。
FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
FastDFS服务端有两个角色:跟踪器(tracker)和存储节点(storage)。跟踪器主要做调度工作,在访问上起负载均衡的作用。
存储节点存储文件,完成文件管理的所有功能:就是这样的存储、同步和提供存取接口,FastDFS同时对文件的metadata进行管理。所谓文件的meta data就是文件的相关属性,以键值对(key value)方式表示,如:width=1024,其中的key为width,value为1024。文件metadata是文件属性列表,可以包含多个键值对。
跟踪器和存储节点都可以由一台或多台服务器构成。跟踪器和存储节点中的服务器均可以随时增加或下线而不会影响线上服务。其中跟踪器中的所有服务器都是对等的,可以根据服务器的压力情况随时增加或减少。
为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起到了冗余备份和负载均衡的作用。
在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。
当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。
FastDFS中的文件标识分为两个部分:卷名和文件名,二者缺一不可。
———简介摘自百度百科
二、FastDFS原理介绍
1.文件上传
FastDFS以客户端库的方式提供基本的文件访问接口如upload、download、append、delete等,Storage 服务会定时的向Tracker服务发送自己的存储信息。当Tracker 服务集群中的Tracker 服务是多个时,各个Tracker服务之间的关系是对等的,因此客户端上传时会任意选择一个Trackre服务。当Tracker服务收到客户端上传文件请求时,会为该文件分配一个可以存储文件的group,当选定了group后就要决定给客户端分配group中的哪一个storage服务。当分配好storage 服务后,客户端向storage发送写文件请求,storage将会为文件分配一个数据存储目录。然后为文件分配一个文件ID标示,然后根据以上的信息生成文件名存储文件。
2.文件同步
上传文件后,客户端将文件写到group内的一个storage 服务即为上传文件成功,storage服务写完文件后,会由后台线程将文件同步至同group内的其他的storage 服务节点上。 每个storage服务写文件后,会同时写一份binlog,binlog里不包含文件数据,只包含文件名等元信息,这份binlog用于后台同步,storage会记录向group内其他storage同步的进度,以便重启后能接上次的进度继续同步;进度以时间戳的方式进行记录,所以最好能保证集群内的所有server的始终保持同步。最后Storage服务的同步进度会作为元数据的一部分汇报到tracker服务上,tracker服务在选择读storage的时候会以同步进度作为参考指标。
3.下载文件
当下载文件时,客户端先询问tracker服务下载文件的storage,参数为文件标识(卷名和文件名);然后tracker向客户端返回一台可用的storage;最后客户端直接和storage通讯完成文件下载。
三、部署环境准备
1.环境说明
操作系统CentOS7.6
fastdfs版本:6.01
nginx版本:1.16.1
keepalived版本:2.0.19
2.系统依赖
gcc gcc-c++ perl pcre pcre-devel zlib zlib-devel openssl openssl-devel libnl libnl-devel
3.软件环境
libevent下载地址:http://libevent.org/
nginx下载地址:http://nginx.org/en/download.html
keepalived下载地址:https://www.keepalived.org/software/
fastdfs下载地址:https://github.com/happyfish200/fastdfs/releases
libfasttcommon下载地址:https://github.com/happyfish200/libfastcommon/releases
fastdfs-nginx-module下载地址:https://github.com/happyfish200/fastdfs-nginx-module/releases
4.机器及网络环境规划
Fdfs Server VIP: 192.168.100.110Tracker Server1: 192.168.100.111Tracker Server2: 192.168.100.112Storage Group1 Node1: 192.168.100.111Storage Group1 Node2: 192.168.100.112
5.防火墙设置
关闭系统防火墙:sudo systemctl stop firewalld && systemctl disable firewalld
四、Keepalived服务安装配置
1.下载Keepalived源码包
官网地址:https://www.keepalived.org/
下载地址:https://www.keepalived.org/software/keepalived-2.0.19.tar.gz
2.上传并解压Keepalived源码包
tar -zxvf keepalived-2.0.19.tar.gz
3.编译Keepalived准备
进入解压目录:cd keepalived-2.0.19
执行编译准备:./configure --prefix=/work/keepalived
注意:一定要有gcc和openssl编译相关的依赖
4.编译安装Keepalived
make && make install
5.安装配置Keepalived
keepalived启动时会从/etc/keepalived/中相关的目录下查找keepalived.conf配置文件,因此将keepalived安装录/usr/local/keepalived/etc/keepalived.conf 拷贝到/etc/keepalived/中。
mkdir /etc/keepalived/
cp /work/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf
cp /work/keepalived/etc/sysconfig/keepalived /etc/sysconfig/keepalived
6.设置Keepalived开机启动项
systemctl enable keepalived
然后就能使用systemctl start/stop/status keepalived管理keepalived了
7.配置Keepalived服务
192.168.100.111机器上配置:vi /etc/keepalived/keepalived.conf
vrrp_script check_nginx { interval 3 script "/work/script/check_nginx.sh"} vrrp_instance fdfs_server { state MASTER interface enp0s3 virtual_router_id 110 priority 100 advert_int 3 authentication { auth_type PASS auth_pass password } virtual_ipaddress { 192.168.100.110 } track_script { check_nginx }}
192.168.100.112机器上配置:vi /etc/keepalived/keepalived.conf
vrrp_script check_nginx { interval 3 script "/work/script/check_nginx.sh"} vrrp_instance fdfs_server { state BACKUP interface enp0s3 virtual_router_id 110 priority 90 advert_int 3 authentication { auth_type PASS auth_pass password } virtual_ipaddress { 192.168.100.110 } track_script { check_nginx }}
8.编写nginx服务检测脚本
vi /work/script/check_nginx.sh
#!/bin/bashactive_status=`netstat -lntp|grep nginx|wc -l`if [ $active_status -gt 0 ]; then exit 0else exit 1fi
然后给脚本赋予执行权限:chmod +x /work/script/check_nginx.sh
9.修改内核参数
vi /etc/sysctl.conf
增加如下内容:
net.ipv4.ip_nonlocal_bind = 1 #允许忽视VIP的存在net.ipv4.ip_forward = 1 #允许转发
sysctl --system 使配置生效
五、安装FastDFS依赖库
1.安装libevent依赖
解压libevent源码包:tar -zxvf libevent-2.1.11-stable.tar.gz
进入源码目录:cd libevent-2.1.11-stable
编译安装前配置:./configure
编译安装:make && make install
默认安装位置:/usr/local/lib
2.安装libfasttcommon依赖
解压libfasttcommon源码包:tar -zxvf libfastcommon-1.0.41.tar.gz
进入源码目录:cd libfastcommon-1.0.41
编译安装:./make.sh && ./make.sh install
默认安装位置:/usr/lib64
六、安装部署Tracker服务和Storage服务
1.安装fastdfs服务
解压fastdfs源码包:tar -zxvf fastdfs-6.01.tar.gz
进入fastdfs源码包:cd fastdfs-6.01
编译安装:./make.sh && ./make.sh install
2.fastdfs服务目录信息
安装完成后服务及脚本拷贝到/usr/bin 目录,配置文件拷贝到/etc/fdfs目录,启动脚本拷贝到/etc/init.d/目录
3.注册开机启动
chkconfig --add fdfs_trackerd
chkconfig fdfs_trackerd on
chkconfig --add fdfs_storaged
chkconfig fdfs_storaged on
4.数据目录规划
创建fdfs数据主目录:mkdir /work/fdfs
创建tracker数据目录:mkdir /work/fdfs/tracker
创建storage数据目录:mkdir /work/fdfs/storage
5.配置tracker服务
将/etc/fdfs目录下的tracker.conf.sample改为tracker.conf:mv tracker.conf.sample tracker.conf修改内容如下:
将base_path=/home/yuqing/fastdfs 改为:/work/fdfs/tracker(该目录为上面定义创建)
启动Tracker服务:systemctl start fdfs_trackerd
6.配置storage服务
将/etc/fdfs目录下的storage.conf.sample改为storage.conf:mv storage.conf.sample storage.conf修改内容如下:
base_path=/home/yuqing/fastdfs 改为:base_path=/work/fdfs/storage(该目录为上面定义创建)
store_path0=/home/yuqing/fastdfs 改为:store_path0=/work/fdfs/storage(该目录为上面定义创建)
tracker_server=192.168.209.121:22122 改为:tracker_server=192.168.100.111:22122和tracker_server=192.168.100.112:22122
将/etc/fdfs/torage_ids.conf.sample 为storage_ids.conf,内容修改为当前group的storage节点信息:
100001 group1 192.168.100.111
100002 group1 192.168.100.112
启动Storage服务:systemctl start fdfs_storaged
七、Nginx服务安装配置
1.下载Nginx源码包
官网地址:http://nginx.org/
下载地址:http://nginx.org/en/download.html
2.上传并解压Nginx源码包及fastdfs插件包
tar -zxvf nginx-1.16.1.tar.gz
tar -zxvf fastdfs-nginx-module-1.21.tar.gz
3.编译Nginx准备
进入解压目录:cd nginx-1.16.1
拷贝fastdfs插件包到nginx源码目录:mv ../fastdfs-nginx-module-1.21 .
执行编译准备:./configure --prefix=/work/nginx \--with-stream \--add-module=fastdfs-nginx-module-1.21/src
注意:一定要有gcc和openssl编译相关的依赖
4.编译安装Nginx
make && make install
5.注册到系统服务
vi /usr/lib/systemd/system/nginx.service
[Unit]Description=nginxDocumentation=http://nginx.org/en/docs/After=network.target[Service]Type=forkingPIDFile=/work/nginx/logs/nginx.pidExecStartPre=/work/nginx/sbin/nginx -t -c /work/nginx/conf/nginx.confExecStart=/work/nginx/sbin/nginx -c /work/nginx/conf/nginx.confExecReload=/bin/kill -s HUP $MAINPIDExecStop=/bin/kill -s QUIT $MAINPIDPrivateTmp=true[Install]WantedBy=multi-user.target
6.设置Nginx开机启动项
systemctl enable nginx
然后就能使用systemctl start/stop/status nginx管理nginx了
7.修改Nginx配置
vi /work/nginx/conf/nginx.conf
#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;}stream { upstream tracker { server 192.168.100.111:22122 weight=1 max_fails=2 fail_timeout=10s; server 192.168.100.112:22122 weight=1 max_fails=2 fail_timeout=10s; } server { listen 7777; proxy_timeout 5m; proxy_pass tracker; proxy_connect_timeout 10s; }}http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; upstream storage { server 192.168.100.111:8888 weight=1 max_fails=2 fail_timeout=10s; server 192.168.100.112:8888 weight=1 max_fails=2 fail_timeout=10s; } server { listen 80; server_name localhost; location /group1 { proxy_pass http://storage; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } server { listen 8888; server_name localhost; location / { alias /work/fdfs/storage/data/; ngx_fastdfs_module; } }}
8.配置nginx的fdfs插件
将/etc/fdfs下的http.conf.sample和mime.types.sample重命名为:http.conf和mime.types
将fastdfs-nginx-module-1.21/src下的mod_fastdfs.conf拷贝到/etc/fdfs下
修改mod_fastdfs.conf如下:
连接超时时间: connect_timeout=5
Tracker服务地址:tracker_server=192.168.100.111:22122 和tracker_server=192.168.100.112:22122
Storage服务端口:storage_server_port=23000
如果文件ID的uri中包含/group**,则要设置为true:url_have_group_name = true
Storage配置的store_path0路径,必须和storage.conf中的一致:store_path0=/work/fdfs/storage
其他详细配置如下:
# connect timeout in seconds# default value is 30sconnect_timeout=5# network recv and send timeout in seconds# default value is 30snetwork_timeout=10# the base path to store log filesbase_path=/work/fdfs/storage# if load FastDFS parameters from tracker server# since V1.12# default value is falseload_fdfs_parameters_from_tracker=true# storage sync file max delay seconds# same as tracker.conf# valid only when load_fdfs_parameters_from_tracker is false# since V1.12# default value is 86400 seconds (one day)storage_sync_file_max_delay = 86400# if use storage ID instead of IP address# same as tracker.conf# valid only when load_fdfs_parameters_from_tracker is false# default value is false# since V1.13use_storage_id = false# specify storage ids filename, can use relative or absolute path# same as tracker.conf# valid only when load_fdfs_parameters_from_tracker is false# since V1.13storage_ids_filename = storage_ids.conf# FastDFS tracker_server can ocur more than once, and tracker_server format is# "host:port", host can be hostname or ip address# valid only when load_fdfs_parameters_from_tracker is truetracker_server=192.168.100.111:22122tracker_server=192.168.100.112:22122# the port of the local storage server# the default value is 23000storage_server_port=23000# the group name of the local storage servergroup_name=group1# if the url / uri including the group name# set to false when uri like /M00/00/00/xxx# set to true when uri like ${group_name}/M00/00/00/xxx, such as group1/M00/xxx# default value is falseurl_have_group_name = true# path(disk or mount point) count, default value is 1# must same as storage.confstore_path_count=1# store_path#, based 0, if store_path0 not exists, it's value is base_path# the paths must be exist# must same as storage.confstore_path0=/work/fdfs/storage# standard log level as syslog, case insensitive, value list:### emerg for emergency### alert### crit for critical### error### warn for warning### notice### info### debuglog_level=info# set the log filename, such as /usr/local/apache2/logs/mod_fastdfs.log# empty for output to stderr (apache and nginx error_log file)log_filename=# response mode when the file not exist in the local file system## proxy: get the content from other storage server, then send to client## redirect: redirect to the original storage server (HTTP Header is Location)response_mode=proxy# the NIC alias prefix, such as eth in Linux, you can see it by ifconfig -a# multi aliases split by comma. empty value means auto set by OS type# this paramter used to get all ip address of the local host# default values is emptyif_alias_prefix=# use "#include" directive to include HTTP config file# NOTE: #include is an include directive, do NOT remove the # before include#include http.conf# if support flv# default value is false# since v1.15flv_support = true# flv file extension name# default value is flv# since v1.15flv_extension = flv# set the group count# set to none zero to support multi-group on this storage server# set to 0 for single group only# groups settings section as [group1], [group2], ..., [groupN]# default value is 0# since v1.14group_count = 1# group settings for group #1# since v1.14# when support multi-group on this storage server, uncomment following section[group1]group_name=group1storage_server_port=23000store_path_count=1store_path0=/work/fdfs/storage# group settings for group #2# since v1.14# when support multi-group, uncomment following section as neccessary#[group2]#group_name=group2#storage_server_port=23000#store_path_count=1#store_path0=/home/yuqing/fastdfs
八、服务启动及验证
分别启动keepalive、nginx、tracker、storage服务
查看服务是否正常服务:
在任意Storage机器上查看集群状态:fdfs_monitor /etc/fdfs/storage.conf
九、Java客户端测试
1.java项目Maven依赖
项目地址:https://github.com/tobato/FastDFS_Client目前客户端主要依赖于SpringBoot,因此必须引入:FastDFS 依赖包: org.springframework.boot spring-boot-starter-parent 2.0.0.RELEASE 将FastDFS引入项目:@Import(FdfsClientConfig.class)在application.yml当中配置Fdfs相关参数:fdfs: pool: #连接池最大数量 max-total: 200 #每个tracker地址的最大连接数 max-total-per-key: 50 #连接耗尽时等待获取连接的最大毫秒数 max-wait-millis: 5000 so-timeout: 1500 connect-timeout: 600 thumb-image: width: 150 height: 150 tracker-list: - 192.168.100.110:7777或者fdfs: pool: #连接池最大数量 max-total: 200 #每个tracker地址的最大连接数 max-total-per-key: 50 #连接耗尽时等待获取连接的最大毫秒数 max-wait-millis: 5000 so-timeout: 1500 connect-timeout: 600 thumb-image: width: 150 height: 150 tracker-list: - 192.168.100.111:22122 - 192.168.100.112:22122使用接口服务对Fdfs服务端进行操作,主要接口包括:TrackerClient - TrackerServer接口GenerateStorageClient - 一般文件存储接口 (StorageServer接口)FastFileStorageClient - 为方便项目开发集成的简单接口(StorageServer接口)AppendFileStorageClient - 支持文件续传操作的接口 (StorageServer接口) com.github.tobato fastdfs-client 1.26.7
2.代码测试
package com.maxbill;import com.github.tobato.fastdfs.FdfsClientConfig;import com.github.tobato.fastdfs.domain.fdfs.MetaData;import com.github.tobato.fastdfs.domain.fdfs.StorePath;import com.github.tobato.fastdfs.domain.proto.storage.DownloadByteArray;import com.github.tobato.fastdfs.service.FastFileStorageClient;import lombok.extern.log4j.Log4j2;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Import;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;import java.io.File;import java.io.FileInputStream;import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.*;@Log4j2@Component@Import(FdfsClientConfig.class)public class FdfsClientUtil { private static final String BASE_URL = "http://192.168.100.110:8888/"; @Autowired private FastFileStorageClient storageClient; private static FdfsClientUtil fdfsClientUtil; @PostConstruct public void init() { fdfsClientUtil = this; } /** * 文件上传 * * @param file 文件信息 * @param infoMap 文件扩展信息 * @return 上传路径 */ public static MapuploadFile(File file, Map infoMap) { try { String fileName = file.getName(); String fileType = fileName.substring(fileName.lastIndexOf("\\") + 1); //String fileType = FilenameUtils.getExtension(file.getName()) log.info("[fdfs-upload]-start upload file ... "); log.info("[fdfs-upload]-request upload file name: {}", fileName); log.info("[fdfs-upload]-request upload file info: {}", infoMap); StorePath path = fdfsClientUtil.storageClient.uploadFile(new FileInputStream(file), file.length(), fileType, getMetaData(infoMap)); log.info("[fdfs-upload]-upload success path: {}", path.getFullPath()); return getResultMap(BASE_URL.concat(path.getFullPath()), null); } catch (Exception e) { log.error("[fdfs-upload]-upload file exception info: {}", e.getMessage()); return getResultMap(null, e.getMessage()); } } /** * 下载文件 * * @param filePath 文件路径标识 * @return 文件字节 */ public static Map downloadFile(String filePath) { try { filePath = filePath.replace(BASE_URL, ""); StorePath storePath = StorePath.parseFromUrl(filePath); String group = storePath.getGroup(); String path = storePath.getPath(); log.info("[fdfs-download]-start download file ... "); log.info("[fdfs-download]-request download file group: {}", group); log.info("[fdfs-download]-request download file path: {}", path); byte[] data = fdfsClientUtil.storageClient.downloadFile(group, path, new DownloadByteArray()); log.info("[fdfs-download]-request download file success ... "); return getResultMap(data, null); } catch (Exception e) { log.error("[fdfs-download]-download file exception info: {}", e.getMessage()); return getResultMap(null, e.getMessage()); } } /** * 删除文件 * * @param filePath 文件路径标识 * @return 操作结果 */ public static boolean deleteFile(String filePath) { try { filePath = filePath.replace(BASE_URL, ""); StorePath storePath = StorePath.parseFromUrl(filePath); String group = storePath.getGroup(); String path = storePath.getPath(); log.info("[fdfs-delete]-start delete file ... "); log.info("[fdfs-delete]-request delete file group: {}", group); log.info("[fdfs-delete]-request delete file path: {}", path); fdfsClientUtil.storageClient.deleteFile(storePath.getGroup(), storePath.getPath()); log.info("[fdfs-delete]-request delete file success ... "); return true; } catch (Exception e) { log.error("[fdfs-delete]-delete file exception info: {}", e.getMessage()); return false; } } /** * 查看文件元信息 * * @param filePath 文件路径标识 * @return 文件信息 */ public static Map getFileInfo(String filePath) { try { filePath = filePath.replace(BASE_URL, ""); StorePath storePath = StorePath.parseFromUrl(filePath); String group = storePath.getGroup(); String path = storePath.getPath(); log.info("[fdfs-meta]-start meta file ... "); log.info("[fdfs-meta]-request meta file group: {}", group); log.info("[fdfs-meta]-request meta file path: {}", path); Map infoMap = new HashMap<>(); infoMap.put("createPath", filePath); Set metaData = fdfsClientUtil.storageClient.getMetadata(storePath.getGroup(), storePath.getPath()); log.info("[fdfs-meta]-request meta file success ... "); if (null != metaData && !metaData.isEmpty()) { metaData.forEach(meta -> { infoMap.put(meta.getName(), meta.getValue()); }); } return getResultMap(infoMap, null); } catch (Exception e) { log.error("[fdfs-meta]-meta file exception info: {}", e.getMessage()); return getResultMap(null, e.getMessage()); } } /** * 封装附件元信息 * * @param infoMap 自定义数据 * @return 附件元信息 */ private static Set getMetaData(Map infoMap) { if (null != infoMap && !infoMap.isEmpty()) { Set metaDataSet = new HashSet<>(); for (String key : infoMap.keySet()) { metaDataSet.add(new MetaData(key, infoMap.get(key))); } return metaDataSet; } else { DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Set metaDataSet = new HashSet<>(); metaDataSet.add(new MetaData("createUser", "MaxBill")); metaDataSet.add(new MetaData("createDate", df.format(new Date()))); return metaDataSet; } } /** * 封装结果信息 * * @param data 数据 * @param info 信息 * @return 操作结果 */ private static Map getResultMap(Object data, String info) { Map resultMap = new HashMap<>(); if (StringUtils.isEmpty(info)) { resultMap.put("flag", true); resultMap.put("data", data); resultMap.put("info", "success"); } else { resultMap.put("flag", false); resultMap.put("info", info); resultMap.put("data", null); } return resultMap; } }