这里先讲分布式文件系统和项目中的一些架构分析,关于fastdfs的分析再另外讲解。
项目的设计,一般有以下几步。一是理清思路,可以通过画框图的方法;二是设计数据库,需要用到哪些数据,明确各个表之间的依赖关系,这一步通过框图实现;三是设计接口;四是程序编码,要弄清整个逻辑流程和每个模块的逻辑流程,一般来说,流程基本是先读所接收数据的长度值,从cgi读数据,解析信息,处理逻辑。这里要注意http数据中的body大小不等于上传的文件大小,因为中间还包括自定义协议的其他协议在内;五是配置fastdfs。在大致完成整个项目后,自己还可以进行一些改进,比如注册流程,可以提前连接mysql,保持长连接,类似连接池的做法,缩短注册流程时间。
分布式文件系统最主要的特性是能支持水平扩展,通过分多个组实现。
同一组内的storage数据是一样的,组内的多个storage可以提高读的并发量,同时还可以应对单点故障的问题。某一个storage出现问题,在更换磁盘时,可以从组内其他storage获取数据。fastdfs组内storage的数据同步是通过binlog来做的,组内storage不支持动态增加,需要手动配置,每个storage都有自己的配置文件,都需要进行一样的配置,这一点再次提示一次。
当然,不同组间的数据是不一样的。
对于fastdfs而言,一般是单集群,再多配集群,同步的代价会非常高昂。
用户在登录时获得一个有时限的随机token值,一个用户名对一个token值,并设置过期时间。以后用户进行操作,我们就检验这个token,而不再每次都检验密码。这里随机生成token值的方法很多,重点是要保证唯一性。
key:⽤户名
value:token
使用有时限嗯token代替密码,有以下几个原因。
一是密码存储在mysql,token存储在redis,使用token就不需要每次都从mysql中读数据,也就是用读内存代替了读磁盘。
二是安全性方面。token即使被截获,因为有过期时限,所以安全性影响不大,如果密码被截获,就会产生较大影响。
三是防盗链。请求数据时,带上token,token超时则返回错误,需要重新登录,token值也会重新生成,这样可以降低盗链带来的影响。
最后再提示一下,token是有时限的,token过期就需要重新用密码登录。
这里使用有序表zset结构
key : filed_id
value: 下载次数
排⾏榜filed_id映射⽂件名是hash结构,这样可以快速获取文件名。
filed_id: md5(⽂件名)+⽂件名
value: ⽂件名
注意文件的md5值与源文件名无关,即使文件名不同,里面的数据相同,md5值还是相同的。
1、上传文件太大,最好不要用http传,用append的方式
2、如果多人同时上传大文件,存储空间和性能会影响到并发。因此,fastdfs并不建议传太大文件,建议不超过500M。
3、本项目上传文件的实际步骤,是要先把文件从客户端保存到cgi所在服务器,再上传到fastdfs中,拼接出http地址,再写数据库。这里上传两次,是因为fastdfs提供的文件上传的接口fastdfs_update_file只能这样操作。给出部分源码
/**
* @brief 解析上传的post数据 保存到本地临时路径
* 同时得到文件上传者、文件名称、文件大小
*
* @param len (in) post数据的长度
* @param user (out) 文件上传者
* @param file_name (out) 文件的文件名
* @param md5 (out) 文件的MD5码
* @param p_size (out) 文件大小
*
* @returns
* 0 succ, -1 fail
*/
int recv_save_file(long len, char *user, char *filename, char *md5, long *p_size)
{
int ret = 0;
char *file_buf = NULL;
char *begin = NULL;
char *p, *q, *k;
char content_text[TEMP_BUF_MAX_LEN] = {0}; //文件头部信息
char boundary[TEMP_BUF_MAX_LEN] = {0}; //分界线信息
//==========> 开辟存放文件的 内存 <===========
file_buf = (char *)malloc(len);
if (file_buf == NULL)
{
LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC, "malloc error! file size is to big!!!!\n");
return -1;
}
int ret2 = fread(file_buf, 1, len, stdin); //从标准输入(web服务器)读取内容
/*
中间略去一部分代码
*/
//=====> 此时begin-->p两个指针的区间就是post的文件二进制数据
//======>将数据写入文件中,其中文件名也是从post数据解析得来 <===========
int fd = 0;
LOG(UPLOAD_LOG_MODULE, UPLOAD_LOG_PROC,"start open %s\n", filename);
fd = open(filename, O_CREAT|O_WRONLY, 0644);
/*
后面代码略去
*/
}
上面是把读到的数据保存到了服务器本地,下面再上传至fastdfs
/**
* @brief 将一个本地文件上传到 后台分布式文件系统中
*
* @param filename (in) 本地文件的路径
* @param fileid (out)得到上传之后的文件ID路径
*
* @returns
* 0 succ, -1 fail
*/
/* -------------------------------------------*/
int upload_to_dstorage(char *filename, char *fileid)
{
/*
只给出上传至fastdfs的代码
*/
//通过execlp执行fdfs_upload_file ,如果函数调用成功,进程自己的执行代码就会变成加载程序的代码,execlp()后边的代码也就不会执行了.
execlp("fdfs_upload_file", "fdfs_upload_file", fdfs_cli_conf_path, filename, NULL);
//这里fdfs_cli_conf_path是client.conf的位置,通过解析cfg.json获得
}
当然,这里并没有完整给出上传文件的逻辑,特别是没有提到秒传文件的逻辑,在上传文件时,会先用md5值进行检验,如果数据库中有相同md5值的文件,只在数据库中增加记录和相应文件的引用计数,不会实际上传文件至fastdfs。
这里注意一点,去重是在数据库、cgi服务器这一层做的,不是在fastdfs中做的,以后还会讲到。
服务端与客户端之间的交互需要协议设计,有两种类型,一是mem dump形式,即定义一个结构体,把sizeof(结构体)发送出去,fastdfs采用此方式,其实是header+body形式;二是序列化的方式,如json。
一般有两个必要元素,扩展名和文件大小。扩展名用来得知所存文件类型,文件大小用于提前告知服务器所传文件大小,便于判断和开辟空间。
fastdfs是不会自己存储源文件名的,都是将fileid返回给客户端,客户端想要去找文件,需要自己存储对应的文件名。
这里为什么不存源文件名呢?因为fastdfs中存储了文件id,又因为fastdfs需要与数据库相联,而数据库中有fileid与文件名的映射,就不需要存储源文件名了。fastdfs下载文件也是通过fileid来查找的。