FastDFS概述

本篇文章是我上级老大所写。 留在这里为了不弄丢。

FastDFS是一款开源的轻量级分布式文件系统

纯C实现,支持Linux, FreeBSD等UNIX系统

类google FS, 不是通用的文件系统,只能够通过专有API访问,目前提供了C,Java和PHP API

为互联网应用量身定做,解决大容量文件存储问题,追求高性能和高扩展性

FastDFS可以看做是基于文件的key-value存储系统,称为分布式文件存储服务更为合适

FastDFS提供的功能

upload 上传文件

download 下载文件

delete 删除文件

心得:一个合适的(不需要选择最复杂的,而是最满足自己的需求。复杂的自己因为理解问题,导致无法掌控。当在出现一些突发性问题时,因为无法及时解决导致灾难性的后果)文件系统需要符合什么样的哲学,或者说应该使用什么样的设计理念?

一个好的分布式文件系统最好提供Nginx的模块,因为对于互联网应用来说,象文件这种静态资源,一般是通过HTTP的下载,此时通过容易扩展的Nginx来访问Fastdfs,能够让文件的上传和下载变得特别简单。另外,网站型应用在互联网领域中的比例是非常高,因此PHP这种语言作为非常成熟,性能也完全能够让人满意的网站开发语言,提供相应的扩展,也是非常重要的。所以在应用领域上,Fastdfs是非常合适的。

文件系统天生是静态资源,因此象可修改或者可追加的文件看起来就没有太大的意义了。文件属性也最好不要支持,因为可以通过文件扩展名和尺寸等属性,通过附加在文件名称上,来避免出现存储属性的信息。另外,通过添加属性支持,还不如用其他的东西, 例如redis等来支持,以避免让此分布式文件系统变得非常复杂。

之所以说FastDFS简单,在于其架构中,只有两种角色,一个是storage, 一个是tracker。但从实现上讲,实际上有三个模块:tracker, storage和fastdfs client。fastdfs纯粹是协议的解析,以及一些简单的策略。关键还是在于tracker和storage。

在设计FastDFS时,除了如上的哲学外,很重要的就是上传,下载,以及删除。以及如何实现同步,以便实现真正的分布式,否则的话这样和普通的单机文件系统就没有什么区别了。

如果是我们自己来设计一下分布式的文件系统,如果我们要上传。那么,必然要面临着下面的一些选择:

上传到哪里去?难道由客户端来指定上传的服务器吗?

只上传一台服务器够吗?

上传后是原样保存吗?(chunk server比较危险,没有把握不要去做)

对于多IDC如何考虑?

对于使用者来说,当需要上传文件的时候,他/她关心什么?

1- 上传的文件必须真实地保留着,不能够有任何的加工。虽然chunk server之类的看起来不错,但是对于中小型组织来说,一旦因为一些技术性的bug,会导致chunk server破坏掉原来的文件内容,风险比较大

2- 上传成功后,能够立马返回文件名称,并根据文件名称马上完整地下载。原始文件名称我们不关心(如果需要关心,例如象论坛的附件,可以在数据库中保存这些信息,而不应该交给DFS来处理)。这样的好处在于DFS能够更加灵活和高效,例如可以在文件名称中加入很多的附属信息,例如图片的尺寸等。

3- 上传后的文件不能够是单点,一定要有备份,以防止文件丢失

4- 对于一些热点文件,希望能够做到保证尽可能快速地大量访问

上面的需求其实是比较简单的。首先让我们回到最原始的时代,即磁盘来保存文件。在这个时代,当我们需要管理文件的时候,通常我们都是在单机的磁盘上创建一个目录,然后在此目录下面存放文件。因为用户往往文件名称是很随意的,所以使用用户指定的文件名称可能会错误地覆盖其他的文件。因此,在处理的时候,绝对不能够使用用户指定的名称,这是分析后得到的第一个结论。

如果用户上传文件后,分配一个文件名称(具体文件名称的分配策略以后再考虑),那么如果所有的文件都存储在同一个目录下面,在做目录项的遍历时将非常麻烦。根据网上的资料,一般单目录下的文件个数一般限制不能够超过3万;同样的,一个目录下面的目录数也最好不要超过这个数。但实际上,为了安全考虑,一般都不要存储这么多的内容。假定,一个目录下面,存储1000个文件,每个文件的平均大小为10KB,则单目录下面可存储的容量是10MB。这个容量太小了,所以我们要多个目录,假定有1000个目录,每个目录存储10MB,则可以存储10GB的内容;这对于目前磁盘的容量来说,利用率还是不够的。我们再想办法,转成两级目录,这样的话,就是第一层目录有1000个子目录,每一级子目录下面又有1000级的二级子目录,每个二级子目录,可以存储10MB的内容,此时就可以存储10T的内容,这基本上超过了目前单机磁盘的容量大小了。所以,使用二级子目录的办法,是平衡存储性能和利用存储容量的办法。

