Spring Boot:Lettuce 原生 API 与 RedisTemplate 对比
Spring Boot 中默认使用 Lettuce 作为 redis-cli 的底层实现。 Lettuce 基于 Netty 实现,提供了相对丰富地同步、异步操作接口。 今天我们将主要对比一下 Lettuce 原生 API 与 spring-boot-data-redis 对其封装后的接口(主要是 RedisTemplate)的使用。
01-Lettuce 原生 API
Lettuce 暴露给用户的接口是非常直观的,针对单实例、集群场景,提供了两个 client 接口:
- io.lettuce.core.RedisClient
- io.lettuce.core.cluster.RedisClusterClient
它们都继承自 io.lettuce.core.AbstractRedisClient,封装了 Netty、ClientOptions(控制 client 行为,例如重连、超时等)和连接过程的实现。
RedisClient 对象的创建是非常简单的:
final RedisURI uri = RedisURI.builder().withPassword("redis123").withHost("example.samson.self").withPort(6379).withDatabase(0).build();
final RedisClient redisClient = RedisClient.create(uri);
/*** 上述 uri 对象等价于:* redis://redis123@example.samson.self:6379/0**/
复制代码
创建了 RedisClient 实例后,可以通过 RedisClient#connect() 方法获得 StatefulRedisConnection 连接对象。 获得连接对象后,可以通过如下接口进行同步、异步及响应式命令的执行:
final StatefulRedisConnection<String, String> connection = client.connect();
final RedisCommands<String, String> sync = connection.sync(); // 同步操作
final RedisAsyncCommands<String, String> async = connection.async(); // 异步操作
final RedisReactiveCommands<String, String> reactive = connection.reactive(); // 响应式操作
复制代码
下文以同步操作为例,演示下通过 Lettuce 原生 API 对 Redis 基本数据结构的操作。
// 字符串
sync.set("str", "str_value");
final String strValue = sync.get("str");
System.out.println("strValue: " + strValue);// 列表
sync.lpush("list", "value1", "value2");
final Long listLen = sync.llen("list");
final List<String> allList = sync.lrange("list", 0, listLen);
System.out.println("allList: " + String.join(",", allList));// 集合
sync.sadd("set", "set1", "set2");
final Long setSize = sync.scard("set");
final Set<String> allSet = sync.smembers("set");
System.out.println("setSize: " + setSize);
System.out.println("allSet: " + String.join(",", allSet));// 哈希表
sync.hset("hashset", "key1", "value1");
sync.hset("hashset", "key2", "value2");
sync.hset("hashset", "key3", "value3");
final Long hlen = sync.hlen("hashset");
final Map<String, String> allHashset = sync.hgetall("hashset");
System.out.println("hlen: " + hlen);
System.out.println("allHashset: " + allHashset.entrySet().stream().map(k -> String.format("%s->%s", k.getKey(), k.getValue())).collect(Collectors.joining(",")));
复制代码
01.1-使用 Lettuce 原生 API 实现订阅、发布功能
发布功能比较容易实现,只需要通过 RedisCommands#publish 接口向指定的 channel 发布消息即可。
final Long published = sync.publish("topic:foo", "hello, from idea!");
复制代码
相比于发布,订阅实现则比较复杂。 Lettuce 提供了 RedisPubSubListener 接口,作为 channel 的监听器。 当特定事件(例如订阅、取消订阅、收到消息等)发生时,回调监听器的特定方法。 接下来,我将通过一个实例来演示,如果订阅一个 channel,并在收到消息时将消息打印到控制台。
RedisPubSubAdapter 是 Lettuce 提供的一个工具类,它实现了 RedisPubSubListener 接口,不过所有的方法都是空实现。
final LinkedBlockingQueue<String> messages = LettuceFactories.newBlockingQueue(); // 阻塞队列,用来存储 channel 中收到的消息final RedisClient client = getClient();
final StatefulRedisPubSubConnection<String, String> pubSubConnection = client.connectPubSub(); // 获取订阅、发布连接
// 向连接中添加一个 listener,且重写其 message 方法,在收到消息时,将消息添加到阻塞队列 messages 中
pubSubConnection.addListener(new RedisPubSubAdapter<>(){@Overridepublic void message(String channel, String message) {try {messages.put(message);} catch (InterruptedException e) {e.printStackTrace();}}
});
// 订阅 channel
final RedisPubSubCommands<String, String> sync = pubSubConnection.sync();
sync.subscribe("topic:foo");
复制代码
避免主线程退出,我们新创建一个线程,负责从阻塞队列 messages 中读取消息并打印到控制台:
final Thread backThread = new Thread() {@Overridepublic void run() {while (true) {try {final String message = messages.take(); // 从阻塞队列中获取消息并打印,若获取不到,则阻塞等待System.out.println("channel [topic:foo] received message: " + message);} catch (InterruptedException e) {e.printStackTrace();}}}
};
backThread.start();// 避免主线程退出
try {backThread.join();
} catch (InterruptedException ie) {ie.printStackTrace();
}
复制代码
01.2-使用 Lettuce 原生 API 进行事务操作
使用 Lettuce 进行 Redis 事务操作与使用 redis-cli 的操作非常相似。
final RedisClient client = getClient();
final StatefulRedisConnection<String, String> connection = client.connect();
final RedisCommands<String, String> sync = connection.sync();
sync.multi(); // 开启事务// 事务执行
sync.set("one", "1");
sync.set("two", "2");
sync.set("three", "3");final TransactionResult execResult = sync.exec();// 提交事务,Server 开始执行
execResult.forEach(System.out::println);
复制代码
Lettuce 不支持 Redis pipeline。
02-Spring Boot 对 Lettuce 的封装
在前面的文章 Spring Boot「17」整合 Redis 中提到,spring-boot-data-redis 对 Redis 进行了封装,并向应用上下文容器中注入了两个工具类:
- RedisTemplate
- StringRedisTemplate
其中,后者是 RedisTemplate 的一个派生类,它按照 String 类型来处理 Redis 中的键、值。
Spring Boot 中,与 Redis 的连接被封装为 RedisConnection 接口,有两个实现类分别用来封装底层 redis-cli 连接:
- LettuceConnection
- JedisConnection
RedisTemplate 依靠 RedisConnectionFactory 来获取与 Redis 实例的连接对象。 Spring Boot 提供了 RedisConnectionFactory 的两个实现,分别针对 Lettuce 和 Jedis 两个常见的 redis-cli 实现:
- LettuceConnectionFactory,获取 LettuceConnection 实例;
- JedisConnectionFactory,获取 JedisConnection 实例;
RedisConnectionFactory 和 RedisConnection 屏蔽了底层不同 redis-cli 实现 API 的差异,使 RedisTemplate 与底层实现解耦。 该接口中有三个关键的接口,针对不同的场景获得连接:
public interface RedisConnectionFactory extends PersistenceExceptionTranslator {RedisConnection getConnection(); // 获取与单 Redis 实例的连接RedisClusterConnection getClusterConnection(); // 获取与 Redis 集群的连接RedisSentinelConnection getSentinelConnection(); // 获取与 Redis 哨兵的连接
}
复制代码
LettuceConnectionFactory 是与 Lettuce 原生 API 深度耦合的。 通过查看它的源码,不难发现我在前面章节中介绍的 Lettuce 原生 API。 它持有一个 AbstractRedisClient client
对象,是与 Redis 交互的句柄; LettuceConnectionProvider connectionProvider
对象,负责从 RedisClient 中获取 Lettuce StatefulConnection<K, V> 连接。
在 Spring Boot 应用中,通过 StringRedisTemplate 进行基本数据结构的操作示例如下:
// 字符串
final Valu eOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
opsForValue.set("str", "str_value");
final String strValue = opsForValue.get("str");
System.out.println("strValue: " + strValue);// 列表
final ListOperations<String, String> opsForList = stringRedisTemplate.opsForList();
opsForList.leftPush("list", "value1", "value2");
final Long listLen = opsForList.size("list");
final List<String> allList = opsForList.range("list", 0, listLen);
System.out.println("allList: " + String.join(",", allList));// 集合
final SetOperations<String, String> opsForSet = stringRedisTemplate.opsForSet();
opsForSet.add("set", "set1", "set2");
final Long setSize = opsForSet.size("set");
final Set<String> allSet = opsForSet.members("set");
System.out.println("setSize: " + setSize);
System.out.println("allSet: " + String.join(",", allSet));// 哈希表
final HashOperations<String, Object, Object> opsForHash = stringRedisTemplate.opsForHash();
opsForHash.put("hashset", "key1", "value1");
opsForHash.put("hashset", "key2", "value2");
opsForHash.put("hashset", "key3", "value3");
final Long hlen = opsForHash.size("hashset");
final Map<Object, Object> allHashset = opsForHash.entries("hashset");
System.out.println("hlen: " + hlen);
System.out.println("allHashset: " + allHashset.entrySet().stream().map(k -> String.format("%s->%s", k.getKey(), k.getValue())).collect(Collectors.joining(",")));
复制代码
02.1-使用 StringRedisTemplate 实现订阅、发布功能
与 Lettuce 一样,发布消息非常简单:
stringRedisTemplate.convertAndSend("topic:foo", "hello, all! _from redis-template");
复制代码
订阅实现起来相对复杂一点:
final RedisConnection connection = stringRedisTemplate.getConnectionFactory().getConnection();
connection.subscribe((m, p) -> {try {messages.put(m.toString()); // 将消息添加到阻塞队列中} catch (InterruptedException e) {e.printStackTrace();}
}, "topic:foo".getBytes());
复制代码
可以看到,StringRedisTemplate 实现订阅与 Lettuce 实现订阅的接口基本类似,都是在 Connection 上调用订阅方法,然后传递一个回调,在收到消息后调用。
除了上述这种方式外,Spring Boot 中还设计了一个 RedisMessageListenerContainer 的类。 官方文档对它的介绍是:
Container providing asynchronous behaviour for Redis message listeners. Handles the low level details of listening, converting and message dispatching.
final RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(stringRedisTemplate.getConnectionFactory());
container.addMessageListener((m, p) -> {try {messages.put(m.toString());} catch (InterruptedException e) {e.printStackTrace();}
}, ChannelTopic.of("topic:foo"));container.afterPropertiesSet();
container.start();
复制代码
02.2-使用 StringRedisTemplate 进行事务操作
Spring Boot 官网给出的 StringRedisTemplate 执行事务的方式是通过 StringRedisTemplate#execute 方法。 例如:
List<String> results = stringRedisTemplate.execute(new SessionCallback<>() {@Overridepublic List<String> execute(RedisOperations operations) throws DataAccessException {operations.multi(); // 开启事务ValueOperations opsForValue = operations.opsForValue();opsForValue.set("one", "1");opsForValue.set("two", "2");opsForValue.set("three", "3");return operations.exec(); // 提交事务}
});
System.out.println(results.size());
复制代码
03-总结
通过上面的 API 对比,不难发现 spring-boot-data-redis 对 redis-cli 的封装后暴露的接口是比较简单易用的。 在开发过程中,如果是 Spring Boot 应用,建议使用 RedisTemplate 及其子类来与 Redis 服务端进行交互。 这样做的好处是可以将业务实现与底层的 redis-cli 实现解耦,当底层不满足需求时,更换底层而不会影响到业务层的代码。
Spring Boot:Lettuce 原生 API 与 RedisTemplate 对比相关推荐
- Spring Boot集成Redis缓存之RedisTemplate的方式
前言 Spring Boot 集成Redis,将自动配置 RedisTemplate,在需要使用的类中注入RedisTemplate的bean即可使用 @Autowired private Redis ...
- Spring Boot 通过Restful API,在PostMan 中返回数据
Spring Boot 通过Restful API,在PostMan 中返回数据 资源组 新增 POST/resource_group/ad 请求体:格式:from-data参数:groupName= ...
- spring boot开发接口api
spring boot开发接口api 在上一次教了大家怎么去搭建一个自己的后端模板之后,现在和大家分享讨论一下如何开发RestfulApi接口. 首先开发api之前要考虑到后端是需要写api文档的 ...
- 如何使用MySQL和JPA使用Spring Boot构建Rest API
Hi Everyone! For the past year, I have been learning JavaScript for full-stack web development. For ...
- Spring Boot与RESTful API
2019独角兽企业重金招聘Python工程师标准>>> 在上一篇Spring Boot开发WEB页面文章中,我们介绍了如何搭建一个有页面的web项目,这一篇我们则着重讲一下Sprin ...
- Spring Boot 之 RESRful API 权限控制
一.为何用RESTful API 1.1 RESTful是什么? RESTful(Representational State Transfer)架构风格,是一个Web自身的架构风格,底层主要基于HT ...
- Spring Boot集成Restful Api在线文档接口调试工具 Swagger
文章目录 一.Swagger简介 Swagger有什么用? 二.环境准备 三.构建Spring Boot工程 四.引入Swagger依赖 五.编写一个Test控制器 六.配置Swagger 七.最终测 ...
- Spring Boot构建RESTful API与单元测试
首先,回顾并详细说明一下在快速入门中使用的@Controller.@RestController.@RequestMapping注解.如果您对Spring MVC不熟悉并且还没有尝试过快速入门案例,建 ...
- Spring Boot 项目的 API 接口防刷
说明:使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供参考 一,技术要点:springboot的基本知识,redis基本操作, 首先是写一个注解类: import java.lang.an ...
最新文章
- centos/Debian/Ubuntu下编译安装pypy
- 第十五届全国大学生智能车竞赛线上竞赛方案(草案)
- C++ const与define
- 关于MySQL 5.6 中文乱码的问题(尤其是windows的gbk编码)
- SQL查询交集、并集、差集
- Android Demos
- 【渝粤题库】国家开放大学2021春1703农村发展理论与实践题目
- java 生产者消费者代码_Java生产者和消费者代码
- android获取错误原因,从http读取数据时发生OutOfMemory错误获取请求android
- react-redux学习笔记
- 字符串-创建//比较
- 视觉slam十四讲 第二版 pdf_第二版SLAM上帝视角,它来啦!!!
- PHP毕业设计项目作品源码选题(9)学校校园教师排课系统毕业设计毕设作品开题报告
- solidworks属性管理器_SOLIDWORKS BOM快捷生成第一步 | 完善产品属性
- 【笔记】不一样的 双11 技术,阿里巴巴经济体云原生实践(上)
- 在CSDN设置“关注博主即可阅读全文”方法增加粉丝量超简单
- 基于Springboot2.x+vue3.x整合实现微信扫码登录
- 安全生产管理云执法平台方案
- re-id #issue
- ShardingSphere分库分表核心原理精讲第十一节 分布式事务详解