首頁 > 軟體

Redis實戰之Lettuce的使用技巧詳解

2022-12-25 14:03:53

一、摘要

Lettuce 是 Redis 的一款高階 Java 使用者端,與 Jedis 並列成為最熱門的使用者端之一,目前已成為 SpringBoot 2.0 版本預設的 redis 使用者端。

相比老牌 Jedis,Lettuce 屬於後起之秀,不僅功能豐富,而且提供了很多新的功能特性,比如非同步操作、響應式程式設計等等,同時還解決了 Jedis 中執行緒不安全的問題。

廢話不多說了,如何使用呢?請看下文!

二、Lettuce

2.1、基本使用

首先,建立一個 maven 專案,引入lettuce-core包,就可以使用了。

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

使用 lettuce 連線 redis,測試是否能正常聯通!

public class LettuceMain {

    public static void main(String[] args) {
        RedisURI redisUri = RedisURI.builder()
                .withHost("127.0.0.1")
                .withPort(6379)
                .withPassword("111111")
                .withTimeout(Duration.of(10, ChronoUnit.SECONDS))
                .build();
        RedisClient redisClient = RedisClient.create(redisUri);
        StatefulRedisConnection<String, String> connection = redisClient.connect();
        RedisCommands<String, String> commands = connection.sync();
        System.out.println(commands.ping());
        connection.close();
        redisClient.shutdown();
    }
}

2.2、同步操作

基本上只要是 Jedis 支援的同步命令操作,Lettuce 都支援。

下面,我們以同步操作字串為例,Lettuce 的 api 操作如下!

public class LettuceSyncMain {

    public static void main(String[] args) {
        RedisURI redisUri = RedisURI.builder()
                .withHost("127.0.0.1").withPort(6379).withPassword("111111")
                .withTimeout(Duration.of(10, ChronoUnit.SECONDS))
                .build();
        RedisClient redisClient = RedisClient.create(redisUri);
        StatefulRedisConnection<String, String> connection = redisClient.connect();
        //獲取同步操作命令工具
        RedisCommands<String, String> commands = connection.sync();

        System.out.println("清空資料:"+commands.flushdb());
        System.out.println("判斷某個鍵是否存在:"+commands.exists("username"));
        System.out.println("新增<'username','xmr'>的鍵值對:"+commands.set("username", "xmr"));
        System.out.println("新增<'password','password'>的鍵值對:"+commands.set("password", "123"));
        System.out.println("獲取<'password'>鍵的值:"+commands.get("password"));
        System.out.println("系統中所有的鍵如下:" + commands.keys("*"));
        System.out.println("刪除鍵password:"+commands.del("password"));
        System.out.println("判斷鍵password是否存在:"+commands.exists("password"));
        System.out.println("設定鍵username的過期時間為5s:"+commands.expire("username", 5L));
        System.out.println("檢視鍵username的剩餘生存時間:"+commands.ttl("username"));
        System.out.println("移除鍵username的生存時間:"+commands.persist("username"));
        System.out.println("檢視鍵username的剩餘生存時間:"+commands.ttl("username"));
        System.out.println("檢視鍵username所儲存的值的型別:"+commands.type("username"));

        connection.close();
        redisClient.shutdown();
    }
}

2.3、非同步操作

除此之外,Lettuce 還支援非同步操作,將上面的操作改成非同步處理,結果如下!

public class LettuceASyncMain {

    public static void main(String[] args) throws Exception {
        RedisURI redisUri = RedisURI.builder()
                .withHost("127.0.0.1").withPort(6379).withPassword("111111")
                .withTimeout(Duration.of(10, ChronoUnit.SECONDS))
                .build();
        RedisClient redisClient = RedisClient.create(redisUri);
        StatefulRedisConnection<String, String> connection = redisClient.connect();
        //獲取非同步操作命令工具
        RedisAsyncCommands<String, String> commands = connection.async();

        System.out.println("清空資料:"+commands.flushdb().get());
        System.out.println("判斷某個鍵是否存在:"+commands.exists("username").get());
        System.out.println("新增<'username','xmr'>的鍵值對:"+commands.set("username", "xmr").get());
        System.out.println("新增<'password','password'>的鍵值對:"+commands.set("password", "123").get());
        System.out.println("獲取<'password'>鍵的值:"+commands.get("password").get());
        System.out.println("系統中所有的鍵如下:" + commands.keys("*").get());
        System.out.println("刪除鍵password:"+commands.del("password").get());
        System.out.println("判斷鍵password是否存在:"+commands.exists("password").get());
        System.out.println("設定鍵username的過期時間為5s:"+commands.expire("username", 5L).get());
        System.out.println("檢視鍵username的剩餘生存時間:"+commands.ttl("username").get());
        System.out.println("移除鍵username的生存時間:"+commands.persist("username").get());
        System.out.println("檢視鍵username的剩餘生存時間:"+commands.ttl("username").get());
        System.out.println("檢視鍵username所儲存的值的型別:"+commands.type("username").get());

        connection.close();
        redisClient.shutdown();
    }
}