这样子的话,就回到了上面的问题,如果我们开始只做一个单机版的基于文件系统的存储服务,假如提供TCP的服务(不基于HTTP,因为HTTP的负载比太低)。很简单,客户端需要知道存储服务器的地址和端口。然后,指定要上传的文件内容;服务器收到了文件内容后,如何选择要存储在哪个目录下呢?这个选择要保证均衡性,即尽量保证文件能够均匀地分散在所有的目录下。

负载均衡性很重要的就是哈希,例如,在PHP中常用的md5,其返回一个32个字符,即16字节的输出,即128位。哈希后要变成桶,才能够分布,自然就有了如下的问题:

1- 如何得到哈希值?md5还是SHA1

2- 哈希值得到后,如何构造哈希桶

3- 根据文件名称如何定位哈希桶

首先来回答第3个问题,根据文件名称如何定位哈希桶。很简单,此时我们只有一个文件名称作为输入,首先要计算哈希值,只有一个办法了,就是根据文件名称来得到哈希值。这个函数可以用整个文件名称作为哈希的输入,也可以根据文件名称的一部分来完成。结合上面说的两级目录,而且每级目录不要超过1000.很简单,如果用32位的字符输出后,可以取出实现上来说,由于文件上传是防止唯一性,所以如果根据文件内容来产生哈希,则比较好的办法就是截取其中的4位,例如:

md5sum fdfs_storaged.pid

52edc4a5890adc59cec82cb60f8af691 fdfs_storaged.pid

上面,这个fdfs_storage.pid中,取出最前面的4个字符,即52和ed。这样的话,假如52是一级目录的名称,ed是二级目录的名称。因为每一个字符有16个取值,所以第一级目录就有16 * 16 = 256个。总共就有256 * 256 = 65526个目录。如果每个目录下面存放1000个文件,每个文件30KB,都可以有1966G,即2TB左右。这样的话,足够我们用好。如果用三个字符,即52e作为一级目录,dc4作为二级目录,这样子的目录数有4096,太多了。所以,取二个字符比较好。

这样的话,上面的第2和第3个问题就解决了,根据文件名称来得到md5,然后取4个字符,前面的2个字符作为一级目录名称,后面的2个字符作为二级目录的名称。服务器上,使用一个专门的目录来作为我们的存储根目录,然后下面建立这么多子目录,自然就很简单了。

这些目录可以在初始化的时候创建出来,而不用在存储文件的时候才建立。

也许你会问,一个目录应该不够吧,实际上很多的廉价机器一般都配置2块硬盘,一块是操作系统盘,一块是数据盘。然后这个数据盘挂在一个目录下面,以这个目录作为我们的存储根目录就好了。这样也可以很大程度上减少运维的难度。

现在就剩下最后一个问题了,就是上传文件时候,如何分配一个唯一的文件名称,避免同以前的文件产生覆盖。

如果没有变量作为输入,很显然,只能够采用类似于计数器的方式,即一个counter,每次加一个文件就增量。但这样的方式会要求维护一个持久化的counter,这样比较麻烦。最好不要有历史状态的纪录。

string md5 ( string $str [, bool $raw_output = false ] )

Calculates the MD5 hash of str using the ? RSA Data Security, Inc. MD5 Message-Digest Algorithm, and returns that hash.

raw_output

If the optional raw_output is set to TRUE, then the md5 digest is instead returned in raw binary format with a length of 16.

Return Values ?

Returns the hash as a 32-character hexadecimal number.

