欧美阿v视频在线大全_亚洲欧美中文日韩V在线观看_www性欧美日韩欧美91_亚洲欧美日韩久久精品

主頁 > 知識庫 > Redis中LRU淘汰策略的深入分析

Redis中LRU淘汰策略的深入分析

熱門標簽:南京手機外呼系統廠家 四川穩定外呼系統軟件 地圖標注工廠入駐 b2b外呼系統 一個地圖標注多少錢 400電話辦理的口碑 臺灣電銷 廊坊外呼系統在哪買 高碑店市地圖標注app

前言

Redis作為緩存使用時,一些場景下要考慮內存的空間消耗問題。Redis會刪除過期鍵以釋放空間,過期鍵的刪除策略有兩種:

  • 惰性刪除:每次從鍵空間中獲取鍵時,都檢查取得的鍵是否過期,如果過期的話,就刪除該鍵;如果沒有過期,就返回該鍵。
  • 定期刪除:每隔一段時間,程序就對數據庫進行一次檢查,刪除里面的過期鍵。

另外,Redis也可以開啟LRU功能來自動淘汰一些鍵值對。

LRU算法

當需要從緩存中淘汰數據時,我們希望能淘汰那些將來不可能再被使用的數據,保留那些將來還會頻繁訪問的數據,但最大的問題是緩存并不能預言未來。一個解決方法就是通過LRU進行預測:最近被頻繁訪問的數據將來被訪問的可能性也越大。緩存中的數據一般會有這樣的訪問分布:一部分數據擁有絕大部分的訪問量。當訪問模式很少改變時,可以記錄每個數據的最后一次訪問時間,擁有最少空閑時間的數據可以被認為將來最有可能被訪問到。

舉例如下的訪問模式,A每5s訪問一次,B每2s訪問一次,C與D每10s訪問一次,|代表計算空閑時間的截止點:

~~~~~A~~~~~A~~~~~A~~~~A~~~~~A~~~~~A~~|
~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~|
~~~~~~~~~~C~~~~~~~~~C~~~~~~~~~C~~~~~~|
~~~~~D~~~~~~~~~~D~~~~~~~~~D~~~~~~~~~D|

可以看到,LRU對于A、B、C工作的很好,完美預測了將來被訪問到的概率B>A>C,但對于D卻預測了最少的空閑時間。

但是,總體來說,LRU算法已經是一個性能足夠好的算法了

LRU配置參數

Redis配置中和LRU有關的有三個:

  • maxmemory: 配置Redis存儲數據時指定限制的內存大小,比如100m。當緩存消耗的內存超過這個數值時, 將觸發數據淘汰。該數據配置為0時,表示緩存的數據量沒有限制, 即LRU功能不生效。64位的系統默認值為0,32位的系統默認內存限制為3GB
  • maxmemory_policy: 觸發數據淘汰后的淘汰策略
  • maxmemory_samples: 隨機采樣的精度,也就是隨即取出key的數目。該數值配置越大, 越接近于真實的LRU算法,但是數值越大,相應消耗也變高,對性能有一定影響,樣本值默認為5。

淘汰策略

淘汰策略即maxmemory_policy的賦值有以下幾種:

  • noeviction:如果緩存數據超過了maxmemory限定值,并且客戶端正在執行的命令(大部分的寫入指令,但DEL和幾個指令例外)會導致內存分配,則向客戶端返回錯誤響應
  • allkeys-lru: 對所有的鍵都采取LRU淘汰
  • volatile-lru: 僅對設置了過期時間的鍵采取LRU淘汰
  • allkeys-random: 隨機回收所有的鍵
  • volatile-random: 隨機回收設置過期時間的鍵
  • volatile-ttl: 僅淘汰設置了過期時間的鍵---淘汰生存時間TTL(Time To Live)更小的鍵

