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

Redisson 全面解析:從使用方法到工作原理的深度探索

數據庫 Redis 開發
本文演示了redisson幾個常用的數據結構以及一些簡單并發流程工具使用示例和底層源碼分析,希望對你有幫助。?

Redisson是基于原生redis操作指令上進一步的封裝,屏蔽了redis數據結構的實現細節,開發可以像操作普通java對象一樣使用redis,而本文將針對Redisson中各種使用的數據結構和工具包使用及其實現進行詳盡的分析,希望對你有幫助。

一、詳解Redisson基本數據類型

1. Redisson前置配置說明

使用redisson的方式比較簡單,我們首先需要引入redisson的依賴包:

<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.23.5</version>
        </dependency>

然后我們指明redis的ip、端口等配置即可:

spring.redis.host=localhost
spring.redis.port=6379

有了上述配置后,我們就可以快速完成redisson客戶端配置:

@Configuration
public class RedissonConfig {

    @Autowired
    private RedisProperties redisProperties;


    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        String redisUrl = String.format("redis://%s:%s", redisProperties.getHost() + "",
                redisProperties.getPort() + "");
        config.useSingleServer().setAddress(redisUrl);
        return Redisson.create(config);
    }


}

后續在進行使用的時候,我們直接注入對應的客戶端依賴即可:

@Autowired
    private RedissonClient redissonClient;

2. 以bucket維度操作字符串

和我們第一次使用redis一樣,我們先用redisson完成一個字符串的鍵值對存儲,對應的使用例子如下所示,我們只需拿到對應的test-key的bucket即可進行讀寫操作:

//生成 test-key 的bucket
        RBucket<Object> bucket = redissonClient.getBucket("test-key");
        //查看對應的bucket是否存在
        if (ObjUtil.isEmpty(bucket.get())) {
            //基于set指令進行插入
            bucket.set("test-value");
            //嘗試通過get獲取值
            Object value = bucket.get();
            log.info("value:{}", value);
        }

對于RBucket對象的set和get操作本質上都是基于redis字符串操作指令set和get的一層封裝,在我們調用getBucket獲取對應key的bucket的時候,redisson會基于當前客戶端的連接信息和bucket鍵進行一次封裝得到一個test-key的bucket對象:

對應的我們給出getBucket的底層實現,可以看到邏輯操作就是封裝維護如下這份信息:

  • 編碼器和解碼器codec,默認情況下是Kryo5Codec
  • 執行命令的commandExecutor,該對象記錄redis客戶端的基本信息。
  • name也就是我們要操作的key的信息,也就是字符串key。
public RedissonObject(Codec codec, CommandAsyncExecutor commandExecutor, String name) {
        this.codec = codec;
        this.commandExecutor = commandExecutor;
        if (name == null) {
            throw new NullPointerException("name can't be null");
        }

        setName(name);
    }

然后就是執行set指令了,我們都知道redisson是基于Netty封裝的redis操作工具,所以在進行redis操作時涉及了大量優秀的異步讀寫涉及,我們以上文set操作為例,實際上其底層執行時做了如下幾件事:

  • 基于傳入的key,也就是我們的test-key定位到slot地址。
  • 獲取到上一步封裝的編碼器codec。
  • 本次執行是set請求,所以如果我們采用主從模式進行部署,這一步是會從主庫獲取連接信息,因為我們就配置了一臺redis,所以默認直接從默認庫獲取連接。
  • 基于連接信息發送指令。
  • 完成操作后歸還連接。

這些步驟完成后,操作結果會被封裝為Future對象,如果需要直到執行結果,我們調用get即可知曉處理情況:

對應的我們也給出set的源碼入口,如筆者所說其底層就是一個set操作的異步調用setAsync,通過該回調會得到一個RFuture對象,通過get即可獲取結果:

@Override
    public void set(V value) {
     //基于setAsync提交異步set操作,然后通過get獲取執行結果
        get(setAsync(value));
    }

對應的我們步入setAsync可以看到它會拿著我們上一步初始化所得來的key名稱、編碼器、set操作指令對象以及編碼后的value值通過commandExecutor進行異步寫入到redis服務端:

@Override
    public RFuture<Void> setAsync(V value) {
       //......
  //基于各種信息通過commandExecutor進行異步提交
        return commandExecutor.writeAsync(getRawName(), codec, RedisCommands.SET, getRawName(), encode(value));
    }

我們再次步入即可來到第一個核心步驟,通過key獲取到slot,因為我們部署結構是單體,所以source拿到的是默認值0,然后調用async正式執行異步寫操作:

@Override
    public <T, R> RFuture<R> writeAsync(String key, Codec codec, RedisCommand<T> command, Object... params) {
     //定位slot
        NodeSource source = getNodeSource(key);
        //執行異步寫
        return async(false, source, codec, command, params, false, false);
    }

步入async即可看到我們的最核心的步驟了,該方法內部會通過RedisExecutor執行execute方法,大體就是執行了上圖所說的:

  • 獲取編碼器
  • 基于讀寫請求獲取連接,注意獲取連接的操作是異步的
  • 得到連接后調用sendCommand發送set請求,其內部本質上就是基于netty所封裝的socketChannel執行set操作。
  • 完成寫操作后釋放連接
public void execute() {
         //......
  //1. 獲取編碼器
        codec = getCodec(codec);
  //2.基于讀寫請求獲取連接,注意獲取連接的操作是異步的
        CompletableFuture<RedisConnection> connectionFuture = getConnection();

        
    //......
  //3. 得到連接后調用sendCommand發送set請求
        connectionFuture.whenComplete((connection, e) -> {
              //......
  
            sendCommand(attemptPromise, connection);

           //......
        });

        attemptPromise.whenComplete((r, e) -> {
         //完成操作后釋放連接
            releaseConnection(attemptPromise, connectionFuture);

            checkAttemptPromise(attemptPromise, connectionFuture);
        });
    }

3. 以Java API風格操作redis列表

列表操作就是對于redis列表的封裝,可以看到redisson給出的操作函數完全按照java開發的習慣命名:

RList<Object> list = redissonClient.getList("list");
        //循環添加元素
        for (int i = 0; i < 10; i++) {
            list.add(i);
        }
        //移除索引0位置的元素
        list.remove(0);

getList和上述bucket操作類似這里就不多追贅述,這里我們就看看add的實現細節,本質上它就是異步調用redis的RPUSH指令將元素追加到列表末尾,整體流程原理和上述set操作差不多,這里就不多做贅述了:

對應的我們也給出底層源碼的核心部分的介紹:

@Override
    public boolean add(V e) {
        return get(addAsync(e));
    }

    @Override
    public RFuture<Boolean> addAsync(V e) {
    //異步執行rpush指令將元素追加到末尾
        return addAsync(e, RPUSH_BOOLEAN);
    }

4. 以Java API格式操作字典

映射集也就是我們java中常說的map,redisson底層使用的就是redis的dict字典,對應示例如下所示,注意這個put方法,每次操作后它會有一個返回值,即如果這個key存在于redis中,那么本次put擦咯做結束后就會返回覆蓋前的值,就像下面這段代碼一樣,第二次put操作后就會返回value1:

RMap<String, String> hashMap = redissonClient.getMap("hashMap");
   //使用put操作,如果這個key存在則返回這個key原有的value值
        String res = hashMap.put("key1", "value1");
        log.info("before res:{}", res);
        res = hashMap.put("key1", "value2");
        log.info("after res:{}", res);

這里我們也給出put的核心實現,對應的核心代碼就是RedissonMap中的putAsync方法,大體邏輯是進行key和value的檢查之后,調用putOperationAsync生成一個異步put操作的任務并得到一個future,最后封裝成mapWriterFuture返回:

@Override
    public RFuture<V> putAsync(K key, V value) {
     //進行鍵值對檢查
        checkKey(key);
        checkValue(value);
        //基于putOperationAsync執行鍵值對插入操作
        RFuture<V> future = putOperationAsync(key, value);
        if (hasNoWriter()) {
            return future;
        }
        //返回結果
        return mapWriterFuture(future, new MapWriterTask.Add(key, value));
    }

所以來到putOperationAsync即可看到這段核心代碼的實現,本質上為了保證返回覆蓋前的值,redis用到的lua腳本,該腳本的執行流程為:

  • 調用hget判斷key是否存在若存在用v記錄這個值。
  • 調用hset進行鍵值對設置。
  • 返回v即覆蓋前的值。

對應的我們也給出這段源代碼示例:

protected RFuture<V> putOperationAsync(K key, V value) {
        String name = getRawName(key);
        return commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_MAP_VALUE,
                "local v = redis.call('hget', KEYS[1], ARGV[1]); "
                + "redis.call('hset', KEYS[1], ARGV[1], ARGV[2]); "
                + "return v",
                Collections.singletonList(name), encodeMapKey(key), encodeMapValue(value));
    }

5. 詳解redisson自實現的阻塞隊列

我們再來個阻塞隊列的例子,整體使用也和java的阻塞隊列差不多:

RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("blockingQueue");
        //添加元素
        blockingQueue.put("element");
        //取出元素
        String value = blockingQueue.take();

        log.info("value:{}", value);

實際上隊列的實現也是基于redis的列表,通過rpush實現入隊,lpop實現出隊:

對應我們也給出入隊的代碼核心實現印證這一點:

@Override
    public RFuture<Void> putAsync(V e) {
     //使用rpush模擬入隊
        return addAsync(e, RedisCommands.RPUSH_VOID);
    }

用blpop實現出隊操作:

@Override
    public RFuture<V> takeAsync() {
        return commandExecutor.writeAsync(getRawName(), codec, RedisCommands.BLPOP_VALUE, getRawName(), 0);
    }

6. 詳解redisson自實現延遲隊列

在上文中我們給出阻塞隊列的概念,實際上redisson在此基礎上更進一步的封裝做出了一個延遲隊列的設計,如下面這段示例,該代碼會在5s后提交給blockingQueue一個element元素,通過blockingQueue的take方法即可實現5s后準時出去元素:

//創建延遲隊列
        RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("blockingQueue");
        RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);

        //添加元素
        delayedQueue.offer("element", 5, TimeUnit.SECONDS);


        //取出元素
        long begin = System.currentTimeMillis();
        String value = blockingQueue.take();
        long end = System.currentTimeMillis();

        log.info("value:{} cost:{}ms", value, end - begin);

對應的我們也給出這段代碼示例的輸出結果,可以看到阻塞隊列必須等到5s左右才能得到元素:

2025-01-14 10:52:27.134  INFO 17684 --- [           main] com.sharkChili.TestRunner                : value:element cost:5034ms

其實現原理也很簡單,上述代碼我們指明了隊列名稱為blockingQueue,在使用offer進行延遲提交本質上就是通過lua腳本實現元素延遲提交,其工作內容為:

  • 基于我們給定的名稱blockingQueue生成一個有序集合redisson_delay_queue_timeout:{blockingQueue}告知element元素的超時時間。
  • 基于我們給定的名稱blockingQueue生成列表redisson_delay_queue:{blockingQueue}一個編碼后的元素值element。
  • 到有序集合redisson_delay_queue:{blockingQueue}中查看第一個元素是否是當前元素,如果是則通過publish發送一個給redisson_delay_queue_channel:{blockingQueue}這個topic告知元素提交的到期時間。

對應的我們給出offer底層的實現,可以看到該方法通過我們傳入的時間得到一個超時后的時間,然后封裝成lua腳本,也就是我們上面所說的含義提交到redis服務端:

public RFuture<Void> offerAsync(V e, long delay, TimeUnit timeUnit) {
        //......
        //計算超時后的時間
        long delayInMs = timeUnit.toMillis(delay);
        long timeout = System.currentTimeMillis() + delayInMs;
  //生成隨機數構成一個唯一的lua腳本
        byte[] random = getServiceManager().generateIdArray(8);
        //基于隨機數生成lua腳本
        return commandExecutor.evalWriteNoRetryAsync(getRawName(), codec, RedisCommands.EVAL_VOID,
                "local value = struct.pack('Bc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3]);"
                //提交到超時隊列redisson_delay_queue_timeout:{blockingQueue}記錄元素value插入的時間為ARGV[1],即入參中的timeout
              + "redis.call('zadd', KEYS[2], ARGV[1], value);"
              //提交到元素隊列redisson_delay_queue:{blockingQueue}當前元素值為element
              + "redis.call('rpush', KEYS[3], value);"
              //從redisson_delay_queue_timeout:{blockingQueue}獲取第一個元素,如果是當前元素則通過redisson_delay_queue_channel:{blockingQueue}這個channel發布元素的到期時間為ARGV[1],即入參中的timeout
              + "local v = redis.call('zrange', KEYS[2], 0, 0); "
              + "if v[1] == value then "
                 + "redis.call('publish', KEYS[4], ARGV[1]); "
              + "end;",
             //這個list代表keys列表,getRawName是blockingqueue、timeout就是redisson_delay_queue_timeout:{blockingQueue}、queueName就是redisson_delay_queue:{blockingQueue}、channel就是基于redisson_delay_queue_channel:{blockingQueue}
              Arrays.asList(getRawName(), timeoutSetName, queueName, channelName),
              //代表arg timeout即超時的時間,random是隨機數、e就是我們本次插入的編碼后的element
              timeout, random, encode(e));
    }

基于上述的執行腳本,我們的延遲隊列在初始化時會創建一個QueueTransferTask,從上一步發布到redisson_delay_queue_channel:{blockingQueue}的信息,這個QueueTransferTask會監聽到元素的到期時間然后生成一個定時任務,到點后執行如下邏輯:

  • 從redisson_delay_queue_timeout:{blockingQueue}這個超時隊列中獲取到期的元素。
  • 將元素值提交到blockingQueue中。
  • 將本次延遲提交的元素從redisson_delay_queue_timeout:{blockingQueue}、redisson_delay_queue:{blockingQueue}中移除。

由此一次完整的元素提交就成功了:

對應的我們給出延遲隊列的初始化代碼,它會進行各種隊列初始化的任務提交工作,整體步驟為:

  • 基于傳入的blockingQueue生成channel、列表、超時隊列。
  • 它會創建一個lua腳本,內容就是上面所說的延遲提交入隊列然后移除延遲提交的任務信息。
  • 調用schedule啟動task。
protected RedissonDelayedQueue(QueueTransferService queueTransferService, Codec codec, final CommandAsyncExecutor commandExecutor, String name) {
        super(codec, commandExecutor, name);
        //基于傳入的blockingQueue生成channel、列表、超時隊列-
        channelName = prefixName("redisson_delay_queue_channel", getRawName());
        queueName = prefixName("redisson_delay_queue", getRawName());
        timeoutSetName = prefixName("redisson_delay_queue_timeout", getRawName());
        
        QueueTransferTask task = new QueueTransferTask(commandExecutor.getServiceManager()) {
            
            @Override
            protected RFuture<Long> pushTaskAsync() {
             //基于初始化的channel、元素列表、延遲隊列信息生成lua提交
                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('Bc0Lc0', 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.asList(getRawName(), timeoutSetName, queueName),
                      System.currentTimeMillis(), 100);
            }
           //初始化channel的topic為 channelName
            @Override
            protected RTopic getTopic() {
                return RedissonTopic.createRaw(LongCodec.INSTANCE, commandExecutor, channelName);
            }
        };
        //調用schedule提交這個task
        queueTransferService.schedule(queueName, task);
        
       //......
    }

對應我們步入這個schedule方法即可看到,封裝的task啟動后會執行會監聽redisson_delay_queue_channel:{blockingqueue}得到元素的到期時間并基于這個時間到點執行提交隊列的lua腳本:

public void start() {
 //獲取到上一步初始化的channel即redisson_delay_queue_channel:{blockingqueue}
        RTopic schedulerTopic = getTopic();
       //......
        //訂閱這個channel收到消息后,基于對應的startTime即延遲提交元素的到期時間通過scheduleTask執行上述的lua腳本將元素提交至blockingqueue中
        messageListenerId = schedulerTopic.addListener(Long.class, new MessageListener<Long>() {
            @Override
            public void onMessage(CharSequence channel, Long startTime) {
                scheduleTask(startTime);
            }
        });
    }

如下以來我們只需通過阻塞隊列的task方法就可以等到元素到期后取出,完成邏輯閉環。

二、更多關于Redisson

1. 詳解Redisson 中的原子類

因為redis執行用戶指令是單線程的,所以針對key執行INCR即可實現元素自增,所以redisson也利用到這一點封裝了一個原子類,對應的使用示例如下:

RAtomicLong atomicLong = redissonClient.getAtomicLong("atomicLong");
        atomicLong.incrementAndGet();
        log.info("atomicLong = {}", atomicLong.get());

2. 詳解redisson中的發布訂閱模型

對應發布訂閱模型,redisson也做了很好的封裝時,使用時的api也非常方便,如下所示,通過publish即可發布消息,通過addListener即可得到對應的channel和message:

CountDownLatch countDownLatch = new CountDownLatch(2);
        //訂閱topic消息
        new Thread(() -> {
            RTopic topic = redissonClient.getTopic("topic");
            topic.addListener(String.class, (c, m) -> {
                log.info("c:{},m:{}", c, m);
            });
            countDownLatch.countDown();
        }).start();

        //發布消息到topic
        new Thread(() -> {
            RTopic topic = redissonClient.getTopic("topic");
            topic.publish("hello redssion");
            countDownLatch.countDown();
        }).start();

        countDownLatch.await();
        log.info("finish");

三、小結

本文演示了redisson幾個常用的數據結構以及一些簡單并發流程工具使用示例和底層源碼分析,希望對你有幫助。

責任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關推薦

2010-01-06 15:03:34

JSON格式封裝

2011-08-11 17:00:33

iPhone數據庫SQLite

2024-11-27 15:49:46

字符串Python

2024-05-28 00:00:02

Java線程程序

2010-03-22 14:22:23

智能交換機

2012-06-29 13:54:11

Java內存原型

2010-08-09 10:16:01

FlexBuilder

2025-09-26 02:00:55

JDKCPU內存

2011-08-29 15:58:51

Lua函數

2010-10-08 14:27:25

JavascriptSplit

2011-06-14 10:18:58

QThread Qt 線程

2010-02-04 10:43:05

Android DDM

2009-12-16 08:57:06

Fedora Live

2013-06-08 17:09:35

Android開發移動開發XML解析

2009-11-25 10:02:27

PHP會話Sessio

2009-12-17 11:37:39

Linux網卡

2011-06-30 16:53:18

QT Creator TableWidge

2025-07-28 09:05:00

su 命令Linux運維

2021-11-23 09:09:27

Applicationandroid系統開發

2021-11-19 17:26:11

AppApplication方法
點贊
收藏

51CTO技術棧公眾號

人妖一区二区三区| 青青草原国产在线| 国内精品视频666| 久久久亚洲福利精品午夜| 亚洲乱码国产乱码精品精大量| 怡红院成人在线| 日韩精品2区| 欧美一区二区三区视频免费| 日日摸日日碰夜夜爽无码| 国产高清视频在线| 成人午夜在线免费| 国产精品视频资源| 国产精品视频久久久久久久| 我不卡神马影院| 色综合久久99| 国产精品一区在线免费观看| 四虎影视精品成人| 伊人成人在线视频| 一本色道久久综合狠狠躁篇怎么玩 | 999国产精品999久久久久久| 色婷婷激情久久| 在线观看av的网址| 色网站免费在线观看| 91在线国产福利| 51午夜精品| 欧美亚洲日本在线| 成人在线免费观看视频| 亚洲国语精品自产拍在线观看| 毛片毛片毛片毛| 影视一区二区三区| 精品久久久在线观看| 波多野结衣 作品| 免费大片在线观看www| 国产网站一区二区三区| 九色视频成人porny| 亚洲AV无码一区二区三区性| 久久99久久精品| 国产精品久久久久久久久久久久 | 精品亚洲免费视频| 国产不卡精品视男人的天堂| 日韩av黄色片| 亚洲成人在线| 欧美大片网站在线观看| 日韩三级在线观看视频| 精品视频国内| 欧美精品乱人伦久久久久久| 欧洲xxxxx| 免费高清在线观看| 成人欧美一区二区三区视频网页| 日本一区不卡| yjizz视频网站在线播放| 久久久青草青青国产亚洲免观| 国产亚洲一区二区三区在线播放 | 国产成人精品免费视频网站| 欧美极品少妇xxxxⅹ裸体艺术| 青青草华人在线视频| 日本不卡电影| 中文字幕亚洲二区| 三级黄色在线观看| 白白在线精品| 精品国产区一区| 国产精品果冻传媒| 狼人精品一区二区三区在线| 欧美性色视频在线| 欧美日韩一区二区三区四区在线观看| 中文无码精品一区二区三区| 北条麻妃在线视频观看| 欧美在线视频一二三| 国产在线不卡一卡二卡三卡四卡| 亚洲综合资源| 黄色av免费观看| 中文字幕伦理免费在线视频 | 亚洲黄色大片| 2020国产精品视频| 中文精品久久久久人妻不卡| 久久99国产精品久久| 51精品国产人成在线观看| 韩国av永久免费| 久久一级大片| 日韩欧美成人网| 天天色综合天天色| 亚洲精品a区| 亚洲毛茸茸少妇高潮呻吟| av免费观看国产| 欧美gv在线| 欧美在线一区二区三区| www.偷拍.com| 色橹橹欧美在线观看视频高清| 亚洲人午夜精品| 国产美女福利视频| 欧美亚洲专区| 51国偷自产一区二区三区| 神马精品久久| 亚洲免费色视频| 国产a级一级片| 999色成人| 亚洲老板91色精品久久| 国产传媒免费在线观看| 亚洲一区二区毛片| 久久久久久欧美| 久久久久久亚洲av无码专区| 国产精品一区在线观看乱码| 欧美高清性xxxxhd| 影音先锋在线视频| 欧美色图在线观看| 成人免费视频久久| 麻豆国产精品| 亚洲图片制服诱惑| 久久精品一级片| 全国精品久久少妇| 久久久www免费人成黑人精品| 欧美激情视频在线播放| 欧美日韩激情小视频| 黄页免费在线观看视频| 九七电影院97理论片久久tvb| 亚洲第一偷拍网| 日韩一级片av| 国产精品久久久久久久久久10秀| 亚州国产精品久久久| 国产亚洲欧美精品久久久www| 美女黄色成人网| 欧美专区国产专区| 精品久久久免费视频| 日本一区二区三区四区在线视频| www.欧美黄色| 成人短视频软件网站大全app| 亚洲欧美国产另类| 福利一区二区三区四区| 国产精品一区二区男女羞羞无遮挡 | 99久久99久久综合| 日韩精品免费一区| 国产精品igao视频网网址不卡日韩| 亚洲免费一级电影| 特黄视频免费看| 美女诱惑黄网站一区| 99精品国产一区二区| 国产一二三区在线观看| 欧美日韩欧美一区二区| 国产精品20p| 9999国产精品| 国产日韩在线看片| 日本免费在线视频| 欧美日韩一区二区在线观看 | 99re6在线观看| 成人短片线上看| 国产精品第一页在线| 国产在线高清| 欧美中文字幕一区二区三区| 色综合99久久久无码国产精品| 欧洲视频一区| 国产国语刺激对白av不卡| 日本v片在线免费观看| 精品久久香蕉国产线看观看gif| 偷偷色噜狠狠狠狠的777米奇| 在线看片成人| 精品欧美一区二区在线观看视频| 91视频欧美| 精品一区二区三区三区| 久久免费手机视频| 美女看a上一区| 中文字幕日韩精品久久| 99视频这里有精品| 欧美老少配视频| 五月天激情国产综合婷婷婷| 99re亚洲国产精品| 成年人小视频网站| 视频在线不卡免费观看| 91久久国产精品91久久性色| 国产三区视频在线观看| 欧美成人免费网站| 午夜影院在线看| 国产色产综合产在线视频| 精品日韩久久久| 成人在线免费观看91| 亚洲综合日韩在线| 女厕盗摄一区二区三区| 一个人看的www久久| 五月婷婷丁香在线| 久久日韩精品一区二区五区| www亚洲成人| 欧美日韩国产成人精品| 蜜桃成人在线| 成人国产精品一区二区网站| 久久久免费高清电视剧观看| 看电影就来5566av视频在线播放| 欧美日韩一区二区电影| 久久综合久久鬼| 国产亚洲一区二区三区在线观看 | 成人av电影在线观看| 成人亚洲视频在线观看| 91精品国产91久久久久久密臀| 国产超碰91| 99久久婷婷国产综合精品首页 | 视频一区国产精品| 亚洲一区二区三区免费| 国产精品ⅴa在线观看h| 欧美性爽视频| 日韩最新av在线| 国产一级一级国产| 日韩理论片网站| 特级西西人体wwwww| 18成人免费观看视频| 天天久久人人| 激情av综合| 成人亚洲欧美一区二区三区| 在线能看的av网址| 欧美高清自拍一区| 在线视频自拍| 日韩高清av在线| 亚洲国产视频一区二区三区| 欧美视频三区在线播放| 国产欧美日韩另类| 亚洲欧美日韩人成在线播放| 黄色片网站免费| 99视频一区二区| 男人的天堂免费| 久久精品噜噜噜成人av农村| 夫妻免费无码v看片| 欧美日韩亚洲国产精品| 最新精品视频| 日本在线电影一区二区三区| 久久久久高清| 久久99精品国产自在现线| 亚洲在线免费看| 欧美激情三区| 国产精品va在线| 亚洲精品mv| 97国产精品久久| 色帝国亚洲欧美在线| 久久精品中文字幕一区| www.五月天激情| 欧美另类z0zxhd电影| 免费精品一区二区| 色狠狠一区二区| 男人天堂av在线播放| 欧美日韩激情美女| 69成人免费视频| 色综合天天做天天爱| 少妇太紧太爽又黄又硬又爽| 天天色综合天天| 1级黄色大片儿| 精品久久久久久中文字幕一区奶水 | 岛国av一区二区| 一级片免费网址| 五月综合激情网| 日韩精品无码一区二区三区久久久| 成人涩涩免费视频| 亚洲香蕉中文网| 99久精品国产| wwwwww日本| 欧美国产一区视频在线观看| 嘿嘿视频在线观看| 国产精品久久久久精k8| 后入内射无码人妻一区| 国产精品久久久久四虎| 香蕉成人在线视频| 亚洲色图19p| 久草免费在线视频观看| 亚洲一区在线播放| 国内免费精品视频| 色国产精品一区在线观看| 国模私拍一区二区| 欧美日韩不卡一区二区| 国产人妻精品一区二区三| 日韩女同互慰一区二区| 日韩一级中文字幕| 亚洲欧美综合v| 欧洲日本在线| 国内精品久久久久久中文字幕| 人在线成免费视频| 国产精品福利网站| 成人av在线播放| 精品毛片久久久久久| 国产成人ay| 青青草原网站在线观看| 亚洲黄网站黄| 国产一区二区在线免费播放| 国产乱理伦片在线观看夜一区| 国产精品成人99一区无码| 久久人人爽爽爽人久久久| 天堂av免费在线| 亚洲一区成人在线| 色老头在线视频| 日韩一级欧美一级| 日韩有码电影| 久久夜精品va视频免费观看| eeuss影院在线播放| 欧美成人免费va影院高清| xxx在线免费观看| 国产精品久久不能| 影音先锋欧美激情| 天天爽天天狠久久久| 一区二区自拍| 激情文学亚洲色图| 97精品久久久久中文字幕 | 性做久久久久久| 中文字幕久久久久| 亚洲国产另类 国产精品国产免费| 大片免费播放在线视频| 精品中文字幕在线| 日本欧美韩国| 精品一区久久久久久| 午夜激情久久| 少妇性l交大片| 99久久精品免费看| h色网站在线观看| 日韩欧美在线国产| xxxx18国产| 日韩在线视频网站| 超黄网站在线观看| a级高清视频欧美日韩| 亚洲综合视频网| 玖玖爱在线观看| 亚洲精品福利视频网站| 精品久久久久久无码人妻| 中文字幕国产精品一区二区| 日本少妇毛茸茸高潮| 欧美精品电影在线播放| 免费人成在线观看网站| 国内精久久久久久久久久人| 国产人与zoxxxx另类91| 一本一本久久a久久精品综合妖精| 久久久精品午夜少妇| 999精品免费视频| 亚洲综合区在线| 一本色道久久综合精品婷婷| 欧美日韩免费视频| 欧美黄色小说| 青青草原一区二区| 日韩系列在线| 日韩精品 欧美| 成人黄色一级视频| 久久9999久久免费精品国产| 欧美一区二区三区白人| 黄色网在线看| 国产综合在线观看视频| 日韩在线观看电影完整版高清免费悬疑悬疑 | 肥臀熟女一区二区三区| 欧美成人h版在线观看| 自拍偷拍亚洲图片| 超碰在线免费观看97| 韩国av一区二区三区四区| frxxee中国xxx麻豆hd| 欧美日韩国产综合一区二区三区 | 国产精品网友自拍| 中文字幕+乱码+中文| 中文日韩在线视频| www.国产精品| 一区二区日本| 精品影视av免费| 中国一级片在线观看| 日韩一区国产二区欧美三区| 18+激情视频在线| 999国内精品视频在线| 欧美日韩三级| 北京富婆泄欲对白| 岛国视频午夜一区免费在线观看| 亚洲色欧美另类| 国产精品免费看久久久香蕉| 国产精品久久久久久麻豆一区软件| 中文字幕在线视频精品| 亚洲精品一二三| 五月天激情开心网| 国产成人精品一区| 国产精品99一区二区三| 麻豆网站免费观看| 亚洲国产精品久久久久秋霞影院| 天天操天天干天天爽| 在线播放国产一区二区三区| 成人在线黄色| 国产精品啪啪啪视频| 99精品视频一区| 中国老头性行为xxxx| 久久视频在线直播| 黑人久久a级毛片免费观看| 日本不卡在线观看视频| 国产精品家庭影院| 午夜精品小视频| 欧美在线观看日本一区| 久久精品99久久无色码中文字幕| 午夜免费视频网站| 天天综合网 天天综合色| 丁香在线视频| av一区观看| 日韩电影一区二区三区四区| 国产一区在线观看免费| 精品国产一区二区国模嫣然| 欧美色网一区| 97超碰在线视| 国产日韩欧美不卡在线| av中文字幕免费在线观看| 欧美在线视频在线播放完整版免费观看| 欧美日韩在线观看视频小说| 免费人成视频在线播放| 日本韩国欧美国产| 激情影院在线| 亚洲一区三区视频在线观看| 成年人国产精品| 国产伦精品一区二区三区四区| 97精品国产97久久久久久|