亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

FastDFS概要

系統 2034 0
本篇文章是我上級老大所寫。 留在這里為了不弄丟。


FastDFS是一款開源的輕量級分布式文件系統
純C實現,支持Linux, FreeBSD等UNIX系統
類google FS, 不是通用的文件系統,僅僅可以通過專有API訪問,眼下提供了C,Java和PHP API
為互聯網應用量身定做,解決大容量文件存儲問題,追求高性能和高擴展性
FastDFS能夠看做是基于文件的key-value存儲系統,稱為分布式文件存儲服務更為合適


FastDFS提供的功能
upload 上傳文件
download 下載文件
delete 刪除文件
心得:一個合適的(不須要選擇最復雜的,而是最滿足自己的需求。復雜的自己由于理解問題,導致無法掌控。當在出現一些突發性問題時,由于無法及時解決導致災難性的后果)文件系統須要符合什么樣的哲學,或者說應該使用什么樣的設計理念?


一個好的分布式文件系統最好提供Nginx的模塊,由于對于互聯網應用來說,象文件這樣的靜態資源,通常是通過HTTP的下載,此時通過easy擴展的Nginx來訪問Fastdfs,可以讓文件的上傳和下載變得特別簡單。另外,站點型應用在互聯網領域中的比例是很高,因此PHP這樣的語言作為很成熟,性能也全然可以讓人愜意的站點開發語言,提供對應的擴展,也是很重要的。所以在應用領域上,Fastdfs是很合適的。


文件系統天生是靜態資源,因此象可改動或者可追加的文件看起來就沒有太大的意義了。文件屬性也最好不要支持,由于能夠通過文件擴展名和尺寸等屬性,通過附加在文件名上,來避免出現存儲屬性的信息。另外,通過加入屬性支持,還不如用其它的東西, 比如redis等來支持,以避免讓此分布式文件系統變得很復雜。






之所以說FastDFS簡單,在于其架構中,僅僅有兩種角色,一個是storage, 一個是tracker。但從實現上講,實際上有三個模塊:tracker, storage和fastdfs client。fastdfs純粹是協議的解析,以及一些簡單的策略。關鍵還是在于tracker和storage。


在設計FastDFS時,除了如上的哲學外,非常重要的就是上傳,下載,以及刪除。以及怎樣實現同步,以便實現真正的分布式,否則的話這樣和普通的單機文件系統就沒有什么差別了。


假設是我們自己來設計一下分布式的文件系統,假設我們要上傳。那么,必定要面臨著以下的一些選擇:
上傳到哪里去?難道由client來指定上傳的server嗎?
僅僅上傳一臺server夠嗎?
上傳后是原樣保存嗎?(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的負載比太低)。非常easy,client須要知道存儲server的地址和port。然后,指定要上傳的文件內容;server收到了文件內容后,怎樣選擇要存儲在哪個文件夾下呢?這個選擇要保證均衡性,即盡量保證文件可以均勻地分散在全部的文件夾下。


負載均衡性非常重要的就是哈希,比如,在PHP中經常使用的md5,其返回一個32個字符,即16字節的輸出,即128位。哈希后要變成桶,才可以分布,自然就有了例如以下的問題:


1- 怎樣得到哈希值?md5還是SHA1
2- 哈希值得到后,怎樣構造哈希桶
3- 依據文件名怎樣定位哈希桶


首先來回答第3個問題,依據文件名怎樣定位哈希桶。非常easy,此時我們僅僅有一個文件名作為輸入,首先要計算哈希值,僅僅有一個辦法了,就是依據文件名來得到哈希值。這個函數能夠用整個文件名作為哈希的輸入,也能夠依據文件名的一部分來完畢。結合上面說的兩級文件夾,并且每級文件夾不要超過1000.非常easy,假設用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個字符作為二級文件夾的名稱。server上,使用一個專門的文件夾來作為我們的存儲根文件夾,然后以下建立這么多子文件夾,自然就非常easy了。


這些文件夾能夠在初始化的時候創建出來,而不用在存儲文件的時候才建立。


或許你會問,一個文件夾應該不夠吧,實際上非常多的便宜機器一般都配置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,僅僅要程序代碼中檢查一下就可以)??墒情L度并不能夠保證唯一,為了填充盡可能實用的信息,CRC32也是非常重要的,這樣下載程序后,不用做額外的交互就能夠知道文件的內容是否正確。一旦發現有問題,立刻要報警,而且想辦法修復。這種話,上傳的時候也要注意帶上CRC32,以防止在網絡傳輸和實際的硬盤存儲過程中出現故障(文件的完整性至關重要)。再加上時間戳,即long型的64位,8個字節。最后再加上計數器,由于這個計數器由storage提供,這種話,整個結構就是:len + crc32 + timestamp + uint32_t = 4 + 4 + 8 + 4 = 20個字節,這樣生成的文件名稱就算做base64計算出來,也就不是什么大問題了。并且,加上計數器,每秒內僅僅要單機不上傳超過1萬的文件 ,就都不是問題了。這個還是很好解決的。


// TODO 怎樣避免文件反復上傳? md5嗎? 還是文件的計算能夠避免此問題?這個信息存儲在trackerserver中嗎?


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- 時間戳(獲取server的當前時間)
4- 計數器(server自己維護)


依據上面的4個數據,組織成base64的編碼,然后生成此文件名。依據此文件名的唯一性,就不會出現被覆蓋的情況。同一時候,唯一性也使得接下來做md5運算后,得到的HASH值離散性得么保證。得到了MD5的哈希值后,取出最前面的2部分,就能夠知道要定位到哪個文件夾以下去。哈希桶的構造是固定的,即二級00-ff的文件夾情況。





FastDFS概要


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦?。。?/p>

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 久久96国产精品久久久 | 亚洲国产麻豆 | 亚洲二三区| 国产精品爱久久久久久久小 | 国产成 人 综合 亚洲网 | 国产网红福利视频网站 | 成人区精品一区二区毛片不卡 | 国内在线观看 | 国产图片区 | 高清一级毛片免免费看 | 亚洲欧洲精品成人久久曰 | 国内精品久久久久香蕉 | 成人短视频在线观看免费 | 日韩精品欧美精品中文精品 | 一七六九1769视频免费观看 | 日本一级黄色录像 | 久久综合给合久久97色美利坚 | 偷亚洲偷国产欧美高清 | 你懂的国产 | 日韩国产精品99久久久久久 | 国产精品久久久久久永久牛牛 | 一级片a级片 | 亚洲美女在线视频 | 色九九 | 成 人 免费 黄 色 视频 | 国产精品久久久久久网站 | 欧美性猛交99久久久久99 | 欧美一区欧美二区 | 欧美综合影院 | 国产精品亚洲一区二区三区正片 | 日批日韩在线观看 | 欧美大片在线观看成人 | 成人久久在线 | 国产激情| 嗯啊在线观看免费影院 | 国产精品久久久久一区二区三区 | 亚洲欧美综合视频 | 伊人第一路线 | 亚洲狠狠婷婷综合久久久久图片 | 中文字幕亚洲欧美 | 欧美 xx性 在线 |