volatile-lru, volatile-random和volatile-ttl這三個淘汰策略使用的不是全量數據,有可能無法淘汰出足夠的內存空間。在沒有過期鍵或者沒有設置超時屬性的鍵的情況下,這三種策略和noeviction差不多。

一般的經驗規則:

  • 使用allkeys-lru策略:當預期請求符合一個冪次分布(二八法則等),比如一部分的子集元素比其它其它元素被訪問的更多時,可以選擇這個策略。
  • 使用allkeys-random:循環連續的訪問所有的鍵時,或者預期請求分布平均(所有元素被訪問的概率都差不多)
  • 使用volatile-ttl:要采取這個策略,緩存對象的TTL值最好有差異

volatile-lru 和 volatile-random策略,當你想要使用單一的Redis實例來同時實現緩存淘汰和持久化一些經常使用的鍵集合時很有用。未設置過期時間的鍵進行持久化保存,設置了過期時間的鍵參與緩存淘汰。不過一般運行兩個實例是解決這個問題的更好方法。

為鍵設置過期時間也是需要消耗內存的,所以使用allkeys-lru這種策略更加節省空間,因為這種策略下可以不為鍵設置過期時間。

近似LRU算法

我們知道,LRU算法需要一個雙向鏈表來記錄數據的最近被訪問順序,但是出于節省內存的考慮,Redis的LRU算法并非完整的實現。Redis并不會選擇最久未被訪問的鍵進行回收,相反它會嘗試運行一個近似LRU的算法,通過對少量鍵進行取樣,然后回收其中的最久未被訪問的鍵。通過調整每次回收時的采樣數量maxmemory-samples,可以實現調整算法的精度。

根據Redis作者的說法,每個Redis Object可以擠出24 bits的空間,但24 bits是不夠存儲兩個指針的,而存儲一個低位時間戳是足夠的,Redis Object以秒為單位存儲了對象新建或者更新時的unix time,也就是LRU clock,24 bits數據要溢出的話需要194天,而緩存的數據更新非常頻繁,已經足夠了。

Redis的鍵空間是放在一個哈希表中的,要從所有的鍵中選出一個最久未被訪問的鍵,需要另外一個數據結構存儲這些源信息,這顯然不劃算。最初,Redis只是隨機的選3個key,然后從中淘汰,后來算法改進到了N個key的策略,默認是5個。

Redis3.0之后又改善了算法的性能,會提供一個待淘汰候選key的pool,里面默認有16個key,按照空閑時間排好序。更新時從Redis鍵空間隨機選擇N個key,分別計算它們的空閑時間idle,key只會在pool不滿或者空閑時間大于pool里最小的時,才會進入pool,然后從pool中選擇空閑時間最大的key淘汰掉。

真實LRU算法與近似LRU的算法可以通過下面的圖像對比:

淺灰色帶是已經被淘汰的對象,灰色帶是沒有被淘汰的對象,綠色帶是新添加的對象??梢钥闯?,maxmemory-samples值為5時Redis 3.0效果比Redis 2.8要好。使用10個采樣大小的Redis 3.0的近似LRU算法已經非常接近理論的性能了。

數據訪問模式非常接近冪次分布時,也就是大部分的訪問集中于部分鍵時,LRU近似算法會處理得很好。

在模擬實驗的過程中,我們發現如果使用冪次分布的訪問模式,真實LRU算法和近似LRU算法幾乎沒有差別。

LRU源碼分析

Redis中的鍵與值都是redisObject對象:

typedef struct redisObject {
 unsigned type:4;
 unsigned encoding:4;
 unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
       * LFU data (least significant 8 bits frequency
       * and most significant 16 bits access time). */
 int refcount;
 void *ptr;
} robj;

unsigned的低24 bits的lru記錄了redisObj的LRU time。

Redis命令訪問緩存的數據時,均會調用函數lookupKey:

