精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Caffeine源碼解讀-緩存過期淘汰相關算法

存儲 算法
這一篇繼續通過示例講解緩存過期相關算法部分,來看看它與guava cache有什么不一樣的設計。

[[410588]]

本文轉載自微信公眾號「肌肉碼農」,作者鄒學。轉載本文請聯系肌肉碼農公眾號。

引子:

上一篇通過使用示例講解了Caffeine的框架部分 Caffeine源碼解讀-架構篇,這一篇繼續通過示例講解緩存過期相關算法部分,來看看它與guava cache有什么不一樣的設計。

使用示例:

繼續使用相同的例子,不過是從PUT、GET開始說起,了解了它的工作流程自然會知道它的緩存過期邏輯:

  1. //初始化         
  2. Cache<String, String> cache = Caffeine.newBuilder().maximumSize(100) 
  3.   .expireAfterWrite(1, TimeUnit.SECONDS).build(); 
  4. //PUT        
  5. cache.put("a""b"); 
  6. //GET 
  7.  System.out.println(cache.getIfPresent("a")); 

guava是在put時候進行過期淘汰,那Caffeine也會是一樣嗎?

put/get:

大部分情況創建的是有界cache,put方法會進入BoundedLocalCache的這個方法中:put(K key, V value, boolean notifyWriter, boolean onlyIfAbsent),當Cache之前不包含該元素時會執行以下的代碼:

  1. //從cache中取出之前的值  
  2. Node<K, V> prior = data.get(nodeFactory.newLookupKey(key)); 
  3. if (prior == null) { 
  4.   //prior =null 表示之前元素不存在 
  5.   //因為不存在該元素,所以需要根據key、value創建一個新的node 
  6.   //這里有null的判斷是這部分邏輯外層是個循環,用循環的原因是后面的異步操作需要保證成功。 
  7.   if (node == null) { 
  8.     //新建node 
  9.     node = nodeFactory.newNode(key, keyReferenceQueue(), 
  10.         value, valueReferenceQueue(), newWeight, now); 
  11.     //設置Node的初始時間,用于過期策略 
  12.     setVariableTime(node, expireAfterCreate(key, value, now)); 
  13.     setAccessTime(node, now); 
  14.     setWriteTime(node, now); 
  15.   } 
  16.   if (notifyWriter && hasWriter()) { 
  17.    ............................ 
  18.   } else { //如果還未完成該key的寫 
  19.       //將新建的node寫入到data中 
  20.     prior = data.putIfAbsent(node.getKeyReference(), node); 
  21.     if (prior == null) { 
  22.       //當之前不存在該值時,執行afterWrite操作,并執行AddTask任務 
  23.       afterWrite(new AddTask(node, newWeight)); 
  24.       return null
  25.     } 
  26.   } 

因為它的保持一致性代碼比較多,所以只需先讀中文注釋部分,從代碼可以看出寫緩存操作還是比較簡單:new一個node然后寫到data中去,最后觸發afterWrite后返回null.

最后一步afterWrite方法做了什么?

首先看一下AddTask是什么?

  1. final class AddTask implements Runnable { 
  2.     final Node<K, V> node; 
  3.     final int weight; 
  4.  
  5.     AddTask(Node<K, V> node, int weight) { 
  6.       this.weight = weight; 
  7.       this.node = node; 
  8.     } 
  9. ................................. 

AddTask實現了runnable接口,也就是說完成add操作后,會異步執行一個add任務,這個就是它與guava最大的不同點-異步, 我們先把同步部分看完,畢竟它還是put操作返回null前要執行這部分的,afterWrite方法如下:

  1. void afterWrite(Runnable task) { 
  2. if (buffersWrites()) { 
  3.   for (int i = 0; i < WRITE_BUFFER_RETRIES; i++) { 
  4.     if (writeBuffer().offer(task)) { 
  5.       //觸發寫后調度 
  6.       scheduleAfterWrite(); 
  7.       return
  8.     } 
  9.     scheduleDrainBuffers(); 
  10.   } 
  11.   .......... 
  12. else { 
  13.   scheduleAfterWrite(); 

從上面代碼來看,該方法觸發了寫后調度,寫后調度最終后異步執行drainBuffersTask,這個任務會整理cache中各node狀態并做出處理:

  1. voidscheduleDrainBuffers() { 
  2.   if (drainStatus() >= PROCESSING_TO_IDLE) { 
  3.     return
  4.   } 
  5.   if (evictionLock.tryLock()) { 
  6.     try { 
  7.       //獲得狀態 
  8.       int drainStatus = drainStatus(); 
  9.       //只允許存在三種狀態 
  10.       if (drainStatus >= PROCESSING_TO_IDLE) { 
  11.         return
  12.       } 
  13.       lazySetDrainStatus(PROCESSING_TO_IDLE); 
  14.       //異步調用內存調整任務 drainBuffersTask 
  15.       executor().execute(drainBuffersTask); 
  16.     } catch (Throwable t) { 
  17.       logger.log(Level.WARNING, "Exception thrown when submitting maintenance task", t); 
  18.       maintenance(/* ignored */ null); 
  19.     } finally { 
  20.       evictionLock.unlock(); 
  21.     } 
  22.   } 

從上面步驟來看,put流程是這樣的:先將元素寫入到cache,然后觸發調度,調度會根據閑忙狀態判斷是否執行異步drainBuffersTask。

get的流程與put之差不多,因為get會改變key的使用情況影響過期結果,所以最終也可能會觸發drainBuffersTask執行maintenance方法來清理緩存:

  1. void maintenance(@Nullable Runnable task) { 
  2.   lazySetDrainStatus(PROCESSING_TO_IDLE); 
  3.  
  4.   try { 
  5.     //排出讀緩存 
  6.     drainReadBuffer(); 
  7.   //排出寫緩存 
  8.     drainWriteBuffer(); 
  9.     if (task != null) { 
  10.       task.run(); 
  11.     } 
  12.    
  13.     //排出key引用 
  14.     drainKeyReferences(); 
  15.     //排出value引用 
  16.     drainValueReferences(); 
  17.     //過期entry 
  18.     expireEntries(); 
  19.     //淘汰entry 
  20.     evictEntries(); 
  21.   } finally { 
  22.     if ((drainStatus() != PROCESSING_TO_IDLE) || !casDrainStatus(PROCESSING_TO_IDLE, IDLE)) { 
  23.       lazySetDrainStatus(REQUIRED); 
  24.     } 
  25.   } 

數據結構

上一篇文章有講到Caffeine使用一個ConcurrencyHashMap來保存所有數據,而這一節主要講過期淘汰策略所采用的數據結構,其中寫過期是使用writeOrderDeque,這個比較簡單無需多說,而讀過期相對復雜很多,使用W-TinyLFU的結構與算法。

網絡上有很多文章介紹W-TinyLFU結構的,大家可以去查一下,這里主要是從源碼來分析,總的來說它使用了三個雙端隊列:accessOrderEdenDeque,accessOrderProbationDeque,accessOrderProtectedDeque,使用雙端隊列的原因是支持LRU算法比較方便。

accessOrderEdenDeque屬于eden區,緩存1%的數據,其余的99%緩存在main區。

accessOrderProbationDeque屬于main區,緩存main內數據的20%,這部分是屬于冷數據,即將補淘汰。

accessOrderProtectedDeque屬于main區,緩存main內數據的20%,這部分是屬于熱數據,是整個緩存的主存區。

我們先看一下淘汰方法入口:

  1. void evictEntries() { 
  2.   if (!evicts()) { 
  3.     return
  4.   } 
  5.   //先從edn區淘汰 
  6.   int candidates = evictFromEden(); 
  7.   //eden淘汰后的數據進入main區,然后再從main區淘汰 
  8.   evictFromMain(candidates); 

accessOrderEdenDeque對應W-TinyLFU的W(window),這里保存的是最新寫入數據的引用,它使用LRU淘汰,這里面的數據主要是應對突發流量的問題,淘汰后的數據進入accessOrderProbationDeque.代碼如下:

  1. int evictFromEden() { 
  2.   int candidates = 0; 
  3.   Node<K, V> node = accessOrderEdenDeque().peek(); 
  4.   while (edenWeightedSize() > edenMaximum()) { 
  5.     // The pending operations will adjust the size to reflect the correct weight 
  6.     if (node == null) { 
  7.       break; 
  8.     } 
  9.  
  10.     Node<K, V> next = node.getNextInAccessOrder(); 
  11.     if (node.getWeight() != 0) { 
  12.       node.makeMainProbation(); 
  13.       //先從eden區移除 
  14.       accessOrderEdenDeque().remove(node); 
  15.       //移除的數據加入到main區的probation隊列 
  16.       accessOrderProbationDeque().add(node); 
  17.       candidates++; 
  18.  
  19.       lazySetEdenWeightedSize(edenWeightedSize() - node.getPolicyWeight()); 
  20.     } 
  21.     node = next
  22.   } 
  23.  
  24.   return candidates; 

數據進入probation隊列后,繼續執行以下代碼:

  1. void evictFromMain(int candidates) { 
  2.   int victimQueue = PROBATION; 
  3.   Node<K, V> victim = accessOrderProbationDeque().peekFirst(); 
  4.   Node<K, V> candidate = accessOrderProbationDeque().peekLast(); 
  5.   while (weightedSize() > maximum()) { 
  6.     // Stop trying to evict candidates and always prefer the victim 
  7.     if (candidates == 0) { 
  8.       candidate = null
  9.     } 
  10.  
  11.     // Try evicting from the protected and eden queues 
  12.     if ((candidate == null) && (victim == null)) { 
  13.       if (victimQueue == PROBATION) { 
  14.         victim = accessOrderProtectedDeque().peekFirst(); 
  15.         victimQueue = PROTECTED; 
  16.         continue
  17.       } else if (victimQueue == PROTECTED) { 
  18.         victim = accessOrderEdenDeque().peekFirst(); 
  19.         victimQueue = EDEN; 
  20.         continue
  21.       } 
  22.  
  23.       // The pending operations will adjust the size to reflect the correct weight 
  24.       break; 
  25.     } 
  26.  
  27.     // Skip over entries with zero weight 
  28.     if ((victim != null) && (victim.getPolicyWeight() == 0)) { 
  29.       victim = victim.getNextInAccessOrder(); 
  30.       continue
  31.     } else if ((candidate != null) && (candidate.getPolicyWeight() == 0)) { 
  32.       candidate = candidate.getPreviousInAccessOrder(); 
  33.       candidates--; 
  34.       continue
  35.     } 
  36.  
  37.     // Evict immediately if only one of the entries is present 
  38.     if (victim == null) { 
  39.       candidates--; 
  40.       Node<K, V> evict = candidate; 
  41.       candidate = candidate.getPreviousInAccessOrder(); 
  42.       evictEntry(evict, RemovalCause.SIZE, 0L); 
  43.       continue
  44.     } else if (candidate == null) { 
  45.       Node<K, V> evict = victim; 
  46.       victim = victim.getNextInAccessOrder(); 
  47.       evictEntry(evict, RemovalCause.SIZE, 0L); 
  48.       continue
  49.     } 
  50.  
  51.     // Evict immediately if an entry was collected 
  52.     K victimKey = victim.getKey(); 
  53.     K candidateKey = candidate.getKey(); 
  54.     if (victimKey == null) { 
  55.       Node<K, V> evict = victim; 
  56.       victim = victim.getNextInAccessOrder(); 
  57.       evictEntry(evict, RemovalCause.COLLECTED, 0L); 
  58.       continue
  59.     } else if (candidateKey == null) { 
  60.       candidates--; 
  61.       Node<K, V> evict = candidate; 
  62.       candidate = candidate.getPreviousInAccessOrder(); 
  63.       evictEntry(evict, RemovalCause.COLLECTED, 0L); 
  64.       continue
  65.     } 
  66.  
  67.     // Evict immediately if the candidate's weight exceeds the maximum 
  68.     if (candidate.getPolicyWeight() > maximum()) { 
  69.       candidates--; 
  70.       Node<K, V> evict = candidate; 
  71.       candidate = candidate.getPreviousInAccessOrder(); 
  72.       evictEntry(evict, RemovalCause.SIZE, 0L); 
  73.       continue
  74.     } 
  75.  
  76.     // Evict the entry with the lowest frequency 
  77.     candidates--; 
  78.     //最核心算法在這里:從probation的頭尾取出兩個node進行比較頻率,頻率更小者將被remove 
  79.     if (admit(candidateKey, victimKey)) { 
  80.       Node<K, V> evict = victim; 
  81.       victim = victim.getNextInAccessOrder(); 
  82.       evictEntry(evict, RemovalCause.SIZE, 0L); 
  83.       candidate = candidate.getPreviousInAccessOrder(); 
  84.     } else { 
  85.       Node<K, V> evict = candidate; 
  86.       candidate = candidate.getPreviousInAccessOrder(); 
  87.       evictEntry(evict, RemovalCause.SIZE, 0L); 
  88.     } 
  89.   } 

上面的代碼邏輯是從probation的頭尾取出兩個node進行比較頻率,頻率更小者將被remove,其中尾部元素就是上一部分從eden中淘汰出來的元素,如果將兩步邏輯合并起來講是這樣的:在eden隊列通過lru淘汰出來的”候選者“與probation隊列通過lru淘汰出來的“被驅逐者“進行頻率比較,失敗者將被從cache中真正移除。下面看一下它的比較邏輯admit:

  1. boolean admit(K candidateKey, K victimKey) { 
  2.   int victimFreq = frequencySketch().frequency(victimKey); 
  3.   int candidateFreq = frequencySketch().frequency(candidateKey); 
  4.   //如果候選者的頻率高就淘汰被驅逐者 
  5.   if (candidateFreq > victimFreq) { 
  6.     return true
  7.     //如果被驅逐者比候選者的頻率高,并且候選者頻率小于等于5則淘汰者 
  8.   } else if (candidateFreq <= 5) { 
  9.     // The maximum frequency is 15 and halved to 7 after a reset to age the history. An attack 
  10.     // exploits that a hot candidate is rejected in favor of a hot victim. The threshold of a warm 
  11.     // candidate reduces the number of random acceptances to minimize the impact on the hit rate. 
  12.     return false
  13.   } 
  14.   //隨機淘汰 
  15.   int random = ThreadLocalRandom.current().nextInt(); 
  16.   return ((random & 127) == 0); 

從frequencySketch取出候選者與被驅逐者的頻率,如果候選者的頻率高就淘汰被驅逐者,如果被驅逐者比候選者的頻率高,并且候選者頻率小于等于5則淘汰者,如果前面兩個條件都不滿足則隨機淘汰。

整個過程中你是不是發現protectedDeque并沒有什么作用,那它是怎么作為主存區來保存大部分數據的呢?

  1. //onAccess方法觸發該方法  
  2. void reorderProbation(Node<K, V> node) { 
  3.   if (!accessOrderProbationDeque().contains(node)) { 
  4.     // Ignore stale accesses for an entry that is no longer present 
  5.     return
  6.   } else if (node.getPolicyWeight() > mainProtectedMaximum()) { 
  7.     return
  8.   } 
  9.  
  10.   long mainProtectedWeightedSize = mainProtectedWeightedSize() + node.getPolicyWeight(); 
  11.  //先從probation中移除 
  12.  accessOrderProbationDeque().remove(node); 
  13. //加入到protected中 
  14.   accessOrderProtectedDeque().add(node); 
  15.   node.makeMainProtected(); 
  16.  
  17.   long mainProtectedMaximum = mainProtectedMaximum(); 
  18. //從protected中移除 
  19.   while (mainProtectedWeightedSize > mainProtectedMaximum) { 
  20.     Node<K, V> demoted = accessOrderProtectedDeque().pollFirst(); 
  21.     if (demoted == null) { 
  22.       break; 
  23.     } 
  24.     demoted.makeMainProbation(); 
  25.     //加入到probation中 
  26.     accessOrderProbationDeque().add(demoted); 
  27.     mainProtectedWeightedSize -= node.getPolicyWeight(); 
  28.   } 
  29.  
  30.   lazySetMainProtectedWeightedSize(mainProtectedWeightedSize); 

當數據被訪問時并且該數據在probation中,這個數據就會移動到protected中去,同時通過lru從protected中淘汰一個數據進入到probation中。

這樣數據流轉的邏輯全部通了:新數據都會進入到eden中,通過lru淘汰到probation,并與probation中通過lru淘汰的數據進行使用頻率pk,如果勝利了就繼續留在probation中,如果失敗了就會被直接淘汰,當這條數據被訪問了,則移動到protected。當其它數據被訪問了,則它可能會從protected中通過lru淘汰到probation中。

TinyLFU

傳統LFU一般使用key-value形式來記錄每個key的頻率,優點是數據結構非常簡單,并且能跟緩存本身的數據結構復用,增加一個屬性記錄頻率就行了,它的缺點也比較明顯就是頻率這個屬性會占用很大的空間,但如果改用壓縮方式存儲頻率呢? 頻率占用空間肯定可以減少,但會引出另外一個問題:怎么從壓縮后的數據里獲得對應key的頻率呢?

TinyLFU的解決方案是類似位圖的方法,將key取hash值獲得它的位下標,然后用這個下標來找頻率,但位圖只有0、1兩個值,那頻率明顯可能會非常大,這要怎么處理呢? 另外使用位圖需要預占非常大的空間,這個問題怎么解決呢?

TinyLFU根據最大數據量設置生成一個long數組,然后將頻率值保存在其中的四個long的4個bit位中(4個bit位不會大于15),取頻率值時則取四個中的最小一個。

Caffeine認為頻率大于15已經很高了,是屬于熱數據,所以它只需要4個bit位來保存,long有8個字節64位,這樣可以保存16個頻率。取hash值的后左移兩位,然后加上hash四次,這樣可以利用到16個中的13個,利用率挺高的,或許有更好的算法能將16個都利用到。

  1. public void increment(@Nonnull E e) { 
  2.     if (isNotInitialized()) { 
  3.       return
  4.     } 
  5.  
  6.     int hash = spread(e.hashCode()); 
  7.     int start = (hash & 3) << 2; 
  8.  
  9.     // Loop unrolling improves throughput by 5m ops/s 
  10.     int index0 = indexOf(hash, 0); //indexOf也是一種hash方法,不過會通過tableMask來限制范圍 
  11.     int index1 = indexOf(hash, 1); 
  12.     int index2 = indexOf(hash, 2); 
  13.     int index3 = indexOf(hash, 3); 
  14.  
  15.     boolean added = incrementAt(index0, start); 
  16.     added |= incrementAt(index1, start + 1); 
  17.     added |= incrementAt(index2, start + 2); 
  18.     added |= incrementAt(index3, start + 3); 
  19.  
  20.     //當數據寫入次數達到數據長度時就重置 
  21.     if (added && (++size == sampleSize)) { 
  22.       reset(); 
  23.     } 
  24.   } 

給對應位置的bit位四位的Int值加1:

  1. boolean incrementAt(int i, int j) { 
  2.   int offset = j << 2; 
  3.   long mask = (0xfL << offset); 
  4.   //當已達到15時,次數不再增加 
  5.   if ((table[i] & mask) != mask) { 
  6.     table[i] += (1L << offset); 
  7.     return true
  8.   } 
  9.   return false

獲得值的方法也是通過四次hash來獲得,然后取最小值:

  1. public int frequency(@Nonnull E e) { 
  2.   if (isNotInitialized()) { 
  3.     return 0; 
  4.   } 
  5.  
  6.   int hash = spread(e.hashCode()); 
  7.   int start = (hash & 3) << 2; 
  8.   int frequency = Integer.MAX_VALUE; 
  9.   //四次hash 
  10.   for (int i = 0; i < 4; i++) { 
  11.     int index = indexOf(hash, i); 
  12.     //獲得bit位四位的Int值 
  13.     int count = (int) ((table[index] >>> ((start + i) << 2)) & 0xfL); 
  14.     //取最小值 
  15.     frequency = Math.min(frequency, count); 
  16.   } 
  17.   return frequency; 

當數據寫入次數達到數據長度時就會將次數減半,一些冷數據在這個過程中將歸0,這樣會使hash沖突降低:

  1. void reset() { 
  2.   int count = 0; 
  3.   for (int i = 0; i < table.length; i++) { 
  4.     count += Long.bitCount(table[i] & ONE_MASK); 
  5.     table[i] = (table[i] >>> 1) & RESET_MASK; 
  6.   } 
  7.   size = (size >>> 1) - (count >>> 2); 

 

責任編輯:武曉燕 來源: 肌肉碼農
相關推薦

2021-11-08 11:21:18

redis 淘汰算法

2020-02-19 19:18:02

緩存查詢速度淘汰算法

2021-07-12 22:50:29

Caffeine數據結構

2021-11-04 08:04:49

緩存CaffeineSpringBoot

2024-12-06 10:02:46

2012-02-03 11:31:33

HibernateJava

2025-03-26 03:25:00

SpringGuavaCaffeine

2024-07-25 14:04:16

2018-07-05 16:15:26

緩存數據cache miss

2023-10-26 07:13:14

Redis內存淘汰

2024-09-26 06:30:36

2021-03-01 18:42:02

緩存LRU算法

2022-03-31 13:58:37

分布式SpringRedis

2021-09-10 18:47:22

Redis淘汰策略

2012-12-17 14:54:55

算法緩存Java

2021-01-19 10:39:03

Redis緩存數據

2009-12-28 15:00:21

ADO操作

2019-03-20 08:00:00

DNS緩存欺騙惡意軟件

2010-01-04 14:54:08

ADO參數

2024-12-03 14:38:07

CaffeineRedis二級緩存
點贊
收藏

51CTO技術棧公眾號

日韩av黄色| 免费a在线观看| 很黄很黄激情成人| 亚洲精品在线电影| 噜噜噜久久亚洲精品国产品麻豆| 天堂√在线中文官网在线| 欧美aⅴ一区二区三区视频| 美女国内精品自产拍在线播放| 亚洲婷婷在线观看| 免费观看成人性生生活片| 国产精品久久久久婷婷二区次| 97在线中文字幕| 久草视频在线免费| 欧美搞黄网站| 一本一道久久a久久精品逆3p | 日韩电影免费观看高清完整| 国产偷拍一区二区| 久久精品道一区二区三区| 久久香蕉国产线看观看av| 日本黄色网址大全| 婷婷综合国产| 欧美日韩你懂得| 亚洲熟妇av一区二区三区漫画| 欧美激情视频在线播放| 91在线免费视频观看| 亚洲伊人第一页| 国产美女www爽爽爽| 激情国产一区| 久久精品视频播放| 欧美性猛交xxxx乱| 日韩三级av| 欧美成人一级视频| 国内av一区二区| 高清成人在线| 欧美日韩亚洲91| www.av片| 色女人在线视频| 亚洲色欲色欲www在线观看| 色爱区成人综合网| 九色在线免费| 92国产精品观看| 国产精品区免费视频| 国产毛片毛片毛片毛片毛片| 免播放器亚洲一区| 日本一区二区三区四区视频| 亚洲欧美在线视频免费| 亚洲大胆视频| 久久久久久网址| 久久综合成人网| 午夜精品偷拍| 精品中文字幕在线观看| 少妇被躁爽到高潮无码文| 手机在线电影一区| 久久精品国产精品亚洲| 手机免费观看av| 久久精品国产68国产精品亚洲| 亚洲日韩中文字幕在线播放| 久久久久久九九九九九| 日韩深夜福利| 国产一区二区三区免费视频| 黄大色黄女片18免费| 青青草成人影院| 国产香蕉一区二区三区在线视频 | 国模吧无码一区二区三区| 国模雨婷捆绑高清在线| 亚洲成人免费视| 鲁一鲁一鲁一鲁一色| 51精品视频| 精品免费在线观看| 美女福利视频在线| 欧美日韩大片| 欧美三级电影网| 日本网站在线看| 91国内精品| 亚洲精品理论电影| 91视频在线网站| 欧美r级电影| 欧美日本亚洲视频| 日韩精品久久久久久久| 欧美一级专区| 国产精品直播网红| 精品久久久中文字幕人妻| 成人久久久精品乱码一区二区三区| 精品视频一区在线| www.久久热.com| 亚洲色图清纯唯美| 欧美视频免费看欧美视频| 日韩a**中文字幕| 欧美男男青年gay1069videost| 99久久综合网| 午夜欧洲一区| 久久精品这里热有精品| 久草视频免费在线播放| 国产模特精品视频久久久久| 国产精品久久久久久久久借妻| 91精品在线视频观看| 成人精品视频一区二区三区| 欧美久久久久久一卡四| 精品欧美色视频网站在线观看| 亚洲精品国产第一综合99久久| 久久国产精品视频在线观看| 欧美一级网址| 亚洲国产精品va| 老熟妇一区二区| 欧美日本一区二区视频在线观看| 欧美一性一乱一交一视频| 最近中文字幕在线免费观看| 高清国产午夜精品久久久久久| 欧美一区二区三区四区在线观看地址| av网站在线免费| 色哟哟国产精品免费观看| 久久人人爽人人片| 国产精品一在线观看| 久青草国产97香蕉在线视频| www毛片com| 国产福利一区在线| 五月婷婷一区| 免费毛片b在线观看| 欧美精品久久一区二区三区| 老牛影视av老牛影视av| 欧美日韩国产色综合一二三四| 国产精品三级网站| 天天色综合久久| 一区二区在线观看免费| 视色视频在线观看| 一呦二呦三呦国产精品| 久久久久久久久综合| 国产wwwxxx| 中文字幕在线观看不卡视频| 日韩av播放器| 日韩成人av在线资源| 欧美成人剧情片在线观看| 潘金莲一级淫片aaaaaa播放| 成人18视频在线播放| 成人高清dvd| 国产精品一区二区美女视频免费看 | 欧美福利视频在线| 伊人精品一区二区三区| 久久久久久久久久久久久夜| 成人毛片一区二区| 成人激情自拍| 欧美激情亚洲另类| av网站在线观看免费| 成人免费在线视频| 成人日韩在线视频| 日韩免费av| 国产高清在线不卡| seseavlu视频在线| 在线精品观看国产| 国产精品久久久视频| 日韩电影在线观看电影| 欧美二区三区在线| 午夜精品成人av| 亚洲人成亚洲人成在线观看| 天堂网视频在线| 国产无人区一区二区三区| 成人免费无码av| 狠狠操综合网| 国产欧美一区二区三区久久人妖| 触手亚洲一区二区三区| 欧美三区在线观看| 日本美女黄色一级片| 国内精品国产三级国产a久久| 一本二本三本亚洲码 | 久久久国产亚洲精品| 免费在线观看一区二区| 日韩欧美看国产| 自拍偷拍亚洲区| 国产精品久久欧美久久一区| 亚洲精品久久久蜜桃| 国产午夜在线一区二区三区| 一区二区三区福利| 日本高清久久一区二区三区| 国产精品99| 欧美精品情趣视频| 五月婷婷六月丁香综合| 91久久国产最好的精华液| 99自拍偷拍视频| 国产精品99久久久久久有的能看| 青春草国产视频| 亚洲日本三级| 成人免费淫片aa视频免费| 爱福利在线视频| 永久免费精品影视网站| 国产麻豆精品一区| 欧美日韩国产一区二区三区| 特级西西人体高清大胆| 国产精品中文字幕欧美| 丰满爆乳一区二区三区| 日韩欧美自拍| 国产精品裸体一区二区三区| 成人亚洲免费| 国模精品一区二区三区色天香| 青草久久伊人| 欧美一级欧美三级| 男人日女人网站| 亚洲日本中文字幕区| 亚洲av无码一区二区三区网址 | 四川一级毛毛片| 亚洲欧美日本国产专区一区| 一区二区三区四区视频在线观看 | 神马影院午夜我不卡影院| 国产一区二区三区免费在线 | 99久久精品一区二区| 日本免费色视频| 夜夜嗨一区二区| 国产高清精品软男同| 国产亚洲成av人片在线观黄桃| 国产精品久久久av久久久| 成人影院在线播放| 日韩有码在线电影| 欧美精品少妇| 精品国产凹凸成av人网站| 亚洲一区精品在线观看| 欧美丝袜一区二区三区| 极品颜值美女露脸啪啪| 欧美激情中文不卡| 日韩Av无码精品| 国产一区在线精品| av五月天在线| 性xx色xx综合久久久xx| 日韩在线视频在线| 全球成人免费直播| 欧美日韩精品一区| 美女av一区| www.久久久| 国产成人视屏| 91精品国产自产在线| 欧美日韩尤物久久| 欧美性视频网站| 国产传媒在线| 欧美精品videosex极品1| 麻豆免费在线视频| 最近更新的2019中文字幕| 理论视频在线| 亚洲欧美国产日韩天堂区| 熟妇人妻av无码一区二区三区| 精品久久久久99| 欧美一级特黄aaaaaa大片在线观看 | 久久99久久久久久久噜噜| 在线毛片网站| 日韩在线欧美在线| 欧美精品电影| 久久久成人av| 国产激情视频在线观看| 精品国产一区二区在线| 777电影在线观看| 一区二区三区视频免费| 国产色a在线| 国产亚洲视频中文字幕视频| 国产黄在线观看免费观看不卡| 亚洲色图激情小说| 国产黄色免费在线观看| 一区二区三区四区视频| 91福利在线视频| 中文欧美日本在线资源| 91av资源在线| 日韩中文在线不卡| 91高清在线| 超碰97人人做人人爱少妇| 女同视频在线观看| 高清一区二区三区四区五区| 高清在线视频不卡| 日本成人黄色片| 精品久久99| 亚洲自拍欧美另类| 66精品视频在线观看| 精品在线不卡| 精品国产午夜| 中文字幕日韩一区二区三区| 亚洲天堂免费| 青青草视频在线免费播放 | 日本久久久久亚洲中字幕| 日本综合字幕| 国产日韩欧美一二三区| 亚洲一区二区三区中文字幕在线观看| 国产99视频精品免费视频36| 天堂在线精品| 一区二区三区在线视频111| 亚洲欧美亚洲| 国产1区2区在线| 国产在线一区二区| 香港三日本8a三级少妇三级99| 久久久噜噜噜久噜久久综合| 乱老熟女一区二区三区| 亚洲国产成人av| 在线观看 亚洲| 日韩一区二区三区四区| 欧美女子与性| 另类图片亚洲另类| heyzo高清在线| 国产精品久久色| jizz性欧美23| 亚洲国产精品www| 激情欧美日韩| 538在线视频观看| 成人综合在线观看| 在线免费观看视频| 亚洲午夜免费视频| 亚洲一级特黄毛片| 亚洲第一视频网| 无遮挡的视频在线观看| 97高清免费视频| 国产一区 二区| 欧美精品一区二区三区在线四季 | 国产高清视频网站| 成人小视频免费观看| 影音先锋男人在线| 亚洲成人资源网| 国产裸体无遮挡| 亚洲性猛交xxxxwww| 少妇视频在线| 国产一区二区视频在线观看| 天堂av一区二区三区在线播放| 黄色录像特级片| 久久精品国产77777蜜臀| 久久午夜夜伦鲁鲁片| 亚洲欧美国产毛片在线| 夜夜躁日日躁狠狠久久av| 亚洲第一中文字幕在线观看| www免费视频观看在线| 国产精品aaa| 欧美美女在线直播| 国内少妇毛片视频| 国产综合色视频| 国产一二三av| 91激情五月电影| 天堂v视频永久在线播放| 久久国产天堂福利天堂| 欧美日韩免费电影| 日本在线播放不卡| a91a精品视频在线观看| 免费在线观看日韩av| 亚洲天堂网中文字| 中文字幕精品一区二| 亚洲欧美第一页| 韩国久久久久久| 欧美激情视频一区二区三区| 亚洲精品社区| 国内精品免费视频| 亚洲自拍偷拍综合| 性生交生活影碟片| 成年无码av片在线| 少妇高潮一区二区三区99| 亚洲高清视频一区| 美国十次了思思久久精品导航| 无码人妻aⅴ一区二区三区69岛| 欧美日韩午夜视频在线观看| 日本一级在线观看| 欧美亚州一区二区三区| 亚洲欧美成人vr| 青青青国产在线视频| 国产日韩欧美制服另类| 亚洲大片免费观看| 一二美女精品欧洲| 欧美亚洲综合视频| 亚洲激情免费视频| 国产xxx精品视频大全| 精品处破女学生| 亚洲国产欧美日韩精品| 女人高潮被爽到呻吟在线观看| 久久99久久99精品蜜柚传媒| 国产精品嫩草99av在线| 91精品人妻一区二区| 欧美这里有精品| 日本高清中文字幕在线| 国产精品青草久久久久福利99| 91久久高清国语自产拍| 一区二区三区人妻| 亚洲成a人v欧美综合天堂| 先锋av资源站| 国产精品1区2区在线观看| 99九九热只有国产精品| 日本一区二区三区在线免费观看| 亚洲综合在线免费观看| 熟妇人妻系列aⅴ无码专区友真希| 午夜精品蜜臀一区二区三区免费| 欧美人体视频| 国产喷水theporn| 一区二区激情视频| 亚州男人的天堂| 国产精品视频久| 在线看片不卡| 欧美成人午夜精品免费| 欧美视频一区二| sm在线观看| 涩涩涩999| 成人深夜福利app| 波多野结衣黄色| 欧美老女人xx| 久久综合亚洲| 性一交一黄一片| 在线精品亚洲一区二区不卡| √天堂8在线网| 欧美日韩精品中文字幕一区二区| 国内精品国产三级国产a久久| 亚洲日本韩国在线| xxx成人少妇69| 偷拍自拍亚洲色图| 国产精品igao网网址不卡|