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

開發實戰:使用Redisson實現分布式延時消息,訂單30分鐘關閉的另外一種實現!

開發 前端
調用 comsume() 之后,如果隊列里沒有消息,會阻塞等待隊列里有消息并且取到了才會返回。之所以這么說是因為可能有別的 Java 進程也在跟你一樣取同一個隊列里的消息,如果消息被另一個搶完了,那這時就還得阻塞等待。

前言

因為工作中需要用到分布式的延時隊列,調研了一段時間,選擇使用 Redisson DelayedQueue,為了搞清楚內部運行流程,特記錄下來。

總體流程大概是圖中的這個樣子,初看一眼有點不知從何下手,接下來我會通過以下幾點來分析流程,相信看完本文你能了解整個運行流程。

  • 基本使用
  • 內部數據結構介紹
  • 基本流程
  • 發送延時消息
  • 獲取延時消息
  • 初始化延時隊列

圖片圖片

基本使用

發送延遲消息代碼如下,發送了一條延遲時間為 5s 的消息。

public void produce() {
  String queuename = "delay-queue";
  RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(queuename);
  RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
  delayedQueue.offer("測試延遲消息", 5, TimeUnit.SECONDS);
}

接收消息代碼如下,可以看到 delayedQueue 是沒有用到的,那么為什么要加這一行呢,這個后面總結部分回答。

public void consume() throws InterruptedException {
 String queuename = "delay-queue";
  RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(queuename);
  RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
  String msg = blockingQueue.take();
  //收到消息進行處理...
}

這兩段代碼可以寫在兩個不同的 Java 工程里,只要連接的是同一個 Redis 就行。

調用 comsume() 之后,如果隊列里沒有消息,會阻塞等待隊列里有消息并且取到了才會返回。之所以這么說是因為可能有別的 Java 進程也在跟你一樣取同一個隊列里的消息,如果消息被另一個搶完了,那這時就還得阻塞等待。

這時看上去的原理是這樣的:

生產者調用 offer() 后,自己內部開啟一個定時器,等到了時間在發送到 redis 的 list 里。

圖片圖片


如果是這樣設計的話,相信大家都能看出來一個很簡單的問題,要是延時時間還沒到,生產者自己掛了,那樣消息就丟了。所以,還是讓我們接著往下看。

內部數據結構介紹

redisson 源碼里一共創建了三個隊列:【消息延時隊列】、【消息順序隊列】、【消息目標隊列】。

圖片圖片


假設在同一時間按照 msg1、msg2、msg3 的順序發消息到延時隊列,這三條消息就會被保存在【消息延時隊列】和【消息順序隊列】。

可以看到【消息延時隊列】的順序是按照到期時間升序排列的,而不是像【消息順序隊列】按照插入順序排。

消息到期后會將消息從前兩個隊列移除(怎么移?誰來移?),插入【消息目標隊列】,也就是圖中第三個隊列。

消費者也是阻塞在【消息目標隊列】上取消息。

這時可以簡單說明下每個隊列的作用:

  • 【消息延時隊列】利用按照到期時間排序的特性,可以很快找到下一個要到期的消息,客戶端內部自己定時到【消息目標隊列】取
  • 【消息順序隊列】這個隊列對分析的流程關聯不大,可以忽略
  • 【消息目標隊列】存放到期的消息,供消費端取

其實【消息延時隊列】隊列里存的時間(也就是 zet 的 score)是到期的時間戳,為了畫圖方便,圖里就畫的是延遲的時間,不過不影響理解。

理解好這幾個隊列的名字和作用,后面還會一直用到,如果忘了可以翻回來回顧下。

因為書寫理解方便和【消息順序隊列】在本文沒涉及到,后面部分好幾次提到的內容:把到期的消息從【消息延時隊列】移到【消息目標隊列】里,這句話實際的代碼邏輯是這樣:把【消息延時隊列】和【消息順序隊列】里的到期消息移除,把它們插入到【消息目標隊列】。