robj *lookupKey(redisDb *db, robj *key, int flags) {
 dictEntry *de = dictFind(db->dict,key->ptr);
 if (de) {
  robj *val = dictGetVal(de);

  /* Update the access time for the ageing algorithm.
   * Don't do it if we have a saving child, as this will trigger
   * a copy on write madness. */
  if (server.rdb_child_pid == -1 
   server.aof_child_pid == -1 
   !(flags  LOOKUP_NOTOUCH))
  {
   if (server.maxmemory_policy  MAXMEMORY_FLAG_LFU) {
    updateLFU(val);
   } else {
    val->lru = LRU_CLOCK();
   }
  }
  return val;
 } else {
  return NULL;
 }
}

該函數在策略為LRU(非LFU)時會更新對象的lru值, 設置為LRU_CLOCK()值:

/* Return the LRU clock, based on the clock resolution. This is a time
 * in a reduced-bits format that can be used to set and check the
 * object->lru field of redisObject structures. */
unsigned int getLRUClock(void) {
 return (mstime()/LRU_CLOCK_RESOLUTION)  LRU_CLOCK_MAX;
}

/* This function is used to obtain the current LRU clock.
 * If the current resolution is lower than the frequency we refresh the
 * LRU clock (as it should be in production servers) we return the
 * precomputed value, otherwise we need to resort to a system call. */
unsigned int LRU_CLOCK(void) {
 unsigned int lruclock;
 if (1000/server.hz = LRU_CLOCK_RESOLUTION) {
  atomicGet(server.lruclock,lruclock);
 } else {
  lruclock = getLRUClock();
 }
 return lruclock;
}

LRU_CLOCK()取決于LRU_CLOCK_RESOLUTION(默認值1000),LRU_CLOCK_RESOLUTION代表了LRU算法的精度,即一個LRU的單位是多長。server.hz代表服務器刷新的頻率,如果服務器的時間更新精度值比LRU的精度值要小,LRU_CLOCK()直接使用服務器的時間,減小開銷。

Redis處理命令的入口是processCommand:

int processCommand(client *c) {

 /* Handle the maxmemory directive.
  *
  * Note that we do not want to reclaim memory if we are here re-entering
  * the event loop since there is a busy Lua script running in timeout
  * condition, to avoid mixing the propagation of scripts with the
  * propagation of DELs due to eviction. */
 if (server.maxmemory  !server.lua_timedout) {
  int out_of_memory = freeMemoryIfNeededAndSafe() == C_ERR;
  /* freeMemoryIfNeeded may flush slave output buffers. This may result
   * into a slave, that may be the active client, to be freed. */
  if (server.current_client == NULL) return C_ERR;

  /* It was impossible to free enough memory, and the command the client
   * is trying to execute is denied during OOM conditions or the client
   * is in MULTI/EXEC context? Error. */
  if (out_of_memory 
   (c->cmd->flags  CMD_DENYOOM ||
    (c->flags  CLIENT_MULTI  c->cmd->proc != execCommand))) {
   flagTransaction(c);
   addReply(c, shared.oomerr);
   return C_OK;
  }
 }
}

只列出了釋放內存空間的部分,freeMemoryIfNeededAndSafe為釋放內存的函數:

