色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

使用Spring Cache實現緩存

jf_ro2CN3Fa ? 來源:勇哥java實戰分享 ? 2023-05-11 17:40 ? 次閱讀

1 硬編碼

在學習Spring Cache之前,筆者經常會硬編碼的方式使用緩存。

舉個例子,為了提升用戶信息的查詢效率,我們對用戶信息使用了緩存,示例代碼如下:

@Autowire
privateUserMapperuserMapper;
@Autowire
privateStringCommandstringCommand;
//查詢用戶
publicUsergetUserById(LonguserId){
StringcacheKey="userId_"+userId;
Useruser=stringCommand.get(cacheKey);
if(user!=null){
returnuser;
}
user=userMapper.getUserById(userId);
if(user!=null){
stringCommand.set(cacheKey,user);
returnuser;
}
//修改用戶
publicvoidupdateUser(Useruser){
userMapper.updateUser(user);
StringcacheKey="userId_"+userId.getId();
stringCommand.set(cacheKey,user);
}
//刪除用戶
publicvoiddeleteUserById(LonguserId){
userMapper.deleteUserById(userId);
StringcacheKey="userId_"+userId.getId();
stringCommand.del(cacheKey);
}
}

相信很多同學都寫過類似風格的代碼,這種風格符合面向過程的編程思維,非常容易理解。但它也有一些缺點:

代碼不夠優雅。業務邏輯有四個典型動作:存儲讀取修改刪除。每次操作都需要定義緩存Key ,調用緩存命令的API,產生較多的重復代碼

緩存操作和業務邏輯之間的代碼耦合度高,對業務邏輯有較強的侵入性。

侵入性主要體現如下兩點:

開發聯調階段,需要去掉緩存,只能注釋或者臨時刪除緩存操作代碼,也容易出錯;

某些場景下,需要更換緩存組件,每個緩存組件有自己的API,更換成本頗高。

2 緩存抽象

首先需要明確一點:Spring Cache不是一個具體的緩存實現方案,而是一個對緩存使用的抽象(Cache Abstraction )。

dc106b70-ed7d-11ed-90ce-dac502259ad0.png

2.1 Spring AOP

Spring AOP是基于代理模式(proxy-based )。

通常情況下,定義一個對象,調用它的方法的時候,方法是直接被調用的。

Pojopojo=newSimplePojo();
pojo.foo();
dc177a50-ed7d-11ed-90ce-dac502259ad0.png

將代碼做一些調整,pojo對象的引用修改成代理類。