为了尽可能地生成唯一的文件名称,可以使用文件长度(假如是100MB的话,相应的整型可能会是4个字节,即不超过2^32, 即uint32_t,只要程序代码中检查一下即可)。但是长度并不能够保证唯一,为了填充尽可能有用的信息,CRC32也是很重要的,这样下载程序后,不用做额外的交互就可以知道文件的内容是否正确。一旦发现有问题,立马要报警,并且想办法修复。这样的话,上传的时候也要注意带上CRC32,以防止在网络传输和实际的硬盘存储过程中出现问题(文件的完整性至关重要)。再加上时间戳,即long型的64位,8个字节。最后再加上计数器,因为这个计数器由storage提供,这样的话,整个结构就是:len
+ crc32 + timestamp + uint32_t = 4 + 4 + 8 + 4 = 20个字节,这样生成的文件名就算做base64计算出来,也就不是什么大问题了。而且,加上计数器,每秒内只要单机不上传超过1万的文件 ,就都不是问题了。这个还是非常好解决的。

// TODO 如何避免文件重复上传? md5吗? 还是文件的计算可以避免此问题?这个信息存储在tracker服务器中吗?

FastDFS中给我们一个非常好的例子,请参考下面的代码:

// 参考FastDFS的文件名称生成算法

/**
1 byte: store path index
8 bytes: file size
FDFS_FILE_EXT_NAME_MAX_LEN bytes: file ext name, do not include dot (.)
file size bytes: file content
**/
static int storage_upload_file(struct fast_task_info *pTask, bool bAppenderFile)
{
 StorageClientInfo *pClientInfo;
 StorageFileContext *pFileContext;
 DisconnectCleanFunc clean_func;
 char *p;
 char filename[128];
 char file_ext_name[FDFS_FILE_PREFIX_MAX_LEN + 1];
 int64_t nInPackLen;
 int64_t file_offset;
 int64_t file_bytes;
 int crc32;
 int store_path_index;
 int result;
 int filename_len;
 pClientInfo = (StorageClientInfo *)pTask->arg;
 pFileContext = &(pClientInfo->file_context);
 nInPackLen = pClientInfo->total_length - sizeof(TrackerHeader);
 if (nInPackLen < 1 + FDFS_PROTO_PKG_LEN_SIZE +
   FDFS_FILE_EXT_NAME_MAX_LEN)
 {
  logError("file: "__FILE__", line: %d, "    "cmd=%d, client ip: %s, package size "    "%"PRId64" is not correct, "    "expect length >= %d", __LINE__,    STORAGE_PROTO_CMD_UPLOAD_FILE,    pTask->client_ip, nInPackLen,    1 + FDFS_PROTO_PKG_LEN_SIZE +    FDFS_FILE_EXT_NAME_MAX_LEN);
  return EINVAL;
 }
 p = pTask->data + sizeof(TrackerHeader);
 store_path_index = *p++;
 if (store_path_index == -1)
 {
  if ((result=storage_get_storage_path_index(    &store_path_index)) != 0)
  {
   logError("file: "__FILE__", line: %d, "     "get_storage_path_index fail, "     "errno: %d, error info: %s", __LINE__,     result, STRERROR(result));
   return result;
  }
 }
 else if (store_path_index < 0 || store_path_index >=   g_fdfs_store_paths.count)
 {
  logError("file: "__FILE__", line: %d, "    "client ip: %s, store_path_index: %d "    "is invalid", __LINE__,    pTask->client_ip, store_path_index);
  return EINVAL;
 }
 file_bytes = buff2long(p);
 p += FDFS_PROTO_PKG_LEN_SIZE;
 if (file_bytes < 0 || file_bytes != nInPackLen -    (1 + FDFS_PROTO_PKG_LEN_SIZE +     FDFS_FILE_EXT_NAME_MAX_LEN))
 {
  logError("file: "__FILE__", line: %d, "    "client ip: %s, pkg length is not correct, "    "invalid file bytes: %"PRId64    ", total body length: %"PRId64,    __LINE__, pTask->client_ip, file_bytes, nInPackLen);
  return EINVAL;
 }
 memcpy(file_ext_name, p, FDFS_FILE_EXT_NAME_MAX_LEN);
 *(file_ext_name + FDFS_FILE_EXT_NAME_MAX_LEN) = '\0';
 p += FDFS_FILE_EXT_NAME_MAX_LEN;
 if ((result=fdfs_validate_filename(file_ext_name)) != 0)
 {
  logError("file: "__FILE__", line: %d, "    "client ip: %s, file_ext_name: %s "    "is invalid!", __LINE__,    pTask->client_ip, file_ext_name);
  return result;
 }
 pFileContext->calc_crc32 = true;
 pFileContext->calc_file_hash = g_check_file_duplicate;
 pFileContext->extra_info.upload.start_time = g_current_time;
 strcpy(pFileContext->extra_info.upload.file_ext_name, file_ext_name);
 storage_format_ext_name(file_ext_name,    pFileContext->extra_info.upload.formatted_ext_name);
 pFileContext->extra_info.upload.trunk_info.path.     store_path_index = store_path_index;
 pFileContext->extra_info.upload.file_type = _FILE_TYPE_REGULAR;
 pFileContext->sync_flag = STORAGE_OP_TYPE_SOURCE_CREATE_FILE;
 pFileContext->timestamp2log = pFileContext->extra_info.upload.start_time;
 pFileContext->op = FDFS_STORAGE_FILE_OP_WRITE;
 if (bAppenderFile)
 {
  pFileContext->extra_info.upload.file_type |=      _FILE_TYPE_APPENDER;
 }
 else
 {
  if (g_if_use_trunk_file && trunk_check_size(    TRUNK_CALC_SIZE(file_bytes)))
  {
   pFileContext->extra_info.upload.file_type |=       _FILE_TYPE_TRUNK;
  }
 }
 if (pFileContext->extra_info.upload.file_type & _FILE_TYPE_TRUNK)
 {
  FDFSTrunkFullInfo *pTrunkInfo;
  pFileContext->extra_info.upload.if_sub_path_alloced = true;
  pTrunkInfo = &(pFileContext->extra_info.upload.trunk_info);
  if ((result=trunk_client_trunk_alloc_space(    TRUNK_CALC_SIZE(file_bytes), pTrunkInfo)) != 0)
  {
   return result;
  }
  clean_func = dio_trunk_write_finish_clean_up;
  file_offset = TRUNK_FILE_START_OFFSET((*pTrunkInfo));
    pFileContext->extra_info.upload.if_gen_filename = true;
  trunk_get_full_filename(pTrunkInfo, pFileContext->filename,     sizeof(pFileContext->filename));
  pFileContext->extra_info.upload.before_open_callback =      dio_check_trunk_file_when_upload;
  pFileContext->extra_info.upload.before_close_callback =      dio_write_chunk_header;
  pFileContext->open_flags = O_RDWR | g_extra_open_file_flags;
 }
 else
 {
  char reserved_space_str[32];
  if (!storage_check_reserved_space_path(g_path_space_list    [store_path_index].total_mb, g_path_space_list    [store_path_index].free_mb - (file_bytes/FDFS_ONE_MB),    g_avg_storage_reserved_mb))
  {
   logError("file: "__FILE__", line: %d, "     "no space to upload file, "
    "free space: %d MB is too small, file bytes: "     "%"PRId64", reserved space: %s",     __LINE__, g_path_space_list[store_path_index].    free_mb, file_bytes,     fdfs_storage_reserved_space_to_string_ex(       g_storage_reserved_space.flag,           g_avg_storage_reserved_mb,       g_path_space_list[store_path_index].       total_mb, g_storage_reserved_space.rs.ratio,      reserved_space_str));
   return ENOSPC;
  }
  crc32 = rand();
  *filename = '\0';
  filename_len = 0;
  pFileContext->extra_info.upload.if_sub_path_alloced = false;
  if ((result=storage_get_filename(pClientInfo,    pFileContext->extra_info.upload.start_time,    file_bytes, crc32, pFileContext->extra_info.upload.   formatted_ext_name, filename, &filename_len,    pFileContext->filename)) != 0)
  {
   return result;
  }
  clean_func = dio_write_finish_clean_up;
  file_offset = 0;
    pFileContext->extra_info.upload.if_gen_filename = true;
  pFileContext->extra_info.upload.before_open_callback = NULL;
  pFileContext->extra_info.upload.before_close_callback = NULL;
  pFileContext->open_flags = O_WRONLY | O_CREAT | O_TRUNC       | g_extra_open_file_flags;
 }
  return storage_write_to_file(pTask, file_offset, file_bytes,    p - pTask->data, dio_write_file,    storage_upload_file_done_callback,    clean_func, store_path_index);
}