int freeMemoryIfNeeded(void) {
 /* By default replicas should ignore maxmemory
  * and just be masters exact copies. */
 if (server.masterhost  server.repl_slave_ignore_maxmemory) return C_OK;

 size_t mem_reported, mem_tofree, mem_freed;
 mstime_t latency, eviction_latency;
 long long delta;
 int slaves = listLength(server.slaves);

 /* When clients are paused the dataset should be static not just from the
  * POV of clients not being able to write, but also from the POV of
  * expires and evictions of keys not being performed. */
 if (clientsArePaused()) return C_OK;
 if (getMaxmemoryState(mem_reported,NULL,mem_tofree,NULL) == C_OK)
  return C_OK;

 mem_freed = 0;

 if (server.maxmemory_policy == MAXMEMORY_NO_EVICTION)
  goto cant_free; /* We need to free memory, but policy forbids. */

 latencyStartMonitor(latency);
 while (mem_freed  mem_tofree) {
  int j, k, i, keys_freed = 0;
  static unsigned int next_db = 0;
  sds bestkey = NULL;
  int bestdbid;
  redisDb *db;
  dict *dict;
  dictEntry *de;

  if (server.maxmemory_policy  (MAXMEMORY_FLAG_LRU|MAXMEMORY_FLAG_LFU) ||
   server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL)
  {
   struct evictionPoolEntry *pool = EvictionPoolLRU;

   while(bestkey == NULL) {
    unsigned long total_keys = 0, keys;

    /* We don't want to make local-db choices when expiring keys,
     * so to start populate the eviction pool sampling keys from
     * every DB. */
    for (i = 0; i  server.dbnum; i++) {
     db = server.db+i;
     dict = (server.maxmemory_policy  MAXMEMORY_FLAG_ALLKEYS) ?
       db->dict : db->expires;
     if ((keys = dictSize(dict)) != 0) {
      evictionPoolPopulate(i, dict, db->dict, pool);
      total_keys += keys;
     }
    }
    if (!total_keys) break; /* No keys to evict. */

    /* Go backward from best to worst element to evict. */
    for (k = EVPOOL_SIZE-1; k >= 0; k--) {
     if (pool[k].key == NULL) continue;
     bestdbid = pool[k].dbid;

     if (server.maxmemory_policy  MAXMEMORY_FLAG_ALLKEYS) {
      de = dictFind(server.db[pool[k].dbid].dict,
       pool[k].key);
     } else {
      de = dictFind(server.db[pool[k].dbid].expires,
       pool[k].key);
     }

     /* Remove the entry from the pool. */
     if (pool[k].key != pool[k].cached)
      sdsfree(pool[k].key);
     pool[k].key = NULL;
     pool[k].idle = 0;

     /* If the key exists, is our pick. Otherwise it is
      * a ghost and we need to try the next element. */
     if (de) {
      bestkey = dictGetKey(de);
      break;
     } else {
      /* Ghost... Iterate again. */
     }
    }
   }
  }

  /* volatile-random and allkeys-random policy */
  else if (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM ||
     server.maxmemory_policy == MAXMEMORY_VOLATILE_RANDOM)
  {
   /* When evicting a random key, we try to evict a key for
    * each DB, so we use the static 'next_db' variable to
    * incrementally visit all DBs. */
   for (i = 0; i  server.dbnum; i++) {
    j = (++next_db) % server.dbnum;
    db = server.db+j;
    dict = (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM) ?
      db->dict : db->expires;
    if (dictSize(dict) != 0) {
     de = dictGetRandomKey(dict);
     bestkey = dictGetKey(de);
     bestdbid = j;
     break;
    }
   }
  }

  /* Finally remove the selected key. */
  if (bestkey) {
   db = server.db+bestdbid;
   robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
   propagateExpire(db,keyobj,server.lazyfree_lazy_eviction);
   /* We compute the amount of memory freed by db*Delete() alone.
    * It is possible that actually the memory needed to propagate
    * the DEL in AOF and replication link is greater than the one
    * we are freeing removing the key, but we can't account for
    * that otherwise we would never exit the loop.
    *
    * AOF and Output buffer memory will be freed eventually so
    * we only care about memory used by the key space. */
   delta = (long long) zmalloc_used_memory();
   latencyStartMonitor(eviction_latency);
   if (server.lazyfree_lazy_eviction)
    dbAsyncDelete(db,keyobj);
   else
    dbSyncDelete(db,keyobj);
   latencyEndMonitor(eviction_latency);
   latencyAddSampleIfNeeded("eviction-del",eviction_latency);
   latencyRemoveNestedEvent(latency,eviction_latency);
   delta -= (long long) zmalloc_used_memory();
   mem_freed += delta;
   server.stat_evictedkeys++;
   notifyKeyspaceEvent(NOTIFY_EVICTED, "evicted",
    keyobj, db->id);
   decrRefCount(keyobj);
   keys_freed++;

   /* When the memory to free starts to be big enough, we may
    * start spending so much time here that is impossible to
    * deliver data to the slaves fast enough, so we force the
    * transmission here inside the loop. */
   if (slaves) flushSlavesOutputBuffers();

   /* Normally our stop condition is the ability to release
    * a fixed, pre-computed amount of memory. However when we
    * are deleting objects in another thread, it's better to
    * check, from time to time, if we already reached our target
    * memory, since the "mem_freed" amount is computed only
    * across the dbAsyncDelete() call, while the thread can
    * release the memory all the time. */
   if (server.lazyfree_lazy_eviction  !(keys_freed % 16)) {
    if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) {
     /* Let's satisfy our stop condition. */
     mem_freed = mem_tofree;
    }
   }
  }

  if (!keys_freed) {
   latencyEndMonitor(latency);
   latencyAddSampleIfNeeded("eviction-cycle",latency);
   goto cant_free; /* nothing to free... */
  }
 }
 latencyEndMonitor(latency);
 latencyAddSampleIfNeeded("eviction-cycle",latency);
 return C_OK;