ProxyFactoryfactory=newProxyFactory(newSimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(newRetryAdvice());

Pojopojo=(Pojo)factory.getProxy();
//thisisamethodcallontheproxy!
pojo.foo();
dc1e6d92-ed7d-11ed-90ce-dac502259ad0.png

調用pojo的foo方法的時候,實際上是動態生成的代理類調用foo方法。

代理類在方法調用前可以獲取方法的參數,當調用方法結束后,可以獲取調用該方法的返回值,通過這種方式就可以實現緩存的邏輯。

2.2 緩存聲明

緩存聲明,也就是標識需要緩存的方法以及緩存策略

Spring Cache 提供了五個注解。

@Cacheable:根據方法的請求參數對其結果進行緩存,下次同樣的參數來執行該方法時可以直接從緩存中獲取結果,而不需要再次執行該方法;

@CachePut:根據方法的請求參數對其結果進行緩存,它每次都會觸發真實方法的調用;

@CacheEvict:根據一定的條件刪除緩存;

@Caching:組合多個緩存注解;

@CacheConfig:類級別共享緩存相關的公共配置。

我們重點講解:@Cacheable,@CachePut,@CacheEvict三個核心注解。

2.2.1 @Cacheable注解

@Cacheble注解表示這個方法有了緩存的功能。

@Cacheable(value="user_cache",key="#userId",unless="#result==null")
publicUsergetUserById(LonguserId){
Useruser=userMapper.getUserById(userId);
returnuser;
}

上面的代碼片段里,getUserById方法和緩存user_cache 關聯起來,若方法返回的User對象不為空,則緩存起來。第二次相同參數userId調用該方法的時候,直接從緩存中獲取數據,并返回。

▍ 緩存key的生成

我們都知道,緩存的本質是key-value存儲模式,每一次方法的調用都需要生成相應的Key, 才能操作緩存。

通常情況下,@Cacheable有一個屬性key可以直接定義緩存key,開發者可以使用SpEL語言定義key值。

若沒有指定屬性key,緩存抽象提供了 KeyGenerator來生成key ,默認的生成器代碼見下圖:

dc258456-ed7d-11ed-90ce-dac502259ad0.png

它的算法也很容易理解:

如果沒有參數,則直接返回SimpleKey.EMPTY

如果只有一個參數,則直接返回該參數;

若有多個參數,則返回包含多個參數的SimpleKey 對象。

當然Spring Cache也考慮到需要自定義Key生成方式,需要我們實現org.springframework.cache.interceptor.KeyGenerator 接口

Objectgenerate(Objecttarget,Methodmethod,Object...params);

然后指定@Cacheable的keyGenerator屬性。

@Cacheable(value="user_cache",keyGenerator="myKeyGenerator",unless="#result==null")
publicUsergetUserById(LonguserId)

▍ 緩存條件

有的時候,方法執行的結果是否需要緩存,依賴于方法的參數或者方法執行后的返回值。

注解里可以通過condition屬性,通過Spel表達式返回的結果是true 還是false 判斷是否需要緩存。

@Cacheable(cacheNames="book",condition="#name.length()

上面的代碼片段里,當參數的長度小于32,方法執行的結果才會緩存。

除了condition,unless屬性也可以決定結果是否緩存,不過是在執行方法后。

@Cacheable(value="user_cache",key="#userId",unless="#result==null")
publicUsergetUserById(LonguserId){

上面的代碼片段里,當返回的結果為null則不緩存。

2.2.2 @CachePut注解

@CachePut注解作用于緩存需要被更新的場景,和 @Cacheable 非常相似,但被注解的方法每次都會被執行。

返回值是否會放入緩存,依賴于condition和unless,默認情況下結果會存儲到緩存。

@CachePut(value="user_cache",key="#user.id",unless="#result!=null")
publicUserupdateUser(Useruser){
userMapper.updateUser(user);
returnuser;
}

當調用updateUser方法時,每次方法都會被執行,但是因為unless屬性每次都是true,所以并沒有將結果緩存。當去掉unless屬性,則結果會被緩存。

2.2.3 @CacheEvict注解

@CacheEvict 注解的方法在調用時會從緩存中移除已存儲的數據。

@CacheEvict(value="user_cache",key="#id")
publicvoiddeleteUserById(Longid){
userMapper.deleteUserById(id);
}

當調用deleteUserById方法完成后,緩存key等于參數id的緩存會被刪除,而且方法的返回的類型是Void ,這和@Cacheable明顯不同。

2.3 緩存配置

Spring Cache是一個對緩存使用的抽象,它提供了多種存儲集成。

dc2c4d22-ed7d-11ed-90ce-dac502259ad0.png

要使用它們,需要簡單地聲明一個適當的CacheManager - 一個控制和管理Cache的實體。

我們以Spring Cache默認的緩存實現Simple 例子,簡單探索下CacheManager的機制。

CacheManager非常簡單:

publicinterfaceCacheManager{
@Nullable
CachegetCache(Stringname);

CollectiongetCacheNames();
}

在CacheConfigurations配置類中,可以看到不同集成類型有不同的緩存配置類。

dc33c98a-ed7d-11ed-90ce-dac502259ad0.png

通過SpringBoot的自動裝配機制,創建CacheManager的實現類ConcurrentMapCacheManager。

dc3d0112-ed7d-11ed-90ce-dac502259ad0.png

而ConcurrentMapCacheManager的getCache方法,會創建ConcurrentCacheMap。

dc486b24-ed7d-11ed-90ce-dac502259ad0.png

ConcurrentCacheMap實現了org.springframework.cache.Cache接口。

dc4dc2e0-ed7d-11ed-90ce-dac502259ad0.png

從Spring Cache的Simple 的實現,緩存配置需要實現兩個接口:

org.springframework.cache.CacheManager

org.springframework.cache.Cache

3 入門例子

首先我們先創建一個工程spring-cache-demo。

dc5a371e-ed7d-11ed-90ce-dac502259ad0.png

caffeine和Redisson分別是本地內存和分布式緩存Redis框架中的佼佼者,我們分別演示如何集成它們。

3.1 集成caffeine

3.1.1 maven依賴


org.springframework.boot
spring-boot-starter-cache


com.github.ben-manes.caffeine
caffeine
2.7.0

3.1.2 Caffeine緩存配置

我們先創建一個緩存配置類MyCacheConfig。

@Configuration
@EnableCaching
publicclassMyCacheConfig{
@Bean
publicCaffeinecaffeineConfig(){
return
Caffeine.newBuilder()
.maximumSize(10000).
expireAfterWrite(60,TimeUnit.MINUTES);
}
@Bean
publicCacheManagercacheManager(Caffeinecaffeine){
CaffeineCacheManagercaffeineCacheManager=newCaffeineCacheManager();
caffeineCacheManager.setCaffeine(caffeine);
returncaffeineCacheManager;
}
}

首先創建了一個Caffeine對象,該對象標識本地緩存的最大數量是10000條,每個緩存數據在寫入60分鐘后失效。

另外,MyCacheConfig類上我們添加了注解:**@EnableCaching** 。

3.1.3 業務代碼

根據緩存聲明 這一節,我們很容易寫出如下代碼。

@Cacheable(value="user_cache",unless="#result==null")
publicUsergetUserById(Longid){
returnuserMapper.getUserById(id);
}
@CachePut(value="user_cache",key="#user.id",unless="#result==null")
publicUserupdateUser(Useruser){
userMapper.updateUser(user);
returnuser;
}
@CacheEvict(value="user_cache",key="#id")
publicvoiddeleteUserById(Longid){
userMapper.deleteUserById(id);
}

這段代碼與硬編碼里的代碼片段明顯精簡很多。

當我們在Controller層調用 getUserById方法時,調試的時候,配置mybatis日志級別為DEBUG,方便監控方法是否會緩存。

第一次調用會查詢數據庫,打印相關日志:

Preparing:select*FROMusertwheret.id=?
Parameters:1(Long)
Total:1

第二次調用查詢方法的時候,數據庫SQL日志就沒有出現了, 也就說明緩存生效了。

3.2 集成Redisson

3.2.1 maven依賴


org.Redisson
Redisson
3.12.0

3.2.2 Redisson緩存配置

@Bean(destroyMethod="shutdown")
publicRedissonClientRedisson(){
Configconfig=newConfig();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6201").setPassword("ts112GpO_ay");
returnRedisson.create(config);
}
@Bean
CacheManagercacheManager(RedissonClientRedissonClient){
Mapconfig=newHashMap();
//create"user_cache"springcachewithttl=24minutesandmaxIdleTime=12minutes
config.put("user_cache",
newCacheConfig(
24*60*1000,
12*60*1000));
returnnewRedissonSpringCacheManager(RedissonClient,config);
}

可以看到,從Caffeine切換到Redisson,只需要修改緩存配置類,定義CacheManager 對象即可。而業務代碼并不需要改動。

Controller層調用 getUserById方法,用戶ID為1的時候,可以從Redis Desktop Manager里看到:用戶信息已被緩存,user_cache緩存存儲是Hash數據結構。

dc6323f6-ed7d-11ed-90ce-dac502259ad0.png

因為Redisson默認的編解碼是FstCodec , 可以看到key的名稱是:xF6x01。

在緩存配置代碼里,可以修改編解碼器。

publicRedissonClientRedisson(){
Configconfig=newConfig();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6201").setPassword("ts112GpO_ay");
config.setCodec(newJsonJacksonCodec());
returnRedisson.create(config);
}

再次調用 getUserById方法 ,控制臺就變成:

dc6cc2da-ed7d-11ed-90ce-dac502259ad0.png

可以觀察到:緩存key已經變成了:["java.lang.Long",1],改變序列化后key和value已發生了變化。

3.3 從列表緩存再次理解緩存抽象

列表緩存在業務中經常會遇到。通常有兩種實現形式:

整體列表緩存;

按照每個條目緩存,通過redis,memcached的聚合查詢方法批量獲取列表,若緩存沒有命中,則從數據庫重新加載,并放入緩存里。

那么Spring cache整合Redisson如何緩存列表數據呢?

@Cacheable(value="user_cache")
publicListgetUserList(ListidList){
returnuserMapper.getUserByIds(idList);
}

執行getUserList方法,參數id列表為:[1,3] 。

wKgZomRcuNWAQQl8AADE0jZKrtw956.jpg

執行完成之后,控制臺里可以看到:列表整體直接被緩存起來,用戶列表緩存和用戶條目緩存并沒有共享 ,他們是平行的關系。

這種情況下,緩存的顆粒度控制也沒有那么細致。

類似這樣的思考,很多開發者也向Spring Framework研發團隊提過。

dc7966a2-ed7d-11ed-90ce-dac502259ad0.png

官方的回答也很明確:對于緩存抽象來講,它并不關心方法返回的數據類型,假如是集合,那么也就意味著需要把集合數據在緩存中保存起來。

還有一位開發者,定義了一個@CollectionCacheable 注解,并做出了原型,擴展了Spring Cache的列表緩存功能。

@Cacheable("myCache")
publicStringfindById(Stringid){
//accessDBbackendreturnitem
}
@CollectionCacheable("myCache")
publicMapfindByIds(Collectionids){
//accessDBbackend,returnmapofidtoitem
}

官方也未采納,因為緩存抽象并不想引入太多的復雜性

寫到這里,相信大家對緩存抽象有了更進一步的理解。當我們想實現更復雜的緩存功能時,需要對Spring Cache做一定程度的擴展。

4 自定義二級緩存

4.1 應用場景

筆者曾經在原來的項目,高并發場景下多次使用多級緩存。多級緩存是一個非常有趣的功能點,值得我們去擴展。

多級緩存有如下優勢:

離用戶越近,速度越快;

減少分布式緩存查詢頻率,降低序列化和反序列化的CPU消耗;

大幅度減少網絡IO以及帶寬消耗。

進程內緩存做為一級緩存,分布式緩存做為二級緩存,首先從一級緩存中查詢,若能查詢到數據則直接返回,否則從二級緩存中查詢,若二級緩存中可以查詢到數據,則回填到一級緩存中,并返回數據。若二級緩存也查詢不到,則從數據源中查詢,將結果分別回填到一級緩存,二級緩存中。

dc91f6ae-ed7d-11ed-90ce-dac502259ad0.png

來自《鳳凰架構》緩存篇

Spring Cache并沒有二級緩存的實現,我們可以實現一個簡易的二級緩存DEMO,加深對技術的理解。

4.2 設計思路

MultiLevelCacheManager :多級緩存管理器;

MultiLevelChannel :封裝Caffeine和RedissonClient;

MultiLevelCache :實現org.springframework.cache.Cache接口;

MultiLevelCacheConfig :配置緩存過期時間等;

MultiLevelCacheManager是最核心的類,需要實現getCachegetCacheNames 兩個接口。

dc9b76de-ed7d-11ed-90ce-dac502259ad0.png

創建多級緩存,第一級緩存是:Caffeine , 第二級緩存是:Redisson。

dca99c3c-ed7d-11ed-90ce-dac502259ad0.png

二級緩存,為了快速完成DEMO,我們使用Redisson對Spring Cache的擴展類RedissonCache 。它的底層是RMap ,底層存儲是Hash。

dcc2aba0-ed7d-11ed-90ce-dac502259ad0.png

我們重點看下緩存的「查詢」和「存儲」的方法:

@Override
publicValueWrapperget(Objectkey){
Objectresult=getRawResult(key);
returntoValueWrapper(result);
}

publicObjectgetRawResult(Objectkey){
logger.info("從一級緩存查詢key:"+key);
Objectresult=localCache.getIfPresent(key);
if(result!=null){
returnresult;
}
logger.info("從二級緩存查詢key:"+key);
result=RedissonCache.getNativeCache().get(key);
if(result!=null){
localCache.put(key,result);
}
returnresult;
}

查詢 」數據的流程:

先從本地緩存中查詢數據,若能查詢到,直接返回;

本地緩存查詢不到數據,查詢分布式緩存,若可以查詢出來,回填到本地緩存,并返回;

若分布式緩存查詢不到數據,則默認會執行被注解的方法。

下面來看下「存儲 」的代碼:

publicvoidput(Objectkey,Objectvalue){
logger.info("寫入一級緩存key:"+key);
localCache.put(key,value);
logger.info("寫入二級緩存key:"+key);
RedissonCache.put(key,value);
}

最后配置緩存管理器,原有的業務代碼不變。

dcd7f21c-ed7d-11ed-90ce-dac502259ad0.png

執行下getUserById方法,查詢用戶編號為1的用戶信息。

-從一級緩存查詢key:1
-從二級緩存查詢key:1
-==>Preparing:select*FROMusertwheret.id=?
-==>Parameters:1(Long)
-<==?Total:?1
-?寫入一級緩存?key:1
-?寫入二級緩存?key:1

第二次執行相同的動作,從日志可用看到從優先會從本地內存中查詢出結果。

-從一級緩存查詢key:1

等待30s , 再執行一次,因為本地緩存會失效,所以執行的時候會查詢二級緩存

-從一級緩存查詢key:1
-從二級緩存查詢key:1

一個簡易的二級緩存就組裝完了。

5 什么場景選擇Spring Cache

在做技術選型的時候,需要針對場景選擇不同的技術。

筆者認為Spring Cache的功能很強大,設計也非常優雅。特別適合緩存控制沒有那么細致的場景。比如門戶首頁,偏靜態展示頁面,榜單等等。這些場景的特點是對數據實時性沒有那么嚴格的要求,只需要將數據源緩存下來,過期之后自動刷新即可。這些場景下,Spring Cache就是神器,能大幅度提升研發效率。

但在高并發大數據量的場景下,精細的緩存顆粒度的控制上,還是需要做功能擴展。





審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • SQL
    SQL
    +關注

    關注

    1

    文章

    762

    瀏覽量

    44117
  • AOP
    AOP
    +關注

    關注

    0

    文章

    40

    瀏覽量

    11098
  • cache技術
    +關注

    關注

    0

    文章

    41

    瀏覽量

    1062
  • Redis
    +關注

    關注

    0

    文章

    374

    瀏覽量

    10871

原文標題:使用 Spring Cache 實現緩存,這種方式才叫優雅!

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    CPU Cache是如何保證緩存一致性的?

    我們介紹`CPU Cache`的組織架構及其進行**讀操作**時的尋址方式,但是緩存不僅僅只有讀操作,還有 **寫操作** ,這會帶來一個新的問題
    的頭像 發表于 12-04 15:05 ?1483次閱讀
    CPU <b class='flag-5'>Cache</b>是如何保證<b class='flag-5'>緩存</b>一致性的?

    阿里巴巴開源的通用緩存訪問框架JetCache介紹

    摘要: JetCache是由阿里巴巴開源的通用緩存訪問框架,如果你對Spring Cache很熟悉的話,請一定花一點時間了解一下JetCache,它更好用。JetCache可以做類似Sprin
    發表于 04-24 16:09

    高速緩存(cache)的工作原理是什么?高速緩存可分為哪幾類

    存儲器系統的層次架構是如何構成的?高速緩存(cache)的工作原理是什么?高速緩存可分為哪幾類?
    發表于 12-23 06:18

    高速緩存Cache介紹

    被訪問,那么將來它附近的位置也會被訪問。比如順序執行代碼,或者使用一個數據結構? 時間局部性:被訪問過一次的存儲器位置,接下來會被多次引用。比如:循環? 緩存行(cache line)? 邏輯上的一組
    發表于 09-07 08:22

    什么是緩存Cache

    什么是緩存Cache 即高速緩沖存儲器,是位于CPU與主內存間的一種容量較小但速度很高的存儲器。由于CPU的速度遠高于主內存,CPU直接
    發表于 01-23 10:57 ?897次閱讀

    什么是Instructions Cache/IMM/ID

    什么是Instructions Cache/IMM/ID  Instructions Cache: (指令緩存)由于系統主內存的速度較慢,當CPU讀取指令的時候,會導致CPU停下來
    發表于 02-04 11:51 ?627次閱讀

    什么是Cache

    什么是Cache  英文縮寫: Cache 中文譯名: 高速緩存器 分  類: IP與多媒體 解  釋: 信息在本地的臨時存儲
    發表于 02-22 17:26 ?1017次閱讀

    高速緩存(Cache),高速緩存(Cache)原理是什么?

    高速緩存(Cache),高速緩存(Cache)原理是什么? 高速緩存Cache是位于CPU和主
    發表于 03-26 10:49 ?6839次閱讀

    一級緩存、二級緩存、三級緩存區別是什么 詳解它們的區分方法

    一級緩存(Level?1?Cache)簡稱L1?Cache,位于CPU內核的旁邊,是與CPU結合最為緊密的CPU緩存,也是歷史上最早出現的CPU緩存
    發表于 08-14 09:27 ?8.1w次閱讀

    你知道linux的cache memory?

    當你讀寫文件的時候,Linux內核為了提高讀寫性能與速度,會將文件在內存中進行緩存,這部分內存就是Cache Memory(緩存內存)。即使你的程序運行結束后,Cache Memory
    發表于 04-26 15:49 ?1239次閱讀

    Linux內核Page Cache和Buffer Cache兩類緩存的作用及關系如何

    page)即為頁緩存(page cache)。塊緩存(buffer cache),則是內核為了加速對底層存儲介質的訪問速度,而構建的一層緩存
    的頭像 發表于 07-02 14:25 ?2749次閱讀
    Linux內核Page <b class='flag-5'>Cache</b>和Buffer <b class='flag-5'>Cache</b>兩類<b class='flag-5'>緩存</b>的作用及關系如何

    關于Cache的其它內容

    關于Cache的其它內容 上面我們所描述情況,在訪問cache前,已經將虛擬地址轉換成了物理地址,其實,不一定,也可是是虛擬地址直接訪問cache,倒底是使用物理地址還是虛擬地址,這就是翻譯方式
    的頭像 發表于 11-21 11:12 ?2373次閱讀

    Spring Cache緩存常規配置

    作者最近在開發公司項目時使用到 Redis 緩存,并在翻看前人代碼時,看到了一種關于 @Cacheable 注解的自定義緩存有效期的解決方案,感覺比較實用,因此作者自己拓展完善了一番后分享給各位。
    的頭像 發表于 11-28 10:44 ?617次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Cache</b><b class='flag-5'>緩存</b>常規配置

    緩存之美——如何選擇合適的本地緩存

    Guava cache是Google開發的Guava工具包中一套完善的JVM本地緩存框架,底層實現的數據結構類似于ConcurrentHashMap,但是進行了更多的能力拓展,包括緩存
    的頭像 發表于 11-17 14:24 ?254次閱讀
    <b class='flag-5'>緩存</b>之美——如何選擇合適的本地<b class='flag-5'>緩存</b>?

    什么是緩存(Cache)及其作用

    緩存Cache)是一種高速存儲器,用于臨時存儲數據,以便快速訪問。在計算機系統中,緩存的作用是減少處理器訪問主存儲器(如隨機存取存儲器RAM)所需的時間。 緩存
    的頭像 發表于 12-18 09:28 ?312次閱讀
    主站蜘蛛池模板: 99久久免费看国产精品| 日韩亚洲人成在线| 久久精品电影久久电影大全| 国产精品私人玩物在线观看| 国产成人免费| 国产AV果冻传奇麻豆| 大肚婆孕妇网| 成人小视频在线观看免费| sihu国产精品永久免费| 9位美女厕所撒尿11分| 99热这里只有精品6| 91亚洲 欧美 国产 制服 动漫| 中文字幕网站在线观看| 中文中幕无码亚洲视频| 最近中文字幕高清中文字幕MV| 在线观看国产日韩| 中国人泡妞xxxxxxxx19| 自拍视频亚洲综合在线精品| 最新高清无码专区在线视频| 97精品视频在线观看| 99视频精品国产免费观看| 99热这里只有精品88| 成人在线免费视频| 国产成人精品电影在线观看| 国产精品免费一区二区三区四区 | 欧美性xxxxxx爱| 青春草久久| 忘忧草研究所 麻豆| 亚洲国产韩国欧美在线不卡| 亚洲精品在看在线观看| 在线国内自拍精品视频| 99pao成人国产永久免费视频| www.狠狠色| 国产精品国产三级国AV在线观看 | 扒开女人下面使劲桶视频| 父亲在线日本综艺免费观看全集| 国产精品久久久久永久免费看| 好吊日视频在线| 免费人成网站永久| 色悠久久久久综合欧美99| 亚洲精品在线看|