2.4、響應式程式設計

Lettuce 除了支援非同步程式設計以外,還支援響應式程式設計,Lettuce 引入的響應式程式設計框架是Project Reactor,如果沒有響應式程式設計經驗可以先自行了解一下,存取地址https://projectreactor.io/

響應式程式設計使用案例如下:

public class LettuceMain {

    public static void main(String[] args) throws Exception {
        RedisURI redisUri = RedisURI.builder()
                .withHost("127.0.0.1").withPort(6379).withPassword("111111")
                .withTimeout(Duration.of(10, ChronoUnit.SECONDS))
                .build();
        RedisClient redisClient = RedisClient.create(redisUri);
        StatefulRedisConnection<String, String> connection = redisClient.connect();
        //獲取響應式API操作命令工具
        RedisReactiveCommands<String, String> commands = connection.reactive();

        Mono<String> setc = commands.set("name", "mayun");
        System.out.println(setc.block());
        Mono<String> getc = commands.get("name");
        getc.subscribe(System.out::println);
        Flux<String> keys = commands.keys("*");
        keys.subscribe(System.out::println);

        //開啟一個事務,先把count設定為1,再將count自增1
        commands.multi().doOnSuccess(r -> {
            commands.set("count", "1").doOnNext(value -> System.out.println("count1:" +  value)).subscribe();
            commands.incr("count").doOnNext(value -> System.out.println("count2:" +  value)).subscribe();
        }).flatMap(s -> commands.exec())
                .doOnNext(transactionResult -> System.out.println("transactionResult:" + transactionResult.wasDiscarded())).subscribe();

        Thread.sleep(1000 * 5);
        connection.close();
        redisClient.shutdown();
    }
}

2.5、釋出和訂閱

Lettuce 還支援 redis 的訊息釋出和訂閱,具體實現案例如下:

public class LettuceReactiveMain1 {

    public static void main(String[] args) throws Exception {
        RedisURI redisUri = RedisURI.builder()
                .withHost("127.0.0.1").withPort(6379).withPassword("111111")
                .withTimeout(Duration.of(10, ChronoUnit.SECONDS))
                .build();
        RedisClient redisClient = RedisClient.create(redisUri);
        //獲取釋出訂閱操作命令工具
        StatefulRedisPubSubConnection<String, String> pubsubConn = redisClient.connectPubSub();
        pubsubConn.addListener(new RedisPubSubListener<String, String>() {
            @Override
            public void unsubscribed(String channel, long count) {
                System.out.println("[unsubscribed]" + channel);
            }
            @Override
            public void subscribed(String channel, long count) {
                System.out.println("[subscribed]" + channel);
            }
            @Override
            public void punsubscribed(String pattern, long count) {
                System.out.println("[punsubscribed]" + pattern);
            }
            @Override
            public void psubscribed(String pattern, long count) {
                System.out.println("[psubscribed]" + pattern);
            }
            @Override
            public void message(String pattern, String channel, String message) {
                System.out.println("[message]" + pattern + " -> " + channel + " -> " + message);
            }
            @Override
            public void message(String channel, String message) {
                System.out.println("[message]" + channel + " -> " + message);
            }
        });
        RedisPubSubAsyncCommands<String, String> pubsubCmd = pubsubConn.async();
        pubsubCmd.psubscribe("CH");
        pubsubCmd.psubscribe("CH2");
        pubsubCmd.unsubscribe("CH");

        Thread.sleep(100 * 5);
        pubsubConn.close();
        redisClient.shutdown();
    }
}

2.6、使用者端資源與引數設定

Lettuce 使用者端的通訊框架整合了 Netty 的非阻塞 IO 操作,使用者端資源的設定與 Lettuce 的效能、並行和事件處理緊密相關,如果不是特別熟悉使用者端引數設定,不建議在沒有經驗的前提下憑直覺修改預設值,保持預設設定就行。

