目录
1. 分布式文件系统
1.1传统文件系统
- 传统的文件系统格式:
- ntfs / fat32 / ext3 / ext4
- ntfs / fat32 / ext3 / ext4
1.2分布式文件系统
文件系统的全部,不在同一台主机上,而是在很多台主机上,多个分散的文件系统组合在一起,形成了一个完整的文件系统。
思考:如何将多台主机组合成为一个分布式文件系统???
- 使用现有的框架实现该功能
-必须有两个角色
-存储文件角色
-管理者角色 - 进程
2. FastDFS
2.1 fastDFS介绍
1.fastDFS概述
- 是用c语言编写的一款开源的分布式文件系统框架。
- 中国人
- 余庆-淘宝架构师
- 为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,注重高可用、高性能等指标
- 冗余备份:- 纵向扩容
- 线性扩容:- 横向扩容
- 可以很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
- 应用场景:高效的存储文件
- 上传过程
- 下载过程
- 案例:
- 图:-图片服务器
- 电商网站-展示的图片
2.fastDFS框架中的三个角色
- 追踪器-tracker
- 管理者角色
- 存储节点-storage
- 存储文件
- 负责实现文件上传/下载功能
- 客户端-client
- 负责进行文件上传/下载操作(发出请求、下达命令)
3.fastDFS三个角色之间的关系
- 1.追踪器
- 可以对存储节点进行管理
- 存储节点和客户端都需要连接追踪器
- 追踪器进程需要先启动,然后存储节点启动,最后客户端启动
- 2.存储节点
- 连接追踪器,上传当前节点的状态信息
- 存储节点当前的使用容量和剩余容量
- 文件的同步情况
- 文件上传/下载的次数
- 客户端
- 先连接追踪器
- 上传和下载,为什么先连接追踪器?
- 譬如上传-1G
- 向追踪器询问:哪个存储节点有足够的容量存储上传的文件?
- 客户端会得到符合条件的存储节点的连接信息
- IP+Port
- 客户端通过得到的IP+port连接存储节点
- 给存储节点发送数据
- 得到存储节点返回的文件ID
- 譬如下载-a.txt
- 客户端连接追踪器,询问要下载的文件信息
- 追踪器进行查找,将对应的存储节点的IP+port发送给客户端
- 客户端根据得到的IP+port连接这个存储节点
- 存储节点将请求的文件数据发送给客户端
- 客户端接收存储节点发送的数据,并且将其写入磁盘
4.fastDFS集群-(了解即可)
1.追踪器集群
为什么集群?
- 避免单点故障
多个Tracker如何工作?
- 轮询
如何实现集群?
- 修改配置文件
2.存储节点集群
- fastDFS管理存储节点的方式?
- 以分组的形式管理的
- 每个组中可以有1台也可以有多台主机(纵向扩容 / 线性扩容:相互备份)
- 同一组的主机之间,一般是相互备份关系,需要相互通信。
- 可以有1个组也可以有多个组
- 集群方式(扩容方式)
- 横向扩容
- 目的:增加存储的容量
- 需要有一台新主机,添加到一个新的不存在的分组中
- 不同组的主机(存储节点),不需要通信
- 纵向扩容
- 目的:备份
- 给一个已经存在的组中添加新的主机,同一组中的主机会相互备份数据,所以同一组中的主机需要通信
- 一个组的容量,是以组内容量最小的主机的容量,作为整个存储节点的容量
2.2 fastDFS安装
fastDFS安装
- 先安装libfastcommon-1.36.zip //它是fastDFS的基础库包
- unzip libfastcommon-1.36.zip //解压
- rm libfastcommon-1.36.zip -rf //(安装完毕删除安装包)
- cd libfastcommon-master //进入到安装包内
- ./make.sh //生成库文件
- ./make.sh install //安装
- 再安装fastdfs-5.10.tar.gz // 安装fastDFS
- tar -zxvf fast.xxx.tar.gz //解压
- ./make.sh //生成库文件
- ./make.sh install // 安装
在fastdfs安装包里,有一个INSTALL文件,里面有全部的安装和配置步骤
安装完成后测试一把,在shell里直接执行
- fdfs_test
ls /usr/bin/fdfs* //查看安装完成后都拷贝进去了哪些可执行文件
/usr/bin/fdfs_appender_test /usr/bin/fdfs_download_file /usr/bin/fdfs_test1
/usr/bin/fdfs_appender_test1 /usr/bin/fdfs_file_info /usr/bin/fdfs_trackerd
/usr/bin/fdfs_append_file /usr/bin/fdfs_monitor /usr/bin/fdfs_upload_appender
/usr/bin/fdfs_crc32 /usr/bin/fdfs_storaged /usr/bin/fdfs_upload_file
/usr/bin/fdfs_delete_file /usr/bin/fdfs_test
- 在shell中,可以直接执行这些可执行程序
cd /etc/fdfs //安装成功之后会在/etc下创建这个目录,并拷贝进去一系列的配置文件
lsclient.conf.sample — 客户端使用的配置文件
storage.conf.sample — 存储节点使用的配置文件
tracker.conf.sample — 追踪器使用的配置文件
storage_ids.conf.sample – 用不着
3.3 fastFDS配置文件修改(部署fastDFS)
cp tracker.conf.sample tracker.conf // 依次去掉 .sample 后缀,保留原来的配置文件不动
cp tracker.conf.sample tracker.conf
cp client.conf.sample client.conf
cp storage.conf.sample storage.conf
cp storage_ids.conf.sample storage_ids.conf
1.tracker.conf
# 追踪器对应的主机的IP地址, 可以不写
bind_addr=
# 追踪器绑定的端口, 只要保证其是一个没有被占用的端口就可以
port=22122
# 追踪器进程写log日志的目录, 一些简单的数据文件也会放到该目录, xxx.pid
base_path=/home/yuqing/fastdfs
# 这个路径必须存在
# 我的改为了:
base_path=/home/lwh/Desktop/study/fastDFSLogFile/tracker # 只需更改这一处
mkdir fastDFSLogFile
mkdir client tracker storage
2.storage.conf
# 存储节点所属的组(横向扩容还是纵向扩容)
group_name=group1
# 当前存储节点对应的主机的IP地址, 也可不写
bind_addr=
# 存储节点绑定的端口
port=23000
# 存储log日志的目录, 也存储少量的数据文件
base_path=/home/lwh/Desktop/study/fastDFSLogFile/storage
# 这个路径必须存在
# 当前存储节点的存储路径个数
store_path_count=1
# 指定具体的存储路径
store_path0=/home/lwh/Desktop/study/fastDFSLogFile/storage
# 如果 当前存储节点的存储路径个数为N个
store_path_count=N
# 依次 指定具体的存储路径
store_path0=/home/lwh/Desktop/study/fastDFSLogFile/storage/1
store_path1=/home/lwh/Desktop/study/fastDFSLogFile/storage/2
...
store_pathN=/home/lwh/Desktop/study/fastDFSLogFile/storage/n
# 要连接的追踪器的IP和端口(很重要)
tracker_server=192.168.184.131:22122
3.client.conf
# 客户端写log日志的目录
base_path=/home/lwh/Desktop/study/fastDFSLogFile/client
# 客户端要连接的追踪器的ip+port信息
tracker_server=192.168.0.197:22122
4.fastDFS集群配置
4.1如何集群tracker
- 修改storage.conf,告诉存储节点,追踪器有集群
- 修改client.conf,告诉客户端,追踪器有集群
- 各个tracker,修改自己的tracker.conf
#storage.conf #配置追踪器集群的IP和Port
tracker_server=192.168.184.131:22122
tracker_server=192.168.185.131:22123
tracker_server=192.168.186.131:22124
tracker_server=192.168.187.131:22125
...
#client.conf #配置追踪器集群的IP和Port
tracker_server=192.168.184.131:22122
tracker_server=192.168.185.131:22123
tracker_server=192.168.186.131:22124
tracker_server=192.168.187.131:22125
...
4.2 如何集群存储节点
- (有足够多的主机)(自己实验时,要有足够多的虚拟机)
- 在各个主机上安装fastDFS,然后分别修改storage.conf
4.3 客户端
- 在各个主机上安装fastDFS,然后分别修改client.conf
3.4 fastDFS的启动
[root@lwh study]# ls /usr/bin/fdfs* # 查看fastDFS的可执行程序
/usr/bin/fdfs_appender_test /usr/bin/fdfs_crc32 /usr/bin/fdfs_file_info /usr/bin/fdfs_test /usr/bin/fdfs_upload_appender
/usr/bin/fdfs_appender_test1 /usr/bin/fdfs_delete_file /usr/bin/fdfs_monitor /usr/bin/fdfs_test1 /usr/bin/fdfs_upload_file
/usr/bin/fdfs_append_file /usr/bin/fdfs_download_file /usr/bin/fdfs_storaged /usr/bin/fdfs_trackerd
第一个:启动追踪器 - 守护进程
# 启动 tracker的可执行文件+配置文件
fdfs_trackerd /etc/fdfs/tracker.conf # 参数是需要加载的配置文件
# 关闭/终止
fdfs_trackerd /etc/fdfs/tracker.conf stop
# 重新加载/重启
fdfs_trackerd /etc/fdfs/tracker.conf restart
[root@lwh study]# ps aux | grep fdfs*
root 28546 0.0 0.1 80044 1664 ? Sl 18:03 0:00 fdfs_trackerd /etc/fdfs/tracker.conf
root 28591 0.0 0.0 112712 968 pts/1 S+ 18:03 0:00 grep --color=auto fdfs*
第二个:启动存储节点 - 守护进程
#启动
fdfs_storaged /etc/fdfs/storage.conf
# 终止
fdfs_storaged /etc/fdfs/storage.conf stop
# 重启
fdfs_storaged /etc/fdfs/storage.conf restart
[root@lwh study]# ps aux | grep fdfs*
root 28546 0.0 0.1 80044 1656 ? Sl 18:03 0:00 fdfs_trackerd /etc/fdfs/tracker.conf
root 28626 64.4 0.0 12256 828 ? R 18:05 0:04 fdfs_storaged /etc/fdfs/storage.conf
root 28629 0.0 0.0 112712 968 pts/1 R+ 18:05 0:00 grep --color=auto fdfs*
最后:启动客户端 - 普通进程
# 上传
[root@lwh study]# fdfs_upload_file /etc/fdfs/client.conf ./a.txt
group1/M00/00/00/wKi4g16z3j-AIFviAAAAGUtZyi8845.txt # 得到返回的file_id(文件ID)
[root@lwh study]#
- 第一个参数: 客户端加载的配置文件
- 第二个参数: 上传的文件
- 执行成功之后得到一个字符串
group1/M00/00/00/wKi4g16z3j-AIFviAAAAGUtZyi8845.txt #file_id 文件ID
- 组/文件夹名/文件夹名/文件名
- 下载的时候需要使用这个字符串
测试
# 上传test.cpp
fdfs_upload_file /etc/fdfs/client.conf /home/lwh/Desktop/study/test/test.cpp
# 得到的file_id为 group1/M00/00/00/wKi4hV7rfYyAd95HAAAGErTwkiY696.cpp
# 下载文件
[root@lwh download]# fdfs_download_file /etc/fdfs/client.conf group1/M00/00/00/wKi4hV7rfYyAd95HAAAGErTwkiY696.cpp
[root@lwh download]# ls
wKi4hV7rfYyAd95HAAAGErTwkiY696.cpp
# 把文件的名字改为原来的文件名
[root@lwh download]# mv wKi4hV7rfYyAd95HAAAGErTwkiY696.cpp test.cpp
[root@lwh download]# ls
test.cpp
# 下载
[root@lwh study]# fdfs_download_file /etc/fdfs/client.conf group1/M00/00/00/wKi4g16z3j-AIFviAAAAGUtZyi8845.txt
[root@lwh study]# ll | grep *.txt
-rw-r--r--. 1 root root 25 May 7 19:52 wKi4g16z3j-AIFviAAAAGUtZyi8845.txt
- 参数1: 客户端使用的配置文件
- 参数2: 上传文件成功之后得到的file_ID
# 分析
1. 文件上传之后得到fileID, 我们需要保存这个FileID, 也需要保存原来的文件名
- 真实的开发环境中如何保存?
写入数据库表中
2. 下载的时候通过这个ID下载
3. 下载完成需要将文件名改为原来的名字
上传文件完成之后,把file_id和原来的文件名,作为一条记录,插入到数据库中
下载下来文件之后,根据file_id进行查询,找到原来的文件名,进行名字的还原,此时完成下载
fastDFS状态检测
- 命令
# fdfs_monitor /etc/fdfs/client.conf # 显示整个分布式文件系统及各个存储节点的状态信息
# 着重看 ip_addr 后的状态是不是 ACTIVE # 证明这个节点能正常工作
# 删除指定组中的某一个存储节点
fdfs_monitor /etc/fdfs/client.conf delete group1 10.120.151.114
# 删除group1中的 IP为10.120.151.114存储节点
[Storage Server的7种状态]
https://blog.csdn.net/u014723529/article/details/46048411
# 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 :在线,尚不能提供服务
某一个存储节点状态不正常,以上状态都不能正常工作
原因:频繁的修改配置文件,导致多次读取,有一些数据混乱,导致不能正常工作
解决:
删除指定组中的某一个存储节点,
把配置文件修改正确之后,然后再添加上去,
重新启动fastDFS,让它能够正确的加载
# FDFS_STORAGE_STATUS:ACTIVE :在线,可以提供服务
#正常状态必须是ACTIVE,如果运行以下命令:
fdfs_monitor /etc/fdfs/client.conf
#发现有以下状态的服务器:
Storage 4:
ip_addr = 10.120.151.114 WAIT_SYNC
#经过各种重启都不解决问题,只好先删除,再加入
#从集群中删除
fdfs_monitor /etc/fdfs/client.conf delete group1 10.120.151.114
#在114服务器中,删除数据文件夹
rm -rf /home/lwh/Desktop/study/fastDFSLogFile/storage/data
#重启114节点
fdfs_storaged /etc/fdfs/storage.conf
#重新检测状态信息
fdfs_monitor /etc/fdfs/client.conf
3.5 对file_id的解释
- group1/M00/00/00/wKi4g16z3j-AIFviAAAAGUtZyi8845.txt
- group1
- 组名, 文件上传到了存储节点上, 这个存储节点所属的组
- M00
- M00是虚拟磁盘目录, M00这个目录是不存在的
- 它实际上是映射到了一个真实存在的目录
- 需要参考存储节点设置的存储目录
- 先找store_path0=/home/lwh/Desktop/study/fastDFSLogFile/storage
- 然后在进入其data目录
- M00等价于/home/lwh/Desktop/study/fastDFSLogFile/storage/data/
- 这个值有可能是M00、M01、M02…
- store_path_count=1
- 每一个真实的存储目录都对应的M0x(x=0,1,2,3…)
- 00/00
- 实际的存储目录
- /home/lwh/Desktop/study/fastDFSLogFile/storage/data/00/00/
- wKi4g16z3j-AIFviAAAAGUtZyi8845.txt
- 文件名包含的信息
- 采用Base64编码
- 包含的字段包括
- 源storage server Ip 地址
- 文件创建时间
- 文件大小
- 文件CRC32效验码
- 循环冗余校验
- 随机数
4. 上传下载代码实现
- 使用多进程方式实现
- 使用fastDFS API实现
源码安装包里面的 ./client 下有作者的源码例程
看明白之后,自己修改成函数,就可以直接使用这个函数完成某项功能了
5. Linux下源码安装 - 回顾
安装流程:
-
以下文件, 里边有安装步骤
readme
readme.md
INSTALL
-
找 可执行文件 configure
- 执行这个可执行文件
- 检测安装环境
- 生成 makefile
- 执行make命令
- 编译源代码
- 生成了动态库
- 静态库
- 可执行程序
- 安装 make install (需要管理员权限)
- 将第三步生成的动态库/动态库/可执行程序拷贝到对应的系统目录
6. fastDFS复习
- fastDFS是什么?
- 分布式文件系统的框架
- 能干什么?
- 实现文件的上传、下载
- 怎么用?
- 三个角色:
- 追踪器 - tracker
- 存储节点 - storage
- 客户端 - client
- 修改配置文件
- 启动
追踪器:
- fdfs_trackerd /etc/fdfs/tracker.conf (stop/restart)
存储节点
- fdfs_storaged /etc/fdfs/storage.conf (stop/restart)
客户端
- 代码根据用户需求由程序员实现
7. 客户端编写
7.1 多线程方式
- 直接执行fdfs_upload_file命令, 得到的文件ID会写到当前终端
- 需要对输出做重定向 - dup2(old, new)
// 调用execlp()这个进程有没有能力读数据?
// 没有!灵魂替换!此时这个进程已经不受控制了
// 所以,在调用execlp()之前,进行重定向
// 把file_id交父进程去读(子进程生成的数据,由父进程去读,其实就是进程间通信)
int dup2(int oldfd, int newfd);
dup2(xxx, stdout_fileno)
execlp(fdfs_upload_file, xxx, xxx, NULL);
// 父进程需要读子进程得到的文件ID
// 通过匿名管道的方式, 实现进程间通信(有血缘关系时,匿名管道最简单)
// dup2重定向的时候,把标准输出重定向到写端,父进程通过管道读端,就把子进程中的数据读出来了
7.2 fastDFS API
1. 多线程方式实现文件上传
// myUpload_file_execlp.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
void myUpload_file_execlp(const char *conf_filename, const char *local_filename, char *file_id, int len)
{
// 1.创建管道
int fd[2];
int ret = pipe(fd);
if (-1 == ret)
{
perror("pipe error");
exit(0);
}
// 2.创建子进程
pid_t pid = fork();
if (pid == 0)
{
//子进程
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("fdfs_upload_file", "fdfs_upload_file", conf_filename, local_filename, NULL);
perror("execlp() error");
exit(-1);
}
else if (pid > 0)
{
//父进程
close(fd[1]);
read(fd[0], file_id, len);
close(fd[0]);
wait();
}
}
int main()
{
char file_id[1024] = {
0};
int len = sizeof(file_id);
myUpload_file_execlp("/etc/fdfs/client.conf", "myUpload_file_execlp.c", file_id, len);
printf("file_id: %s", file_id);
return 0;
}
# Makefile
#---------------------------------------------------------
# 生成可执行文件 execute
PROJECT = execute
#---------------------------------------------------------
# .o文件
SrcSuf = c
SrcSuf2 = cpp
ObjSuf = o
LibSuf = so
OBJFILES = ./myUpload_file_execlp.$(ObjSuf)
#---------------------------------------------------------
CC = g++
# 编译选项
CFlag = -w -g -ggdb -fshort-wchar -std=c++11
#---------------------------------------------------------
.SUFFIXES: .$(SrcSuf) .$(ObjSuf) .$(LibSuf) .$(SrcSuf2)
all: $(PROJECT) clean
$(PROJECT):$(OBJFILES)
@echo "creating $(PROJECT) start..."
$(CC) $(OBJFILES) -o $(PROJECT)
@echo "creating $(PROJECT) end"
#---------------------------------------------------------
.$(SrcSuf).$(ObjSuf):
@echo "Compiling $(PROJECT) $<"
$(CC) -MM $^
$(CC) -c $< -o $@ $(CFlag)
#---------------------------------------------------------
.$(SrcSuf2).$(ObjSuf):
@echo "Compiling $(PROJECT) $<"
$(CC) -MM $^
$(CXX) -c $< -o $@
#---------------------------------------------------------
clean:
@echo "Cleaning $(PROJECT) project files"
@rm -f $(OBJFILES) core
.PHONY:cleanobj
cleanobj:
-rm -f $(OBJFILES)
.PHONY:cleanexe
cleanexe:
-rm -f $(PROJECT)
.PHONY:cleanall
cleanall:cleanobj cleanexe
2. 使用fastDFS API实现文件上传
// myUpload_file_fdfsAPI.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "fdfs_client.h"
int myUpload_file_fdfsAPI(const char *conf_filename, const char *local_filename, char *file_id)
{
char group_name[FDFS_GROUP_NAME_MAX_LEN + 1];
ConnectionInfo *pTrackerServer;
int result;
int store_path_index;
ConnectionInfo storageServer;
if ((result = fdfs_client_init(conf_filename)) != 0)
{
return result;
}
pTrackerServer = tracker_get_connection();
if (pTrackerServer == NULL)
{
fdfs_client_destroy();
return errno != 0 ? errno : ECONNREFUSED;
}
*group_name = '\0';
if ((result = tracker_query_storage_store(pTrackerServer,
&storageServer,
group_name,
&store_path_index)) != 0)
{
fdfs_client_destroy();
fprintf(stderr, "tracker_query_storage fail, "
"error no: %d, error info: %s\n",
result, STRERROR(result));
return result;
}
result = storage_upload_by_filename1(pTrackerServer,
&storageServer, store_path_index,
local_filename, NULL,
NULL, 0, group_name, file_id);
if (result == 0)
{
printf("%s\n", file_id);
}
else
{
fprintf(stderr, "upload file fail, "
"error no: %d, error info: %s\n",
result, STRERROR(result));
}
tracker_disconnect_server_ex(pTrackerServer, true);
fdfs_client_destroy();
return result;
}
int main()
{
char file_id[1024] ;
memset(file_id,0,sizeof(file_id));
myUpload_file_fdfsAPI("/etc/fdfs/client.conf", "myUpload_file_fdfsAPI.c", file_id);
printf("file_id: %s", file_id);
return 0;
}
#---------------------------------------------------------
# 生成可执行文件 execute
PROJECT = execute
#---------------------------------------------------------
# .o文件
SrcSuf = c
SrcSuf2 = cpp
ObjSuf = o
LibSuf = so
OBJFILES = ./myUpload_file_fdfsAPI.$(ObjSuf)
# 头文件目录
INCLUDEPATH = -I /usr/include/fastdfs/
INCLUDEPATH += -I /usr/include/fastcommon/
# 库目录
LIBPATH += -L /usr/lib/
#---------------------------------------------------------
CC = g++
# 编译选项
CFlag = -w -g -ggdb -fshort-wchar -std=c++11 $(INCLUDEPATH)
# 链接选项
LDFLAGS += -l fdfsclient $(LIBPATH)
#---------------------------------------------------------
.SUFFIXES: .$(SrcSuf) .$(ObjSuf) .$(LibSuf) .$(SrcSuf2)
all: $(PROJECT) clean
# 生成可执行文件
$(PROJECT):$(OBJFILES)
@echo "creating $(PROJECT) start..."
$(CC) $(LDFLAGS) $(OBJFILES) -o $(PROJECT)
@echo "creating $(PROJECT) end"
#---------------------------------------------------------
# .c 生成 .o 文件
.$(SrcSuf).$(ObjSuf):
@echo "Compiling $(PROJECT) $<"
$(CC) -c $(CFlag) $< -o $@
#---------------------------------------------------------
# .cpp 生成 .o 文件
.$(SrcSuf2).$(ObjSuf):
@echo "Compiling $(PROJECT) $<"
$(CXX) -c $(CFlag) $< -o $@
#---------------------------------------------------------
# 删除 .o 文件
clean:
@echo "Cleaning $(PROJECT) project files"
@rm -f $(OBJFILES) core
# make cleanall 删除 .o 文件 execute文件
.PHONY:cleanobj
cleanobj:
-rm -f $(OBJFILES)
.PHONY:cleanexe
cleanexe:
-rm -f $(PROJECT)
.PHONY:cleanall
cleanall:cleanobj cleanexe