前言
不知不觉已经是 Redis 系列的第二十篇了,也是开始写博客的第二十二天,现在的文章数量二十五篇,因为 Redis 的前几篇还是篇基础的,所以大概的话也就是每天一篇的样子。一直坚持写是真的挺难的,不过收获还是挺多的,如果有好哥哥现在遇到技术瓶颈了,不妨试试写博客出来分享。我保证能颠覆你对某个技术的认知。昨天有个同学好哥哥问我真的值得吗,我很肯定的回答是值得。因为这里面的收获只有我自己知道,哪怕没人看,那又怎样,我搞定了我自己就 OK(希望不要被打)。
今天的话还是和上一篇Redis 客户端 Jedis 详解 一样,讲的是另外一个 Redis 的高级客户端,是真的高级。为什么要讲两个客户端呢,实际上跟下篇的内容有关系,弄懂了这两个客户端下篇就很简单了。
概述
Lettuce
是一个 Redis 的Java
驱动包,Lettuce
翻译为生菜,没错,就是吃的那种生菜,所以它的 Logo 是这样的
Lettuce
是一个高性能
、基于Java编写
、可伸缩
、线程安全
的 Redis 客户端,底层集成了Project Reactor
提供天然的反应式编程,通信框架集成了Netty
(不熟悉的好哥哥先忍忍,有时间弄这个的源码解析)使用了非阻塞 IO,提供同步, 异步和反应式 API。如果多个线程避免阻塞和事务操作(例如BLPOP
和 MULTI
),则可以共享一个连接EXEC
。出色的netty NIO
框架可有效管理多个连接。包括对高级 Redis 功能的支持,例如Sentinel
,Cluster
和 Redis 数据模型。
在5.x
版本之后融合了JDK1.8
的异步编程特性,在保证高性能的同时提供了十分丰富易用的 API。5.1
版本提供了很多新的特性,如下:
- 支持 Redis 的新增命令
ZPOPMIN
、ZPOPMAX
、BZPOPMIN
、BZPOPMAX
。 - 支持通过
Brave
模块跟踪 Redis 命令执行。 - 支持
Redis Streams
。 - 支持异步的主从连接。
- 支持异步连接池。
- 新增命令最多执行一次模式(禁止自动重连)。
- 全局命令超时设置(对异步和反应式命令也有效)。
- …等等
使用
1 maven 依赖
至于版本的会好哥哥们自行选择,我这个目前是最新版本
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.0.1.RELEASE</version>
</dependency>
2 连接 Redis
2.1 使用 URI
public static void main(String[] args) {
RedisURI redisURI = RedisURI.create("redis://localhost/");
}
2.2 使用构造器
public static void main(String[] args) {
// 需要注意的是不同版本的包对应的API也可能不一样
RedisURI redisURI = RedisURI.Builder.redis("127.0.0.1", 6379).withDatabase(0).build();
RedisClient client = RedisClient.create(redisURI);
}
2.3 使用构造函数
public static void main(String[] args) {
// 需要注意的是不同版本的包对应的API也可能不一样
RedisURI redisURI = new RedisURI("localhost", 6379, Duration.ofSeconds(60));
RedisClient client = RedisClient.create(redisURI);
}
3 基础使用
3.1 获取 key
public static void main(String[] args) {
RedisURI redisURI = RedisURI.Builder.redis("127.0.0.1", 6379).withDatabase(0).build();
// 1. 创建连接
RedisClient redisClient = RedisClient.create(redisURI);
// 2. 打开Redis连接
StatefulRedisConnection<String, String> connection = redisClient.connect();
// 3. 获取用于同步执行的命令API
RedisCommands<String, String> redisCommands = connection.sync();
redisCommands.get("hello");
// 4. 关闭连接
connection.close();
// 5. 关闭客户端
redisClient.shutdown();
}
3.2 三种模式 API
上面有提到Lettuce
支持同步(sync)、异步(async)、反应式(reactive),对应的 API 是RedisCommands
、RedisAsyncCommands
、RedisReactiveCommands
。
在使用其操作其他数据结构 API 之前,先把上面代码进行一下优化(将连接抽离成一个方法),不然会有很多重复代码,篇幅会过长。
/**
* 创建成功的URI
*/
private static RedisClient redisClient = null;
/**
* 同步执行的命令API
*/
private static StatefulRedisConnection<String, String> connection = null;
/**
* 初始化操作
*
* @return
*/
public static void getConnection() {
RedisURI redisURI = RedisURI.Builder.redis("127.0.0.1", 6379).withDatabase(0).build();
// 1. 创建连接
redisClient = RedisClient.create(redisURI);
// 2. 打开Redis连接
connection = redisClient.connect();
}
3.2.1 同步 API
public static void main(String[] args) {
// 1. 获取一个连接,方法在上面
getConnection();
// 2. 获取用于同步执行的命令API
RedisCommands<String, String> redisCommands = connection.sync();
}
3.2.2 异步 API
public static void main(String[] args) {
// 1. 获取一个连接,方法在上面
getConnection();
// 2. 获取用于同步执行的命令API
RedisAsyncCommands<String, String> async = connection.async();
}
3.2.3 反应式 API
public static void main(String[] args) {
// 1. 获取一个连接,方法在上面
getConnection();
// 2. 获取用于同步执行的命令API
RedisReactiveCommands<String, String> reactive = connection.reactive();
}
3.3 操作基础数据类型
由于Lettuce
支持三种模式的 API,其实主要是获取对应的模式上会有差异,在操作其他 API 上基本还是一样的,下面使用同步模式来操作。
public static void main(String[] args) {
// 1. 获取一个连接,方法在上面
getConnection();
// 2. 获取用于同步执行的命令API
RedisCommands<String, String> redisCommands = connection.sync();
// 3. 操作字符串
redisCommands.set("hello", "world");
// 3.1. 获取字符串
String hello = redisCommands.get("hello");
// 4. 设置hash
redisCommands.hset("myHash", "key1", "val1");
// 4.1. 获取hash
redisCommands.hget("myHash", "key1");
// 5. 操作list
redisCommands.rpush("myList", "val1");
redisCommands.rpush("myList", "val2");
// 5.1. 获取list
redisCommands.lrange("myList", 0, -1);
// 6. 操作set
redisCommands.sadd("mySet", "val1");
redisCommands.sadd("mySet", "val2");
// 6.1. 获取作set
redisCommands.smembers("mySet");
// 7. 操作sorted set
redisCommands.zadd("mySortedSet", 100, "member1");
redisCommands.zadd("mySortedSet", 99, "member2");
// 7.1. 获取sorted set
redisCommands.zrangeWithScores("mySortedSet", 0, -1);
}
3.4 发布/订阅
Lettuce
提供了对单机和集群连接上的发布/订阅的支持。订阅频道或模式后,将在消息/已订阅/未订阅事件中通知连接。提供了同步,异步和反应性API
来与 Redis Pub/Sub
功能进行交互。非集群模式下的发布订阅依赖于定制的连接StatefulRedisPubSubConnection
,集群模式下的发布订阅依赖于定制的连接StatefulRedisClusterPubSubConnection
,两者分别来源于RedisClient#connectPubSub()
系列方法和RedisClusterClient#connectPubSub()
。
3.4.1 自定义一个监听器
public static class MyRedisPubSubListener implements RedisPubSubListener {
@Override
public void message(Object channel, Object message) {
System.out.println("channel:" + channel + " say: " + message);
}
@Override
public void message(Object pattern, Object channel, Object message) {
System.out.println("pattern:" + pattern + "say: " + message);
}
@Override
public void subscribed(Object channel, long count) {
System.out.println(channel + " subscribed");
}
@Override
public void psubscribed(Object pattern, long count) {
System.out.println(pattern + " psubscribed");
}
@Override
public void unsubscribed(Object channel, long count) {
System.out.println(channel + " unsubscribed");
}
@Override
public void punsubscribed(Object pattern, long count) {
System.out.println(pattern + " punsubscribed");
}
}
3.4.2 三种方式订阅
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 获取一个连接,方法在上面(可能是单机、普通主从、哨兵等非集群模式的客户端)
getConnection();
StatefulRedisPubSubConnection<String, String> connection = redisClient.connectPubSub();
connection.addListener(new MyRedisPubSubListener());
// 同步订阅
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 -> {
System.out.println("observeChannels doOnNext " + patternMessage.getMessage());
}).subscribe();
}
3.4.3 发布消息
这里测试的时候需要注意的是不能使用同一个连接,否则会报错的。
public static void main(String[] args) {
// 重新初始化连接
LettuceTest.getConnection();
// 发布消息,执行完后再看另外一个控制台能把接受到的消息打印
LettuceTest.connection.sync().publish("channel", "hello");
}
3.5 执行 Lua
有不熟悉 Redis 执行Lua
的好哥哥可以看Redis 万字长文 Lua 详解
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 获取一个连接,方法在上面(可能是单机、普通主从、哨兵等非集群模式的客户端)
getConnection();
RedisCommands<String, String> redisCommands = connection.sync();
// 2. 定义一个简单的脚本
String script = "return redis.call('get',KEYS[1])";
// 3. 第一种方式:直接执行脚本
Object hello = redisCommands.eval(script, ScriptOutputType.VALUE, "hello");
// 4. 第二种方式:加载脚本到Redis,然后执行
String scriptSha = redisCommands.scriptLoad(script);
// 4.1. 根据sha执行脚本
Object helloResult = redisCommands.evalsha(scriptSha, ScriptOutputType.VALUE, "hello");
}
3.6 流 API
这个跟 Java8 中的流是一个概念,就是Lettuce
支持了这种方式。
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 获取一个连接,方法在上面(可能是单机、普通主从、哨兵等非集群模式的客户端)
getConnection();
RedisCommands<String, String> redisCommands = connection.sync();
// 添加元素到list
redisCommands.lpush("key", "one");
redisCommands.lpush("key", "two");
redisCommands.lpush("key", "three");
// 流式返回
Long count = redisCommands.lrange(new ValueStreamingChannel<String>() {
@Override
public void onValue(String value) {
System.out.println("Value: " + value);
}
}, "key", 0, -1);
}
与 Jedis 对比
- 线程安全上,
Jedis
是线程不安全的,Lettuce
是线程安全的。
这个指的是直连方式来说,实际上Jedis
使用连接池也是线程安全的。 - 从实现方式上来说,
Jedis
实现简单、使用简单,而Lettuce
实现是很复杂的,但是还好使用起来简单。 - 从网络上来说,
Jedis
使用的是阻塞I/O
,而Lettuce
是基于Netty
使用NIO
。 - 从抽象程度来说,
Jedis
并没有做特别的抽象处理,而Lettuce
抽象了三种处理命令的模式(同步、异步、反应式)。 - 从获取连接上来说,
Jedis
一般使用的是多个连接(从连接池获取),而Lettuce
共享连接,连接是 long-lived 的和线程安全的,并且会自动重连。 - 从社区上来讲,
Jedis
社区较活跃,版本更新快,而Lettuce
的话活跃度就一般了,版本更新较慢(很少关于Lettuce
的资料)。
总结
还有一些关于Lettuce
API、高可用配置等相关的东西没有写进来。一方面是篇幅的原因,一方面的话也是我们平时不会单独使用Lettuce
,都是基于 Spring 的spring-boot-starter-data-redis
来对 Redis 做相关操作。这里贴一下Lettuce
的源码地址(Lettuce 源码),感兴趣的好哥哥可以上去了解一下。
本期就到这啦,有不对的地方欢迎好哥哥们评论区留言,另外求关注、求点赞