非叢集環境下,具體的設定案例如下:

public class LettuceMain {

    public static void main(String[] args) throws Exception {
        ClientResources resources = DefaultClientResources.builder()
                .ioThreadPoolSize(4) //I/O執行緒數
                .computationThreadPoolSize(4) //任務執行緒數
                .build();
        RedisURI redisUri = RedisURI.builder()
                .withHost("127.0.0.1").withPort(6379).withPassword("111111")
                .withTimeout(Duration.of(10, ChronoUnit.SECONDS))
                .build();
        ClientOptions options = ClientOptions.builder()
                .autoReconnect(true)//是否自動重連
                .pingBeforeActivateConnection(true)//連線啟用之前是否執行PING命令
                .build();
        RedisClient client = RedisClient.create(resources, redisUri);
        client.setOptions(options);
        StatefulRedisConnection<String, String> connection = client.connect();
        RedisCommands<String, String> commands = connection.sync();
        commands.set("name", "關羽");
        System.out.println(commands.get("name"));

        connection.close();
        client.shutdown();
        resources.shutdown();
    }
}

叢集環境下,具體的設定案例如下:

public class LettuceMain {

    public static void main(String[] args) throws Exception {
        ClientResources resources = DefaultClientResources.builder()
                .ioThreadPoolSize(4) //I/O執行緒數
                .computationThreadPoolSize(4) //任務執行緒數
                .build();
        RedisURI redisUri = RedisURI.builder()
                .withHost("127.0.0.1").withPort(6379).withPassword("111111")
                .withTimeout(Duration.of(10, ChronoUnit.SECONDS))
                .build();
        ClusterClientOptions options = ClusterClientOptions.builder()
                .autoReconnect(true)//是否自動重連
                .pingBeforeActivateConnection(true)//連線啟用之前是否執行PING命令
                .validateClusterNodeMembership(true)//是否校驗叢集節點的成員關係
                .build();
        RedisClusterClient client = RedisClusterClient.create(resources, redisUri);
        client.setOptions(options);
        StatefulRedisClusterConnection<String, String> connection = client.connect();
        RedisAdvancedClusterCommands<String, String> commands = connection.sync();
        commands.set("name", "張飛");
        System.out.println(commands.get("name"));

        connection.close();
        client.shutdown();
        resources.shutdown();
    }
}

2.7、執行緒池設定

Lettuce 連線設計的時候,就是執行緒安全的,所以一個連線可以被多個執行緒共用,同時 lettuce 連線預設是自動重連的,使用單連線基本可以滿足業務需求,大多數情況下不需要設定連線池,多連線並不會給操作帶來效能上的提升。

但在某些特殊場景下,比如事物操作,使用連線池會是一個比較好的方案,那麼如何設定執行緒池呢?

public class LettuceMain {

    public static void main(String[] args) throws Exception {
        RedisURI redisUri = RedisURI.builder()
                .withHost("127.0.0.1")
                .withPort(6379)
                .withPassword("111111")
                .withTimeout(Duration.of(10, ChronoUnit.SECONDS))
                .build();
        RedisClient client = RedisClient.create(redisUri);
        //連線池設定
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxIdle(2);

        GenericObjectPool<StatefulRedisConnection<String, String>> pool = ConnectionPoolSupport.createGenericObjectPool(client::connect, poolConfig);
        StatefulRedisConnection<String, String> connection = pool.borrowObject();
        RedisCommands<String, String> commands = connection.sync();
        commands.set("name", "張飛");
        System.out.println(commands.get("name"));

        connection.close();
        pool.close();
        client.shutdown();
    }
}

2.8、主從模式設定

redis 一般採用主從複製模式,搭建高可用的架構,簡單的說就一個主節點,多個從節點,自動從主節點同步最新資料。

Lettuce 支援自動發現主從模式下的節點資訊,然後儲存到本地,具體設定如下:

public class LettuceMain {

    public static void main(String[] args) throws Exception {
        //這裡只需要設定一個節點的連線資訊,不一定需要是主節點的資訊,從節點也可以;可以自動發現主從節點
        RedisURI uri = RedisURI.builder().withHost("192.168.31.111").withPort(6379).withPassword("123456").build();
        RedisClient client = RedisClient.create(uri);
        StatefulRedisMasterReplicaConnection<String, String> connection = MasterReplica.connect(client, StringCodec.UTF8, uri);
        //從節點讀取資料
        connection.setReadFrom(ReadFrom.REPLICA);

        RedisCommands<String, String> commands = connection.sync();
        commands.set("name", "張飛");
        System.out.println(commands.get("name"));

        connection.close();
        client.shutdown();
    }
}

