<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      分布式鎖2 Java非常用技術(shù)方案探討之ZooKeeper

      前言:

            由于在平時(shí)的工作中,線上服務(wù)器是分布式多臺(tái)部署的,經(jīng)常會(huì)面臨解決分布式場景下數(shù)據(jù)一致性的問題,那么就要利用分布式鎖來解決這些問題。以自己結(jié)合實(shí)際工作中的一些經(jīng)驗(yàn)和網(wǎng)上看到的一些資料,做一個(gè)講解和總結(jié)。之前我已經(jīng)寫了一篇關(guān)于分布式鎖的文章: 分布式鎖1 Java常用技術(shù)方案 。上一篇文章中主要寫的是在日常項(xiàng)目中,較為常見的幾種實(shí)現(xiàn)分布式鎖的方法。通過這些方法,基本上可以解決我們?nèi)粘9ぷ髦写蟛糠謭鼍跋率褂梅植际芥i的問題。

            本篇文章主要是在上一篇文章的基礎(chǔ)上,介紹一些雖然日常工作中不常用或者比較實(shí)現(xiàn)起來比較重,但是可以作為技術(shù)方案學(xué)習(xí)了解一下的分布式鎖方案。希望這篇文章可以方便自己以后查閱,同時(shí)要是能幫助到他人那也是很好的。

       

      ===============================================================長長的分割線====================================================================

       

      正文:

            第一步,使用zookeeper節(jié)點(diǎn)名稱唯一性,用于分布式鎖:

            關(guān)于zookeeper集群的搭建,可以參考我之前寫的一篇文章: ZooKeeper1 利用虛擬機(jī)搭建自己的ZooKeeper集群

            zookeeper抽象出來的節(jié)點(diǎn)結(jié)構(gòu)是一個(gè)和文件系統(tǒng)類似的小型的樹狀的目錄結(jié)構(gòu),同時(shí)zookeeper機(jī)制規(guī)定:同一個(gè)目錄下只能有一個(gè)唯一的文件名。例如:我們?cè)趜ookeeper的根目錄下,由兩個(gè)客戶端同時(shí)創(chuàng)建一個(gè)名為/myDistributeLock,只有一個(gè)客戶端可以成功。

            上述方案和memcached的add()方法、redis的setnx()方法實(shí)現(xiàn)分布式鎖有著相同的思路。這樣的方案實(shí)現(xiàn)起來如果不考慮搭建和維護(hù)zookeeper集群的成本,由于正確性和可靠性是zookeeper機(jī)制自己保證的,實(shí)現(xiàn)還是比較簡單的。

            

          第二步,使用zookeeper臨時(shí)順序節(jié)點(diǎn),用于分布式鎖:

       

            在討論這套方案之前,我們有必要先“吹毛求疵”般的說明一下使用zookeeper節(jié)點(diǎn)名稱唯一性來做分布式鎖這個(gè)方案的缺點(diǎn)。比如,當(dāng)許多線程在等待一個(gè)鎖時(shí),如果鎖得到釋放的時(shí)候,那么所有客戶端都被喚醒,但是僅僅有一個(gè)客戶端得到鎖。在這個(gè)過程中,大量的線程根本沒有獲得鎖的可能性,但是也會(huì)引起大量的上下文切換,這個(gè)系統(tǒng)開銷也是不小的,對(duì)于這樣的現(xiàn)象有一個(gè)專業(yè)名詞,稱之為“驚群效應(yīng)”。

           我們首先說明一下zookeeper的順序節(jié)點(diǎn)、臨時(shí)節(jié)點(diǎn)和watcher機(jī)制:

           所謂順序節(jié)點(diǎn),假如我們?cè)?myDisLocks/目錄下創(chuàng)建3個(gè)節(jié)點(diǎn),zookeeper集群會(huì)按照發(fā)起創(chuàng)建的順序來創(chuàng)建節(jié)點(diǎn),節(jié)點(diǎn)分別為/myDisLocks/0000000001、/myDisLocks/0000000002、/myDisLocks/0000000003。

           所謂臨時(shí)節(jié)點(diǎn),臨時(shí)節(jié)點(diǎn)由某個(gè)客戶端創(chuàng)建,當(dāng)客戶端與zookeeper集群斷開連接,則該節(jié)點(diǎn)自動(dòng)被刪除。

           所謂對(duì)于watcher機(jī)制,大家可以參考Apache ZooKeeper Watcher機(jī)制源碼解釋。當(dāng)然如果你之前不知道watcher機(jī)制是個(gè)什么東東,不建議你直接去看前邊我提供的文章鏈接,這樣你極有可能忘掉我們的討論主線,即分布式鎖的實(shí)現(xiàn)方案,而陷入到watcher機(jī)制的源碼實(shí)現(xiàn)中。所以你也可以先看看下面的具體方案,猜測一下watcher是用來干嘛的,我這里先總結(jié)一句話做個(gè)引子: 所謂watcher機(jī)制,你可以簡單一點(diǎn)兒理解成任何一個(gè)連接zookeeper的客戶端可以通過watcher機(jī)制關(guān)注自己感興趣的節(jié)點(diǎn)的增刪改查,當(dāng)這個(gè)節(jié)點(diǎn)發(fā)生增刪改查的操作時(shí),會(huì)“廣播”自己的消息,所有對(duì)此感興趣的節(jié)點(diǎn)可以在收到這些消息后,根據(jù)自己的業(yè)務(wù)需要執(zhí)行后續(xù)的操作。

           具體的使用步驟如下:

            1. 每個(gè)業(yè)務(wù)線程調(diào)用create()方法創(chuàng)建名為“/myDisLocks/thread”的節(jié)點(diǎn),需要注意的是,這里節(jié)點(diǎn)的創(chuàng)建類型需要設(shè)置為EPHEMERAL_SEQUENTIAL,即節(jié)點(diǎn)類型為臨時(shí)順序節(jié)點(diǎn)。此時(shí)/myDisLocks節(jié)點(diǎn)下會(huì)出現(xiàn)諸如/myDisLocks/thread0000000001、/myDisLocks/thread0000000002、/myDisLocks/thread0000000003這樣的子節(jié)點(diǎn)。

           2. 每個(gè)業(yè)務(wù)線程調(diào)用getChildren(“myDisLocks”)方法來獲取/myDisLocks這個(gè)節(jié)點(diǎn)下所有已經(jīng)創(chuàng)建的子節(jié)點(diǎn)。

            3. 每個(gè)業(yè)務(wù)線程獲取到所有子節(jié)點(diǎn)的路徑之后,如果發(fā)現(xiàn)自己在步驟1中創(chuàng)建的節(jié)點(diǎn)的尾綴編號(hào)是所有節(jié)點(diǎn)中序號(hào)最小的,那么就認(rèn)為自己獲得了鎖。

            4. 如果在步驟3中發(fā)現(xiàn)自己并非是所有子節(jié)點(diǎn)中序號(hào)最小的,說明自己還沒有獲取到鎖。使用watcher機(jī)制監(jiān)視比自己創(chuàng)建節(jié)點(diǎn)的序列號(hào)小的節(jié)點(diǎn)(比自己創(chuàng)建的節(jié)點(diǎn)小的最大節(jié)點(diǎn)),進(jìn)入等待。比如,如果當(dāng)前業(yè)務(wù)線程創(chuàng)建的節(jié)點(diǎn)是/myDisLocks/thread0000000003,那么在沒有獲取到鎖的情況下,他只需要監(jiān)視/myDisLocks/thread0000000002的情況。只有當(dāng)/myDisLocks/thread0000000002獲取到鎖并釋放之后,當(dāng)前業(yè)務(wù)線程才啟動(dòng)獲取鎖,這樣可以避免一個(gè)業(yè)務(wù)線程釋放鎖之后,其他所有線程都去競爭鎖,引起不必要的上下文切換,最終造成“驚群現(xiàn)象”。

           5. 釋放鎖的過程相對(duì)比較簡單,就是刪除自己創(chuàng)建的那個(gè)子節(jié)點(diǎn)即可。

            注意: 這個(gè)方案實(shí)現(xiàn)的分布式鎖還帶著一點(diǎn)兒公平鎖的味道!為什么呢?我們?cè)诶妹總€(gè)節(jié)點(diǎn)的序號(hào)進(jìn)行排隊(duì)以此來避免進(jìn)群現(xiàn)象時(shí),實(shí)際上所有業(yè)務(wù)線程獲得鎖的順序就是自己創(chuàng)建節(jié)點(diǎn)的順序,也就是哪個(gè)業(yè)務(wù)線程先來,哪個(gè)就可以最快獲得鎖。

            下面貼出我自己實(shí)現(xiàn)的上述方案的代碼:

            1. 代碼中有兩個(gè)Java類: MyDistributedLockByZK.java和LockWatcher.java。其中MyDistributedLockByZK.java中的main函數(shù)利用線程池啟動(dòng)5個(gè)線程,以此來模擬多個(gè)業(yè)務(wù)線程競爭鎖的情況;而LockWatcher.java定義分布式鎖和實(shí)現(xiàn)了watcher機(jī)制。

            2. 同時(shí),我使用的zookeeper集群是自己以前利用VMWare搭建的集群,所以zookeeper鏈接是192.168.224.170:2181,大家可以根據(jù)替換成自己的zookeeper鏈接即可。

       1 public class MyDistributedLockByZK {
       2     /** 線程池 **/
       3     private static ExecutorService executorService = null;
       4     private static final int THREAD_NUM = 5;
       5     private static int threadNo = 0;
       6     private static CountDownLatch threadCompleteLatch = new CountDownLatch(THREAD_NUM);
       7     
       8     /** ZK的相關(guān)配置常量 **/
       9     private static final String CONNECTION_STRING = "192.168.224.170:2181";
      10     private static final int SESSION_TIMEOUT = 10000;
      11     // 此變量在LockWatcher中也有一個(gè)同名的靜態(tài)變量,正式使用的時(shí)候,提取到常量類中共同維護(hù)即可。
      12     private static final String LOCK_ROOT_PATH = "/myDisLocks";
      13     
      14     public static void main(String[] args) {
      15         // 定義線程池
      16         executorService = Executors.newFixedThreadPool(THREAD_NUM, new ThreadFactory() {
      17             @Override
      18             public Thread newThread(Runnable r) {
      19                 String name = String.format("第[%s]個(gè)測試線程", ++threadNo);
      20                 Thread ret = new Thread(Thread.currentThread().getThreadGroup(), r, name, 0);
      21                 ret.setDaemon(false);
      22                 return ret;
      23             }
      24         });
      25         
      26         // 啟動(dòng)線程
      27         if (executorService != null) {
      28             startProcess();
      29         }
      30     }
      31     
      32     /**
      33      * @author zhangyi03
      34      * @date 2017-5-23 下午5:57:27
      35      * @description 模擬并發(fā)執(zhí)行任務(wù)
      36      */
      37      public static void startProcess() {    
      38         Runnable disposeBusinessRunnable= new Thread(new Runnable() {
      39             public void run() {
      40                 String threadName = Thread.currentThread().getName();
      41                 
      42                 LockWatcher lock = new LockWatcher(threadCompleteLatch);
      43                 try {
      44                     /** 步驟1: 當(dāng)前線程創(chuàng)建ZK連接  **/
      45                     lock.createConnection(CONNECTION_STRING, SESSION_TIMEOUT);
      46                     
      47                     /** 步驟2: 創(chuàng)建鎖的根節(jié)點(diǎn)  **/
      48                     // 注意,此處創(chuàng)建根節(jié)點(diǎn)的方式其實(shí)完全可以在初始化的時(shí)候由主線程單獨(dú)進(jìn)行根節(jié)點(diǎn)的創(chuàng)建,沒有必要在業(yè)務(wù)線程中創(chuàng)建。
      49                     // 這里這樣寫只是一種思路而已,不必局限于此
      50                     synchronized (MyDistributedLockByZK.class){
      51                         lock.createPersistentPath(LOCK_ROOT_PATH, "該節(jié)點(diǎn)由" + threadName + "創(chuàng)建", true);
      52                     }
      53                     
      54                     /** 步驟3: 開啟鎖競爭并執(zhí)行任務(wù) **/
      55                     lock.getLock();
      56                 } catch (Exception e) {
      57                     e.printStackTrace();
      58                 } 
      59             }  
      60         });
      61         
      62         for (int i = 0; i < THREAD_NUM; i++) {
      63             executorService.execute(disposeBusinessRunnable);
      64         }
      65         executorService.shutdown();
      66         
      67         try {
      68             threadCompleteLatch.await();
      69             System.out.println("所有線程運(yùn)行結(jié)束!");
      70         } catch (InterruptedException e) {
      71             e.printStackTrace();
      72         }
      73      }
      74 }
        1 public class LockWatcher implements Watcher {
        2     /** 成員變量 **/
        3     private ZooKeeper zk = null;
        4     // 當(dāng)前業(yè)務(wù)線程競爭鎖的時(shí)候創(chuàng)建的節(jié)點(diǎn)路徑
        5     private String selfPath = null;
        6     // 當(dāng)前業(yè)務(wù)線程競爭鎖的時(shí)候創(chuàng)建節(jié)點(diǎn)的前置節(jié)點(diǎn)路徑
        7     private String waitPath = null;
        8     // 確保連接zk成功;只有當(dāng)收到Watcher的監(jiān)聽事件之后,才執(zhí)行后續(xù)的操作,否則請(qǐng)求阻塞在createConnection()創(chuàng)建ZK連接的方法中
        9     private CountDownLatch connectSuccessLatch = new CountDownLatch(1);
       10     // 標(biāo)識(shí)線程是否執(zhí)行完任務(wù)
       11     private CountDownLatch threadCompleteLatch = null;
       12     
       13     /** ZK的相關(guān)配置常量 **/
       14     private static final String LOCK_ROOT_PATH = "/myDisLocks";
       15     private static final String LOCK_SUB_PATH = LOCK_ROOT_PATH + "/thread";
       16     
       17     public LockWatcher(CountDownLatch latch) {
       18         this.threadCompleteLatch = latch;
       19     }
       20     
       21     @Override
       22     public void process(WatchedEvent event) {
       23         if (event == null) {
       24             return;
       25         }
       26         
       27         // 通知狀態(tài)
       28         Event.KeeperState keeperState = event.getState();
       29         // 事件類型
       30         Event.EventType eventType = event.getType();
       31         
       32         // 根據(jù)通知狀態(tài)分別處理
       33         if (Event.KeeperState.SyncConnected == keeperState) {
       34             if ( Event.EventType.None == eventType ) {
       35                 System.out.println(Thread.currentThread().getName() + "成功連接上ZK服務(wù)器");
       36                 // 此處代碼的主要作用是用來輔助判斷當(dāng)前線程確實(shí)已經(jīng)連接上ZK
       37                 connectSuccessLatch.countDown();
       38             }else if (event.getType() == Event.EventType.NodeDeleted && event.getPath().equals(waitPath)) {
       39                 System.out.println(Thread.currentThread().getName() + "收到情報(bào),排我前面的家伙已掛,我準(zhǔn)備再次確認(rèn)我是不是最小的節(jié)點(diǎn)?。?);
       40                 try {
       41                     if(checkMinPath()){
       42                         getLockSuccess();
       43                     }
       44                 } catch (Exception e) {
       45                     e.printStackTrace();
       46                 } 
       47             }
       48         } else if ( Event.KeeperState.Disconnected == keeperState ) {
       49             System.out.println(Thread.currentThread().getName() + "與ZK服務(wù)器斷開連接");
       50         } else if ( Event.KeeperState.AuthFailed == keeperState ) {
       51             System.out.println(Thread.currentThread().getName() + "權(quán)限檢查失敗");
       52         } else if ( Event.KeeperState.Expired == keeperState ) {
       53             System.out.println(Thread.currentThread().getName() + "會(huì)話失效");
       54         }
       55     }
       56     
       57      /**
       58      * @author zhangyi03
       59      * @date 2017-5-23 下午6:07:03
       60      * @description 創(chuàng)建ZK連接
       61      * @param connectString ZK服務(wù)器地址列表
       62      * @param sessionTimeout Session超時(shí)時(shí)間
       63      * @throws IOException
       64      * @throws InterruptedException
       65      */
       66     public void createConnection(String connectString, int sessionTimeout) throws IOException, InterruptedException {
       67         zk = new ZooKeeper(connectString, sessionTimeout, this);
       68         // connectSuccessLatch.await(1, TimeUnit.SECONDS) 正式實(shí)現(xiàn)的時(shí)候可以考慮此處是否采用超時(shí)阻塞
       69         connectSuccessLatch.await();
       70     }
       71     
       72     /**
       73      * @author zhangyi03
       74      * @date 2017-5-23 下午6:15:48
       75      * @description 創(chuàng)建ZK節(jié)點(diǎn)
       76      * @param path 節(jié)點(diǎn)path
       77      * @param data 初始數(shù)據(jù)內(nèi)容
       78      * @param needWatch
       79      * @return
       80      * @throws KeeperException
       81      * @throws InterruptedException
       82      */
       83     public boolean createPersistentPath(String path, String data, boolean needWatch) throws KeeperException, InterruptedException {
       84         if(zk.exists(path, needWatch) == null){
       85             String result = zk.create( path,data.getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
       86             System.out.println(Thread.currentThread().getName() + "創(chuàng)建節(jié)點(diǎn)成功, path: " + result + ", content: " + data);
       87         }
       88         return true;
       89     }
       90     
       91     /**
       92      * @author zhangyi03
       93      * @date 2017-5-23 下午6:24:46
       94      * @description 獲取分布式鎖
       95      * @throws KeeperException
       96      * @throws InterruptedException
       97      */
       98      public void getLock() throws Exception {
       99         selfPath = zk.create(LOCK_SUB_PATH, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
      100         System.out.println(Thread.currentThread().getName() + "創(chuàng)建鎖路徑:" + selfPath);
      101         if(checkMinPath()){
      102             getLockSuccess();
      103         }
      104      }
      105      
      106      /**
      107      * @author zhangyi03
      108      * @date 2017-5-23 下午7:02:41
      109      * @description 獲取鎖成功
      110      * @throws KeeperException
      111      * @throws InterruptedException
      112      */
      113     private void getLockSuccess() throws KeeperException, InterruptedException {
      114          if(zk.exists(selfPath, false) == null){
      115              System.err.println(Thread.currentThread().getName() + "本節(jié)點(diǎn)已不在了...");
      116              return;
      117          }
      118          System.out.println(Thread.currentThread().getName() + "獲取鎖成功,開始處理業(yè)務(wù)數(shù)據(jù)!");
      119          Thread.sleep(2000);
      120          System.out.println(Thread.currentThread().getName() + "處理業(yè)務(wù)數(shù)據(jù)完成,刪除本節(jié)點(diǎn):" + selfPath);
      121          zk.delete(selfPath, -1);
      122          releaseConnection();
      123          threadCompleteLatch.countDown();
      124      }
      125 
      126      /**
      127      * @author zhangyi03
      128      * @date 2017-5-23 下午7:06:46
      129      * @description 關(guān)閉ZK連接
      130      */
      131     private void releaseConnection() {
      132         if (zk != null) {
      133             try {
      134                 zk.close();
      135             } catch (InterruptedException e) {
      136                 e.printStackTrace();
      137             }
      138         }
      139         System.out.println(Thread.currentThread().getName() + "釋放ZK連接");
      140      }
      141 
      142      /**
      143      * @author zhangyi03
      144      * @date 2017-5-23 下午6:57:14
      145      * @description 檢查自己是不是最小的節(jié)點(diǎn)
      146      * @param selfPath
      147      * @return
      148      * @throws KeeperException
      149      * @throws InterruptedException
      150      */
      151     private boolean checkMinPath() throws Exception {
      152           List<String> subNodes = zk.getChildren(LOCK_ROOT_PATH, false);
      153           // 根據(jù)元素按字典序升序排序
      154           Collections.sort(subNodes);
      155           System.err.println(Thread.currentThread().getName() + "創(chuàng)建的臨時(shí)節(jié)點(diǎn)名稱:" + selfPath.substring(LOCK_ROOT_PATH.length()+1));
      156           int index = subNodes.indexOf(selfPath.substring(LOCK_ROOT_PATH.length()+1));
      157           System.err.println(Thread.currentThread().getName() + "創(chuàng)建的臨時(shí)節(jié)點(diǎn)的index:" + index);
      158           switch (index){
      159               case -1: {
      160                   System.err.println(Thread.currentThread().getName() + "創(chuàng)建的節(jié)點(diǎn)已不在了..." + selfPath);
      161                   return false;
      162               }
      163               case 0:{
      164                   System.out.println(Thread.currentThread().getName() +  "子節(jié)點(diǎn)中,我果然是老大" + selfPath);
      165                   return true;
      166               }
      167               default:{
      168                   // 獲取比當(dāng)前節(jié)點(diǎn)小的前置節(jié)點(diǎn),此處只關(guān)注前置節(jié)點(diǎn)是否還在存在,避免驚群現(xiàn)象產(chǎn)生
      169                   waitPath = LOCK_ROOT_PATH +"/"+ subNodes.get(index - 1);
      170                   System.out.println(Thread.currentThread().getName() + "獲取子節(jié)點(diǎn)中,排在我前面的節(jié)點(diǎn)是:" + waitPath);
      171                   try {
      172                       zk.getData(waitPath, true, new Stat());
      173                       return false;
      174                   } catch (Exception e) {
      175                       if (zk.exists(waitPath, false) == null) {
      176                           System.out.println(Thread.currentThread().getName() + "子節(jié)點(diǎn)中,排在我前面的" + waitPath + "已失蹤,該我了");
      177                           return checkMinPath();
      178                       } else {
      179                           throw e;
      180                       }
      181                   }
      182               }
      183                   
      184           }
      185      }
      186 }

       

             第三步,使用memcached的cas()方法,用于分布式鎖:

             下篇文章我們?cè)偌?xì)說!

       

             第四步,使用redis的watch、multi、exec命令,用于分布式鎖:

             下篇文章我們?cè)偌?xì)說!

       

             第五步,總結(jié):

            綜上,對(duì)于分布式鎖這些非常用或者實(shí)現(xiàn)起來比較重的方案,大家可以根據(jù)自己在項(xiàng)目中的需要,酌情使用。最近在和別人討論的過程中,以及我的第一篇關(guān)于分布式鎖的文章分布式鎖1 Java常用技術(shù)方案  大家的回復(fù)中,總結(jié)來看,對(duì)于用redis實(shí)現(xiàn)分布式鎖確實(shí)存在著比較多的細(xì)節(jié)問題可以進(jìn)行深入討論,歡迎大家留言,相互學(xué)習(xí)。

            忍不住嘚瑟一下,我媳婦兒此刻在我旁邊看AbstractQueuedSynchronizer,厲害吧?!,一會(huì)兒出去吃飯,哈哈~

       

            第六步,線上使用補(bǔ)充篇:

             截止到2017.08.25(周五),使用上述文章中的”臨時(shí)節(jié)點(diǎn)+watcher機(jī)制方案”解決一個(gè)分布式鎖的問題時(shí),最終發(fā)現(xiàn)在實(shí)現(xiàn)過程中,由于watcher機(jī)制類似于通知等待機(jī)制的特點(diǎn),如果主線程在經(jīng)歷“獲取鎖操作”、“處理業(yè)務(wù)代碼”、“釋放鎖操作”這三步的過程中,使用watcher機(jī)制阻塞的獲取鎖時(shí),會(huì)導(dǎo)致根本無法將獲取鎖結(jié)果返回給主線程,而在實(shí)際的時(shí)候過程中,一般情況下主線程在“獲取鎖操作”時(shí)都希望可以同步獲得一個(gè)返回值。

            所以,上述的”臨時(shí)節(jié)點(diǎn)+watcher機(jī)制方案”從技術(shù)方案角度足夠完美,但是在實(shí)際使用過程中,個(gè)人覺得還不是特別的方便。

            

            轉(zhuǎn)載請(qǐng)注明來自博客園http://www.rzrgm.cn/PurpleDream/p/5573040.html ,版權(quán)歸本人和博客園所有,謝謝!

       

      posted on 2017-06-03 19:11  Sky_YiBai  閱讀(4356)  評(píng)論(10)    收藏  舉報(bào)

      主站蜘蛛池模板: 国产69精品久久久久乱码免费| 99精品国产成人一区二区| 在线精品亚洲区一区二区| 韩国无码av片在线观看| 九九热在线精品视频观看| 亚洲精品人成网线在播放VA | 亚洲美免无码中文字幕在线| 亚洲综合色丁香婷婷六月图片| 久久精品国产久精国产果冻传媒 | 东京热无码av男人的天堂| 日本韩无专砖码高清观看| 欧美一级黄色影院| 超碰成人人人做人人爽| 草草浮力影院| 亚洲免费成人av一区| 久久综合色之久久综合色| 久久中文字幕av第二页| 女人高潮流白浆视频| 亚洲国产一区二区三区| 亚洲一区精品视频在线| 激情综合色综合啪啪开心| 五月婷婷深开心五月天| 武装少女在线观看高清完整版免费| h无码精品3d动漫在线观看| 国产又色又爽又黄的在线观看| 国产剧情视频一区二区麻豆| 黑人大战中国av女叫惨了| 92国产精品午夜福利免费| 一区二区三区av天堂| 九九色这里只有精品国产| 亚洲av无码专区在线厂| 日韩精品人妻黄色一级片| 国产成人亚洲综合| 国产精品亚洲mnbav网站| 在线播放亚洲成人av| 亚洲最大成人在线播放| 国产精品亚洲第一区在线| 国产在线精品一区二区中文| 国产色婷婷亚洲99精品小说| 亚洲 a v无 码免 费 成 人 a v| 日韩 一区二区在线观看|