基本流程

知道了內部所使用到的數據結構后,這里可以簡單說下整體的基本流程。

先說發送延遲消息,發送的延遲消息會先存在【消息延時隊列】和【消息順序隊列】,如果【消息延時隊列】原本是空的,會發布訂閱信息提醒有新的消息。

獲取延遲消息只需要從【消息目標隊列】阻塞的取就行了,因為里面都是到期數據。

那么問題就只剩下怎么樣判斷時間到了,把【消息延時隊列】里的消息移動到【消息目標隊列】里呢?

這部分工作交給了初始化延時隊列來處理。

這里面會定時從【消息延時隊列】查詢最新到期時間,定時去把【消息延時隊列】里的消息移動到【消息目標隊列】里。

如果【消息延時隊列】是空的,就不會再定時查,而是等待發布訂閱信息提醒,再定時把【消息延時隊列】里的消息移動到【消息目標隊列】里。

剛開始看可能有點抽象,可以看完底下一節內容之后,再回頭來看這里對應的流程總結,可能會比較清晰。

發送延時消息

發送延時消息的邏輯比較簡單,先看下發送的代碼。

public void produce() {
  String queuename = "delay-queue";
  RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(queuename);
  RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
  delayedQueue.offer("測試延遲消息", 5, TimeUnit.SECONDS);
}

從 delayedQueue.offer 方法開始,最終會執行到 RedissonDelayedQueue 的 offerAsync 方法里。

offerAsync 方法的作用就是發送一段腳本給 redis 執行,腳本內容是:

  1. 將消息和到期時間插入【消息延時隊列】和【消息順序隊列】
  2. 如果最近到期的消息是剛剛插入的消息,則對指定主題發布到期時間,目的是為了讓客戶端定時去把【消息延時隊列】里的到期數據移動到【消息目標隊列】
@Override
public RFuture<Void> offerAsync(V e, long delay, TimeUnit timeUnit) {
  if (delay < 0) {
   throw new IllegalArgumentException("Delay can't be negative");
  }

  long delayInMs = timeUnit.toMillis(delay);
  long timeout = System.currentTimeMillis() + delayInMs;

  long randomId = ThreadLocalRandom.current().nextLong();
  return commandExecutor.evalWriteNoRetryAsync(getRawName(), codec, RedisCommands.EVAL_VOID,
  "local value = struct.pack('dLc0', tonumber(ARGV[2]), string.len(ARGV[3]), ARGV[3]);" 
  + "redis.call('zadd', KEYS[2], ARGV[1], value);"
  + "redis.call('rpush', KEYS[3], value);"
  // if new object added to queue head when publish its startTime 
  // to all scheduler workers 
  + "local v = redis.call('zrange', KEYS[2], 0, 0); "
  + "if v[1] == value then "
  + "redis.call('publish', KEYS[4], ARGV[1]); "
  + "end;",
  Arrays.<Object>asList(getRawName(), timeoutSetName, queueName, channelName),
  timeout, randomId, encode(e));
}

獲取延時消息

獲取延時消息是本文最簡單的一部分。

public void consume() throws InterruptedException {
  String queuename = "delay-queue";
  RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(queuename);
  RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
  String msg = blockingQueue.take();
  //收到消息進行處理...
}

blockingQueue.take() 方法其實只是對【消息目標隊列】執行 blpop 阻塞的獲取到期消息

初始化延時隊列

看一下初始化的代碼。

public void init() {
    String queuename = "delay-queue";
    RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(queuename);
    RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
}

入口就是在 redissonClient.getDelayedQueue(blockingQueue) 中,創建了 RedissonDelayedQueue 對象,并執行了構造方法里的邏輯。

那么這里面主要做了什么事呢?

主要是調用了 QueueTransferTask 的 start() 方法。

public void start() {
  RTopic schedulerTopic = getTopic();
  statusListenerId = schedulerTopic.addListener(new BaseStatusListener() {
      @Override
    public void onSubscribe(String channel) {
      pushTask();
    }
 });

 messageListenerId = schedulerTopic.addListener(Long.class, new MessageListener<Long>() {
      @Override
      public void onMessage(CharSequence channel, Long startTime) {
     scheduleTask(startTime);
   }
 });
}

這段代碼主要是設置了指定主題(主題名:redisson_delay_queue_channel:{queuename})兩個發布訂閱的監聽器。

  1. 當指定主題有新訂閱時調用 pushTask() 方法,里面又會調用 pushTaskAsync() 方法
  2. 當指定主題有新消息時調用 scheduleTask(startTime) 方法

需要注意的是,這里會先訂閱指定主題,然后觸發執行 onSubscribe() 方法。

所以我們主要搞懂這三個方法都是做什么的,那么整個初始化流程就明白了。

因為這三個方法是相互調用的,只看文字的話容易云里霧里,這里有個流程圖,看方法解釋文字的時候可以對照著流程圖看比較有印象。

圖片圖片

三個方法調用流程圖.drawio.png

  • scheduleTask()
    這個方法看起來多,但核心內容就是根據方法參數指定的時間調用 pushTask()。
private void scheduleTask(final Long startTime) {
  TimeoutTask oldTimeout = lastTimeout.get();
  if (startTime == null) {
    return;
  }

  if (oldTimeout != null) {
    oldTimeout.getTask().cancel();
  }

  long delay = startTime - System.currentTimeMillis();
  if (delay > 10) {
    Timeout timeout = connectionManager.newTimeout(new TimerTask() {                    
      @Override
      public void run(Timeout timeout) throws Exception {
        pushTask();

        TimeoutTask currentTimeout = lastTimeout.get();
        if (currentTimeout.getTask() == timeout) {
          lastTimeout.compareAndSet(currentTimeout, null);
        }
      }
    }, delay, TimeUnit.MILLISECONDS);
    if (!lastTimeout.compareAndSet(oldTimeout, new TimeoutTask(startTime, timeout))) {
      timeout.cancel();
    }
  } else {
    pushTask();
  }
}
  • pushTaskAsync()
    這個方法是抽象方法,在創建 RedissonDelayedQueue 對象的時候傳進來的,代碼如下:
@Override
protected RFuture<Long> pushTaskAsync() {
  return commandExecutor.evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG,
  "local expiredValues = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); "
  + "if #expiredValues > 0 then "
  + "for i, v in ipairs(expiredValues) do "
  + "local randomId, value = struct.unpack('dLc0', v);"
  + "redis.call('rpush', KEYS[1], value);"
  + "redis.call('lrem', KEYS[3], 1, v);"
  + "end; "
  + "redis.call('zrem', KEYS[2], unpack(expiredValues));"
  + "end; "
  // get startTime from scheduler queue head task
  + "local v = redis.call('zrange', KEYS[2], 0, 0, 'WITHSCORES'); "
  + "if v[1] ~= nil then "
  + "return v[2]; "
  + "end "
  + "return nil;",
  Arrays.<Object>asList(getRawName(), timeoutSetName, queueName),
  System.currentTimeMillis(), 100);
}

看不懂也不要緊,聽我解釋下就明白了。

這里發送了一段腳本給 redis 執行:

我的理解就是初始化的時候

1是為了處理舊的消息,比如生產者1發送了消息,然后時間沒到自己下線了,這時如果沒有其他客戶端在線,就沒有人能把數據從【消息目標隊列】移到【消息目標隊列】了。

2是返回的這個時間戳,會拿這個定時,等時間到了去【消息目標隊列】拉去到期的消息。

簡單總結就是這個方法是把到期消息從【消息延時隊列】放到【消息目標隊列】里,并且返回了最近要到期消息的時間戳。

從【消息延時隊列】取出前一百條到期的消息,如果有的話,添加到【消息目標隊列】里,并將這些消息從【消息延時隊列】和【消息順序隊列】中移除

從【消息延時隊列】取出下一條要到期的消息,返回它的到期時間戳(如果隊列里沒消息返回空)。

  • pushTask()
private void pushTask() {
  RFuture<Long> startTimeFuture = pushTaskAsync();
  startTimeFuture.whenComplete((res, e) -> {
    if (e != null) {
      if (e instanceof RedissonShutdownException) {
        return;
      }
      log.error(e.getMessage(), e);
      scheduleTask(System.currentTimeMillis() + 5 * 1000L);
      return;
    }

    if (res != null) {
      scheduleTask(res);
    }
  });
}

這個代碼看起來就比較簡單,調用了 pushTaskAsync() 獲取最近要到期消息的時間戳(異步封裝了一下)。

有異常的話就調用 scheduleTask() 五秒后再執行一次 pushTask()。

沒有異常的話如果有最近要到期消息的時間戳(說明【消息延時隊列】里還有未到期消息),用這個最新到期時間調用 scheduleTask(),在這個指定的時間調用 pushTask()。

這個方法簡單總結就是決定了要不要調用、什么時候再調用 pushTask(),主要操作邏輯都在 pushTaskAsync() 里(把到期的消息從【消息延時隊列】移到【消息目標隊列】供消費端消費)。

了解了上面幾個方法的流程和含義,還記得一開頭提到的添加了兩個發布訂閱的監聽器嗎?

1.當指定主題有新訂閱時調用 pushTask() 方法,里面又會調用 pushTaskAsync() 方法

2.當指定主題有新消息時調用 scheduleTask(startTime) 方法

需要注意的是,這里會先訂閱指定主題,然后觸發執行 onSubscribe() 方法

  1. 在初始化延時隊列剛啟動的時候,處理到期舊數據:把到期的消息從【消息延時隊列】移到【消息目標隊列】供消費端消費;處理新數據:獲取下次到期時間決定下次調用 pushTask() 的時間。
    上面講的這種情況是站在當前客戶端的視角,但畢竟這是監聽訂閱信息,如果啟動不止一個客戶端的話(就算是1個生產者1個消費者,也算兩個客戶端),總有一個客戶端的訂閱信息回調函數,會不會有問題?
    仔細想想是沒有的,處理到期舊數據:之前啟動的客戶端已經處理完了;處理新數據:獲取最近到期時間,在 scheduleTask() 里,如果之前有正在定時的任務,會把原來正在定時的任務取消掉。這個被取消的任務,時間要么就是當前這個時間,要嘛是之后的時間,取消掉不會影響邏輯。
  2. 為了應對原本【消息延時隊列】里沒消息了這種情況,流程結束了,重啟定時去調用 pushTask() ,把到期的消息從【消息延時隊列】移到【消息目標隊列】供消費端消費。

總結

再放一下開頭的圖總體流程圖:

圖片圖片

  1. 初始化延時隊列時會把【消息延時隊列】里的到期數據移動到【消息目標隊列】,沒有也有可能;然后是找最近要到期的消息時間,定時去拉,這個剛啟動也是可能沒有的,不過不要緊,這兩步是為了處理滯留在【消息延時隊列】的舊數據(在發送了延時消息后,還沒到期時所有客戶端都下線了,這樣就沒人能把【消息延時隊列】里的到期數據移動到【消息目標隊列】里,就會出現這種情況);
    最主要的還是設置了發布訂閱監聽器,當有人發送延時消息的時候能收到通知,定時去將【消息延時隊列】里的到期數據移動到【消息目標隊列】。
  2. 發送延時消息會先發送到【消息延時隊列】和【消息順序隊列】,如果【消息延時隊列】里沒有數據,則將剛發送的到期時間發布到指定主題,提醒其他客戶端有新消息。
  3. 初始化延時隊列時設置的發布訂閱監聽器把【消息延時隊列】里的到期數據移動到【消息目標隊列】里。
  4. 獲取延遲消息只需要執行 blpop 阻塞的獲取【消息目標隊列】的消息就可以了。

這里回答開頭部分說的問題,到這看完了本文,你可以試著自己想一想這個問題的答案。

接收消息代碼如下,可以看到 delayedQueue 是沒有用到的,那么為什么要加這一行呢,這個后面總結部分回答。

public void consume() throws InterruptedException {
    String queuename = "delay-queue";
    RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(queuename);
    RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
    String msg = blockingQueue.take();
    //收到消息進行處理...
}

其實這個問題也是我開發過程中遇到的一個奇怪的地方,接收方代碼沒有初始化延時隊列。

首先再啰嗦一句,初始化延時隊列的作用是會定時去把【消息延時隊列】里的到期數據移動到【消息目標隊列】。

如果只有發送方初始化延時隊列:

  1. 發送方發送了延遲消息,在到期之前下線了(它就不能把【消息延時隊列】里的到期數據移動到【消息目標隊列】),而且沒有其他發送方。
  2. 接收方不管有多少個,都沒人能把【消息延時隊列】里的到期數據移動到【消息目標隊列】。

所以接收方代碼里也初始化延時隊列能夠避免一部分數據丟失問題。

- End-DailyMart是一個基于 DDD 和Spring Cloud Alibaba的微服務商城系統,采用SpringBoot3.x以及JDK17。旨在為開發者提供集成式的學習體驗,并將其無縫地應用于實際項目中。該專欄包含領域驅動設計(DDD)、Spring Cloud Alibaba企業級開發實踐、設計模式實際應用場景解析、分庫分表戰術及實用技巧等內容。如果你對這個系列感興趣,可在本公眾號回復關鍵詞 DDD 獲取完整文檔以及相關源碼。


責任編輯:武曉燕 來源: JAVA日知錄
相關推薦

2024-05-13 08:02:10

PostgreSQLRedisson監控

2016-12-16 11:05:00

分布式互斥線程

2024-08-27 13:43:38

Spring系統業務

2024-01-02 13:15:00

分布式鎖RedissonRedis

2020-05-18 14:00:01

Dubbo分布式架構

2023-10-09 16:35:19

方案Spring支付

2025-03-11 08:50:00

CASID分布式

2022-06-28 08:37:07

分布式服務器WebSocket

2018-06-28 14:00:01

分布式集群架構

2017-03-16 08:46:57

延時消息環形隊列數據結構

2023-07-26 07:28:55

WebSocket服務器方案

2024-04-29 07:57:46

分布式流控算法

2022-05-23 09:10:00

分布式工具算法

2023-11-27 08:15:26

Spring訂單取消

2023-11-20 08:39:24

Spring定時任務

2024-09-12 14:50:08

2024-11-14 11:56:45

2021-04-20 08:00:31

Redisson關閉訂單支付系統

2023-09-13 09:52:14

分布式鎖Java

2023-09-04 08:12:16

分布式鎖Springboot
點贊
收藏

51CTO技術棧公眾號

