日本高清免费一本视频100禁_在线不卡欧美精品一区二区三区_国产一区二区好的精华液_中文综合在线_国产啊啊啊视频在线观看_大地资源网免费观看高清

IT之道-艾銻知道

您當前位置: 主頁 > 資訊動態 > 艾銻分享 >

服務器運維維護 Redis分布式鎖踩坑不慌,這里有超全的解決方案


2020-04-29 20:13 作者:admin

服務器運維維護 Redis分布式鎖踩坑不慌,這里有超全的解決方案

本文從一個簡單的基于redis的分布式鎖出發,到更復雜的Redlock的實現,介紹了在使用分布式鎖的過程中才踩過的一些坑以及解決方案。

開頭

基于Redis的分布式鎖對大家來說并不陌生,可是你的分布式鎖有失敗的時候嗎?在失敗的時候可曾懷疑過你在用的分布式鎖真的靠譜嗎?以下是結合自己的踩坑經驗總結的一些經驗之談。

你真的需要分布式鎖嗎?

用到分布式鎖說明遇到了多個進程共同訪問同一個資源的問題,
一般是在兩個場景下會防止對同一個資源的重復訪問:
· 提高效率。比如多個節點計算同一批任務,如果某個任務已經有節點在計算了,那其他節點就不用重復計算了,以免浪費計算資源。不過重復計算也沒事,不會造成其他更大的損失。也就是允許偶爾的失敗。
· 保證正確性。這種情況對鎖的要求就很高了,如果重復計算,會對正確性造成影響。這種不允許失敗。
引入分布式鎖勢必要引入一個第三方的基礎設施,比如MySQL,Redis,Zookeeper等,這些實現分布式鎖的基礎設施出問題了,也會影響業務,所以在使用分布式鎖前可以考慮下是否可以不用加鎖的方式實現?
不過這個不在本文的討論范圍內,本文假設加鎖的需求是合理的,并且偏向于上面的第二種情況,為什么是偏向?因為不存在100%靠譜的分布式鎖,看完下面的內容就明白了。

從一個簡單的分布式鎖實現說起

分布式鎖的Redis實現很常見,自己實現和使用第三方庫都很簡單,至少看上去是這樣的,這里就介紹一個最簡單靠譜的Redis實現。
最簡單的實現
實現很經典了,這里只提兩個要點?
· 加鎖和解鎖的鎖必須是同一個,常見的解決方案是給每個鎖一個鑰匙(唯一ID),加鎖時生成,解鎖時判斷。
· 不能讓一個資源永久加鎖。常見的解決方案是給一個鎖的過期時間。當然了還有其他方案,后面再說。
一個可復制粘貼的實現方式如下:
加鎖
1. public static boolean tryLock(String key, String uniqueId, int seconds) { 
2.     return "OK".equals(jedis.set(key, uniqueId, "NX", "EX", seconds)); 
3. } 
這里其實是調用了 SET key value PX milliseoncds NX。
不明白這個命令的參考下SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]:https://redis.io/commands/set
解鎖
1. public static boolean releaseLock(String key, String uniqueId) { 
2.     String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " + 
3.             "return redis.call('del', KEYS[1]) else return 0 end"; 
4.     return jedis.eval( 
5.         luaScript,  
6.         Collections.singletonList(key),  
7.         Collections.singletonList(uniqueId) 
8.     ).equals(1L); 
9. } 
這段實現的精髓在那個簡單的lua腳本上,先判斷唯一ID是否相等再操作。
靠譜嗎?
這樣的實現有什么問題呢?
單點問題。上面的實現只要一個master節點就能搞定,這里的單點指的是單master,就算是個集群,如果加鎖成功后,鎖從master復制到slave的時候掛了,也是會出現同一資源被多個client加鎖的。
執行時間超過了鎖的過期時間。上面寫到為了不出現一直上鎖的情況,加了一個兜底的過期時間,時間到了鎖自動釋放,但是,如果在這期間任務并沒有做完怎么辦?由于GC或者網絡延遲導致的任務時間變長,很難保證任務一定能在鎖的過期時間內完成。
如何解決這兩個問題呢?試試看更復雜的實現吧。

Redlock算法

