fastdfs-nginx擴展模塊源碼分析

FastDFS-Nginx擴展模塊源碼分析

1. 背景

在大多數業務場景中,每每須要爲FastDFS存儲的文件提供http下載服務,而儘管FastDFS在其storage及tracker都內置了http服務, 但性能表現卻不盡如人意;
做者餘慶在後來的版本中增長了基於當前主流web服務器的擴展模塊(包括nginx/apache),其用意在於利用web服務器直接對本機storage數據文件提供http服務,以提升文件下載的性能。html

 

2. 概要介紹

關於FastDFS的架構原理再也不贅述,有興趣能夠參考:http://code.google.com/p/fastdfs/wiki/Overview nginx

2.1 參考架構

使用FastDFS整合Nginx的參考架構以下所示web

 
   

說明: 在每一臺storage服務器主機上部署Nginx及FastDFS擴展模塊,由Nginx模塊對storage存儲的文件提供http下載服務, 僅噹噹前storage節點找不到文件時會向源storage主機發起redirect或proxy動做。 
注:圖中的tracker可能爲多個tracker組成的集羣;且當前FastDFS的Nginx擴展模塊支持單機多個group的狀況apache

 

2.2 幾個概念

storage_id:指storage server的id,從FastDFS4.x版本開始,tracker能夠對storage定義一組ip到id的映射,以id的形式對storage進行管理。而文件名寫入的再也不是storage的ip而是id,這樣的方式對於數據遷移十分有利。 
storage_sync_file_max_delay:指storage節點同步一個文件最大的時間延遲,是一個閾值;若是當前時間與文件建立時間的差距超過該值則認爲同步已經完成。 
anti_steal_token:指文件ID防盜鏈的方式,FastDFS採用token認證的方式進行文件防盜鏈檢查。服務器

 

3. 實現原理

 3.1 源碼包說明

下載後的源碼包很小,僅包括如下文件:架構

 ngx_http_fastdfs_module.c   //nginx-module接口實現文件,用於接入fastdfs-module核心模塊邏輯
 common.c                    //fastdfs-module核心模塊,實現了初始化、文件下載的主要邏輯
 common.h                    //對應於common.c的頭文件
 config                      //編譯模塊所用的配置,裏面定義了一些重要的常量,如擴展配置文件路徑、文件下載chunk大小
 mod_fastdfs.conf            //擴展配置文件的demo

 

3.2 初始化

3.2.1 加載配置文件

目標文件:/etc/fdfs/mod_fastdfs.conf源碼分析

3.2.2 讀取擴展模塊配置

一些重要參數包括:性能

      group_count           //group個數
      url_have_group_name   //url中是否包含group
      group.store_path      //group對應的存儲路徑
      connect_timeout       //鏈接超時
      network_timeout       //接收或發送超時
      storage_server_port   //storage_server端口,用於在找不到文件狀況下鏈接源storage下載文件(該作法已過期)
      response_mode         //響應模式,proxy或redirect
      load_fdfs_parameters_from_tracker //是否從tracker下載服務端配置

3.2.3 加載服務端配置

根據load_fdfs_parameters_from_tracker參數肯定是否從tracker獲取server端的配置信息優化

  • load_fdfs_parameters_from_tracker=true:
  1. 調用fdfs_load_tracker_group_ex解析tracker鏈接配置 ;
  2. 調用fdfs_get_ini_context_from_tracker鏈接tracker獲取配置信息;
  3. 獲取storage_sync_file_max_delay閾值
  4. 獲取use_storage_id
  5. 若是use_storage_id爲true,則鏈接tracker獲取storage_ids映射表(調用方法:fdfs_get_storage_ids_from_tracker_group)
  • load_fdfs_parameters_from_tracker=false:
  1. 從mod_fastdfs.conf加載所需配置:storage_sync_file_max_delay、use_storage_id;
  2. 若是use_storage_id爲true,則根據storage_ids_filename獲取storage_ids映射表(調用方法:fdfs_load_storage_ids_from_file)

 


3.3 下載過程

3.3.1 解析訪問路徑

    獲得group和file_id_without_group兩個參數;google

 

 3.3.2 防盜鏈檢查

  • 根據g_http_params.anti_steal_token配置(見http.conf文件),判斷是否進行防盜鏈檢查;
  • 採用token的方式實現防盜鏈, 該方式要求下載地址帶上token,且token具備時效性(由ts參數指明);

檢查方式:

   md5(fileid_without_group + privKey + ts) = token; 同時ts沒有超過ttl範圍 (可參考JavaClient CommonProtocol)

調用方法:fdfs_http_check_token 
關於FastDFS的防盜鏈可參考: http://bbs.chinaunix.net/thread-1916999-1-1.html

 

3.3.3 獲取文件元數據

根據文件ID 獲取元數據信息, 包括:源storage ip,文件路徑、名稱,大小 
代碼

    if ((result=fdfs_get_file_info_ex1(file_id, false, &file_info)) != 0)...

fdfs_get_file_info_ex1 的實現中,存在一個取巧的邏輯: 
  當得到文件的ip段以後,仍然須要肯定該段落是storage的id仍是ip。 
代碼

  fdfs_shared.func.c
  -> fdfs_get_server_id_type(ip_addr.s_addr) == FDFS_ID_TYPE_SERVER_ID
  ...
       if (id > 0 && id <= FDFS_MAX_SERVER_ID) {
          return FDFS_ID_TYPE_SERVER_ID;
       } else  {
         return FDFS_ID_TYPE_IP_ADDRESS;
       }

 

判斷標準爲ip段的整數值是否在 0 到 -> FDFS_MAX_SERVER_ID(見tracker_types.h)之間; 
其中FDFS_MAX_SERVER_ID = (1 << 24) - 1,該作法利用了ipv4地址的特色(由4*8個二進制位組成),即ipv4地址數值務必大於該閾值