static int storage_get_filename(StorageClientInfo *pClientInfo,  const int start_time, const int64_t file_size, const int crc32,  const char *szFormattedExt, char *filename,  int *filename_len, char *full_filename)
{
 int i;
 int result;
 int store_path_index;
 store_path_index = pClientInfo->file_context.extra_info.upload.
    trunk_info.path.store_path_index;
 for (i=0; i<10; i++)
 {
  if ((result=storage_gen_filename(pClientInfo, file_size,    crc32, szFormattedExt, FDFS_FILE_EXT_NAME_MAX_LEN+1,    start_time, filename, filename_len)) != 0)
  {
   return result;
  }
  sprintf(full_filename, "%s/data/%s",    g_fdfs_store_paths.paths[store_path_index], filename);
  if (!fileExists(full_filename))
  {
   break;
  }
  *full_filename = '\0';
 }
 if (*full_filename == '\0')
 {
  logError("file: "__FILE__", line: %d, "    "Can't generate uniq filename", __LINE__);
  *filename = '\0';
  *filename_len = 0;
  return ENOENT;
 }
 return 0;
}
static int storage_gen_filename(StorageClientInfo *pClientInfo,   const int64_t file_size, const int crc32,   const char *szFormattedExt, const int ext_name_len,   const time_t timestamp, char *filename, int *filename_len)
{
 char buff[sizeof(int) * 5];
 char encoded[sizeof(int) * 8 + 1];
 int len;
 int64_t masked_file_size;
 FDFSTrunkFullInfo *pTrunkInfo;
 pTrunkInfo = &(pClientInfo->file_context.extra_info.upload.trunk_info);
 int2buff(htonl(g_server_id_in_filename), buff);
 int2buff(timestamp, buff+sizeof(int));
 if ((file_size >> 32) != 0)
 {
  masked_file_size = file_size;
 }
 else
 {
  COMBINE_RAND_FILE_SIZE(file_size, masked_file_size);
 }
 long2buff(masked_file_size, buff+sizeof(int)*2);
 int2buff(crc32, buff+sizeof(int)*4);
 base64_encode_ex(&g_fdfs_base64_context, buff, sizeof(int) * 5, encoded,    filename_len, false);
 if (!pClientInfo->file_context.extra_info.upload.if_sub_path_alloced)
 {
  int sub_path_high;
  int sub_path_low;
  storage_get_store_path(encoded, *filename_len,    &sub_path_high, &sub_path_low);
  pTrunkInfo->path.sub_path_high = sub_path_high;
  pTrunkInfo->path.sub_path_low = sub_path_low;
  pClientInfo->file_context.extra_info.upload.     if_sub_path_alloced = true;
 }
 len = sprintf(filename, FDFS_STORAGE_DATA_DIR_FORMAT"/"    FDFS_STORAGE_DATA_DIR_FORMAT"/",    pTrunkInfo->path.sub_path_high,
   pTrunkInfo->path.sub_path_low);
 memcpy(filename+len, encoded, *filename_len);
 memcpy(filename+len+(*filename_len), szFormattedExt, ext_name_len);
 *filename_len += len + ext_name_len;
 *(filename + (*filename_len)) = '\0';
 return 0;
}