對于第一個單點問題,順著redis的思路,接下來想到的肯定是Redlock了。Redlock為了解決單機的問題,需要多個(大于2)redis的master節點,多個master節點互相獨立,沒有數據同步。
Redlock的實現如下:
1)獲取當前時間。
2)依次獲取N個節點的鎖。每個節點加鎖的實現方式同上。這里有個細節,就是每次獲取鎖的時候的過期時間都不同,需要減去之前獲取鎖的操作的耗時:
比如傳入的鎖的過期時間為500ms;
· 獲取第一個節點的鎖花了1ms,那么第一個節點的鎖的過期時間就是499ms;
· 獲取第二個節點的鎖花了2ms,那么第二個節點的鎖的過期時間就是497ms;
· 如果鎖的過期時間小于等于0了,說明整個獲取鎖的操作超時了,整個操作失敗。
3)判斷是否獲取鎖成功。如果client在上述步驟中獲取到了(N/2 + 1)個節點鎖,并且每個鎖的過期時間都是大于0的,則獲取鎖成功,否則失敗。失敗時釋放鎖。
4)釋放鎖。對所有節點發送釋放鎖的指令,每個節點的實現邏輯和上面的簡單實現一樣。為什么要對所有節點操作?因為分布式場景下從一個節點獲取鎖失敗不代表在那個節點上加速失敗,可能實際上加鎖已經成功了,但是返回時因為網絡抖動超時了。
以上就是大家常見的redlock實現的描述了,一眼看上去就是簡單版本的多master版本,如果真是這樣就太簡單了,接下來分析下這個算法在各個場景下是怎樣被玩壞的。

分布式鎖的坑

