FastDFS
50张图片详解 VMware Workstation 16 + CentOS 7 下载与安装
【FastDFS 普通安装所需要的包】
libfastcommon
、fastdfs
、fastdfs-nginx-module
百度云盘下载
【HHS 工具】Xshell&Xftp、FinalShell、SecureCRT&SecureFX
简介
FastDFS 由淘宝的余庆在 2008 年开源的一款轻量级分布式文件管理系统,FastDFS 用 C 语言实现,支持 Linux、MacOS 等 UNIX 系统。FastDFS 类似 Google FS,属于应用级文件系统,不是通用的文件系统,只能通过专有 API 访问,目前提供了 C 和 Java SDK ,以及 PHP 扩展 SDK。
FastDFS 专为互联网应用量身定做,解决大容量文件存储问题,追求高性能和高扩展性,它可以看做是基于文件的 key/value 存储系统,key 为文件 ID,value 为文件内容,因此称作分布式文件存储服务更为合适。
优势
大容量存储和高性能访问
架构
作为一款分布式文件管理系统,FastDFS 主要包括四个方面的功能:
- 文件存储
- 文件同步
- 文件上传
- 文件下载
FastDFS 官网的系统架构图:
FastDFS 架构包括 Tracker 和 Storage 两部分,Tracker 用来追踪文件,相当于是文件的索引,而 Storage 则用来保存文件。
上传的文件最终保存在 Storage 上,文件的元数据信息保存在 Tracker 上,通过 Tracker 可以实现对 Storage 的负载均衡。
Storage 一般会搭建成集群,一个 Storage Cluster 可以由多个组构成,不同的组之间不进行通信,一个组又相当于一个小的集群,组由多个 Storage Server 组成,组内的 Storage Server 会通过连接进行文件同步来保证高可用。
安装
Docker 安装
安装
# 1、拉取镜像
docker pull delron/fastdfs
# 2、使用 docker 镜像构建 tracker 容器(跟踪服务器,起到调度的作用)
docker run -dti --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs -v /etc/localtime:/etc/localtime delron/fastdfs tracker
# 3、使用 docker 镜像构建 storage 容器(存储服务器,提供容量和备份服务)
docker run -dti --network=host --name storage -e TRACKER_SERVER=192.168.226.128:22122 -e GROUP_NAME=group1 -v /var/fdfs/storage:/var/fdfs -v /etc/localtime:/etc/localtime delron/fastdfs storage
# 若不修改端口,跳过【4-6】
# 4、进入 storage 容器,到 storage 的配置文件中配置 http 访问的端口(默认:8888),配置文件在 /etc/fdfs 目录下的 storage.conf
# 进入容器
docker exec -it storage /bin/bash
# 进入目录
cd /etc/fdfs
# 编辑文件
vi storage.conf
# 5、修改 storage 中的 nginx (不需要安装)
cd /usr/local/nginx/conf
vi nginx.conf
# 6、修改完配置重启容器,没有修改就不需要重启
docker restart storage
# 开放端口
# nginx
firewall-cmd --zone=public --permanent --add-port=8888/tcp
# tracker
firewall-cmd --zone=public --permanent --add-port=22122/tcp
# storage
firewall-cmd --zone=public --permanent --add-port=23000/tcp
# 重启防火墙
systemctl restart firewalld
# 容器开机自启
docker update --restart=always tracker
docker update --restart=always storage
java客户端调用
结构
依赖
<!-- Swagger3 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!-- FastDFS -->
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>1.27.2</version>
</dependency>
配置
application.properties
# FastDFS 配置
# 读取时间
fdfs.so-timeout=1500
# 连接超时时间
fdfs.connect-timeout=600
# 缩略图
fdfs.thumb-image.width=150
fdfs.thumb-image.height=150
# Tracker 服务配置地址列表,确保 tracker storage nginx 已经启动
fdfs.tracker-list[0]=192.168.226.128:22122
fdfs.web-server-url=http://192.168.226.128:8888/
FdfsConfig
@Configuration
@Import(FdfsClientConfig.class) // 提供拥有 FastDFS 的 Java 客户端
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING) // 解决 JMX 重复注册 Bean 的问题
public class FdfsConfig {
}
Swagger2Config
@Configuration
public class Swagger2Config {
@Bean
Docket docket() {
return new Docket(DocumentationType.OAS_30)
// 配置网站的基本信息
.apiInfo(new ApiInfoBuilder()
// 网站标题
.title("FastDFS接口文档")
// 标题后面的版本号
.version("v1.0")
.description("FastDFS接口文档")
// 联系人信息
.contact(new Contact("yueyazhui", "https://www.yueyazhui.top", "15235032479@163.com"))
.build())
.select()
// 指定接口的位置
.apis(RequestHandlerSelectors.basePackage("top.yueyazhui.learnfastdfs02.controller"))
.build();
}
}
FdfsClientUtil
@Component
public class FdfsClientUtil {
@Autowired
private FastFileStorageClient fastFileStorageClient;
@Value("${fdfs.web-server-url}")
private String fdfsWebServerUrl;
/**
* 文件上传
*
* @param file
* @return 返回文件路径(卷名和文件名)
* @throws IOException
*/
public String upload(MultipartFile file) throws IOException {
if (file.isEmpty()) {
return "文件不存在";
}
byte[] bytes = file.getBytes();
long fileSize = file.getSize();
String originalFilename = file.getOriginalFilename();
String extension = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
InputStream is = new ByteArrayInputStream(bytes);
Set<MetaData> metaDataSet = new HashSet<MetaData>();
metaDataSet.add(new MetaData("date", DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss")));
metaDataSet.add(new MetaData("author", "yueyazhui"));
StorePath storePath = fastFileStorageClient.uploadFile(is, fileSize, extension, metaDataSet);
return fdfsWebServerUrl + storePath.getFullPath();
}
/**
* 下载文件
*
* @param filePath 文件路径
* @return 文件字节
* @throws IOException
*/
public byte[] download(String filePath) {
byte[] bytes = null;
if (StringUtils.isNotBlank(filePath)) {
String group = filePath.substring(0, filePath.indexOf("/"));
String path = filePath.substring(filePath.indexOf("/") + 1);
DownloadByteArray byteArray = new DownloadByteArray();
bytes = fastFileStorageClient.downloadFile(group, path, byteArray);
}
return bytes;
}
/**
* 删除文件
*
* @param filePath 文件路径
*/
public void delete(String filePath) {
if (StringUtils.isNotBlank(filePath)) {
fastFileStorageClient.deleteFile(filePath);
}
}
}
FileController
@Controller
@Api(tags = "文件")
public class FileController {
@Autowired
private FdfsClientUtil fdfsClientUtil;
@GetMapping
@ApiIgnore
public String index() {
return "index";
}
/**
* 上传文件
*
* @param file 文件
* @return 文件路径
*/
@PostMapping(value = "upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ResponseBody
public String upload(@RequestPart("file") MultipartFile file) throws IOException {
String filePath = fdfsClientUtil.upload(file);
return filePath;
}
/**
* 下载文件
*
* @param filePath 文件路径
* @return
*/
@GetMapping(value = "download")
@ResponseBody
@ApiOperation("下载")
@ApiImplicitParams({
@ApiImplicitParam(name = "filePath", value = "文件路径", required = true),
@ApiImplicitParam(name = "fileName", value = "文件名", required = true)
})
public ResponseEntity<byte[]> download(@RequestParam("filePath") String filePath, @RequestParam("fileName") String fileName) throws UnsupportedEncodingException {
byte[] bytes = fdfsClientUtil.download(filePath);
HttpHeaders headers = new HttpHeaders();
headers.setContentDispositionFormData("attachment", new String(fileName.getBytes("UTF-8"), "ISO-8859-1"));
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return new ResponseEntity<>(bytes, headers, HttpStatus.CREATED);
}
/**
* 删除文件
*
* @param filePath 文件路径
* @return 删除结果
*/
@DeleteMapping(value = "delete")
@ResponseBody
@ApiOperation("删除")
@ApiImplicitParam(name = "filePath", value = "文件路径", required = true)
public void delete(@RequestParam("filePath") String filePath) {
fdfsClientUtil.delete(filePath);
}
}
普通安装
图片上传一般使用 FastDFS,图片上传成功后,图片访问一般采用 Nginx,因此 FastDFS 安装将从三个方面来介绍:
- Tracker 安装
- Storage 安装
- Nginx 安装
Tracker 安装
首先需要准备一个环境两个库以及一个安装包。
一个环境
FastDFS 采用 C 语言开发,需要 gcc 环境,安装命令如下:
yum -y install gcc-c++
两个库
FastDFS 依赖 libevent 库,安装命令如下:
yum -y install libevent
FastDFS 运行所需要的基础库 libfastcommon,由 FastDFS 官方提供。
注:也可从文章开头的百度云盘中下载。
将下载好的 libfastcommon 拷贝至 /usr/local/ 目录下,然后依次执行如下命令:
cd /usr/local
tar -zxvf libfastcommon-1.0.43.tar.gz
cd libfastcommon-1.0.43
./make.sh
./make.sh install
一个安装包
Tracker 和 Storage 安装包是相同的,下载一次即可。
安装文件可以从 FastDFS 的 GitHub 仓库上下载,下载地址
注:也可从文章开头的百度云盘中下载。
将下载好的文件拷贝到 /usr/local 目录下,然后依次执行如下命令安装:
cd /usr/local
tar -zxvf fastdfs-6.06.tar.gz
cd fastdfs-6.06
./make.sh
./make.sh install
安装成功后,执行如下命令,将安装目录的 conf 目录下的配置文件拷贝到 /etc/fdfs 目录下:
cd conf
cp ./* /etc/fdfs/
配置
进入 /etc/fdfs 目录下进行配置:
打开 tracker.conf 文件:
cd /etc/fdfs
vi tracker.conf
修改如下配置:
默认端口是 22122,可以根据实际需求修改。然后配置一下元数据的保存目录(注意目录必须存在)。
启动
启动 Tracker:
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf start
Tracker 安装成功。
Storage 安装
简单起见,搭建一个 Storage 实例即可。
Storage 安装也需要 libevent 和 libfastcommon,这两个库的安装参考上文。
Storage 安装,也和 Tracker 一致,执行命令也都一样,如果将 Tracker 和 Storage 安装在同一台服务器上。(相当于安装 Tracker 时已经安装了 Storage)
唯一需要做的,就是进入到 /etc/fdfs 目录下,配置 Storage:
cd /etc/fdfs
vi storage.conf
配置三个地方,分别是 base_path、store_path0 以及 tracker_server;tracker_server 模板有两个地址,如果只有一个 Tracker,配置一个,注释掉另一个。
配置完成后,执行如下命令启动 Storage:
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf start
Tracker 和 Storage 启动完成后,就文件上传了;但上传的文件如果是图片,我们还需要提供一个图片的访问功能,目前的最佳方案 Nginx。
Nginx 安装
Nginx 是 FastDFS 的重要搭档。
Nginx 的安装分为两个步骤:
- 安装 Nginx
- 在 Storage 下安装 fastdfs-nginx-module
安装 Nginx
cd /root/install-package
wget http://nginx.org/download/nginx-1.22.0.tar.gz
tar -zxvf nginx-1.22.0.tar.gz
cd nginx-1.22.0
yum -y install pcre-devel
yum -y install openssl openssl-devel
./configure
make
make install
# 启动
cd /usr/local/nginx/sbin
./nginx
# 修改 Nginx 配置文件,重新加载 Nginx
./nginx -s reload
Nginx 启动成功之后,在浏览器中访问 Nginx 地址,看到此页面,表示 Nginx 已经安装成功了。
在 Storage 下安装 fastdfs-nginx-module
注:也可从文章开头的百度云盘中下载。
将下载的文件拷贝到 /usr/local 目录下。然后进入 /usr/local 目录,分别执行如下命令:
cd /usr/local
tar -zxvf fastdfs-nginx-module-1.22.tar.gz
然后将 /usr/local/fastdfs-nginx-module-1.22/src/mod_fastdfs.conf
文件拷贝到 /etc/fdfs/
目录下,并修改该文件的内容:
cp /usr/local/fastdfs-nginx-module-1.22/src/mod_fastdfs.conf /etc/fdfs/
vi /etc/fdfs/mod_fastdfs.conf
接下来,回到第一步下载的 nginx 安装文件的解压目录中,执行如下命令,重新配置编译安装:
cd /root/install-package/nginx-1.22.0
./configure --add-module=/usr/local/fastdfs-nginx-module-1.22/src
make
make install
安装完成后,修改 nginx 的配置文件,如下:
vi /usr/local/nginx/conf/nginx.conf
在这里配置 nginx 请求转发。
配置完成后,启动 nginx;
cd /usr/local/nginx/sbin
ps aux | grep nginx
kill -9 25665
kill -9 25666
./nginx
看到如下日志,表示 nginx 启动成功:
ngx_http_fastdfs_set pid=25899
fastdfs-nginx-module 的作用
Storage 由很多组构成,每个组又是一个小的集群,在每一个组里边,数据会进行同步,但是如果数据还没同步,这个时候就有请求发来了,该怎么办?此时fastdfs-nginx-module 会直接从源 Storage 上获取文件。
安装成功了。
Java 客户端调用
依赖:
<dependency>
<groupId>net.oschina.zcx7878</groupId>
<artifactId>fastdfs-client-java</artifactId>
<version>1.27.0.0</version>
</dependency>
在项目的 resources 目录下添加 FastDFS 的配置文件 fastdfs-client.properties,内容如下:
## fastdfs-client.properties
fastdfs.connect_timeout_in_seconds = 5
fastdfs.network_timeout_in_seconds = 30
fastdfs.charset = UTF-8
fastdfs.http_anti_steal_token = false
fastdfs.http_secret_key = yueyazhui
fastdfs.http_tracker_http_port = 80
fastdfs.tracker_servers = 47.95.1.150:22122
## Whether to open the connection pool, if not, create a new connection every time
fastdfs.connection_pool.enabled = true
## max_count_per_entry: max connection count per host:port , 0 is not limit
fastdfs.connection_pool.max_count_per_entry = 500
## connections whose the idle time exceeds this time will be closed, unit: second, default value is 3600
fastdfs.connection_pool.max_idle_time = 3600
## Maximum waiting time when the maximum number of connections is reached, unit: millisecond, default value is 1000
fastdfs.connection_pool.max_wait_time_in_ms = 1000
fastdfs.tracker_servers Tracker 的地址,根据实际情况配置即可。
fastdfs.http_secret_key 秘钥。
代码如下:
StorageClient1 client1 = null;
@BeforeEach
void before() throws MyException, IOException {
// 加载配置文件
ClientGlobal.initByProperties("fastdfs-client.properties");
// 构建TrackerClient
TrackerClient trackerClient = new TrackerClient();
// 获得TrackerServer
TrackerServer trackerServer = trackerClient.getConnection();
// 初始化StorageServer
StorageServer storageServer = null;
// 获得一个StorageClient1
client1 = new StorageClient1(trackerServer, storageServer);
}
@Test
void upload() throws MyException, IOException {
// 元数据信息(额外信息)
NameValuePair pairs[] = null;
// 上传
String fileId = client1.upload_file1("C:\\Users\\15235\\Pictures\\Saved Pictures\\images\\avatar_admin.gif", "gif", pairs);
System.out.println("fileId = " + fileId);
}
@Test
void download() throws MyException, IOException {
// 下载
byte[] bytes = client1.download_file1("group1/M00/00/00/L18BlmNQ7Q-ALxnTAAItxHZKwEI729.gif");
FileOutputStream fos = new FileOutputStream(new File("C:\\Users\\15235\\Pictures\\Saved Pictures\\images\\avatar_admin-copy.gif"));
fos.write(bytes);
fos.close();
}
安全访问
访问时,加一个上传时候的令牌即可。
首先在服务端开启令牌校验:
vi /etc/fdfs/http.conf
配置完成后,记得重启服务端:
cd /usr/local/nginx/sbin
./nginx -s stop
./nginx
获取令牌,代码如下:
@Test
void token() throws MyException, UnsupportedEncodingException, NoSuchAlgorithmException {
// 获取时间戳
int ts = (int) Instant.now().getEpochSecond();
// 获取Token
String token = ProtoCommon.getToken("M00/00/00/L18BlmNQ7Q-ALxnTAAItxHZKwEI729.gif", ts, "yueyazhui");
StringBuffer sb = new StringBuffer();
sb
.append("http://47.95.1.150/")
.append("group1/M00/00/00/L18BlmNQ7Q-ALxnTAAItxHZKwEI729.gif")
.append("?token=")
.append(token)
.append("&ts=")
.append(ts);
System.out.println("sb.toString() = " + sb.toString());
}
主要是根据 ProtoCommon.getToken 方法来获取令牌,注意这个方法的第一个参数是你要访问的文件 id,**注意,这个地址里边不包含 group;**第二个参数是时间戳,第三个参数是密钥,密钥要和服务端的配置一致。
将生成的字符串拼接,追加到访问路径后面,如:http://47.95.1.150/group1/M00/00/00/L18BlmNQ7Q-ALxnTAAItxHZKwEI729.gif?token=108711d7651b1b78905896181d7693cb&ts=1666256470
。「此时访问路径里边如果没有令牌,会访问失败。」
完成!