目录
3.2 搭建虚拟主机(暴露主机端口,外部可以通过http访问)
一、项目架构的改变
我们采用上面的方式进行图片存储。图片在哪个项目中上传的图片就存储到哪个项目所在的服务器。其他项目模块通过HTTP请求进行获取图片。
虽然说可以实现不同模块之间图片的访问,但是问题也很明显,包含如下问题:
- 图片存储过于分散。
- 图片多的服务器压力比较大,可能会影响其他功能。
- 存储到项目路径中,重启会丢失。存储到外部文件中,I/O操作性能低。
针对上面的问题可以搭建单独的图片服务器,专门做图片存储及图片访问的。而想要搭建图片服务器就需要应用图片存储技术/工具。
二、FastDFS简介
1.简介
FastDFS是一个轻量级的开源分布式文件系统。2008年4月份开始启动。类似google FS的一个轻量级分布式文件系统,纯C实现,支持Linux、FreeBSD、AIX等UNIX系统。
主要解决了大容量的文件存储和高并发访问的问题,文件存取时实现了负载均衡。实现了软件方式的磁盘阵列(Redundant Arrays of Independent Drives,RAID),可以使用廉价的IDE(Integrated Drive Electronics)硬盘进行存储。并且支持存储服务器在线扩容。支持相同内容的文件只保存一份,节约磁盘空间。
FastDFS只能通过Client API访问,不支持POSIX访问方式。
FastDFS特别适合大中型网站使用,用来存储资源文件(如:图片、文档、音频、视频等等)
2.网址
FastDFS没有官网。但是作者余庆(happy_fish100)担任chinaunix中FastDFS板块版主。
并且会不定期更新板块中的内容
http://bbs.chinaunix.net/ |
FastDFS软件可以在sourceforge中进行下载,最新版本为5.08
https://sourceforge.net/projects/fastdfs/files/ |
三、FastDFS架构(面试)
1.架构图
2.角色
client:客户端。使用java语言编写的项目属于客户端。
Tracker Server:跟踪服务器,主要做调度工作,在访问上起负载均衡的作用。在内存中记录集群中group和storage server的状态信息,是连接client和storage server的枢纽。
storage Server:存储服务器,文件和文件属性(meta data)都保存到存储服务器上
3.架构解读
只有两个角色,tracker server 和storage server,不需要存储文件索引信息。
所有服务器都是对等的,不存在Master-slave关系。
存储服务器采用分组方式,同组内存储服务器上的文件完全相同(RAID 1)
不同组的storage server之间不会相互通信。
由storage server 主动向tracker server报告状态信息,tracker server之间不会相互通信
四、FastDFS安装
1.拉取镜像
docker中fastdfs没有官方镜像
docker pull delron/fastdfs
2.创建并启动tracker容器
Tracker默认端口是22122.
--network表示与宿主共用网络
docker run -d --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs delron/fastdfs tracker |
3.创建并启动storage容器
docker run -d --network=host --name storage -e TRACKER_SERVER=192.168.8.128:22122 -v /var/fdfs/storage:/var/fdfs -e GROUP_NAME=group1 delron/fastdfs storage |
注意:
第一次启动是成功的,如果希望重新启动,必须要先删除Linux中/var/fdfs/xxx/data/fdfs_ .pid文件然后在启动。否则查看docker容器日志会出现
五、文件上传流程
1.时序图
2.流程说明
a) 客户端访问tracker
b) Tracker返回storage的ip和端口
c) 客户端直接访问storage,把文件的内容和元数据发送过去
d)Storage返回文件存储id。包含了组名和文件名
六、Fastdfs-java-client
1.添加依赖
<dependencies>
<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>
</dependencies> |
2.编写配置文件
在resources下新建fastdfs的配置文件
文件名:fdfs_client.conf
修改成自己的tracker服务器ip
connect_timeout = 10
network_timeout = 30
charset = UTF-8
http.tracker_http_port = 8080
tracker_server = 192.168.8.131:22122 |
3.导入工具类
在com.bjsxt.utils.FastDFSClient下粘贴配置工具类。
如果我们新建聚合项目完成FastDFS文件上传操作需要修改
private static final String CONF_FILENAME = "fdfs_client.conf"
package com.bjsxt.utils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.lang3.StringUtils;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.ClientGlobal;
import org.csource.fastdfs.StorageClient;
import org.csource.fastdfs.StorageClient1;
import org.csource.fastdfs.StorageServer;
import org.csource.fastdfs.TrackerClient;
import org.csource.fastdfs.TrackerServer;
/**
* 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.编写测试代码
随意新建一个包含主方法的类。com.bjsxt.MyMain
public class MyMain {
public static void main(String[] args) {
try {
File file = new File("D:/b.png");
InputStream is = new FileInputStream(file);
String fileName = UUID.randomUUID().toString()+".png";
String[] result = FastDFSClient.uploadFile(is, fileName);
System.out.println(Arrays.toString(result));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
七.文件下载
1.时序图
2.下载说明
1. client询问tracker下载文件的storage,参数为文件标识(组件和文件名);
2.tracker返回一台可用的storage;
3.client直接和storage通讯完成文件下载。
3.代码实现
直接使用工具方法完成下载。
try {
InputStream is = FastDFSClient.downloadFile("group1", "M00/00/00/wKg0gF3zAKCARs6kAAASjQVYlWA098.png");
OutputStream os = new FileOutputStream(new File("D:/jqk.png"));
int index = 0 ;
while((index = is.read())!=-1){
os.write(index);
}
os.flush();
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
八、Nginx简介
1.简介
FastDFS是没有文件访问功能的,需要借助其他工具实现图片HTTP访问的。Nginx就具备代理虚拟机主机功能。
Nginx (engine x) 是一个高性能的HTTP和反向代理服务。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的,第一个公开版本0.1.0发布于2004年10月4日。
Nginx 是一个很强大的高性能Web和反向代理服务,它具有很多非常优越的特性:在连接高并发的情况下,Nginx是Apache服务不错的替代品:Nginx在美国是做虚拟主机生意的老板们经常选择的软件平台之一。
2.代理方式
2.1 正向代理
正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。
2.2 反向代理
反向代理(Reverse Proxy) 方式是指代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外表现为一个反向代理服务器。
2.3 二者之间的区别
位置不同
正向代理,架设在客户机和目标主机之间;
反向代理,架设在服务器端;
代理对象不同
正向代理,代理客户端,服务端不知道实际发起请求的客户端;
反向代理,代理服务端,客户端不知道实际提供服务的服务端;
3.Nginx作用
3.1 HTTP协议代理(反向代理)
只要支持HTTP协议访问的内容,都可以由Nginx进行代理。Nginx只支持HTTP协议的代理,其他协议不支持。
3.2 搭建虚拟主机(暴露主机端口,外部可以通过http访问)
Nginx可以监听所安装的主机的某个端口,对外支持这个端口的HTTP访问。当接收到外部HTTP请求后把本机中资源返回给客户端。今天的课程内容就是使用Nginx的搭建虚拟主机功能,外部请求图片时,把图片信息响应给请求发。
3.3 负载均衡(反向代理)
Nginx可以代理多个主机,内置负载均衡策略。
九、Nginx安装
1.修改nginx配置
进入到storage容器后修改nginx配置文件
docker exec -it storage /bin/bash vi /tmp/nginx/nginx-1.12.2/conf/nginx.conf |
在配置文件的http里面添加下面的内容
server{ listen 8888; server_name localhost; location ~/group([0-9])/M00{ ngx_fastdfs_module; } } |
2.测试
保证在容器命令行中,任意目录下执行命令。
向a.html尾加<b>bjsxt</b>,nihao。如果a.html不存在会新建这个文件
echo "<b>bjsxt</b>,nihao" > a.html |
使用fastdfs客户端命令进行上传文件。
fdfs_upload_file 客户端工具,client.conf 客户端配置文件 a.html要上传的文件
/usr/bin/fdfs_upload_file /etc/fdfs/client.conf a.html |
执行完成后会输出可访问访问路径。
在浏览器中访问,如果正常访问,说明安装成功。