回头来看一下我们的问题:

1- 如何得到哈希值?md5还是SHA1

2- 哈希值得到后,如何构造哈希桶

3- 根据文件名称如何定位哈希桶

根据上面分析的结果,我们看到,当上传一个文件的时候,我们会获取到如下的信息

1- 文件的大小(通过协议中包的长度字段可以知道,这样的好处在于服务端实现的时候简单,不用过于担心网络缓冲区的问题)

2- CRC32(也是协议包中传输,以便确定网络传输是否出错)

3- 时间戳(获取服务器的当前时间)

4- 计数器(服务器自己维护)

根据上面的4个数据,组织成base64的编码,然后生成此文件名称。根据此文件名称的唯一性,就不会出现被覆盖的情况。同时,唯一性也使得接下来做md5运算后,得到的HASH值离散性得么保证。得到了MD5的哈希值后,取出最前面的2部分,就可以知道要定位到哪个目录下面去。哈希桶的构造是固定的,即二级00-ff的目录情况。

时间: 2024-08-29 23:46:23

FastDFS概述的相关文章

linux运维之路

Linux入门必备内容 成为一个优秀的linux运维人员必要条件 如何能高标准的掌握linux运维实战技术 计算机硬件组成.工作原理 CPU.内存.磁盘.raid卡生产工作标准详解 企业生产主流品牌服务器及型号内部拆卸讲解 操作系统cache与buffer的原理及硬件设计哲学 结合企业生产实战运维场景如何利用硬件优化 操作系统.Unix及Linux介绍 linux操作系统种类及运维人员选择建议 CentOS Linux原理深入.安装 Linux入门知识进阶 远程连接工具原理及使用 编辑工具 比较