高并發場景下的問題
以下問題不是說在并發不高的場景下不容易出現,只是在高并發場景下出現的概率更高些而已。
性能問題。 性能問題來自于兩個方面。
· 獲取鎖的時間上。如果redlock運用在高并發的場景下,存在N個master節點,一個一個去請求,耗時會比較長,從而影響性能。這個好解決。通過上面描述不難發現,從多個節點獲取鎖的操作并不是一個同步操作,可以是異步操作,這樣可以多個節點同時獲取。即使是并行處理的,還是得預估好獲取鎖的時間,保證鎖的TTL > 獲取鎖的時間+任務處理時間。
· 被加鎖的資源太大。加鎖的方案本身就是會為了正確性而犧牲并發的,犧牲和資源大小成正比。這個時候可以考慮對資源做拆分,拆分的方式有兩種:
從業務上將鎖住的資源拆分成多段,每段分開加鎖。比如,我要對一個商戶做若干個操作,操作前要鎖住這個商戶,這時我可以將若干個操作拆成多個獨立的步驟分開加鎖,提高并發。
用分桶的思想,將一個資源拆分成多個桶,一個加鎖失敗立即嘗試下一個。比如批量任務處理的場景,要處理200w個商戶的任務,為了提高處理速度,用多個線程,每個線程取100個商戶處理,就得給這100個商戶加鎖,如果不加處理,很難保證同一時刻兩個線程加鎖的商戶沒有重疊,這時可以按一個維度,比如某個標簽,對商戶進行分桶,然后一個任務處理一個分桶,處理完這個分桶再處理下一個分桶,減少競爭。
重試的問題。無論是簡單實現還是redlock實現,都會有重試的邏輯。如果直接按上面的算法實現,是會存在多個client幾乎在同一時刻獲取同一個鎖,然后每個client都鎖住了部分節點,但是沒有一個client獲取大多數節點的情況。解決的方案也很常見,在重試的時候讓多個節點錯開,錯開的方式就是在重試時間中加一個隨機時間。這樣并不能根治這個問題,但是可以有效緩解問題,親試有效。
節點宕機
對于單master節點且沒有做持久化的場景,宕機就掛了,這個就必須在實現上支持重復操作,自己做好冪等。
對于多master的場景,比如redlock,我們來看這樣一個場景:
· 假設有5個redis的節點:A、B、C、D、E,沒有做持久化。
· client1從A、B、C 3個節點獲取鎖成功,那么client1獲取鎖成功。
· 節點C掛了。
· client2從C、D、E獲取鎖成功,client2也獲取鎖成功,那么在同一時刻client1和client2同時獲取鎖,redlock被玩壞了。
怎么解決呢?最容易想到的方案是打開持久化。持久化可以做到持久化每一條redis命令,但這對性能影響會很大,一般不會采用,如果不采用這種方式,在節點掛的時候肯定會損失小部分的數據,可能我們的鎖就在其中。
另一個方案是延遲啟動。就是一個節點掛了修復后,不立即加入,而是等待一段時間再加入,等待時間要大于宕機那一刻所有鎖的最大TTL。
但這個方案依然不能解決問題,如果在上述步驟3中B和C都掛了呢,那么只剩A、D、E三個節點,從D和E獲取鎖成功就可以了,還是會出問題。那么只能增加master節點的總量,緩解這個問題了。增加master節點會提高穩定性,但是也增加了成本,需要在兩者之間權衡。
任務執行時間超過鎖的TTL
之前產線上出現過因為網絡延遲導致任務的執行時間遠超預期,鎖過期,被多個線程執行的情況。
這個問題是所有分布式鎖都要面臨的問題,包括基于zookeeper和DB實現的分布式鎖,這是鎖過期了和client不知道鎖過期了之間的矛盾。
在加鎖的時候,我們一般都會給一個鎖的TTL,這是為了防止加鎖后client宕機,鎖無法被釋放的問題。但是所有這種姿勢的用法都會面臨同一個問題,就是沒法保證client的執行時間一定小于鎖的TTL。雖然大多數程序員都會樂觀的認為這種情況不可能發生,我也曾經這么認為,直到被現實一次又一次的打臉。
Martin Kleppmann也質疑過這一點:
· Client1獲取到鎖;
· Client1開始任務,然后發生了STW的GC,時間超過了鎖的過期時間;
· Client2 獲取到鎖,開始了任務;
· Client1的GC結束,繼續任務,這個時候Client1和Client2都認為自己獲取了鎖,都會處理任務,從而發生錯誤。
Martin Kleppmann舉的是GC的例子,我碰到的是網絡延遲的情況。不管是哪種情況,不可否認的是這種情況無法避免,一旦出現很容易懵逼。
如何解決呢?一種解決方案是不設置TTL,而是在獲取鎖成功后,給鎖加一個watchdog,watchdog會起一個定時任務,在鎖沒有被釋放且快要過期的時候會續期。這樣說有些抽象,下面結合redisson源碼說下:
1. public class RedissonLock extends RedissonExpirable implements RLock { 
2.     ... 
3.     @Override 
4.     public void lock() { 
5.         try { 
6.             lockInterruptibly(); 
7.         } catch (InterruptedException e) { 
8.             Thread.currentThread().interrupt(); 
9.         } 
10.     } 
11.  
12.     @Override 
13.     public void lock(long leaseTime, TimeUnit unit) { 
14.         try { 
15.             lockInterruptibly(leaseTime, unit); 
16.         } catch (InterruptedException e) { 
17.             Thread.currentThread().interrupt(); 
18.         } 
19.     } 
20.     ... 
21.  } 
redisson常用的加鎖api是上面兩個,一個是不傳入TTL,這時是redisson自己維護,會主動續期;另外一種是自己傳入TTL,這種redisson就不會幫我們自動續期了,或者自己將leaseTime的值傳成-1,但是不建議這種方式,既然已經有現成的API了,何必還要用這種奇怪的寫法呢。
接下來分析下不傳參的方法的加鎖邏輯:
1. public class RedissonLock extends RedissonExpirable implements RLock { 
2.  
3.     ... 
4.          
5.     public static final long LOCK_EXPIRATION_INTERVAL_SECONDS = 30; 
6.     protected long internalLockLeaseTime = TimeUnit.SECONDS.toMillis(LOCK_EXPIRATION_INTERVAL_SECONDS); 
7.  
8.          
9.     @Override 
10.     public void lock() { 
11.         try { 
12.             lockInterruptibly(); 
13.         } catch (InterruptedException e) { 
14.             Thread.currentThread().interrupt(); 
15.         } 
16.     } 
17.          
18.     @Override 
19.     public void lockInterruptibly() throws InterruptedException { 
20.         lockInterruptibly(-1, null); 
21.     } 
22.      
23.     @Override 
24.     public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException { 
25.         long threadId = Thread.currentThread().getId(); 
26.         Long ttl = tryAcquire(leaseTime, unit, threadId); 
27.         // lock acquired 
28.         if (ttl == null) { 
29.             return; 
30.         } 
31.  
32.         RFuture<RedissonLockEntry> future = subscribe(threadId); 
33.         commandExecutor.syncSubscription(future); 
34.  
35.         try { 
36.             while (true) { 
37.                 ttl = tryAcquire(leaseTime, unit, threadId); 
38.                 // lock acquired 
39.                 if (ttl == null) { 
40.                     break; 
41.                 } 
42.  
43.                 // waiting for message 
44.                 if (ttl >= 0) { 
45.                     getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); 
46.                 } else { 
47.                     getEntry(threadId).getLatch().acquire(); 
48.                 } 
49.             } 
50.         } finally { 
51.             unsubscribe(future, threadId); 
52.         } 
53. //        get(lockAsync(leaseTime, unit)); 
54.     } 
55.  
56.     private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) { 
57.         return get(tryAcquireAsync(leaseTime, unit, threadId)); 
58.     } 
59.  
60.     private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) { 
61.         if (leaseTime != -1) { 
62.             return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG); 
63.         } 
64.         RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(LOCK_EXPIRATION_INTERVAL_SECONDS, TimeUnit.SECONDS, threadId, RedisCommands.EVAL_LONG); 
65.         ttlRemainingFuture.addListener(new FutureListener<Long>() { 
66.             @Override 
67.             public void operationComplete(Future<Long> future) throws Exception { 
68.                 if (!future.isSuccess()) { 
69.                     return; 
70.                 } 
71.  
72.                 Long ttlRemaining = future.getNow(); 
73.                 // lock acquired 
74.                 if (ttlRemaining == null) { 
75.                     scheduleExpirationRenewal(threadId); 
76.                 } 
77.             } 
78.         }); 
79.         return ttlRemainingFuture; 
80.     } 
81.  
82.     private void scheduleExpirationRenewal(final long threadId) { 
83.         if (expirationRenewalMap.containsKey(getEntryName())) { 
84.             return; 
85.         } 
86.  
87.         Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { 
88.             @Override 
89.             public void run(Timeout timeout) throws Exception { 
90.                  
91.                 RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, 
92.                         "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + 
93.                             "redis.call('pexpire', KEYS[1], ARGV[1]); " + 
94.                             "return 1; " + 
95.                         "end; " + 
96.                         "return 0;", 
97.                           Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); 
98.                  
99.                 future.addListener(new FutureListener<Boolean>() { 
100.                     @Override 
101.                     public void operationComplete(Future<Boolean> future) throws Exception { 
102.                         expirationRenewalMap.remove(getEntryName()); 
103.                         if (!future.isSuccess()) { 
104.                             log.error("Can't update lock " + getName() + " expiration", future.cause()); 
105.                             return; 
106.                         } 
107.                          
108.                         if (future.getNow()) { 
109.                             // reschedule itself 
110.                             scheduleExpirationRenewal(threadId); 
111.                         } 
112.                     } 
113.                 }); 
114.             } 
115.         }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); 
116.  
117.         if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) { 
118.             task.cancel(); 
119.         } 
120.     } 
121.  
122.      
123.     ... 
124. } 
可以看到,最后加鎖的邏輯會進入到org.redisson.RedissonLock#tryAcquireAsync中,在獲取鎖成功后,會進入scheduleExpirationRenewal,這里面初始化了一個定時器,dely的時間是internalLockLeaseTime / 3。在redisson中,internalLockLeaseTime是30s,也就是每隔10s續期一次,每次30s。
如果是基于zookeeper實現的分布式鎖,可以利用zookeeper檢查節點是否存活,從而實現續期,zookeeper分布式鎖沒用過,不詳細說。
不過這種做法也無法百分百做到同一時刻只有一個client獲取到鎖,如果續期失敗,比如發生了Martin Kleppmann所說的STW的GC,或者client和redis集群失聯了,只要續期失敗,就會造成同一時刻有多個client獲得鎖了。在我的場景下,我將鎖的粒度拆小了,redisson的續期機制已經夠用了。
如果要做得更嚴格,得加一個續期失敗終止任務的邏輯。這種做法在以前Python的代碼中實現過,Java還沒有碰到這么嚴格的情況。
這里也提下Martin Kleppmann的解決方案,我自己覺得這個方案并不靠譜,原因后面會提到。
他的方案是讓加鎖的資源自己維護一套保證不會因加鎖失敗而導致多個client在同一時刻訪問同一個資源的情況。
 
在客戶端獲取鎖的同時,也獲取到一個資源的token,這個token是單調遞增的,每次在寫資源時,都檢查當前的token是否是較老的token,如果是就不讓寫。對于上面的場景,Client1獲取鎖的同時分配一個33的token,Client2獲取鎖的時候分配一個34的token,在client1 GC期間,Client2已經寫了資源,這時最大的token就是34了,client1 從GC中回來,再帶著33的token寫資源時,會因為token過期被拒絕。這種做法需要資源那一邊提供一個token生成器。
對于這種fencing的方案,我有幾點問題:
· 無法保證事務。示意圖中畫的只有34訪問了storage,但是在實際場景中,可能出現在一個任務內多次訪問storage的情況,而且必須是原子的。如果client1帶著33token在GC前訪問過一次storage,然后發生了GC。client2獲取到鎖,帶著34的token也訪問了storage,這時兩個client寫入的數據是否還能保證數據正確?如果不能,那么這種方案就有缺陷,除非storage自己有其他機制可以保證,比如事務機制;如果能,那么這里的token就是多余的,fencing的方案就是多此一舉。
· 高并發場景不實用。因為每次只有最大的token能寫,這樣storage的訪問就是線性的,在高并發場景下,這種方式會極大的限制吞吐量,而分布式鎖也大多是在這種場景下用的,很矛盾的設計。
· 這是所有分布式鎖的問題。這個方案是一個通用的方案,可以和Redlock用,也可以和其他的lock用。所以我理解僅僅是一個和Redlock無關的解決方案。
系統時鐘漂移
這個問題只是考慮過,但在實際項目中并沒有碰到過,因為理論上是可能出現的,這里也說下。
redis的過期時間是依賴系統時鐘的,如果時鐘漂移過大時會影響到過期時間的計算。
為什么系統時鐘會存在漂移呢?先簡單說下系統時間,linux提供了兩個系統時間:clock realtime和clock monotonic。clock realtime也就是xtime/wall time,這個時間可以被用戶改變的,被NTP改變,gettimeofday拿的就是這個時間,redis的過期計算用的也是這個時間。
clock monotonic ,直譯過來時單調時間,不會被用戶改變,但是會被NTP改變。
· 最理想的情況,所有系統的時鐘都時時刻刻和NTP服務器保持同步,但這顯然是不可能的。導致系統時鐘漂移的原因有兩個:
· 系統的時鐘和NTP服務器不同步。這個目前沒有特別好的解決方案,只能相信運維同學了。
· clock realtime被人為修改。在實現分布式鎖時,不要使用clock realtime。不過很可惜,redis使用的就是這個時間,我看了下Redis 5.0源碼,使用的還是clock realtime。Antirez說過改成clock monotonic的,不過大佬還沒有改。也就是說,人為修改redis服務器的時間,就能讓redis出問題了。



相關文章

IT外包服務
二維碼 關閉
主站蜘蛛池模板: 成人免费视频www在线观看我_日韩免费无码成人久久久久久片_91影院高清_一级毛片超级播放_亚洲综合伊人_911网站大全在线观看_成人综合婷婷国产精品久久_蝌蚪91在线 | 青青草精品在线_成人91免费_狠狠久久亚洲欧美专_www中文字幕在线观看_99re在线播放_抽搐一进一出gif免费_野花社区影视在线www官网_日本丰满熟妇videossex8k | 国产亚洲情侣一区二区无_亚洲精品视频免费观看_樱桃视频大全免费观看_撕开奶罩揉吮奶头高潮av_成人免费精品网站在线观看影片_va在线观看_欧美爆乳精品国产一区二区三区_久久久久亚洲AV无码A片男男 | 黑人狂躁日本妞hd_亚洲欧洲日产国码无码_日韩一区二区a片免费观看_久久之久久_日本va在线视频播放_欧美日韩欧美_在线免费观看国产_自拍偷拍99 | 国产九九免费_91精品久久久久久久久入口_久久国产精品成人免费_国产美熟女乱又伦av果冻传媒_精品国产精品三级精品av网址_中国娇小与黑人巨大交_中国毛片一级片_鲁一鲁AV2019在线 | 99视频网站_精品久久亚洲中文字幕_亚洲不卡高清免V无码屋_久久精品日_午夜美女国产毛片福利视频_free欧美日韩免费在线观看_久久久久久亚洲精品不卡4k岛国_涩涩小视频 | 成人一在线视频日韩国产_超碰在线公开97_久久亚洲精品国产一区_国产精品久久不能_午夜亚洲精品专区高潮日w_kaori肉感在线播放_www.四虎影视.com_欧美日韩久 | 男女一级裸片_国产永久免费高清在线_日韩欧美一级二级_天天干夜夜骑_欧美3p激情一区二区三区猛视频_高清欧美精品xxxxx_A级毛片毛片免费观的看久_久草在线免费新视频 | 久久影院一区二区三区_69xxx免费_日韩中文字幕2018_国内高清在线观看视频_青春草在线视频免费观看_91精品中文字幕_免费看www_91精品免费 | 日韩1页_欧美极品25p_黄视频在线观看网站_青青青草视频_久久综合九色综合网站_国产精品欧美一区乱破_成人在线97_国产交换配乱婬视频偷 | 少妇把腿扒开让我舔18_video日本老熟妇_亚洲欧美成人影院_亚洲av无码网站yw尤物_国产h视频在线观看播放_某机关少妇下班酒店在线播放_经典三级欧美在线播放_亚洲综合视频在线 | 亚洲人a成www在线影院_久久动漫在线观看_亚洲再线_AV无码爆乳护士在线播放_五月天婷亚洲天综合网鲁鲁鲁_japansex久久高清精品_成人cosplay福利网站18禁_国产欧美日韩亚洲更新 | 日日干天夜夜_国产xxxx69_亚洲国产精品久久久天堂不卡海量_亚洲精品一区二区三区国产_国产成人啪精品午夜在线观看_日韩三级视频_欧美高清一级_www.欧美日本 | 999精品在线观看_97午夜理论片影院在线播放_亚洲精品美女久久17c_亚洲丝袜制服美女av_中文乱码字幕高清一区二区_国精一区二区三区_久久精品视频网址_日韩AV无码久久精品免费 | AV国产剧情MD精品麻豆_亚洲国产综合精品在线一区_91碰在线视频_国产偷窥女洗浴在线观看亚洲_99热5_综合激情五月综合激情五月激情1_精品国产aⅴ无码一区二区_久久婷婷是五月综合色 | 福利免费在线_中文字幕无码日韩专区免费_亚洲成人一区二区三区四区_久久99精品久久久97夜夜嗨_内射高潮享受视频在线观看_中文字幕av无码专区第一页_一区二区三区在线观看免费视频_新疆老熟女厉害 | 亚洲成色WWW久久网站夜月_久久久久久久久久久久国产_日本成人午夜视频_成人夜色视频网站在线观看_成本人h片动漫网站在线看_精品久久高清_91精品国产色综合久久久蜜臀_av伊人天堂 | 精品一区二区三区91_国产黄网在线_国产精品熟妇一区二区三区四区_亚洲αv在线精品糸列_无遮挡边摸边吃奶边做的视频刺激_在线免费观看h视频_狠狠爱网_免费无码AV片在线观看软件 | 男人的天堂Aⅴ在线_特级毛片A片全部免费97_97免费看_免费无码一区二区三区A片百度_精品成人网_狠狠噜天天噜日日噜视频跳一跳_中国熟妇人妻videos_日日碰日日摸夜夜爽无码 | 国产在线一区视频播放_狠狠操91_免费看男女高潮又爽又猛视_欧美色蜜桃97_亚洲欧洲综合有码无码_日本人伦一区二区三区_日本逼视频_国产一级牲交高潮片16 | 看全色黄大色黄女片_亚洲欧洲日产国码综合在线_99精品自产国偷产在线_国精品**一区二区三区在线蜜桃_亚洲91p_欧美日韩在线精品_538久久_欧美国产精品久久久久久免费 | 中字无码AV点击进入_国产麻豆成人传媒免费观看_亚洲精品成人AV在线观看爽翻_久久亚洲成人av_欧美videosdesexo吹潮_xxxxxx性受_精品一区二区久久久久黄大片_亚洲人av高清无码 | 国产精品无码久久av嫩草_日韩午夜在线播放_亚洲人成亚洲人成在线观看_国产三级在线观看_超碰人人美国_欧美日韩八区_136av福利视频导航入口_日韩高清免费在线观看 | AV片亚洲国产男人的天堂_天天综合网7799精品视频_av护士_国产欧美va天堂在线观看视频_久久免费毛片视频_久久99国产精一区二区三区_一边吃奶一边扎下边爽了_午夜一级黄色片 | 亚洲一区二区久久久久久_亚洲精品国产高清一线久久_丝袜美女被遭强高潮网站_鲁一鲁操一操_中文字幕精品视频在线观看_精品在线一区_中国成人亚色综合网站_久久久123 | 成年人观看免费视频_国产精品免费不_伦理二区_国产中文在线播放_97人人干_裸体丰满少妇做爰视频_aⅴ色欲AV片无码精品小说_日本在线视频二区 | 国产一级毛片视频在线!_天天草天天_国产精品无码永久免费不卡_91久久夜色精品国产九色_日韩亚洲在线观看_久久久妻_久久免费看黄A级毛片连期A片_久久精品国产久精国产69 | 欧美一区二区三区老人_久久久女女女女999久久_日本熟妇浓毛_又粗又硬成人免费大片_日韩成人在线播放_青青草视频污_自拍av在线_国偷自产一区二区三区蜜臀 | 台湾佬中文娱乐22vvvv_日日干天天干_性爱免费视频_国产在线精品一区在线观看_一区二区不卡免费视频_国产无一区二区_偷看少妇自慰XXXX_亚洲永久精品www | 国产裸体写真av一区二区_337p日本大胆欧美人_久久久久久久亚洲AV无码_国产在线高清精品_亚洲成色精品一区二区三区_99久久免费精品国产免费高清_激情五月人体_国产iv一区二区三区 | 国产亚洲欧美一区二区三区四区_99热综合_一区二区精彩视频_欧美一二三四五六七区_日韩免费视频在线观看_四虎一区二区_影音先锋黄色网址_成人hd | jvid视频_超碰CAO已满18进入离开_日韩精品三区四区_国产成人a片免费观看_被仇人调教成禁脔h虐_久草在线视频新_久久久久夜夜夜综合国产_黄色视频网站夜夜嗨转域97 | 日本三级香港三级人妇少妇_国内精品999_国产成人久久av免费高清蜜臀_奥门一级毛片_国产欧美日韩精品专区_国产精品禁漫天堂视频_国产午夜精品av一区二区_乳首av | 久久久无码精品亚州日韩免费看_国产精品一区二区亚洲_国产一二三视频_99精品在免费线偷拍_亚洲国产成人在线播放_亚洲国产精品专区久久_人妻巨大乳HD免费看_男生白内裤自慰gv白袜男同 | 色婷婷av一区二区三区之红樱桃_精品日韩亚洲AV无码一区破坏_美女扒开尿口给男人看_性生大片免费观看高清视频_久久久久久久久久99精品_91成人国产精品_东京热一本无码av_久久网伊人 | 国产九九免费_91精品久久久久久久久入口_久久国产精品成人免费_国产美熟女乱又伦av果冻传媒_精品国产精品三级精品av网址_中国娇小与黑人巨大交_中国毛片一级片_鲁一鲁AV2019在线 | 波多野结衣一区二区在线_国产一级一片免费播放放_狠狠色婷婷久久一区二区_av在线不卡播放_最近2019中文字幕第二页_国产精品人妻一区免费看8c0m_亚洲色大成网站www久久九九_日韩精品视频免费观看 | 青草一区二区_五月香蕉网_久久综合色一综合色88欧美_中文字幕在线中文一页_www.操操操.com_国产一久久香蕉国产线看观看_超碰在线一区_精品视频一区二区三区四区五区 | 欧洲亚洲免费视频_99久久久久久久_91综合网站_在线观看国产h_亚州国产_欧洲久久久久_国产精品毛片一区_欧美白丰满老太aaa片 | 亚洲一区av_毛片无码免费无码播放_亚洲国产一区二区波多野结衣_亚洲一级片内射无码_99久久69五月天_啦啦啦在线视频免费观看高清中文_亚洲AV成人影视综合网_国产午夜91 狠狠操婷婷_国产一区二区三区中文字幕_欧美a级片视频_高清国产在线一区_男人和女人做爽爽免费视频_亚洲精品无码人妻无码_国产啪亚洲国产精品无码_超碰97久久国产精品牛牛 | 中日韩精品无码一区二区三区_国产精品福利视频一区_国产成人一区二区视频免费_国产精品麻豆视频国产馆_日本国产一区二区三区在线观看_free国产粉嫩熟妇xxxhd_国产精品无码久久综合_激情影院内射美女 |