cant_free:
 /* We are here if we are not able to reclaim memory. There is only one
  * last thing we can try: check if the lazyfree thread has jobs in queue
  * and wait... */
 while(bioPendingJobsOfType(BIO_LAZY_FREE)) {
  if (((mem_reported - zmalloc_used_memory()) + mem_freed) >= mem_tofree)
   break;
  usleep(1000);
 }
 return C_ERR;
}

/* This is a wrapper for freeMemoryIfNeeded() that only really calls the
 * function if right now there are the conditions to do so safely:
 *
 * - There must be no script in timeout condition.
 * - Nor we are loading data right now.
 *
 */
int freeMemoryIfNeededAndSafe(void) {
 if (server.lua_timedout || server.loading) return C_OK;
 return freeMemoryIfNeeded();
}

幾種淘汰策略maxmemory_policy就是在這個函數里面實現的。

當采用LRU時,可以看到,從0號數據庫開始(默認16個),根據不同的策略,選擇redisDb的dict(全部鍵)或者expires(有過期時間的鍵),用來更新候選鍵池子pool,pool更新策略是evictionPoolPopulate:

void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {
 int j, k, count;
 dictEntry *samples[server.maxmemory_samples];

 count = dictGetSomeKeys(sampledict,samples,server.maxmemory_samples);
 for (j = 0; j  count; j++) {
  unsigned long long idle;
  sds key;
  robj *o;
  dictEntry *de;

  de = samples[j];
  key = dictGetKey(de);

  /* If the dictionary we are sampling from is not the main
   * dictionary (but the expires one) we need to lookup the key
   * again in the key dictionary to obtain the value object. */
  if (server.maxmemory_policy != MAXMEMORY_VOLATILE_TTL) {
   if (sampledict != keydict) de = dictFind(keydict, key);
   o = dictGetVal(de);
  }

  /* Calculate the idle time according to the policy. This is called
   * idle just because the code initially handled LRU, but is in fact
   * just a score where an higher score means better candidate. */
  if (server.maxmemory_policy  MAXMEMORY_FLAG_LRU) {
   idle = estimateObjectIdleTime(o);
  } else if (server.maxmemory_policy  MAXMEMORY_FLAG_LFU) {
   /* When we use an LRU policy, we sort the keys by idle time
    * so that we expire keys starting from greater idle time.
    * However when the policy is an LFU one, we have a frequency
    * estimation, and we want to evict keys with lower frequency
    * first. So inside the pool we put objects using the inverted
    * frequency subtracting the actual frequency to the maximum
    * frequency of 255. */
   idle = 255-LFUDecrAndReturn(o);
  } else if (server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL) {
   /* In this case the sooner the expire the better. */
   idle = ULLONG_MAX - (long)dictGetVal(de);
  } else {
   serverPanic("Unknown eviction policy in evictionPoolPopulate()");
  }

  /* Insert the element inside the pool.
   * First, find the first empty bucket or the first populated
   * bucket that has an idle time smaller than our idle time. */
  k = 0;
  while (k  EVPOOL_SIZE 
    pool[k].key 
    pool[k].idle  idle) k++;
  if (k == 0  pool[EVPOOL_SIZE-1].key != NULL) {
   /* Can't insert if the element is  the worst element we have
    * and there are no empty buckets. */
   continue;
  } else if (k  EVPOOL_SIZE  pool[k].key == NULL) {
   /* Inserting into empty position. No setup needed before insert. */
  } else {
   /* Inserting in the middle. Now k points to the first element
    * greater than the element to insert. */
   if (pool[EVPOOL_SIZE-1].key == NULL) {
    /* Free space on the right? Insert at k shifting
     * all the elements from k to end to the right. */

    /* Save SDS before overwriting. */
    sds cached = pool[EVPOOL_SIZE-1].cached;
    memmove(pool+k+1,pool+k,
     sizeof(pool[0])*(EVPOOL_SIZE-k-1));
    pool[k].cached = cached;
   } else {
    /* No free space on right? Insert at k-1 */
    k--;
    /* Shift all elements on the left of k (included) to the
     * left, so we discard the element with smaller idle time. */
    sds cached = pool[0].cached; /* Save SDS before overwriting. */
    if (pool[0].key != pool[0].cached) sdsfree(pool[0].key);
    memmove(pool,pool+1,sizeof(pool[0])*k);
    pool[k].cached = cached;
   }
  }

  /* Try to reuse the cached SDS string allocated in the pool entry,
   * because allocating and deallocating this object is costly
   * (according to the profiler, not my fantasy. Remember:
   * premature optimizbla bla bla bla. */
  int klen = sdslen(key);
  if (klen > EVPOOL_CACHED_SDS_SIZE) {
   pool[k].key = sdsdup(key);
  } else {
   memcpy(pool[k].cached,key,klen+1);
   sdssetlen(pool[k].cached,klen);
   pool[k].key = pool[k].cached;
  }
  pool[k].idle = idle;
  pool[k].dbid = dbid;
 }
}