Fastdfs分布式文件系统之文件同步机制

在前面几篇文章中我们对fastdfs系统的概述.tracker server.storage server以及文件的上传.下载.删除等功能的介绍, 本文将对同一组的不同storage server之间的同步以及新增storage server的同步进行介绍. fastdfs文件系统结构 fastdfs文件系统原理 从fastdfs文件系统结构中我们可以看出不管是上传文件.删除文件.修改文件及新增storager server,文件的同步都是同组 内多台storager server之间进行的.下

CentOS6.4系统下FastDFS部署

    (一)   概述 FastDFS是一个开源的分布式文件系统,她对文件进行管理,功能包括:文件存储.文件同步.文件访问(文件上传.文件下载)等,解决了大容量存储和负载均衡的问题.特别适合以文件为载体的在线服务,如相册网站.视频网站等等. FastDFS服务端有两个角色:跟踪器(tracker)和存储节点(storage).跟踪器主要做调度工作,在访问上起负载均衡的作用. FastDFS系统结构如下图所示: 上传文件交互过程: 1. client询问tracker上传到的storage,不需

FastDFS 的安装与配置

FastDFS 的安装与配置 ============================================================================= 概述: ============================================================================= FastDFS介绍  1.简介 ★介绍 开源的轻量级分布式文件系统: 使用 C++语言编写 FastDFS是一个开源的分布式文件系统,她对文件进行管理

【架构设计】分布式文件系统 FastDFS的原理和安装使用

本文地址 分享提纲: 1.概述 2. 原理 3. 安装 4. 使用 5. 参考文档 1. 概述 1.1)[常见文件系统] Google了一下,流行的开源分布式文件系统有很多,介绍如下: -- mogileFS:Key-Value型元文件系统,不支持FUSE,应用程序访问它时需要API,主要用在web领域处理海量小图片,效率相比mooseFS高很多. -- fastDFS:国人 余庆老师(GitHub)在mogileFS的基础上进行改进的key-value型文件系统,同样不支持FUSE,提供比mo

fastdfs分布式文件系统之Storage server介绍

在上一篇文章的fastdfs结构图中,我们可以看出FastDFS服务端有两个角色:跟踪器(tracker)和存储节点(storage).跟踪器(tracker)主要做 调度工作,就像公交车站里面的调务员一样,它负责通过负载均衡选出最优的存储节点(storage).存储节点(storage)顾名思义就是负责存储. 数据同步.数据的操作的一个服务.今天我们将会重点对Storage server进行介绍. 概述 Storage server(简称storage)以组group为单位,如上图一个grou

FastDFS之Binlog同步

转载自:http://m.blog.csdn.net/blog/hfty290/42041155 基于FastDFS 5.03/5.04 2014-12-20 一.Binlog同步概述 FastDFS中为了维护文件的多个副本,会在同组的Storage之间互相同步文件,也就是一个备份过程,若一组有三台机器,那么互相备份后,一个文件就有三个副本.本篇将主要描述Binlog同步的相关概念,与同步逻辑,以及一些注意事项. 1.Binlog结构 1)目录结构 在Storage.conf配置文件中,有一个配

FastDFS原理系列文章

基于FastDFS 5.03/5.04 2014-12-19 一.概述 FastDFS文档极少,仅仅能找到一些宽泛的架构文档,以及ChinaUnix论坛上作者对网友提问的一些回答.对于要将FastDFS应用到生产系统来说,这点了解绝对是不够的. 这段时间研究FastDFS源代码,而且做了大量的性能測试.中间也做了大量的笔记.基本上把程序的结构与基本的操作摸索清楚,因此写了一些文章即是对前段工作的总结,同一时候也分享给想很多其它了解FastDFS内部的同行们. 这里对每篇文章做个介绍. 1.机器之

FastDFS文件系统(二) fastdfs和其他文件系统区别

一.概述 普通存储方案:Rsync.DAS(IDE/SATA/SAS/SCSI等块).NAS(NFS.CIFS.SAMBA等文件系统).SAN(FibreChannel, iSCSI, FoE存储网络块),Openfiler.FreeNas(ZFS快照复制)由于生产环境中往往由于对存储数据量很大,而SAN存储价格又比较昂贵,因此大多会选择分布式 存储来解决一下问题: 海量数据存储问题 数据高可用问题(冗余备份)问题 较高的读写性能和负载均衡问题 支持多平台多语言问题 高并发问题 主要对别指标 c