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

FastDFS概要

系統(tǒng) 2337 0
本篇文章是我上級老大所寫。 留在這里為了不弄丟。


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


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


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


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






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


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


假設(shè)是我們自己來設(shè)計一下分布式的文件系統(tǒng),假設(shè)我們要上傳。那么,必定要面臨著以下的一些選擇:
上傳到哪里去?難道由client來指定上傳的server嗎?
僅僅上傳一臺server夠嗎?
上傳后是原樣保存嗎?(chunk server比較危急,沒有把握不要去做)
對于多IDC怎樣考慮?
對于使用者來說,當(dāng)須要上傳文件的時候,他/她關(guān)心什么?


1- 上傳的文件必須真實地保留著,不可以有不論什么的加工。盡管chunk server之類的看起來不錯,可是對于中小型組織來說,一旦由于一些技術(shù)性的bug,會導(dǎo)致chunk server破壞掉原來的文件內(nèi)容,風(fēng)險比較大
2- 上傳成功后,可以立刻返回文件名,并依據(jù)文件名立即完整地下載。原始文件名我們不關(guān)心(假設(shè)須要關(guān)心,比如象論壇的附件,可以在數(shù)據(jù)庫中保存這些信息,而不應(yīng)該交給DFS來處理)。這種優(yōu)點在于DFS可以更加靈活和高效,比如可以在文件名中增加非常多的附屬信息,比如圖片的尺寸等。
3- 上傳后的文件不可以是單點,一定要有備份,以防止文件丟失
4- 對于一些熱點文件,希望可以做到保證盡可能高速地大量訪問


上面的需求事實上是比較簡單的。首先讓我們回到最原始的時代,即磁盤來保存文件。在這個時代,當(dāng)我們須要管理文件的時候,通常我們都是在單機的磁盤上創(chuàng)建一個文件夾,然后在此文件夾以下存放文件。由于用戶往往文件名是非常任意的,所以使用用戶指定的文件名可能會錯誤地覆蓋其它的文件。因此,在處理的時候,絕對不可以使用用戶指定的名稱,這是分析后得到的第一個結(jié)論。


假設(shè)用戶上傳文件后,分配一個文件名(詳細(xì)文件名的分配策略以后再考慮),那么假設(shè)全部的文件都存儲在同一個文件夾以下,在做文件夾項的遍歷時將很麻煩。依據(jù)網(wǎng)上的資料,一般單文件夾下的文件個數(shù)一般限制不能夠超過3萬;相同的,一個文件夾以下的文件夾數(shù)也最好不要超過這個數(shù)。但實際上,為了安全考慮,一般都不要存儲這么多的內(nèi)容。假定,一個文件夾以下,存儲1000個文件,每一個文件的平均大小為10KB,則單文件夾以下可存儲的容量是10MB。這個容量太小了,所以我們要多個文件夾,假定有1000個文件夾,每一個文件夾存儲10MB,則能夠存儲10GB的內(nèi)容;這對于眼下磁盤的容量來說,利用率還是不夠的。我們再想辦法,轉(zhuǎn)成兩級文件夾,這種話,就是第一層文件夾有1000個子文件夾,每一級子文件夾以下又有1000級的二級子文件夾,每一個二級子文件夾,能夠存儲10MB的內(nèi)容,此時就能夠存儲10T的內(nèi)容,這基本上超過了眼下單機磁盤的容量大小了。所以,使用二級子文件夾的辦法,是平衡存儲性能和利用存儲容量的辦法。


這樣子的話,就回到了上面的問題,假設(shè)我們開始僅僅做一個單機版的基于文件系統(tǒng)的存儲服務(wù),假如提供TCP的服務(wù)(不基于HTTP,由于HTTP的負(fù)載比太低)。非常easy,client須要知道存儲server的地址和port。然后,指定要上傳的文件內(nèi)容;server收到了文件內(nèi)容后,怎樣選擇要存儲在哪個文件夾下呢?這個選擇要保證均衡性,即盡量保證文件可以均勻地分散在全部的文件夾下。


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


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


首先來回答第3個問題,依據(jù)文件名怎樣定位哈希桶。非常easy,此時我們僅僅有一個文件名作為輸入,首先要計算哈希值,僅僅有一個辦法了,就是依據(jù)文件名來得到哈希值。這個函數(shù)能夠用整個文件名作為哈希的輸入,也能夠依據(jù)文件名的一部分來完畢。結(jié)合上面說的兩級文件夾,并且每級文件夾不要超過1000.非常easy,假設(shè)用32位的字符輸出后,能夠取出實現(xiàn)上來說,因為 文件上傳 是防止唯一性,所以假設(shè)依據(jù)文件內(nèi)容來產(chǎn)生哈希,則比較好的辦法就是截取當(dāng)中的4位,比如:


md5sum fdfs_storaged.pid
52edc4a5890adc59cec82cb60f8af691 fdfs_storaged.pid


上面,這個fdfs_storage.pid中,取出最前面的4個字符,即52和ed。這種話,假如52是一級文件夾的名稱,ed是二級文件夾的名稱。由于每一個字符有16個取值,所以第一級文件夾就有16 * 16 = 256個。總共就有256 * 256 = 65526個文件夾。假設(shè)每一個文件夾以下存放1000個文件,每一個文件30KB,都能夠有1966G,即2TB左右。這種話,足夠我們用好。假設(shè)用三個字符,即52e作為一級文件夾,dc4作為二級文件夾,這樣子的文件夾數(shù)有4096,太多了。所以,取二個字符比較好。


這種話,上面的第2和第3個問題就攻克了,依據(jù)文件名來得到md5,然后取4個字符,前面的2個字符作為一級文件夾名稱,后面的2個字符作為二級文件夾的名稱。server上,使用一個專門的文件夾來作為我們的存儲根文件夾,然后以下建立這么多子文件夾,自然就非常easy了。


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


或許你會問,一個文件夾應(yīng)該不夠吧,實際上非常多的便宜機器一般都配置2塊硬盤,一塊是操作系統(tǒng)盤,一塊是數(shù)據(jù)盤。然后這個數(shù)據(jù)盤掛在一個文件夾以下,以這個文件夾作為我們的存儲根文件夾就好了。這樣也能夠非常大程度上降低運維的難度。


如今就剩下最后一個問題了,就是上傳文件時候,怎樣分配一個唯一的文件名,避免同曾經(jīng)的文件產(chǎn)生覆蓋。


假設(shè)沒有變量作為輸入,非常顯然,僅僅可以採用類似于計數(shù)器的方式,即一個counter,每次加一個文件就增量。但這種方式會要求維護一個持久化的counter,這樣比較麻煩。最好不要有歷史狀態(tài)的紀(jì)錄。


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


// TODO 怎樣避免文件反復(fù)上傳? 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- 哈希值得到后,怎樣構(gòu)造哈希桶
3- 依據(jù)文件名怎樣定位哈希桶


依據(jù)上面分析的結(jié)果,我們看到,當(dāng)上傳一個文件的時候,我們會獲取到例如以下的信息


1- 文件的大小(通過協(xié)議中包的長度字段能夠知道,這種優(yōu)點在于服務(wù)端實現(xiàn)的時候簡單,不用過于操心網(wǎng)絡(luò)緩沖區(qū)的問題)
2- CRC32(也是協(xié)議包中傳輸,以便確定網(wǎng)絡(luò)傳輸是否出錯)
3- 時間戳(獲取server的當(dāng)前時間)
4- 計數(shù)器(server自己維護)


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





FastDFS概要


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

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

【本文對您有幫助就好】

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

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 午夜在线网址 | 免费一级欧美大片视频在线 | 男人的天堂一区二区视频在线观看 | 成人精品视频 | 久操免费在线视频 | 秒播影视 午夜福利毛片 | 第四色激情网 | 日本免费毛片在线高清看 | 亚洲精品综合一区在线 | 四虎影院永久网站 | 亚欧乱色视频大全 | 性做久久久久久坡多野结衣 | 女人18毛片特级一级免费视频 | 国产亚洲综合在线 | 欧美大片日韩精品四虎影视 | 国产精品一区三区 | 性一交一乱一欲0 | 老司机永久免费视频 | 免费一级特黄 欧美大片 | 九九久久精品这里久久网 | 欧美成年黄网站色高清视频 | 亚洲精品一区二区三区不卡 | 国产亚洲精品久久综合影院 | 久久精品国产视频 | 天天摸日日舔 | 日日狠狠的日日日日 | 香蕉视频黄在线观看 | snh48欧洲大片在线观看 | 亚洲欧美色综合自拍 | 四虎精品在线观看 | 精品国产日韩久久亚洲 | 亚洲精品老司机综合影院 | 九九在线观看免费视频 | 国产精品suv一区二区 | 114一级毛片免费观看 | 久久久影院 | 亚洲最大的成人网 | 国产在线观看a | 成人在线观看网站 | 免费黄色的视频 | 99re只有精品|