分布式--分布式文件系统FastDFS
大型项目中,文件服务器是很重要的角色,如果只有一台文件服务器,一旦当机,会产生很大影响,和业务服务器不同,文件服务器主要还是处理存放文件,和读取文件的功能
专用分布式文件系统是基于google File System的思想,文件上传后不能修改。需要专门的api对文件进行访问,也可称作分布式文件存储服务。典型代表:MogileFS、FastDFS、TFS
FastDFS由国人余庆开发,在chinaunix中担任FastDFS版主。FastDFS的架构如下:
三个角色:
角色 | 描述 |
---|---|
Client | 客户端,调用方,就是我们的业务服务器的代码 |
Tracker | 跟踪服务器(索引服务器),调度中心,作负载均衡作用 |
Storage | 存储服务器,文件真实的存储服务器,同组内使用RAID1 |
由图可知,Tracker和Storage都支持集群,Storage内部以组来区分
一、FastDFS安装
1. 工具安装
FastDFS是由c/c++编写,使用了cmake进行编译,所以需要安装cmake,至于make、gcc、g++编译器,一般linux都自带
yum install cmake
2. FastDFS下载
地址(墙):https://sourceforge.net/projects/fastdfs/files/
可以从我代码仓库中下载:https://gitee.com/aruba/fast-dfs.git
传到服务器中:
3. 解压编译libfastcommon-master
解压:
unzip libfastcommon-master.zip
cd libfastcommon-master
使用make.sh编译:
./make.sh
编译完后安装:
./make.sh install
创建软连接:
ln -s /user/lib64/libfastcommon.so /usr/local/lib/libfastcommon.so
ln -s /usr/local/lib64/libfdfsclient.so /usr/local/lib/libfdfsclient.so
4. 解压编译FastDFS
解压:
tar zxf FastDFS_v5.08.tar.gz
cd FastDFS
编译安装:
./make.sh
./make.sh install
安装后 FastDFS主程序所在的位置:
目录 | 描述 |
---|---|
/usr/bin | 可执行文件所在的位置、主程序代码所在位置 |
/etc/fdfs | 配置文件所在的位置 |
/usr/include/fastdfs | 包含一些插件组所在的位置 |
5. 配置和启动Tracker
创建存放tracker数据的目录:
mkdir -p /usr/local/fastdfs/tracker
进入配置目录:
cd /etc/fdfs
复制sample配置文件:
cp tracker.conf.sample tracker.conf
修改配置文件:
vi tracker.conf
指定为上面创建的tracker目录路径:
启动服务和查看服务启动状态命令:
service fdfs_trackerd start
service fdfs_trackerd status
6. 配置和启动Storage
Tracker和Storage可以在不同服务器上
创建两个目录, 把base用于存储基础数据和日志,store用于存储上传数据:
mkdir -p /usr/local/fastdfs/storage/base
mkdir -p /usr/local/fastdfs/storage/store
复制sample配置文件:
cp storage.conf.sample storage.conf
修改配置文件:
vi storage.conf
指定上面创建的base和store目录路径,以及Tracker服务器地址
-
base_path:
-
store_path0:
-
tracker_server:
启动和查看服务:
service fdfs_storaged start
service fdfs_storaged status
最后记得关闭防火墙
二、FastDFS文件上传和下载
FastDFS文件操作需要专门的API,不过都已经封装成工具类了
1. 依赖
<dependency>
<groupId>cn.bestwu</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
2. fdfs_client.conf
在resources目录下新建配置文件fdfs_client.conf:
connect_timeout = 10
network_timeout = 30
charset = UTF-8
http.tracker_http_port = 8080
tracker_server = 192.168.42.4:22122
tracker服务器ip改为自己的
3. 工具类
/**
* FastDFS分布式文件系统操作客户端.
*/
public class FastDFSClient {
private static final String CONF_FILENAME = Thread.currentThread().getContextClassLoader().getResource("").getPath() + "fdfs_client.conf";
private static StorageClient storageClient = null;
/**
* 只加载一次.
*/
static {
try {
ClientGlobal.init(CONF_FILENAME);
TrackerClient trackerClient = new TrackerClient(ClientGlobal.g_tracker_group);
TrackerServer trackerServer = trackerClient.getConnection();
StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
storageClient = new StorageClient(trackerServer, storageServer);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @param inputStream 上传的文件输入流
* @param fileName 上传的文件原始名
* @return
*/
public static String[] uploadFile(InputStream inputStream, String fileName) {
try {
// 文件的元数据
NameValuePair[] meta_list = new NameValuePair[2];
// 第一组元数据,文件的原始名称
meta_list[0] = new NameValuePair("file name", fileName);
// 第二组元数据
meta_list[1] = new NameValuePair("file length", inputStream.available() + "");
// 准备字节数组
byte[] file_buff = null;
if (inputStream != null) {
// 查看文件的长度
int len = inputStream.available();
// 创建对应长度的字节数组
file_buff = new byte[len];
// 将输入流中的字节内容,读到字节数组中。
inputStream.read(file_buff);
}
// 上传文件。参数含义:要上传的文件的内容(使用字节数组传递),上传的文件的类型(扩展名),元数据
String[] fileids = storageClient.upload_file(file_buff, getFileExt(fileName), meta_list);
return fileids;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
/**
* @param file 文件
* @param fileName 文件名
* @return 返回Null则为失败
*/
public static String[] uploadFile(File file, String fileName) {
FileInputStream fis = null;
try {
NameValuePair[] meta_list = null; // new NameValuePair[0];
fis = new FileInputStream(file);
byte[] file_buff = null;
if (fis != null) {
int len = fis.available();
file_buff = new byte[len];
fis.read(file_buff);
}
String[] fileids = storageClient.upload_file(file_buff, getFileExt(fileName), meta_list);
return fileids;
} catch (Exception ex) {
return null;
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 根据组名和远程文件名来删除一个文件
*
* @param groupName 例如 "group1" 如果不指定该值,默认为group1
* @param remoteFileName 例如"M00/00/00/wKgxgk5HbLvfP86RAAAAChd9X1Y736.jpg"
* @return 0为成功,非0为失败,具体为错误代码
*/
public static int deleteFile(String groupName, String remoteFileName) {
try {
int result = storageClient.delete_file(groupName == null ? "group1" : groupName, remoteFileName);
return result;
} catch (Exception ex) {
return 0;
}
}
/**
* 修改一个已经存在的文件
*
* @param oldGroupName 旧的组名
* @param oldFileName 旧的文件名
* @param file 新文件
* @param fileName 新文件名
* @return 返回空则为失败
*/
public static String[] modifyFile(String oldGroupName, String oldFileName, File file, String fileName) {
String[] fileids = null;
try {
// 先上传
fileids = uploadFile(file, fileName);
if (fileids == null) {
return null;
}
// 再删除
int delResult = deleteFile(oldGroupName, oldFileName);
if (delResult != 0) {
return null;
}
} catch (Exception ex) {
return null;
}
return fileids;
}
/**
* 文件下载
*
* @param groupName 卷名
* @param remoteFileName 文件名
* @return 返回一个流
*/
public static InputStream downloadFile(String groupName, String remoteFileName) {
try {
byte[] bytes = storageClient.download_file(groupName, remoteFileName);
InputStream inputStream = new ByteArrayInputStream(bytes);
return inputStream;
} catch (Exception ex) {
return null;
}
}
public static NameValuePair[] getMetaDate(String groupName, String remoteFileName) {
try {
NameValuePair[] nvp = storageClient.get_metadata(groupName, remoteFileName);
return nvp;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
/**
* 获取文件后缀名(不带点).
*
* @return 如:"jpg" or "".
*/
private static String getFileExt(String fileName) {
if (StringUtils.isBlank(fileName) || !fileName.contains(".")) {
return "";
} else {
return fileName.substring(fileName.lastIndexOf(".") + 1); // 不带最后的点
}
}
}
4. 文件上传
public class Upload {
public static void main(String[] args) {
InputStream is = null;
try {
is = new FileInputStream(new File("C:\\Users\\tyqhc\\Pictures\\苹果.png"));
String[] ret = FastDFSClient.uploadFile(is, "苹果.png");
System.out.println(Arrays.toString(ret));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
执行结果:
5. 文件下载
public class DownLoad {
public static void main(String[] args) {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = FastDFSClient.downloadFile("group1", "M00/00/00/wKgqBGKsFF2AToPoAAAnvQxtles494.png");
outputStream = new FileOutputStream(new File("D:\\a.jpg"));
int index = 0;
while ((index = inputStream.read()) != -1) {
outputStream.write(index);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
执行结果:
三、Nginx反向代理
由于FastDFS不支持http,上传后就无法通过http访问了,所以使用Nginx反向代理,来支持使用http进行访问
Nginx安装前,可以使用yum下载工具,就不用手动配置openssl等了:
yum install -y automake autoconf libtool pcre pcre-devel zlib zlib-devel openssl openssl-devel
Nginx下载可以参考以前的文章:Nginx流媒体服务器搭建,只需要下载nginx,并解压
1. 解压fastdfs-nginx-module_v1.16.tar.gz
tar zxf fastdfs-nginx-module_v1.16.tar.gz
2. 修改module配置
cd fastdfs-nginx-module
cd src
vi config
路径中local去掉,修改后:
3. 编译和安装nginx
编译nginx,指定编译安装到当前目录的bin目录下,并添加FastDFS的module:
cd /root/nginx/nginx-1.12.1
./configure --prefix=`pwd`/bin --add-module=/root/nginx/fastdfs-nginx-module/src
安装nginx:
make install
4. 配置FastDFS
4.1 将fastdfs-nginx-module模块中的配置文件mod_fastdfs.conf,复制到FastDFS配置文件目录/etc/fdfs下
cp /root/nginx/fastdfs-nginx-module/src/mod_fastdfs.conf /etc/fdfs
并进行编辑:
vi /etc/fdfs/mod_fastdfs.conf
修改内容如下:
方便复制:
connect_timeout=10
tracker_server=192.168.42.4:22122
url_have_group_name=true
store_path0=/usr/local/fastdfs/storage/store
4.2 复制FastDFS解压后的conf文件至/etc/fdfs下:
cp /root/fastdfs/FastDFS/conf/http.conf /etc/fdfs
cp /root/fastdfs/FastDFS/conf/mime.types /etc/fdfs
4.3 创建软连接
M00是FastDFS保存数据时使用的虚拟目录,需要当它链接到真实目录上:
ln -s /usr/local/fastdfs/storage/store/data/ /usr/local/fastdfs/storage/store/data/M00
5. 配置nginx
接下来就到了配置nginx的环节,配置nginx只需要修改它的nginx.conf文件
5.1 打开nginx.conf文件
定位到nginx安装的目录,通过vi编辑器打开:
cd /root/nginx/nginx-1.12.1/bin/conf
vi nginx.conf
5.2 权限使用root用户
nginx需要访问文件权限,所以给它最大的用户权限:
5.3 配置反向代理
FastDFS为http预留的是哪个端口呢?查看/etc/fdfs/storage.conf 文件:
vi /etc/fdfs/storage.conf
可以看到默认使用的是8888:
所以我们需要反向代理8888端口,匹配group0到group9的/M00下的文件请求
5.4 启动nginx
cd ../sbin
./nginx
使用浏览器访问我们之前上传的图片:
ip:8888/group1/M00/00/00/wKgqBGKsFF2AToPoAAAnvQxtles494.png
四、SpringBoot中使用
服务器配置好后,后台开发上传功能,无非就是在service层使用FastDFS工具类上传,使用之前SpringBoot文件上传的项目:SpringBoot--文件上传
将上面文件上传的工具类和依赖进行相同配置:
1. service层
定义上传接口:
public interface UploadService {
String upload(MultipartFile file) throws IOException;
}
实现接口:
@Service
public class UploadServiceImpl implements UploadService {
@Override
public String upload(MultipartFile file) throws IOException {
String[] ret = FastDFSClient.uploadFile(file.getInputStream(), file.getName());
StringBuilder stringBuilder = new StringBuilder();
for (String item : ret) {
stringBuilder.append(File.separator).append(item);
}
return stringBuilder.toString();
}
}
2. controller层
注入UploadService并上传:
@Controller
public class PlayerController {
// 文件存储位置
private final static String FILESERVER = "http://192.168.42.4:8888/";
@Autowired
private UploadService uploadService;
@RequestMapping("uploadImg.do")
@ResponseBody
public Map<String, Object> uploadImg(MultipartFile img, HttpServletRequest req) throws IOException {
Map<String, Object> model = new HashMap<>();
System.out.println("OriginalFilename:" + img.getOriginalFilename());
String suffix = img.getOriginalFilename().substring(img.getOriginalFilename().lastIndexOf('.'));
System.out.println("suffix:" + suffix);
if (!suffix.equalsIgnoreCase(".jpg")) {
model.put("msg", "文件类型必须为jpg");
return model;
}
String path = uploadService.upload(img);
model.put("msg", "上传成功");
model.put("filepath", FILESERVER + path);
model.put("filetype", img.getContentType());
System.out.println("返回结果:" + model);
return model;
}
}
启动SpringBoot后,浏览器访问并上传:
后台控制台也打印了日志: