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

Redis 分布式鎖的 5個坑,真是又大又深

存儲 存儲軟件 分布式 Redis
最近項目上線的頻率頗高,連著幾天加班熬夜,身體有點吃不消精神也有些萎靡,無奈業務方催的緊,工期就在眼前只能硬著頭皮上了。腦子渾渾噩噩的時候,寫的就不能叫代碼,可以直接叫做Bug。我就熬夜寫了一個bug被罵慘了。

 [[323418]]

引言

最近項目上線的頻率頗高,連著幾天加班熬夜,身體有點吃不消精神也有些萎靡,無奈業務方催的緊,工期就在眼前只能硬著頭皮上了。腦子渾渾噩噩的時候,寫的就不能叫代碼,可以直接叫做Bug。我就熬夜寫了一個bug被罵慘了。

由于是做商城業務,要頻繁的對商品庫存進行扣減,應用是集群部署,為避免并發造成庫存超買超賣等問題,采用 redis 分布式鎖加以控制。本以為給扣庫存的代碼加上鎖lock.tryLock就萬事大吉了

  1. /** 
  2.      * @author xiaofu 
  3.      * @description 扣減庫存 
  4.      * @date 2020/4/21 12:10 
  5.      */ 
  6.    public String stockLock() { 
  7.         RLock lock = redissonClient.getLock("stockLock"); 
  8.         try { 
  9.             /** 
  10.              * 獲取鎖 
  11.              */ 
  12.             if (lock.tryLock(10, TimeUnit.SECONDS)) { 
  13.                 /** 
  14.                  * 查詢庫存數 
  15.                  */ 
  16.                 Integer stock = Integer.valueOf(stringRedisTemplate.opsForValue().get("stockCount")); 
  17.                 /** 
  18.                  * 扣減庫存 
  19.                  */ 
  20.                 if (stock > 0) { 
  21.                     stock = stock - 1; 
  22.                     stringRedisTemplate.opsForValue().set("stockCount", stock.toString()); 
  23.                     LOGGER.info("庫存扣減成功,剩余庫存數量:{}", stock); 
  24.                 } else { 
  25.                     LOGGER.info("庫存不足~"); 
  26.                 } 
  27.             } else { 
  28.                 LOGGER.info("未獲取到鎖業務結束.."); 
  29.             } 
  30.         } catch (Exception e) { 
  31.             LOGGER.info("處理異常", e); 
  32.         } finally { 
  33.             lock.unlock(); 
  34.         } 
  35.         return "ok"
  36.   } 

結果業務代碼執行完以后我忘了釋放鎖lock.unlock(),導致redis線程池被打滿,redis服務大面積故障,造成庫存數據扣減混亂,被領導一頓臭罵,這個月績效~ 哎·~。

隨著 使用redis 鎖的時間越長,我發現 redis 鎖的坑遠比想象中要多。就算在面試題當中redis分布式鎖的出鏡率也比較高,比如:“用鎖遇到過哪些問題?” ,“又是如何解決的?” 基本都是一套連招問出來的。

今天就分享一下我用redis 分布式鎖的踩坑日記,以及一些解決方案,和大家一起共勉。

一、鎖未被釋放

這種情況是一種低級錯誤,就是我上邊犯的錯,由于當前線程 獲取到redis 鎖,處理完業務后未及時釋放鎖,導致其它線程會一直嘗試獲取鎖阻塞,例如:用Jedis客戶端會報如下的錯誤信息

  1. redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool 

redis線程池已經沒有空閑線程來處理客戶端命令。

解決的方法也很簡單,只要我們細心一點,拿到鎖的線程處理完業務及時釋放鎖,如果是重入鎖未拿到鎖后,線程可以釋放當前連接并且sleep一段時間。

  1. public void lock() { 
  2.       while (true) { 
  3.           boolean flag = this.getLock(key); 
  4.           if (flag) { 
  5.                 TODO ......... 
  6.           } else { 
  7.                 // 釋放當前redis連接 
  8.                 redis.close(); 
  9.                 // 休眠1000毫秒 
  10.                 sleep(1000); 
  11.           } 
  12.         } 
  13.     } 

二、B的鎖被A給釋放了

我們知道Redis實現鎖的原理在于 SETNX命令。當 key不存在時將 key的值設為 value ,返回值為 1;若給定的 key已經存在,則 SETNX不做任何動作,返回值為 0 。

  1. SETNX key value 

我們來設想一下這個場景:A、B兩個線程來嘗試給key myLock加鎖,A線程先拿到鎖(假如鎖3秒后過期),B線程就在等待嘗試獲取鎖,到這一點毛病沒有。

那如果此時業務邏輯比較耗時,執行時間已經超過redis鎖過期時間,這時A線程的鎖自動釋放(刪除key),B線程檢測到myLock這個key不存在,執行 SETNX命令也拿到了鎖。

但是,此時A線程執行完業務邏輯之后,還是會去釋放鎖(刪除key),這就導致B線程的鎖被A線程給釋放了。

為避免上邊的情況,一般我們在每個線程加鎖時要帶上自己獨有的value值來標識,只釋放指定value的key,否則就會出現釋放鎖混亂的場景。

三、數據庫事務超時

emm~ 聊redis鎖咋還扯到數據庫事務上來了?別著急往下看,看下邊這段代碼:

  1. @Transaction 
  2.    public void lock() { 
  3.  
  4.         while (true) { 
  5.             boolean flag = this.getLock(key); 
  6.             if (flag) { 
  7.                 insert(); 
  8.             } 
  9.         } 
  10.     } 

給這個方法添加一個@Transaction注解開啟事務,如代碼中拋出異常進行回滾,要知道數據庫事務可是有超時時間限制的,并不會無條件的一直等一個耗時的數據庫操作。

比如:我們解析一個大文件,再將數據存入到數據庫,如果執行時間太長,就會導致事務超時自動回滾。

一旦你的key長時間獲取不到鎖,獲取鎖等待的時間遠超過數據庫事務超時時間,程序就會報異常。

一般為解決這種問題,我們就需要將數據庫事務改為手動提交、回滾事務。

  1. @Autowired 
  2.     DataSourceTransactionManager dataSourceTransactionManager; 
  3.  
  4.     @Transaction 
  5.     public void lock() { 
  6.         //手動開啟事務 
  7.         TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition); 
  8.         try { 
  9.             while (true) { 
  10.                 boolean flag = this.getLock(key); 
  11.                 if (flag) { 
  12.                     insert(); 
  13.                     //手動提交事務 
  14.                     dataSourceTransactionManager.commit(transactionStatus); 
  15.                 } 
  16.             } 
  17.         } catch (Exception e) { 
  18.             //手動回滾事務 
  19.             dataSourceTransactionManager.rollback(transactionStatus); 
  20.         } 
  21.     } 

四、鎖過期了,業務還沒執行完

這種情況和我們上邊提到的第二種比較類似,但解決思路上略有不同。

同樣是redis分布式鎖過期,而業務邏輯沒執行完的場景,不過,這里換一種思路想問題,把redis鎖的過期時間再弄長點不就解決了嗎?

那還是有問題,我們可以在加鎖的時候,手動調長redis鎖的過期時間,可這個時間多長合適?業務邏輯的執行時間是不可控的,調的過長又會影響操作性能。

要是redis鎖的過期時間能夠自動續期就好了。

為了解決這個問題我們使用redis客戶端redisson,redisson很好的解決了redis在分布式環境下的一些棘手問題,它的宗旨就是讓使用者減少對Redis的關注,將更多精力用在處理業務邏輯上。

redisson對分布式鎖做了很好封裝,只需調用API即可。

  1. RLock lock = redissonClient.getLock("stockLock"); 

redisson在加鎖成功后,會注冊一個定時任務監聽這個鎖,每隔10秒就去查看這個鎖,如果還持有鎖,就對過期時間進行續期。默認過期時間30秒。這個機制也被叫做:“看門狗”,這名字。。。

舉例子:假如加鎖的時間是30秒,過10秒檢查一次,一旦加鎖的業務沒有執行完,就會進行一次續期,把鎖的過期時間再次重置成30秒。

通過分析下邊redisson的源碼實現可以發現,不管是加鎖、解鎖、續約都是客戶端把一些復雜的業務邏輯,通過封裝在Lua腳本中發送給redis,保證這段復雜業務邏輯執行的原子性。

 

  1. @Slf4j 
  2. @Service 
  3. public class RedisDistributionLockPlus { 
  4.  
  5.     /** 
  6.      * 加鎖超時時間,單位毫秒, 即:加鎖時間內執行完操作,如果未完成會有并發現象 
  7.      */ 
  8.     private static final long DEFAULT_LOCK_TIMEOUT = 30; 
  9.  
  10.     private static final long TIME_SECONDS_FIVE = 5 ; 
  11.  
  12.     /** 
  13.      * 每個key的過期時間 {@link LockContent} 
  14.      */ 
  15.     private Map<String, LockContent> lockContentMap = new ConcurrentHashMap<>(512); 
  16.  
  17.     /** 
  18.      * redis執行成功的返回 
  19.      */ 
  20.     private static final Long EXEC_SUCCESS = 1L; 
  21.  
  22.     /** 
  23.      * 獲取鎖lua腳本, k1:獲鎖key, k2:續約耗時key, arg1:requestId,arg2:超時時間 
  24.      */ 
  25.     private static final String LOCK_SCRIPT = "if redis.call('exists', KEYS[2]) == 1 then ARGV[2] = math.floor(redis.call('get', KEYS[2]) + 10) end " + 
  26.             "if redis.call('exists', KEYS[1]) == 0 then " + 
  27.                "local t = redis.call('set', KEYS[1], ARGV[1], 'EX', ARGV[2]) " + 
  28.                "for k, v in pairs(t) do " + 
  29.                  "if v == 'OK' then return tonumber(ARGV[2]) end " + 
  30.                "end " + 
  31.             "return 0 end"
  32.  
  33.     /** 
  34.      * 釋放鎖lua腳本, k1:獲鎖key, k2:續約耗時key, arg1:requestId,arg2:業務耗時 arg3: 業務開始設置的timeout 
  35.      */ 
  36.     private static final String UNLOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then " + 
  37.             "local ctime = tonumber(ARGV[2]) " + 
  38.             "local biz_timeout = tonumber(ARGV[3]) " + 
  39.             "if ctime > 0 then  " + 
  40.                "if redis.call('exists', KEYS[2]) == 1 then " + 
  41.                    "local avg_time = redis.call('get', KEYS[2]) " + 
  42.                    "avg_time = (tonumber(avg_time) * 8 + ctime * 2)/10 " + 
  43.                    "if avg_time >= biz_timeout - 5 then redis.call('set', KEYS[2], avg_time, 'EX', 24*60*60) " + 
  44.                    "else redis.call('del', KEYS[2]) end " + 
  45.                "elseif ctime > biz_timeout -5 then redis.call('set', KEYS[2], ARGV[2], 'EX', 24*60*60) end " + 
  46.             "end " + 
  47.             "return redis.call('del', KEYS[1]) " + 
  48.             "else return 0 end"
  49.     /** 
  50.      * 續約lua腳本 
  51.      */ 
  52.     private static final String RENEW_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end"
  53.  
  54.  
  55.     private final StringRedisTemplate redisTemplate; 
  56.  
  57.     public RedisDistributionLockPlus(StringRedisTemplate redisTemplate) { 
  58.         this.redisTemplate = redisTemplate; 
  59.         ScheduleTask task = new ScheduleTask(this, lockContentMap); 
  60.         // 啟動定時任務 
  61.         ScheduleExecutor.schedule(task, 1, 1, TimeUnit.SECONDS); 
  62.     } 
  63.  
  64.     /** 
  65.      * 加鎖 
  66.      * 取到鎖加鎖,取不到鎖一直等待知道獲得鎖 
  67.      * 
  68.      * @param lockKey 
  69.      * @param requestId 全局唯一 
  70.      * @param expire   鎖過期時間, 單位秒 
  71.      * @return 
  72.      */ 
  73.     public boolean lock(String lockKey, String requestId, long expire) { 
  74.         log.info("開始執行加鎖, lockKey ={}, requestId={}", lockKey, requestId); 
  75.         for (; ; ) { 
  76.             // 判斷是否已經有線程持有鎖,減少redis的壓力 
  77.             LockContent lockContentOld = lockContentMap.get(lockKey); 
  78.             boolean unLocked = null == lockContentOld; 
  79.             // 如果沒有被鎖,就獲取鎖 
  80.             if (unLocked) { 
  81.                 long startTime = System.currentTimeMillis(); 
  82.                 // 計算超時時間 
  83.                 long bizExpire = expire == 0L ? DEFAULT_LOCK_TIMEOUT : expire; 
  84.                 String lockKeyRenew = lockKey + "_renew"
  85.  
  86.                 RedisScript<Long> script = RedisScript.of(LOCK_SCRIPT, Long.class); 
  87.                 List<String> keys = new ArrayList<>(); 
  88.                 keys.add(lockKey); 
  89.                 keys.add(lockKeyRenew); 
  90.                 Long lockExpire = redisTemplate.execute(script, keys, requestId, Long.toString(bizExpire)); 
  91.                 if (null != lockExpire && lockExpire > 0) { 
  92.                     // 將鎖放入map 
  93.                     LockContent lockContent = new LockContent(); 
  94.                     lockContent.setStartTime(startTime); 
  95.                     lockContent.setLockExpire(lockExpire); 
  96.                     lockContent.setExpireTime(startTime + lockExpire * 1000); 
  97.                     lockContent.setRequestId(requestId); 
  98.                     lockContent.setThread(Thread.currentThread()); 
  99.                     lockContent.setBizExpire(bizExpire); 
  100.                     lockContent.setLockCount(1); 
  101.                     lockContentMap.put(lockKey, lockContent); 
  102.                     log.info("加鎖成功, lockKey ={}, requestId={}", lockKey, requestId); 
  103.                     return true
  104.                 } 
  105.             } 
  106.             // 重復獲取鎖,在線程池中由于線程復用,線程相等并不能確定是該線程的鎖 
  107.             if (Thread.currentThread() == lockContentOld.getThread() 
  108.                       && requestId.equals(lockContentOld.getRequestId())){ 
  109.                 // 計數 +1 
  110.                 lockContentOld.setLockCount(lockContentOld.getLockCount()+1); 
  111.                 return true
  112.             } 
  113.  
  114.             // 如果被鎖或獲取鎖失敗,則等待100毫秒 
  115.             try { 
  116.                 TimeUnit.MILLISECONDS.sleep(100); 
  117.             } catch (InterruptedException e) { 
  118.                 // 這里用lombok 有問題 
  119.                 log.error("獲取redis 鎖失敗, lockKey ={}, requestId={}", lockKey, requestId, e); 
  120.                 return false
  121.             } 
  122.         } 
  123.     } 
  124.  
  125.  
  126.     /** 
  127.      * 解鎖 
  128.      * 
  129.      * @param lockKey 
  130.      * @param lockValue 
  131.      */ 
  132.     public boolean unlock(String lockKey, String lockValue) { 
  133.         String lockKeyRenew = lockKey + "_renew"
  134.         LockContent lockContent = lockContentMap.get(lockKey); 
  135.  
  136.         long consumeTime; 
  137.         if (null == lockContent) { 
  138.             consumeTime = 0L; 
  139.         } else if (lockValue.equals(lockContent.getRequestId())) { 
  140.             int lockCount = lockContent.getLockCount(); 
  141.             // 每次釋放鎖, 計數 -1,減到0時刪除redis上的key 
  142.             if (--lockCount > 0) { 
  143.                 lockContent.setLockCount(lockCount); 
  144.                 return false
  145.             } 
  146.             consumeTime = (System.currentTimeMillis() - lockContent.getStartTime()) / 1000; 
  147.         } else { 
  148.             log.info("釋放鎖失敗,不是自己的鎖。"); 
  149.             return false
  150.         } 
  151.  
  152.         // 刪除已完成key,先刪除本地緩存,減少redis壓力, 分布式鎖,只有一個,所以這里不加鎖 
  153.         lockContentMap.remove(lockKey); 
  154.  
  155.         RedisScript<Long> script = RedisScript.of(UNLOCK_SCRIPT, Long.class); 
  156.         List<String> keys = new ArrayList<>(); 
  157.         keys.add(lockKey); 
  158.         keys.add(lockKeyRenew); 
  159.  
  160.         Long result = redisTemplate.execute(script, keys, lockValue, Long.toString(consumeTime), 
  161.                 Long.toString(lockContent.getBizExpire())); 
  162.         return EXEC_SUCCESS.equals(result); 
  163.  
  164.     } 
  165.  
  166.     /** 
  167.      * 續約 
  168.      * 
  169.      * @param lockKey 
  170.      * @param lockContent 
  171.      * @return true:續約成功,false:續約失敗(1、續約期間執行完成,鎖被釋放 2、不是自己的鎖,3、續約期間鎖過期了(未解決)) 
  172.      */ 
  173.     public boolean renew(String lockKey, LockContent lockContent) { 
  174.  
  175.         // 檢測執行業務線程的狀態 
  176.         Thread.State state = lockContent.getThread().getState(); 
  177.         if (Thread.State.TERMINATED == state) { 
  178.             log.info("執行業務的線程已終止,不再續約 lockKey ={}, lockContent={}", lockKey, lockContent); 
  179.             return false
  180.         } 
  181.  
  182.         String requestId = lockContent.getRequestId(); 
  183.         long timeOut = (lockContent.getExpireTime() - lockContent.getStartTime()) / 1000; 
  184.  
  185.         RedisScript<Long> script = RedisScript.of(RENEW_SCRIPT, Long.class); 
  186.         List<String> keys = new ArrayList<>(); 
  187.         keys.add(lockKey); 
  188.  
  189.         Long result = redisTemplate.execute(script, keys, requestId, Long.toString(timeOut)); 
  190.         log.info("續約結果,True成功,False失敗 lockKey ={}, result={}", lockKey, EXEC_SUCCESS.equals(result)); 
  191.         return EXEC_SUCCESS.equals(result); 
  192.     } 
  193.  
  194.  
  195.     static class ScheduleExecutor { 
  196.  
  197.         public static void schedule(ScheduleTask task, long initialDelay, long period, TimeUnit unit) { 
  198.             long delay = unit.toMillis(initialDelay); 
  199.             long period_ = unit.toMillis(period); 
  200.             // 定時執行 
  201.             new Timer("Lock-Renew-Task").schedule(task, delay, period_); 
  202.         } 
  203.     } 
  204.  
  205.     static class ScheduleTask extends TimerTask { 
  206.  
  207.         private final RedisDistributionLockPlus redisDistributionLock; 
  208.         private final Map<String, LockContent> lockContentMap; 
  209.  
  210.         public ScheduleTask(RedisDistributionLockPlus redisDistributionLock, Map<String, LockContent> lockContentMap) { 
  211.             this.redisDistributionLock = redisDistributionLock; 
  212.             this.lockContentMap = lockContentMap; 
  213.         } 
  214.  
  215.         @Override 
  216.         public void run() { 
  217.             if (lockContentMap.isEmpty()) { 
  218.                 return
  219.             } 
  220.             Set<Map.Entry<String, LockContent>> entries = lockContentMap.entrySet(); 
  221.             for (Map.Entry<String, LockContent> entry : entries) { 
  222.                 String lockKey = entry.getKey(); 
  223.                 LockContent lockContent = entry.getValue(); 
  224.                 long expireTime = lockContent.getExpireTime(); 
  225.                 // 減少線程池中任務數量 
  226.                 if ((expireTime - System.currentTimeMillis())/ 1000 < TIME_SECONDS_FIVE) { 
  227.                     //線程池異步續約 
  228.                     ThreadPool.submit(() -> { 
  229.                         boolean renew = redisDistributionLock.renew(lockKey, lockContent); 
  230.                         if (renew) { 
  231.                             long expireTimeNew = lockContent.getStartTime() + (expireTime - lockContent.getStartTime()) * 2 - TIME_SECONDS_FIVE * 1000; 
  232.                             lockContent.setExpireTime(expireTimeNew); 
  233.                         } else { 
  234.                             // 續約失敗,說明已經執行完 OR redis 出現問題 
  235.                             lockContentMap.remove(lockKey); 
  236.                         } 
  237.                     }); 
  238.                 } 
  239.             } 
  240.         } 
  241.     } 

五、redis主從復制的坑

redis高可用最常見的方案就是主從復制(master-slave),這種模式也給redis分布式鎖挖了一坑。

redis cluster集群環境下,假如現在A客戶端想要加鎖,它會根據路由規則選擇一臺master節點寫入key mylock,在加鎖成功后,master節點會把key異步復制給對應的slave節點。

如果此時redis master節點宕機,為保證集群可用性,會進行主備切換,slave變為了redis master。B客戶端在新的master節點上加鎖成功,而A客戶端也以為自己還是成功加了鎖的。

此時就會導致同一時間內多個客戶端對一個分布式鎖完成了加鎖,導致各種臟數據的產生。

至于解決辦法嘛,目前看還沒有什么根治的方法,只能盡量保證機器的穩定性,減少發生此事件的概率。

總結

上面就是我在使用Redis 分布式鎖時遇到的一些坑,有點小感慨,經常用一個方法填上這個坑,沒多久就發現另一個坑又出來了,其實根本沒有什么十全十美的解決方案,哪有什么銀彈,只不過是在權衡利弊后,選一個在接受范圍內的折中方案而已。

責任編輯:武曉燕 來源: 程序員內點事
相關推薦

2025-09-29 00:00:00

2022-12-18 20:07:55

Redis分布式

2019-06-19 15:40:06

分布式鎖RedisJava

2021-09-26 09:16:45

RedisGeo 類型數據類型

2020-07-30 09:35:09

Redis分布式鎖數據庫

2020-09-01 07:36:29

分布式鎖分布式進程

2022-06-16 08:01:24

redis分布式鎖

2023-08-21 19:10:34

Redis分布式

2022-01-06 10:58:07

Redis數據分布式鎖

2019-02-26 09:51:52

分布式鎖RedisZookeeper

2024-02-19 00:00:00

Redis分布式

2022-11-14 07:23:32

RedisJedis分布式鎖

2023-03-01 08:07:51

2024-10-07 10:07:31

2020-11-16 12:55:41

Redis分布式鎖Zookeeper

2022-09-19 08:17:09

Redis分布式

2021-06-16 07:56:21

Redis分布式

2019-07-16 09:22:10

RedisZookeeper分布式鎖

2024-04-01 05:10:00

Redis數據庫分布式鎖

2021-11-01 12:25:56

Redis分布式
點贊
收藏

51CTO技術棧公眾號

中国av一区| 国产理论电影在线| 久国产精品韩国三级视频| 色老头一区二区三区在线观看| 国产精品亚洲a| 午夜在线视频| 国产成a人亚洲| 欧美在线影院在线视频| 欧日韩不卡视频| 中文字幕亚洲在线观看| 日韩欧美国产中文字幕| 一区二区三区四区免费视频| 亚洲av无码乱码在线观看性色| 国产亚洲网站| 久久久国产精品x99av| 国产精品久久久久久在线观看| 欧美www.| 亚洲成av人片观看| 国产免费色视频| 青春有你2免费观看完整版在线播放高清 | 国产精品vip| 精品无人国产偷自产在线| 伊人影院综合在线| 捆绑调教日本一区二区三区| 亚洲视频一区二区免费在线观看| 久久资源av| 99久久精品国产色欲| 久久裸体视频| 韩国日本不卡在线| 人妻人人澡人人添人人爽| 一区二区三区日本久久久 | 欧美一区二区三区久久| 91精品蜜臀在线一区尤物| 亚洲成熟丰满熟妇高潮xxxxx| 污视频网站免费在线观看| 欧美国产日韩在线观看| 极品日韩久久| 亚洲黄色在线播放| 国产一区二区剧情av在线| 国产精品成人一区二区| 91视频免费网址| 亚洲性色视频| 欧美精品性视频| 精品在线观看一区| 欧美日韩激情在线一区二区三区| 日韩精品中文字幕在线| 亚洲欧美高清在线| 日韩一区二区三区色| 欧美精品自拍偷拍| 欧美激情第3页| 欧美123区| 在线观看视频91| 欧美激情国产精品日韩| 中文字幕在线视频久| 精品久久久久久久久久久| 成年人午夜免费视频| heyzo一区| 亚洲成a人片在线不卡一二三区 | 蜜桃av综合| 欧美亚洲另类在线| 国产成人无码av| 美女91精品| 国产精品免费观看在线| 中文字幕一二区| 蜜臀久久99精品久久久久久9| 国产激情综合五月久久| 免费一级a毛片| 日韩高清在线观看| 国产精品一区二区三区免费视频| 中文字幕人妻一区二区三区视频| 蜜桃视频一区二区| 国产日本欧美在线观看| 一级特黄aa大片| 国产一区二区三区国产| 成人免费看片网址| 色噜噜在线播放| 久久蜜桃av一区精品变态类天堂 | 黄色一区二区三区| 日本黄色三级大片| 国产经典一区| 3d动漫精品啪啪1区2区免费 | 国产小视频免费| 538在线精品| 亚洲一区二区三区四区的| 男人日女人下面视频| 激情开心成人网| 制服.丝袜.亚洲.另类.中文| 四虎国产精品免费| 日本一区福利在线| 日韩在线视频线视频免费网站| 四虎永久免费在线| 一区二区黄色| 国产日韩视频在线观看| 亚洲第一大网站| 久久精品一区二区三区av| 色中文字幕在线观看| 国产剧情av在线播放| 欧美丝袜丝交足nylons| 永久看看免费大片| 亚洲自拍电影| 欧美精品制服第一页| youjizz在线视频| 国产最新精品免费| 欧美不卡三区| 91三级在线| 在线观看三级视频欧美| 手机免费看av片| 第一会所亚洲原创| 91精品国产91久久久久久吃药 | 少妇高潮一区二区三区喷水| 亚洲大胆在线| 91精品久久久久久久久| 天堂91在线| 亚洲精品免费在线播放| 日韩精品一区二区三区不卡| 中文字幕久久精品一区二区| 中文字幕不卡av| 中日韩黄色大片| 国产一区二区成人久久免费影院| 久久亚洲午夜电影| 尤物在线网址| 欧美日韩精品二区第二页| 给我免费观看片在线电影的| 亚洲电影影音先锋| 国产精品极品在线| 日韩欧美电影在线观看| 亚洲综合免费观看高清在线观看| 国内自拍视频网| 婷婷成人在线| 欧美精品国产精品日韩精品| 亚洲中文字幕在线观看| 久久午夜免费电影| 奇米影视亚洲色图| 日韩欧美高清一区二区三区| 色悠悠久久88| 中文字幕 日韩有码| 久久综合色天天久久综合图片| wwwwww欧美| 日韩成人在线看| 久久久精品在线观看| 最近日韩免费视频| 国产欧美一区二区三区鸳鸯浴| 无罩大乳的熟妇正在播放| 国产精品极品在线观看| 欧美激情区在线播放| 国产高清免费av| 一区二区三区在线观看动漫 | 99久久99久久精品免费观看| 久久综合久久网| 亚洲精品一区二区三区中文字幕| 麻豆国产精品va在线观看不卡| 一级黄色片在线观看| 国产精品毛片无遮挡高清| www.色偷偷.com| 成人在线免费观看视频| 国产精品久久久久久久久久久久久| 你懂的免费在线观看| 色婷婷亚洲婷婷| 免费看污黄网站在线观看| 免费中文字幕日韩欧美| 欧美日韩一区二区视频在线 | 国产乱子伦三级在线播放| 色呦呦一区二区三区| 亚洲图片另类小说| 秋霞午夜av一区二区三区| 亚洲精品一区二区三区樱花| 亚州欧美在线| 精品中文字幕在线| 日本美女一级片| 色偷偷成人一区二区三区91| 欧美性受xxxx黑人| 国产一区二区在线免费观看| 中文精品无码中文字幕无码专区 | 午夜精品视频在线观看一区二区 | 国产精品.com| 国产高清自产拍av在线| 国产亚洲xxx| 一级黄色片在线播放| 一二三四社区欧美黄| 欧美成人三级伦在线观看| 巨乳诱惑日韩免费av| 亚洲成人一区二区三区| 日本精品国产| 97碰在线观看| 成人在线免费公开观看视频| 7777精品伊人久久久大香线蕉经典版下载| 黄色一级片中国| 99国产精品一区| 91制片厂毛片| 国内在线观看一区二区三区| 欧美精品七区| 中文字幕日本一区| 午夜免费久久久久| 日本暖暖在线视频| 亚洲成年人影院在线| 日日夜夜狠狠操| 中文字幕综合网| 菠萝菠萝蜜网站| 看片网站欧美日韩| 成人免费aaa| 91综合网人人| 久久青青草综合| 欧美专区视频| 国产97在线|亚洲| 羞羞视频在线观看免费| 在线观看日韩专区| 天天摸天天干天天操| 欧美精品九九99久久| 国产区在线观看视频| 亚洲私人影院在线观看| 精品人妻无码一区二区三区换脸| 国产精品一区二区三区四区| 少妇一级淫免费放| 美女尤物久久精品| 久久综合九色综合88i| 888久久久| 午夜精品一区二区在线观看| 国产主播性色av福利精品一区| 国产日韩欧美综合| 精品欧美一区二区三区在线观看 | 欧美舌奴丨vk视频| 欧美精品成人在线| 综合图区亚洲| 日韩性生活视频| 三级av在线| 亚洲成人在线视频播放| 99热这里只有精品在线观看| 欧美在线免费观看视频| 久久久午夜影院| 亚洲国产乱码最新视频 | 午夜精品视频一区| 综合五月激情网| 亚洲欧洲三级电影| 女人黄色一级片| 国产亚洲综合在线| 亚洲最大成人网站| 久久久久高清精品| 精品少妇一区二区三区免费观| 不卡免费追剧大全电视剧网站| 中文字幕一二三区| 国产麻豆精品theporn| 网站在线你懂的| 精品一区二区在线看| 色噜噜狠狠一区二区| 蜜臀av性久久久久蜜臀av麻豆| 国产黄色片免费在线观看| 国产精品扒开腿做爽爽爽软件| 国产卡一卡二在线| 一区二区在线| 久久福利一区二区| 国产精品jizz在线观看美国| 欧美黄色免费网址| 欧美午夜不卡影院在线观看完整版免费| 国产精品一二三在线观看| 中文字幕一区二区三区乱码图片| 亚洲小说欧美另类激情| 午夜激情一区| www.男人天堂网| 国产偷自视频区视频一区二区| 国产又黄又猛视频| 奇米影视一区二区三区小说| 天天操天天干天天做| 国产在线精品一区二区三区不卡| 国产精品熟女一区二区不卡| 国产成人免费在线视频| 国产精品麻豆入口| 久久美女艺术照精彩视频福利播放| 久久精品—区二区三区舞蹈| 国产精品伦一区二区三级视频| 天天做夜夜爱爱爱| 亚洲五码中文字幕| 久久久久女人精品毛片九一| 欧美在线一区二区| www.日韩高清| 日韩成人在线视频观看| 福利视频在线导航| 久久色免费在线视频| 97超碰免费在线| 国产成人精品日本亚洲专区61| jvid一区二区三区| av成人午夜| 九一亚洲精品| 成年人黄色在线观看| 精品1区2区3区4区| 男女啪啪网站视频| 国产乱色国产精品免费视频| 真人bbbbbbbbb毛片| 欧美激情一区二区在线| 欧美高清视频一区二区三区| 欧美性xxxx在线播放| 一区二区三区www污污污网站| 精品久久一区二区三区| 国产在线黄色| 欧美男插女视频| 亚洲成av在线| aa成人免费视频| 日韩国产专区| 国产av人人夜夜澡人人爽麻豆| 日本vs亚洲vs韩国一区三区| 亚洲欧美日韩色| 国产精品家庭影院| 欧美一级视频免费观看| 欧美一卡二卡三卡| 毛片在线能看| 久久久久久久电影一区| 久久精品xxxxx| 精品免费视频123区| 亚洲国产老妈| 狠狠热免费视频| 成人动漫精品一区二区| 国产一二三av| 一本大道av一区二区在线播放| www.香蕉视频| www高清在线视频日韩欧美| 亚洲伊人av| 国产精选一区二区| 91精品啪在线观看国产81旧版| 欧美黄网站在线观看| 粉嫩高潮美女一区二区三区| 999精品在线视频| 欧洲国内综合视频| 日韩二区三区| 韩国v欧美v日本v亚洲| 国产一区二区三区免费观看在线 | 成人欧美一区二区三区黑人| 亚洲国产最新| 极品粉嫩国产18尤物| 国产精品123| 三级在线观看免费大全| 欧美日韩一区二区三区四区 | 久久精子c满五个校花| 日本三级网站在线观看| 欧美成人一区二区三区片免费| 在线观看黄色av| 国产精品v日韩精品| 免费电影一区二区三区| 777久久久精品一区二区三区| 成人av电影在线网| 久久久国产精品人人片| 欧美一区二区免费视频| 黄色免费在线看| 成人午夜激情网| 国产精品传媒精东影业在线| 亚欧激情乱码久久久久久久久| 国产女人18毛片水真多成人如厕 | 欧美嫩在线观看| www.av在线播放| 国产免费久久av| 四季av一区二区三区免费观看| www.com操| 国产精品久久精品日日| 国产巨乳在线观看| 美女精品久久久| 超碰97成人| 日本欧美黄色片| 久久色在线视频| 亚洲成人av网址| 日韩亚洲综合在线| 国产亚洲字幕| 日韩精品在线视频免费观看| 成人不卡免费av| 中文字幕一区在线播放| 亚洲深夜福利在线| 成人在线免费av| 大桥未久一区二区| 成人一区二区视频| 国产在线视频你懂的| 亚洲国产三级网| 欧美日韩免费观看视频| 一级做a爰片久久| 国产福利精品导航| 国产一级18片视频| 色哟哟入口国产精品| www.亚洲一二| 十八禁视频网站在线观看| 国产精品美女久久久久久久久| 国产av一区二区三区| 91精品国产高清久久久久久久久| 欧美激情在线精品一区二区三区| 亚洲 国产 图片| 亚洲丰满少妇videoshd| 国产在线一二三| 99久久伊人精品影院| 久久精品亚洲| 丁香花五月激情| 亚洲免费视频在线观看| 伊人国产精品| 337p粉嫩大胆噜噜噜鲁| 欧美国产一区二区在线观看| 精品人妻一区二区三区麻豆91| 2019日本中文字幕| 99久久www免费| a级一a一级在线观看| 欧美日韩一区二区三区在线 | 99久久亚洲精品蜜臀| 日韩Av无码精品| 欧美精品久久一区| 在线观看欧美日韩电影| 日本国产中文字幕| 国产午夜精品久久久久久久 |