Redis实战之Lettuce的使用技巧详解


Posted in Redis onDecember 24, 2022

一、摘要

Lettuce 是 Redis 的一款高级 Java 客户端,与 Jedis 并列成为最热门的客户端之一,目前已成为 SpringBoot 2.0 版本默认的 redis 客户端。

Redis实战之Lettuce的使用技巧详解

相比老牌 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内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
redis限流的实际应用
Apr 24 Redis
Redis数据结构之链表与字典的使用
May 11 Redis
详解Redis基本命令与使用场景
Jun 01 Redis
详解Redis复制原理
Jun 04 Redis
redis cluster支持pipeline的实现思路
Jun 23 Redis
缓存替换策略及应用(以Redis、InnoDB为例)
Jul 25 Redis
springboot使用Redis作缓存使用入门教程
Jul 25 Redis
为什么RedisCluster设计成16384个槽
Sep 25 Redis
redis击穿 雪崩 穿透超详细解决方案梳理
Mar 17 Redis
详解Redis的三种常用的缓存读写策略步骤
May 06 Redis
Redis 报错 error:NOAUTH Authentication required
May 15 Redis
Redis特殊数据类型bitmap位图
Jun 01 Redis
python中使用redis用法详解
Dec 24 #Redis
Redis主从复制操作和配置详情
Sep 23 #Redis
基于Redission的分布式锁实战
基于redis+lua进行限流的方法
Jul 23 #Redis
Redis过期数据是否会被立马删除
Jul 23 #Redis
如何使用注解方式实现 Redis 分布式锁
Jul 23 #Redis
redis lua限流算法实现示例
You might like
PH P5.2至5.5、5.6的新增功能详解
2014/07/14 PHP
微信公众号开发之文本消息自动回复php代码
2016/08/08 PHP
PHPTree――php快速生成无限级分类
2018/03/30 PHP
javascript 写类方式之一
2009/07/05 Javascript
js下拉框二级关联菜单效果代码具体实现
2013/08/03 Javascript
jquery实现图片灯箱明暗的遮罩效果
2013/11/15 Javascript
js验证整数加保留小数点的简单实例
2013/12/02 Javascript
js 本地预览的简单实现方法
2014/02/18 Javascript
readonly和disabled属性的区别
2015/07/26 Javascript
PHP+jQuery+Ajax+Mysql如何实现发表心情功能
2015/08/06 Javascript
jquery插件pagination实现无刷新ajax分页
2015/09/30 Javascript
CascadeView级联组件实现思路详解(分离思想和单链表)
2016/04/12 Javascript
jQuery用FormData实现文件上传的方法
2016/11/21 Javascript
Angular 通过注入 $location 获取与修改当前页面URL的实例
2017/05/31 Javascript
VUE利用vuex模拟实现新闻点赞功能实例
2017/06/28 Javascript
自适应布局meta标签中viewport、content、width、initial-scale、minimum-scale、maximum-scale总结
2017/08/18 Javascript
浅谈在Vue-cli里基于axios封装复用请求
2017/11/06 Javascript
详解vue mixins和extends的巧妙用法
2017/12/20 Javascript
浅谈Vue服务端渲染框架Nuxt的那些事
2018/12/21 Javascript
JQuery实现简单的复选框树形结构图示例【附源码下载】
2019/07/16 jQuery
在JavaScript中实现链式调用的实现
2019/12/24 Javascript
微信小程序动态添加和删除组件的现实
2020/02/28 Javascript
Python2实现的LED大数字显示效果示例
2017/09/04 Python
python 将有序数组转换为二叉树的方法
2019/03/26 Python
节日快乐! Python画一棵圣诞树送给你
2019/12/24 Python
Python 中@property的用法详解
2020/01/15 Python
用什么库写 Python 命令行程序(示例代码详解)
2020/02/20 Python
使用Nibabel库对nii格式图像的读写操作
2020/07/01 Python
python开根号实例讲解
2020/08/30 Python
美国最大的在线水培用品商店:GrowersHouse.com
2018/08/14 全球购物
光电信息专业应届生求职信
2013/10/07 职场文书
学雷锋先进个人事迹
2014/05/26 职场文书
学校门卫岗位职责范本
2014/06/30 职场文书
电工实训报告总结
2014/11/05 职场文书
初二数学教学反思
2016/02/17 职场文书
解除合同协议书范本
2016/03/21 职场文书