3.3.4 檢查本地文件是否存在

調用trunk_file_stat_ex1獲取本地文件信息,該方法將實現:

  1. 辨別當前文件是trunkfile仍是singlefile
  2. 得到文件句柄fd
  3. 若是文件是trunk形式則同時也將相關信息(偏移量/長度)一併得到

代碼

    if (bSameGroup)
    {
            FDFSTrunkHeader trunkHeader;
        if ((result=trunk_file_stat_ex1(pStorePaths, store_path_index, \
            true_filename, filename_len, &file_stat, \
            &trunkInfo, &trunkHeader, &fd)) != 0)
        {
            bFileExists = false;
        }
        else
        {
            bFileExists = true;
        }
    }
    else
    {
        bFileExists = false;
        memset(&trunkInfo, 0, sizeof(trunkInfo));
    }

3.3.5 文件不存在的處理

  • 進行有效性檢查

檢查項有二:

A. 源storage是本機或者當前時間與文件建立時間的差距已經超過閾值,報錯;

代碼

     if (is_local_host_ip(file_info.source_ip_addr) || \
        (file_info.create_timestamp > 0 && (time(NULL) - \
            file_info.create_timestamp > '''storage_sync_file_max_delay''')))

 

B. 若是是redirect後的場景,一樣報錯;
若是是由其餘storage節點redirect過來的請求,其url參數中會存在redirect一項


在經過有效性檢查以後將進行代理或重定向處理

  • 重定向模式

配置項response_mode = redirect,此時服務端返回返回302響應碼,url以下:

http:// {源storage地址} : {當前port} {當前url} {參數"redirect=1"}(標記已重定向過)

 

代碼

      response.redirect_url_len = snprintf( \
                response.redirect_url, \
                sizeof(response.redirect_url), \
                "http://%s%s%s%s%c%s", \
                file_info.source_ip_addr, port_part, \
                path_split_str, url, \
                param_split_char, "redirect=1");

 

注:該模式下要求源storage配備公開訪問的webserver、一樣的端口(通常是80)、一樣的path配置。

  • 代理模式

配置項response_mode = proxy,該模式的工做原理如同反向代理的作法,而僅僅使用源storage地址做爲代理proxy的host,其他部分保持不變。 
代碼

       if (pContext->proxy_handler != NULL)
		{
			return pContext->proxy_handler(pContext->arg, \
					file_info.source_ip_addr);
		}
        //其中proxy_handler方法來自ngx_http_fastdfs_module.c文件的ngx_http_fastdfs_proxy_handler方法
        //其實現中設置了大量回調、變量,並最終調用代理請求方法,返回結果:
        rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);  //執行代理請求,並返回結果

3.3.6 輸出本地文件

當本地文件存在時,將直接輸出。
  • 根據是否trunkfile獲取文件名,文件名長度、文件offset;

代碼

    bTrunkFile = IS_TRUNK_FILE_BY_ID(trunkInfo);
    if (bTrunkFile)
    {
        trunk_get_full_filename_ex(pStorePaths, &trunkInfo, \
                full_filename, sizeof(full_filename));
        full_filename_len = strlen(full_filename);
        file_offset = TRUNK_FILE_START_OFFSET(trunkInfo) + \
                pContext->range.start;
    }
    else
    {
        full_filename_len = snprintf(full_filename, \
                sizeof(full_filename), "%s/data/%s", \
                pStorePaths->paths[store_path_index], \
                true_filename);
        file_offset = pContext->range.start;
    }

 

  • 若nginx開啓了send_file開關並且當前爲非chunkFile的狀況下嘗試使用sendfile方法以優化性能;

代碼

    if (pContext->send_file != NULL && !bTrunkFile)
    {
        http_status = pContext->if_range ? \
                HTTP_PARTIAL_CONTENT : HTTP_OK;
        OUTPUT_HEADERS(pContext, (&response), http_status)
        ......
        return pContext->send_file(pContext->arg, full_filename, \
                full_filename_len, file_offset, download_bytes);
    }

 

  • 不然使用lseek 方式隨機訪問文件,並輸出相應的段;

作法:使用chunk方式循環讀,輸出... 
代碼

    while (remain_bytes > 0)
    {
        read_bytes = remain_bytes <= FDFS_OUTPUT_CHUNK_SIZE ? \
                 remain_bytes : FDFS_OUTPUT_CHUNK_SIZE;
        if (read(fd, file_trunk_buff, read_bytes) != read_bytes)
        {
            close(fd);
            ......
            return HTTP_INTERNAL_SERVER_ERROR;
        }

        remain_bytes -= read_bytes;
        if (pContext->send_reply_chunk(pContext->arg, \
            (remain_bytes == 0) ? 1: 0, file_trunk_buff, \
            read_bytes) != 0)
        {
            close(fd);
            return HTTP_INTERNAL_SERVER_ERROR;
        }
    }

 

其中chunk大小見config文件配置: -DFDFS_OUTPUT_CHUNK_SIZE='256*1024'

 

4. 擴展閱讀

基於Referer實現防盜鏈: 
http://www.cnblogs.com/wJiang/archive/2010/04/04/1704445.html

FastDFS使用FAQ: 
http://bbs.chinaunix.net/thread-1920470-1-1.html

FastDFS-Nginx擴展的配置參考: 
http://blog.csdn.net/poechant/article/details/7036594

FastDFS配置、部署資料整理-CSDN博客: 
http://blog.csdn.net/poechant/article/details/6996047

關於C語言open和fopen區別 
http://blog.csdn.net/hairetz/article/details/4150193

相關文章
相關標籤/搜索