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 对比相关推荐

  1. Spring Boot集成Redis缓存之RedisTemplate的方式

    前言 Spring Boot 集成Redis,将自动配置 RedisTemplate,在需要使用的类中注入RedisTemplate的bean即可使用 @Autowired private Redis ...

  2. Spring Boot 通过Restful API,在PostMan 中返回数据

    Spring Boot 通过Restful API,在PostMan 中返回数据 资源组 新增 POST/resource_group/ad 请求体:格式:from-data参数:groupName= ...

  3. spring boot开发接口api

    spring boot开发接口api ​ 在上一次教了大家怎么去搭建一个自己的后端模板之后,现在和大家分享讨论一下如何开发RestfulApi接口. 首先开发api之前要考虑到后端是需要写api文档的 ...

  4. 如何使用MySQL和JPA使用Spring Boot构建Rest API

    Hi Everyone! For the past year, I have been learning JavaScript for full-stack web development. For ...

  5. Spring Boot与RESTful API

    2019独角兽企业重金招聘Python工程师标准>>> 在上一篇Spring Boot开发WEB页面文章中,我们介绍了如何搭建一个有页面的web项目,这一篇我们则着重讲一下Sprin ...

  6. Spring Boot 之 RESRful API 权限控制

    一.为何用RESTful API 1.1 RESTful是什么? RESTful(Representational State Transfer)架构风格,是一个Web自身的架构风格,底层主要基于HT ...

  7. Spring Boot集成Restful Api在线文档接口调试工具 Swagger

    文章目录 一.Swagger简介 Swagger有什么用? 二.环境准备 三.构建Spring Boot工程 四.引入Swagger依赖 五.编写一个Test控制器 六.配置Swagger 七.最终测 ...

  8. Spring Boot构建RESTful API与单元测试

    首先,回顾并详细说明一下在快速入门中使用的@Controller.@RestController.@RequestMapping注解.如果您对Spring MVC不熟悉并且还没有尝试过快速入门案例,建 ...

  9. Spring Boot 项目的 API 接口防刷

    说明:使用了注解的方式进行对接口防刷的功能,非常高大上,本文章仅供参考 一,技术要点:springboot的基本知识,redis基本操作, 首先是写一个注解类: import java.lang.an ...

最新文章

  1. centos/Debian/Ubuntu下编译安装pypy
  2. 第十五届全国大学生智能车竞赛线上竞赛方案(草案)
  3. C++ const与define
  4. 关于MySQL 5.6 中文乱码的问题(尤其是windows的gbk编码)
  5. SQL查询交集、并集、差集
  6. Android Demos
  7. 【渝粤题库】国家开放大学2021春1703农村发展理论与实践题目
  8. java 生产者消费者代码_Java生产者和消费者代码
  9. android获取错误原因,从http读取数据时发生OutOfMemory错误获取请求android
  10. react-redux学习笔记
  11. 字符串-创建//比较
  12. 视觉slam十四讲 第二版 pdf_第二版SLAM上帝视角,它来啦!!!
  13. PHP毕业设计项目作品源码选题(9)学校校园教师排课系统毕业设计毕设作品开题报告
  14. solidworks属性管理器_SOLIDWORKS BOM快捷生成第一步 | 完善产品属性
  15. 【笔记】不一样的 双11 技术,阿里巴巴经济体云原生实践(上)
  16. 在CSDN设置“关注博主即可阅读全文”方法增加粉丝量超简单
  17. 基于Springboot2.x+vue3.x整合实现微信扫码登录
  18. 安全生产管理云执法平台方案
  19. re-id #issue
  20. ShardingSphere分库分表核心原理精讲第十一节 分布式事务详解

热门文章

  1. 【c语言】和【Java】版本的猜数字小游戏
  2. 尼得科Nidec风扇4015风机和G40R风机介绍
  3. ROS2机器人笔记20-07-08
  4. debian无法使用sudo
  5. 在cygwin下如何转到D盘
  6. [灯火阑珊] 关于cmd命令里的findstr匹配多个关键词
  7. 针对媒体和娱乐行业的NVIDIA MAXIMUS
  8. :linux 字符串拼接,Shell 字符串拼接的实现示例
  9. C# Delegate 使用
  10. Abnova ABCB10(人)重组蛋白说明书