Redis隨機選擇maxmemory_samples數量的key,然后計算這些key的空閑時間idle time,當滿足條件時(比pool中的某些鍵的空閑時間還大)就可以進pool。pool更新之后,就淘汰pool中空閑時間最大的鍵。

estimateObjectIdleTime用來計算Redis對象的空閑時間:

/* Given an object returns the min number of milliseconds the object was never
 * requested, using an approximated LRU algorithm. */
unsigned long long estimateObjectIdleTime(robj *o) {
 unsigned long long lruclock = LRU_CLOCK();
 if (lruclock >= o->lru) {
  return (lruclock - o->lru) * LRU_CLOCK_RESOLUTION;
 } else {
  return (lruclock + (LRU_CLOCK_MAX - o->lru)) *
     LRU_CLOCK_RESOLUTION;
 }
}

空閑時間基本就是就是對象的lru和全局的LRU_CLOCK()的差值乘以精度LRU_CLOCK_RESOLUTION,將秒轉化為了毫秒。

參考鏈接

  • Random notes on improving the Redis LRU algorithm
  • Using Redis as an LRU cache

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。

您可能感興趣的文章:
  • 關于redis Key淘汰策略的實現方法
  • 淺談redis的maxmemory設置以及淘汰策略
  • 淺談Redis緩存有哪些淘汰策略

標簽:畢節 定州 伊春 河源 甘南 南寧 拉薩 泰州