當然我們也可以手動指定叢集節點來載入,具體設定如下:

public class LettuceMain {

    public static void main(String[] args) throws Exception {
        //叢集節點
        List<RedisURI> uris = new ArrayList();
        uris.add(RedisURI.builder().withHost("192.168.31.111").withPort(6379).withPassword("111111").build());
        uris.add(RedisURI.builder().withHost("192.168.31.112").withPort(6379).withPassword("111111").build());
        uris.add(RedisURI.builder().withHost("192.168.31.113").withPort(6379).withPassword("111111").build());

        RedisClient client = RedisClient.create();
        StatefulRedisMasterReplicaConnection<String, String> connection = MasterReplica.connect(client, StringCodec.UTF8, uris);
        //從節點讀取資料
        connection.setReadFrom(ReadFrom.REPLICA);

        RedisCommands<String, String> commands = connection.sync();
        commands.set("name", "張飛");
        System.out.println(commands.get("name"));

        connection.close();
        client.shutdown();
    }
}

2.9、哨兵模式設定

哨兵模式,也是 redis 實現服務高可用的一大亮點,具體設定實現如下:

public class LettuceMain {

    public static void main(String[] args) throws Exception {
        //叢集節點
        List<RedisURI> uris = new ArrayList();
        uris.add(RedisURI.builder().withSentinel("192.168.31.111", 26379).withSentinelMasterId("mymaster").withPassword("123456").build());
        uris.add(RedisURI.builder().withSentinel("192.168.31.112", 26379).withSentinelMasterId("mymaster").withPassword("123456").build());
        uris.add(RedisURI.builder().withSentinel("192.168.31.113", 26379).withSentinelMasterId("mymaster").withPassword("123456").build());

        RedisClient client = RedisClient.create();
        StatefulRedisMasterReplicaConnection<String, String> connection = MasterReplica.connect(client, StringCodec.UTF8, uris);
        //從節點讀取資料
        connection.setReadFrom(ReadFrom.REPLICA);

        RedisCommands<String, String> commands = connection.sync();
        commands.set("name", "趙雲");
        System.out.println(commands.get("name"));

        connection.close();
        client.shutdown();
    }
}

2.10、Cluster 叢集模式設定

Cluster 叢集模式,是之後推出的一種高可用的架構模型,主要是採用分片方式來儲存資料,具體設定如下:

public class LettuceReactiveMain4 {

    public static void main(String[] args) throws Exception {
        Set<RedisURI> uris = new HashSet<>();
        uris.add(RedisURI.builder().withHost("192.168.31.111").withPort(7000).withPassword("123456").build());
        uris.add(RedisURI.builder().withHost("192.168.31.112").withPort(7000).withPassword("123456").build());
        uris.add(RedisURI.builder().withHost("192.168.31.113").withPort(7000).withPassword("123456").build());
        uris.add(RedisURI.builder().withHost("192.168.31.114").withPort(7000).withPassword("123456").build());
        uris.add(RedisURI.builder().withHost("192.168.31.115").withPort(7000).withPassword("123456").build());
        uris.add(RedisURI.builder().withHost("192.168.31.116").withPort(7001).withPassword("123456").build());

        RedisClusterClient client = RedisClusterClient.create(uris);
        StatefulRedisClusterConnection<String, String> connection = client.connect();
        RedisAdvancedClusterCommands<String, String> commands = connection.sync();
        commands.set("name", "關羽");
        System.out.println(commands.get("name"));

        //選擇從節點,唯讀
        NodeSelection<String, String> replicas = commands.replicas();
        NodeSelectionCommands<String, String> nodeSelectionCommands = replicas.commands();
        Executions<List<String>> keys = nodeSelectionCommands.keys("*");
        keys.forEach(key -> System.out.println(key));

        connection.close();
        client.shutdown();
    }
}

三、小結

Lettuce 相比老牌的 Jedis 使用者端,功能更加強大,不僅解決了執行緒安全的問題,還支援非同步和響應式程式設計,支援叢集,Sentinel,管道和編碼器等等功能。

以上介紹的可能只是冰山一角,如果想要了解更多的資訊,可以存取它的官網地址:https://lettuce.io/

到此這篇關於Redis實戰之Lettuce的使用技巧詳解的文章就介紹到這了,更多相關Redis Lettuce內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com