北条麻妃在线视频观看| 国产精品com| 国产原创剧情av| 亚洲天堂免费电影| 国产精品久久久久久妇女6080| 91久久国产精品91久久性色| 国产精品不卡av| 精品久久久久久久| 日韩精品影音先锋| 国产激情在线观看视频| 午夜羞羞小视频在线观看| 久久综合九色综合欧美就去吻 | 555www色欧美视频| 国产伦精品一区二区三区四区视频_| 国产黄色片在线观看| 国产成人免费在线观看| 国产精品国产福利国产秒拍| 精品无码久久久久| 久久激情电影| 精品爽片免费看久久| 北条麻妃亚洲一区| 日韩精品影院| 狠狠做深爱婷婷久久综合一区| 天天爱天天做天天操| 你懂得在线网址| 成人精品一区二区三区中文字幕| 国产美女精品免费电影| 4438国产精品一区二区| 亚洲国产二区| 欧美成年人视频网站欧美| 中文字幕第24页| 日韩欧美美女在线观看| 日韩欧美的一区| 超碰在线免费av| 国产福利一区二区三区在线播放| 黄网站色欧美视频| 国产欧美日韩小视频| 最新黄网在线观看| 中文字幕字幕中文在线中不卡视频| 日韩久久久久久久| 色综合888| 91在线一区二区三区| 国产91aaa| 精品人妻一区二区三区蜜桃 | 香蕉视频免费在线播放| 久久精品一级爱片| 欧洲精品在线一区| 清纯唯美亚洲色图| 久久综合久色欧美综合狠狠| 精品在线不卡| 午夜在线观看视频18| 菠萝蜜视频在线观看一区| 国产福利久久精品| 国产91免费在线观看| 国产成人免费av在线| 波多野结衣久草一区| 亚洲大尺度网站| 成人精品电影在线观看| 99久久久久国产精品免费| 国产高清免费在线观看| 国产99一区视频免费| 成人9ⅰ免费影视网站| www.五月激情| 成a人片亚洲日本久久| 国产高清精品一区| 亚洲 欧美 自拍偷拍| 久久夜色精品一区| 四虎永久在线精品免费一区二区| 97超碰人人在线| 亚洲美女屁股眼交3| 成人国产一区二区三区| cao在线视频| 色88888久久久久久影院按摩| 亚洲免费av一区二区三区| 国精品产品一区| 欧美一级午夜免费电影| 日本在线不卡一区二区| 国产成人ay| 日韩中文字幕在线看| 久久精品视频免费在线观看| 9色国产精品| 国产精品久久久久久婷婷天堂| 一级特黄aaa大片在线观看| 国产精品69毛片高清亚洲| 狠狠色狠狠色综合人人| av中文资源在线| 一区二区在线免费观看| 久久久免费视频网站| 另类一区二区| 亚洲精品videossex少妇| 最近中文字幕免费视频| 99精品视频在线| 91av视频在线| 91亚洲国产成人久久精品麻豆| 国产福利一区二区三区视频| 麻豆av一区二区| 免费在线看a| 精品久久久久国产| 网站在线你懂的| 裸体女人亚洲精品一区| 国产成人精品免高潮费视频| 久草视频在线免费| 国产69精品久久99不卡| 欧美极品色图| 欧美人与牲禽动交com| 色悠久久久久综合欧美99| 国产精品igao网网址不卡| 欧美猛男做受videos| 久久99热精品这里久久精品| 久久精品偷拍视频| youjizz国产精品| 亚洲欧洲精品在线 | 国产口爆吞精一区二区| 91免费精品国自产拍在线不卡| 2025韩国大尺度电影| 在线看的毛片| 精品奇米国产一区二区三区| 网爆门在线观看| 亚洲欧美日韩专区| 粉嫩av一区二区三区免费观看| 国产高清在线观看| 精品久久香蕉国产线看观看亚洲 | 欧美成年网站| 日韩在线资源网| 午夜精品久久久久久久蜜桃| 99在线精品视频| 国产高清不卡无码视频| 亚洲色图图片| 日韩中文字幕久久| 亚洲免费视频二区| 91麻豆福利精品推荐| 精品少妇人欧美激情在线观看| 国产激情精品一区二区三区| 精品国产一区二区在线 | 中文字幕人成乱码在线观看| 精品久久久久久久人人人人传媒| 亚洲视频重口味| 日本va欧美va精品发布| 日本在线视频一区| 日韩大片欧美大片| 亚洲人成网站色ww在线| 中文字幕国产在线观看| 久久久三级国产网站| www.com毛片| 三级小说欧洲区亚洲区| 国内精品免费午夜毛片| 人妻视频一区二区三区| 亚洲国产中文字幕| 国产污在线观看| 亚洲福利精品| 免费国产一区二区| 88xx成人永久免费观看| 国产亚洲综合久久| 在线观看免费中文字幕| 中文字幕一区二区三区视频| 污污的视频免费观看| 中文视频一区| 国产精品一区在线观看| 1区2区3区在线| 亚洲嫩模很污视频| www.av88| 亚洲欧美欧美一区二区三区| 亚洲av无码成人精品区| 亚洲免费黄色| 日韩成人av电影在线| 69堂精品视频在线播放| 久久夜色精品国产欧美乱| www.天堂在线| 欧美日韩在线免费观看| 夫妇露脸对白88av| 国产精品一级片在线观看| 岛国大片在线播放| 国模精品一区| 亚洲一区二区三区视频| 国产高清自产拍av在线| 亚洲网站在线观看| 国产精品无码专区av免费播放| 亚洲精品国产视频| 黄瓜视频污在线观看| 免费人成精品欧美精品| 精品无码av无码免费专区| 欧美人妖视频| 国产在线视频2019最新视频| 不卡一本毛片| 伊人久久久久久久久久| 精品国精品国产自在久不卡| 岛国精品视频在线播放| 亚洲欧美卡通动漫| 成人小视频免费观看| 日本免费观看网站| 一区视频在线看| 日韩在线电影一区| 久久婷婷国产| 成人美女av在线直播| 日韩伦理福利| 久久综合久久八八| 黄色av网站在线| 欧美变态tickle挠乳网站| 波多野结衣绝顶大高潮| 亚洲国产精品久久久久秋霞影院 | 中文字幕一区二区三区乱码图片| 精品999在线观看| 一级欧美视频| 日本91av在线播放| 另类视频在线| 日韩中文有码在线视频| 婷婷国产在线| 日韩欧美中文一区二区| 中文亚洲av片在线观看| 午夜精品一区二区三区三上悠亚| 秋霞欧美一区二区三区视频免费| 26uuu国产一区二区三区| 在线成人精品视频| 久久国产人妖系列| 欧美激情国产精品日韩| 在线播放不卡| 国产午夜精品视频一区二区三区| 久久精品国产亚洲夜色av网站| 欧美精品国产精品久久久| 亚洲综合网站| 91夜夜未满十八勿入爽爽影院| 免费欧美电影| 国产成人一区二区| 欧美男男tv网站在线播放| 欧美精品激情blacked18| www免费视频观看在线| 久久九九亚洲综合| 日本成人在线播放| 永久免费毛片在线播放不卡 | 粉嫩av一区二区| 147欧美人体大胆444| 日本免费一区二区三区等视频| 日韩免费中文字幕| 99re66热这里只有精品4| 91成人福利在线| 色在线免费观看| 97视频在线观看视频免费视频 | 伊人天天综合| 国产精品视频网站在线观看 | 黄色免费视频大全| 99精品热视频只有精品10| 男人插女人视频在线观看| 韩国一区二区三区在线观看| 国产91在线亚洲| 欧美日韩在线大尺度| 青青草视频在线视频| 韩日成人在线| 亚洲人成无码网站久久99热国产| 极品少妇一区二区三区| 激情深爱综合网| 国产欧美日本| 男人操女人免费| 捆绑紧缚一区二区三区视频| 欧美成人福利在线观看| 国产在线精品一区在线观看麻豆| 毛片毛片毛片毛片毛| 国产91丝袜在线观看| 成人手机在线免费视频| 久久久噜噜噜久久人人看| 亚洲色图第四色| 亚洲欧美日韩人成在线播放| 欧美久久久久久久久久久久| 午夜精品久久久久久久久| av大全在线观看| 欧美日韩一区二区欧美激情| 91丨九色丨丰满| 精品动漫一区二区三区在线观看| 色丁香婷婷综合久久| 亚洲日本成人女熟在线观看| 欧美激情午夜| 久久久久久久999精品视频| 性国裸体高清亚洲| 国产精品久久久久久久久久久久久| 亚洲视频资源| 国产欧美亚洲日本| 欧美码中文字幕在线| 影音先锋男人的网站| 精品动漫3d一区二区三区免费版 | 热久久最新网址| 国产情侣一区| 免费黄频在线观看| 99精品视频在线播放观看| 欧美色图17p| 亚洲成a人片在线不卡一二三区| 无码人妻丰满熟妇区bbbbxxxx| 欧美二区在线观看| 天天色综合久久| 久久久999精品免费| 成人勉费视频| 亚洲aa中文字幕| 久久成人av| 成人毛片100部免费看| 石原莉奈在线亚洲三区| 丰满少妇一区二区三区专区| 91在线小视频| 久草视频在线资源站| 欧美伊人久久大香线蕉综合69| www.中文字幕| 中文字幕一区二区精品| 波多野结衣视频一区二区| 91免费版网站入口| 国产精品探花在线观看| 日本男女交配视频| 精品在线观看免费| 中文字幕免费高清| 亚洲大片在线观看| 国产视频在线观看视频| 夜夜嗨av一区二区三区免费区| 波多野结衣中文在线| 成人免费黄色网| 日本在线电影一区二区三区| 凹凸国产熟女精品视频| 国产综合成人久久大片91| 久久久久亚洲av无码a片| 亚洲成人手机在线| 99久久久久成人国产免费| 中文字幕日韩精品有码视频| 美女100%一区| 精品在线视频一区二区| 在线成人国产| 台湾佬美性中文| 亚洲男人的天堂在线aⅴ视频| 嫩草影院一区二区三区| 亚洲免费视频一区二区| caoporn视频在线| 国产亚洲情侣一区二区无| 女生裸体视频一区二区三区| caoporm在线视频| 国产精品青草综合久久久久99| 免费看污视频的网站| 精品伊人久久97| 永久免费毛片在线播放| 精品免费视频123区| 亚洲毛片网站| 国产精品嫩草av| 天天av天天翘天天综合网| 天堂av中文字幕| 欧美一区二区三区……| 日本中文字幕在线一区| 黄www在线观看| 久久亚洲精华国产精华液 | 免费久久一级欧美特大黄| 妖精视频成人观看www| 黄色网址在线视频| 精品久久中文字幕| 黄色软件在线| 国产日韩精品一区二区| 久久久久久免费视频| 国产不卡的av| 亚洲综合在线免费观看| 秋霞网一区二区| **欧美日韩vr在线| 精品在线观看入口| 亚洲黄色小视频在线观看| 国产欧美一区视频| 亚洲天天综合网| 不卡毛片在线看| 亚洲视频国产| 浮妇高潮喷白浆视频| 国产亚洲综合性久久久影院| 免费av中文字幕| www.日本久久久久com.| 澳门精品久久国产| 欧美牲交a欧美牲交aⅴ免费真| 国产拍揄自揄精品视频麻豆| 国产精品久久久久久久免费| 九九热这里只有精品6| 欧美爱爱网站| 国产野外作爱视频播放| 亚洲精品中文字幕在线观看| 黑人精品一区二区| 国产91对白在线播放| 手机在线一区二区三区| 日批免费观看视频| 色网综合在线观看| 高清全集视频免费在线| 国产亚洲精品美女久久久m| 三级在线观看一区二区| 日本精品在线免费观看| 亚洲国产免费av| 日日夜夜综合| www.中文字幕在线| 中文字幕中文在线不卡住| 天天干天天摸天天操| 国产精品视频白浆免费视频| 狠狠色丁香久久综合频道| 自拍偷拍视频亚洲| 欧美成人精品1314www| 亚洲成人av观看| www.日本三级| 国产精品美女久久久久aⅴ| 肥臀熟女一区二区三区| 国产精品va在线播放| 亚洲第一毛片| 天天做夜夜爱爱爱| 亚洲欧洲黄色网| 97久久综合区小说区图片区| 色国产在线视频| 五月天中文字幕一区二区| 久久精品视频观看|