巨人網絡通訊聲明:本文標題《Redis中LRU淘汰策略的深入分析》,本文關鍵詞  Redis,中,LRU,淘汰,策略,的,;如發現本文內容存在版權問題,煩請提供相關信息告之我們,我們將及時溝通與處理。本站內容系統采集于網絡,涉及言論、版權與本站無關。
  • 相關文章
  • 下面列出與本文章《Redis中LRU淘汰策略的深入分析》相關的同類信息!
  • 本頁收集關于Redis中LRU淘汰策略的深入分析的相關信息資訊供網民參考!
  • 推薦文章
    欧美阿v视频在线大全_亚洲欧美中文日韩V在线观看_www性欧美日韩欧美91_亚洲欧美日韩久久精品
  • <rt id="w000q"><acronym id="w000q"></acronym></rt>
  • <abbr id="w000q"></abbr>
    <rt id="w000q"></rt>
    国产精品亚洲一区二区三区在线| 你懂得视频在线观看| 色噜噜夜夜夜综合网| 国产人成亚洲第一网站在线播放| 久久精品国产77777蜜臀| 51调教丨国产调教视频| 337p亚洲精品色噜噜| 午夜电影网亚洲视频| xxxx视频在线观看| 欧美日韩国产首页在线观看| 亚洲精品成人在线| 黑人巨大猛交丰满少妇| 欧美午夜电影网| 亚洲成av人片在线| 中国极品少妇videossexhd| 91精品国产综合久久久久久| 日韩不卡在线观看日韩不卡视频| 欧美大片免费播放器| 精品乱人伦小说| 韩国理伦片一区二区三区在线播放| 一区二区三区久久久久| 国产亚洲综合色| 国产成人综合自拍| 日本高清成人免费播放| 亚洲综合激情另类小说区| 911亚洲精选| 欧美不卡视频一区| 国产精品一区二区三区99| 无码黑人精品一区二区| 一区二区三区国产| 星空大象在线观看免费播放| 日韩欧美国产午夜精品| 国产一区二区伦理| 青青草原在线免费观看| 亚洲一区精品在线| 短视频在线观看| 中文字幕不卡在线播放| 99久久精品免费| 欧美一区二区三区免费视频| 久久精品国产精品青草| 免费国产羞羞网站美图| 一区二区欧美视频| 成人片黄网站色大片免费毛片| 国产丝袜在线精品| 91麻豆国产福利精品| 日韩免费看网站| 国产91丝袜在线播放| 欧美日韩一区二区在线观看视频 | 亚洲综合视频网站| 一区二区三区在线看| 瑟瑟视频在线观看| 国产精品久久久久桃色tv| 精品人妻二区中文字幕 | 深夜视频在线观看| 亚洲精品一区二区三区蜜桃下载| 岛国精品一区二区| 欧美美女直播网站| 国产在线不卡视频| 欧美主播一区二区三区美女| 麻豆国产欧美日韩综合精品二区| 性欧美videos| 天天影视涩香欲综合网| 一级性生活免费视频| 午夜婷婷国产麻豆精品| 日韩av手机在线免费观看| 午夜激情一区二区三区| 男人的天堂av网| 亚洲午夜激情网页| 波多野结衣欲乱| 视频一区二区三区在线| 久久国产美女视频| 日本系列欧美系列| 91国模大尺度私拍在线视频 | 欧美撒尿777hd撒尿| 激情久久久久久久久久久久久久久久| 91久久精品日日躁夜夜躁欧美| 免费观看久久久4p| 色婷婷av一区二区三区软件| 美女看a上一区| 欧美日韩午夜影院| 粉嫩av亚洲一区二区图片| 欧美成人女星排名| 丰满人妻一区二区三区53视频| 久久精品亚洲一区二区三区浴池| 亚洲综合中文网| 国产精品伦一区二区三级视频| 久久精品综合视频| 一区二区三区日韩欧美精品| 中文字幕无码日韩专区免费| 蜜臀av性久久久久蜜臀aⅴ | 国产亚洲色婷婷久久99精品91| 1024成人网色www| 天堂资源在线视频| 日本视频一区二区| 欧美日韩日本视频| 波多野结衣中文字幕一区二区三区| 欧美精品一区二区三区在线播放| 年下总裁被打光屁股sp| 亚洲欧洲中文日韩久久av乱码| 亚洲女人毛茸茸高潮| 日本欧美一区二区| 欧美剧在线免费观看网站| www.av亚洲| 国产精品天美传媒| 国产一级淫片久久久片a级| 麻豆91在线观看| 日韩一区二区免费电影| 扒开伸进免费视频| 一区二区三区精品在线观看| 日本乱码高清不卡字幕| zzijzzij亚洲日本少妇熟睡| 欧美激情一区二区三区全黄 | 香蕉久久一区二区不卡无毒影院| 色诱亚洲精品久久久久久| 成人综合日日夜夜| 中文av一区特黄| www深夜成人a√在线| 国产成人免费视频网站高清观看视频| 2017欧美狠狠色| 国产视频三区四区| 极品美女销魂一区二区三区免费 | 日韩一区二区在线免费观看| 国产性猛交96| 婷婷综合久久一区二区三区| 欧美日韩精品一区二区三区蜜桃 | 日韩成人午夜电影| 日韩一区二区三免费高清| 国产在线观看无码免费视频| 日本色综合中文字幕| 精品久久久久久无| 美国黑人一级大黄| 国产乱子伦视频一区二区三区| 久久久国产精品午夜一区ai换脸| 亚洲精品91在线| 国产成人超碰人人澡人人澡| 国产精品美女一区二区三区| 国产精品久久久精品四季影院| 成人黄色软件下载| 亚洲乱码精品一二三四区日韩在线| 在线观看中文字幕不卡| 四虎永久免费观看| 日本成人在线一区| 26uuu亚洲综合色欧美| 久久久久麻豆v国产| 成人性生交大片免费看中文网站| 中文字幕亚洲精品在线观看| 91国产福利在线| 一级欧美一级日韩片| 九色综合狠狠综合久久| 欧美国产日韩a欧美在线观看| 国产女人18水真多毛片18精品| 成人av动漫网站| 亚洲午夜在线视频| 精品国产电影一区二区| 娇小11一12╳yⅹ╳毛片| 波多野结衣视频一区| 亚洲成人av免费| 欧美电视剧在线观看完整版| 国产成人在线网址| 91影视在线播放| 日本伊人精品一区二区三区观看方式| 久久精品视频免费| 日本韩国视频一区二区| 先锋资源av在线| 国产福利91精品一区二区三区| 亚洲精品欧美专区| 日韩片之四级片| 亚洲波多野结衣| 天天躁日日躁狠狠躁免费麻豆| 麻豆精品国产传媒mv男同| 国产精品成人免费在线| 欧美久久一区二区| 国产一区二区三区四区在线| 99精品久久只有精品| 日本欧美肥老太交大片| 国产精品成人免费在线| 91精品中文字幕一区二区三区| 国产黄色录像视频| 香蕉视频1024| 国产一区二区三区观看| 一区二区三区精品在线| 久久久综合激的五月天| 欧美午夜在线观看| 人人爽人人爽人人片| 韩国av中国字幕| 国产乱码字幕精品高清av | 日本国产在线视频| 国产精品18久久久久久久久| 一二三四社区欧美黄| 国产亚洲一区二区三区| 欧美群妇大交群中文字幕| 精品在线观看一区| 小毛片在线观看| 成人高清视频在线| 麻豆成人免费电影| 亚洲一区二区三区四区五区黄 | 在线观看国产精品网站| 91l九色lporny| 欧洲熟妇的性久久久久久| 国产91精品精华液一区二区三区|