天天看點

吃透Redis系列(十一):Jedis和Lettuce用戶端詳細介紹

Redis系列文章:

吃透Redis系列(一):Linux下Redis安裝

吃透Redis系列(二):Redis六大資料類型詳細用法

吃透Redis系列(三):Redis管道,釋出/訂閱,事物,過期時間 詳細介紹

吃透Redis系列(四):布隆(bloom)過濾器詳細介紹

吃透Redis系列(五):RDB和AOF持久化詳細介紹

吃透Redis系列(六):主從複制詳細介紹

吃透Redis系列(七):哨兵機制詳細介紹

吃透Redis系列(八):叢集詳細介紹

吃透Redis系列(九):Redis代理twemproxy和predixy詳細介紹

吃透Redis系列(十):Redis記憶體模型詳細介紹

吃透Redis系列(十一):Jedis和Lettuce用戶端詳細介紹

文章目錄

    • 前言
    • 一,Jedis使用
      • 1,導入maven包
      • 2,五種基本類型
      • 3,管道
      • 4,事物
      • 5,哨兵
      • 6,叢集
    • 二,Lettuce使用
      • 1,Lettuce簡單使用
      • 2,API
        • 2.1,同步API
        • 2.2,異步API
        • 2.3,反應式API
      • 3,釋出/訂閱
      • 4,管道
      • 5,事物
      • 6,普通主從模式
      • 7,哨兵
      • 8,叢集

前言

我們打開Redis官網(https://redis.io/clients)發現,Redis支援以下語言:

吃透Redis系列(十一):Jedis和Lettuce用戶端詳細介紹

因為Redis是二進制安全的,隻存位元組數組,是以隻要各種語言用戶端互相編解碼一緻,那麼就可以互相通用,比如c存java取,等等。

當然,我們最關心的還是在Java中的使用,是以我們還是點開Java,看都有哪些用戶端支援:

吃透Redis系列(十一):Jedis和Lettuce用戶端詳細介紹

有如上幾種用戶端,那麼你看最常用的就是标星的Jedis,Lettuce,Redisson。

接下來我們隻詳細了解Jedis,Lettuce,因為我們Spring中也是用了這兩種用戶端,具體在Spring中怎麼使用,請參考挂網 Spring Data Redis

一,Jedis使用

首先打開Jedis的github:https://github.com/redis/jedis?_ga=2.241806790.538319447.1611192789-1620311179.1610019103

1,導入maven包

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.5.0</version>
    <type>jar</type>
    <scope>compile</scope>
</dependency>
           

2,五種基本類型

public class MyRedisTest {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);

        /**
         * 1,字元串操作
         */
        jedis.set("name", "bobo");
        jedis.set("age", "18");
        jedis.incr("age");
        System.out.println("name:" + jedis.get("name") + " age:" + jedis.get("age"));

        /**
         * 2,list操作
         */
        String keyList = "myList";
        jedis.rpush(keyList, "1");
        jedis.rpush(keyList, "2");
        jedis.rpush(keyList, "3");
        System.out.println(jedis.lrange(keyList, 0, -1));

        /**
         * 3,set操作
         */
        String keySet1 = "mySet1";
        String keySet2 = "mySet2";
        jedis.sadd(keySet1, "a", "b", "c");
        jedis.sadd(keySet2, "d", "b", "f");
        //取交集
        jedis.sinterstore("result1", keySet1, keySet2);
        System.out.println("交集:" + jedis.smembers("result1"));
        //取并集
        jedis.sunionstore("result2", keySet1, keySet2);
        System.out.println("并集:" + jedis.smembers("result2"));

        /**
         * 4,有序集合
         */
        String keyStoreSet = "myStoreSet";
        jedis.zadd(keyStoreSet, 3, "score3");
        jedis.zadd(keyStoreSet, 1, "score1");
        jedis.zadd(keyStoreSet, 2, "score2");
        System.out.println("zSet:" + jedis.zrange(keyStoreSet, 0, -1));

        /**
         * 5,哈希操作
         */
        String hashKey = "myHashKey";
        jedis.hset(hashKey, "name", "bobo");
        jedis.hset(hashKey, "age", "18");
        System.out.println("hash value:" + jedis.hvals(hashKey));
    }
}
           

運作輸出:

name:bobo age:19
[1, 2, 3]
交集:[b]
并集:[f, a, b, c, d]
zSet:[score1, score2, score3]
hash value:[bobo, 18]
           

redis-cli查詢key:

吃透Redis系列(十一):Jedis和Lettuce用戶端詳細介紹

3,管道

@Test
    public void testPipe() {
        // 清空資料
        jedis.flushDB();
        Pipeline pipelined = jedis.pipelined();
        pipelined.set("name", "bobo");
        pipelined.zadd("storeSet", 3, "score3");
        pipelined.zadd("storeSet", 1, "score1");
        pipelined.zadd("storeSet", 2, "score2");
        pipelined.sync();
        System.out.println("name:" + jedis.get("name"));
        System.out.println("storeSet:" + jedis.zrange("storeSet", 0, -1));
    }
           

運作輸出:

name:bobo
storeSet:[score1, score2, score3]
           

4,事物

@Test
    public void testTransaction() {
        jedis.flushDB();
        Transaction transaction = jedis.multi();
        transaction.set("name", "bobo");
        transaction.lpush("myList", "a", "b", "c");
        transaction.lpop("myList");
        transaction.exec();

        System.out.println("name:" + jedis.get("name"));
        System.out.println("myList:" + jedis.lrange("myList", 0, -1));
    }
           

運作輸出:

name:bobo
myList:[b, a]
           

5,哨兵

部署三個哨兵節點

哨兵部署詳細操作請看:吃透Redis系列(七):哨兵機制詳細介紹

# sentinel_26379.conf
port 26379
daemonize yes
logfile /var/lib/sentinel_26379.log
sentinel monitor mymaster 127.0.0.1 6379 2

# sentinel_26380.conf
port 26380
daemonize yes
logfile /var/lib/sentinel_26380.log
sentinel monitor mymaster 127.0.0.1 6379 2

# sentinel_26381.conf
port 26381
daemonize yes
logfile /var/lib/sentinel_26381.log
sentinel monitor mymaster 127.0.0.1 6379 2
           

并啟動以上三個哨兵

sudo redis-sentinel /etc/redis/sentinel_26379.conf
sudo redis-sentinel /etc/redis/sentinel_26380.conf
sudo redis-sentinel /etc/redis/sentinel_26381.conf
           

檢視哨兵運作狀态:

吃透Redis系列(十一):Jedis和Lettuce用戶端詳細介紹

jedis通路:

@Test
    public void testSentinel(){
        Set<String> set = new HashSet<String>();
        set.add("127.0.0.1:26379");
        set.add("127.0.0.1:26380");
        set.add("127.0.0.1:26381");
        JedisSentinelPool sentinelPool = new JedisSentinelPool("mymaster", set);
        Jedis jedis = sentinelPool.getResource();
        jedis.set("name","bo");
        System.out.println(jedis.get("name"));
    }
           

運作輸出:

bo
           

6,叢集

部署redis cluster叢集

redis cluster叢集的部署詳細介紹參考:吃透Redis系列(八):叢集詳細介紹

部署叢集,其中7000,7001,7002是三個主節點

8000,8001,8002是三個從節點

搭建完成之後,檢視目前叢集節點狀态:

吃透Redis系列(十一):Jedis和Lettuce用戶端詳細介紹

Jedis通路叢集:

@Test
    public void testCluster(){
        Set<HostAndPort> set = new HashSet<HostAndPort>();
        set.add(new HostAndPort("127.0.0.1",7000));
        set.add(new HostAndPort("127.0.0.1",7001));
        set.add(new HostAndPort("127.0.0.1",7002));
        JedisCluster jedisCluster = new JedisCluster(set);
        jedisCluster.set("name","helloword");
        System.out.println(jedisCluster.get("name"));
    }
           

運作輸出:

helloword
           

二,Lettuce使用

Lettuce是一個高性能基于Java編寫的Redis驅動架構,底層內建了Project Reactor提供天然的反應式程式設計,通信架構內建了Netty使用了非阻塞IO,5.x版本之後融合了JDK1.8的異步程式設計特性,在保證高性能的同時提供了十分豐富易用的API。

Lettuce

使用的時候依賴于四個主要元件:

  • RedisURI

    :連接配接資訊。
  • RedisClient

    Redis

    用戶端,特殊地,叢集連接配接有一個定制的

    RedisClusterClient

  • Connection

    Redis

    連接配接,主要是

    StatefulConnection

    或者

    StatefulRedisConnection

    的子類,連接配接的類型主要由連接配接的具體方式(單機、哨兵、叢集、訂閱釋出等等)標明,比較重要。
  • RedisCommands

    Redis

    指令

    API

    接口,基本上覆寫了

    Redis

    發行版本的所有指令,提供了同步(

    sync

    )、異步(

    async

    )、反應式(

    reative

    )的調用方式,對于使用者而言,會經常跟

    RedisCommands

    系列接口打交道。

使用到的軟體版本:Java 1.8.0_191、Redis 6.0.6、lettuce 5.3.1.RELEASE。

Lettuce内容參考連接配接:https://www.cnblogs.com/throwable/p/11601538.html#lettuce

1,Lettuce簡單使用

導入maven包

<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>5.3.1.RELEASE</version>
</dependency>
           

基本使用

@Test
    public void testSetGet() {
        // 連接配接資訊
        RedisURI redisURI = RedisURI.builder().withHost("127.0.0.1")
                .withPort(6379)
                .withTimeout(Duration.of(10, ChronoUnit.SECONDS))
                .build();
        // redis用戶端
        RedisClient redisClient = RedisClient.create(redisURI);
        // 建立redis連接配接
        StatefulRedisConnection<String, String> connect = redisClient.connect();
        // 同步調用方式
        RedisCommands<String, String> redisCommands = connect.sync();
        // set參數,設定5秒過期時間
        SetArgs setArgs = SetArgs.Builder.nx().ex(5);
        // 設定key,value
        redisCommands.set("name", "bobo", setArgs);
        // 擷取name的值
        System.out.println(redisCommands.get("name"));


        // 關閉連接配接
        connect.close();
        // 關閉用戶端
        redisClient.shutdown();

    }
           

運作輸出:

bobo
           

2,API

Lettuce

主要提供三種

API

  • 同步(

    sync

    ):

    RedisCommands

  • 異步(

    async

    ):

    RedisAsyncCommands

  • 反應式(

    reactive

    ):

    RedisReactiveCommands

先準備好一個單機

Redis

連接配接備用:

// redis用戶端
    private RedisClient redisClient;
    // redis連接配接
    private StatefulRedisConnection<String, String> connection;

    @Before
    public void before() {
        RedisURI redisURI = RedisURI.builder().withHost("127.0.0.1")
                .withPort(6379)
                .withTimeout(Duration.of(10, ChronoUnit.SECONDS))
                .build();
        // redis用戶端
        redisClient = RedisClient.create(redisURI);
        // 建立redis連接配接
        connection = redisClient.connect();
    }

    @After
    public void after() {
        connection.close();
        redisClient.shutdown();
    }
           

Redis

指令

API

的具體實作可以直接從

StatefulRedisConnection

執行個體擷取,見其接口定義:

public interface StatefulRedisConnection<K, V> extends StatefulConnection<K, V> {

    boolean isMulti();

    RedisCommands<K, V> sync();

    RedisAsyncCommands<K, V> async();

    RedisReactiveCommands<K, V> reactive();
}    
           

值得注意的是,在不指定編碼解碼器

RedisCodec

的前提下,

RedisClient

建立的

StatefulRedisConnection

執行個體一般是泛型執行個體

StatefulRedisConnection<String,String>

,也就是所有指令

API

KEY

VALUE

都是

String

類型,這種使用方式能滿足大部分的使用場景。當然,必要的時候可以定制編碼解碼器

RedisCodec<K,V>

2.1,同步API

同步

API

在所有指令調用之後會立即傳回結果。和上一篇介紹的

Jedis

差不多,

RedisCommands

的用法其實和它相差不大。

@Test
    public void testSync(){
        RedisCommands<String, String> redisCommands = connection.sync();
        redisCommands.set("name","bobo");
        System.out.println(redisCommands.get("name"));
    }
           

2.2,異步API

@Test
    public void testAsync() throws ExecutionException, InterruptedException {
        RedisAsyncCommands<String, String> redisAsyncCommands = connection.async();
        RedisFuture<String> redisFuture = redisAsyncCommands.set("name", "bobo");
        System.out.println(redisFuture.get());
        System.out.println("get2:" + redisAsyncCommands.get("name").get());
    }
           

RedisAsyncCommands

所有方法執行傳回結果都是

RedisFuture

執行個體,而

RedisFuture

接口的定義如下:

public interface RedisFuture<V> extends CompletionStage<V>, Future<V> {

    String getError();

    boolean await(long timeout, TimeUnit unit) throws InterruptedException;
}    
           

也就是,

RedisFuture

可以無縫使用

Future

或者

JDK

1.8中引入的

CompletableFuture

提供的方法。

2.3,反應式API

Lettuce

引入的反應式程式設計架構是Project Reactor,如果沒有反應式程式設計經驗可以先自行了解一下

Project Reactor

建構

RedisReactiveCommands

執行個體:

@Test
    public void testReactive(){
        RedisReactiveCommands<String, String> redisReactiveCommands = connection.reactive();
        redisReactiveCommands.set("name", "bobo");
        System.out.println(redisReactiveCommands.get("name"));

        redisReactiveCommands.sadd("fruit","apple","banana");
        System.out.println(redisReactiveCommands.smembers("fruit"));
    }
           

運作輸出:

MonoFromPublisher
FluxSource
           

RedisReactiveCommands

的方法如果傳回的結果隻包含0或1個元素,那麼傳回值類型是

Mono

,如果傳回的結果包含0到N(N大于0)個元素,那麼傳回值是

Flux

3,釋出/訂閱

非叢集模式下的釋出訂閱依賴于定制的連接配接

StatefulRedisPubSubConnection

,叢集模式下的釋出訂閱依賴于定制的連接配接

StatefulRedisClusterPubSubConnection

,兩者分别來源于

RedisClient#connectPubSub()

系列方法和

RedisClusterClient#connectPubSub()

非叢集模式:

// 可能是單機、普通主從、哨兵等非叢集模式的用戶端
RedisClient client = ...
StatefulRedisPubSubConnection<String, String> connection = client.connectPubSub();
connection.addListener(new RedisPubSubListener<String, String>() { ... });

// 同步指令
RedisPubSubCommands<String, String> sync = connection.sync();
sync.subscribe("channel");

// 異步指令
RedisPubSubAsyncCommands<String, String> async = connection.async();
RedisFuture<Void> future = async.subscribe("channel");

// 反應式指令
RedisPubSubReactiveCommands<String, String> reactive = connection.reactive();
reactive.subscribe("channel").subscribe();

reactive.observeChannels().doOnNext(patternMessage -> {...}).subscribe()
           

叢集模式:

// 使用方式其實和非叢集模式基本一緻
RedisClusterClient clusterClient = ...
StatefulRedisClusterPubSubConnection<String, String> connection = clusterClient.connectPubSub();
connection.addListener(new RedisPubSubListener<String, String>() { ... });
RedisPubSubCommands<String, String> sync = connection.sync();
sync.subscribe("channel");
// ...
           

4,管道

Redis

Pipeline

也就是管道機制可以了解為把多個指令打包在一次請求發送到

Redis

服務端,然後

Redis

服務端把所有的響應結果打包好一次性傳回,進而節省不必要的網絡資源(最主要是減少網絡請求次數)。

Redis

對于

Pipeline

機制如何實作并沒有明确的規定,也沒有提供特殊的指令支援

Pipeline

機制。

Jedis

中底層采用

BIO

(阻塞IO)通訊,是以它的做法是用戶端緩存将要發送的指令,最後需要觸發然後同步發送一個巨大的指令清單包,再接收和解析一個巨大的響應清單包。

Pipeline

Lettuce

中對使用者是透明的,由于底層的通訊架構是

Netty

,是以網絡通訊層面的優化

Lettuce

不需要過多幹預,換言之可以這樣了解:

Netty

Lettuce

從底層實作了

Redis

Pipeline

機制。

5,事物

事務相關的指令就是

WATCH

UNWATCH

EXEC

MULTI

DISCARD

,在

RedisCommands

系列接口中有對應的方法。舉個例子:

@Test
    public void testMulti() {
        RedisCommands<String, String> redisCommands = connection.sync();
        redisCommands.multi();
        redisCommands.set("name", "bobo");
        redisCommands.set("age", "18");
        redisCommands.exec();
        System.out.println(redisCommands.get("name"));
        System.out.println(redisCommands.get("age"));
    }
           

運作輸出:

bobo
18
           

6,普通主從模式

假設現在有三個

Redis

服務形成樹狀主從關系如下:

  • 節點一:localhost:6379,角色為Master。
  • 節點二:localhost:6380,角色為Slavor,節點一的從節點。
  • 節點三:localhost:6381,角色為Slavor,節點二的從節點。
@Test
public void testStaticReplica() throws Exception {
    List<RedisURI> uris = new ArrayList<>();
    RedisURI uri1 = RedisURI.builder().withHost("localhost").withPort(6379).build();
    RedisURI uri2 = RedisURI.builder().withHost("localhost").withPort(6380).build();
    RedisURI uri3 = RedisURI.builder().withHost("localhost").withPort(6381).build();
    uris.add(uri1);
    uris.add(uri2);
    uris.add(uri3);
    RedisClient redisClient = RedisClient.create();
    StatefulRedisMasterSlaveConnection<String, String> connection = MasterSlave.connect(redisClient,
            new Utf8StringCodec(), uris);
    // 隻從主節點讀取資料
    connection.setReadFrom(ReadFrom.MASTER);
    // 執行其他Redis指令
    connection.close();
    redisClient.shutdown();
}
           

7,哨兵

由于

Lettuce

自身提供了哨兵的拓撲發現機制,是以隻需要随便配置一個哨兵節點的

RedisURI

執行個體即可:

@Test
public void testDynamicSentinel() throws Exception {
    RedisURI redisUri = RedisURI.builder()
            .withPassword("你的密碼")
            .withSentinel("localhost", 26379)
            .withSentinelMasterId("哨兵Master的ID")
            .build();
    RedisClient redisClient = RedisClient.create();
    StatefulRedisMasterSlaveConnection<String, String> connection = MasterSlave.connect(redisClient, new Utf8StringCodec(), redisUri);
    // 隻允許從從節點讀取資料
    connection.setReadFrom(ReadFrom.SLAVE);
    RedisCommands<String, String> command = connection.sync();
    SetArgs setArgs = SetArgs.Builder.nx().ex(5);
    command.set("name", "throwable", setArgs);
    String value = command.get("name");
    log.info("Get value:{}", value);
}
           

8,叢集

下面的API提供跨槽位(

Slot

)調用的功能:

  • RedisAdvancedClusterCommands

  • RedisAdvancedClusterAsyncCommands

  • RedisAdvancedClusterReactiveCommands

靜态節點選擇功能:

  • masters

    :選擇所有主節點執行指令。
  • slaves

    :選擇所有從節點執行指令,其實就是隻讀模式。
  • all nodes

    :指令可以在所有節點執行。

叢集拓撲視圖動态更新功能:

  • 手動更新,主動調用

    RedisClusterClient#reloadPartitions()

  • 背景定時更新。
  • 自适應更新,基于連接配接斷開和

    MOVED/ASK

    指令重定向自動更新。

簡單的叢集連接配接和使用方式如下:

@Test
    public void testCluster() {
        Set<RedisURI> redisURISet = new HashSet<>();
        redisURISet.add(RedisURI.builder().withHost("127.0.0.1").withPort(7000).build());
        redisURISet.add(RedisURI.builder().withHost("127.0.0.1").withPort(7001).build());
        redisURISet.add(RedisURI.builder().withHost("127.0.0.1").withPort(7002).build());
        RedisClusterClient redisClusterClient = RedisClusterClient.create(redisURISet);
        StatefulRedisClusterConnection<String, String> connect = redisClusterClient.connect();
        RedisAdvancedClusterCommands<String, String> clusterCommands = connect.sync();
        clusterCommands.set("name","bobo");
        System.out.println(clusterCommands.get("name"));
    }
           

叢集節點資訊如下:

吃透Redis系列(十一):Jedis和Lettuce用戶端詳細介紹

繼續閱讀