Lettuce
是一个Redis
的Java
驱动包,初识她的时候是使用RedisTemplate
的时候遇到点问题Debug
到底层的一些源码,发现spring-data-redis
的驱动包在某个版本之后替换为Lettuce
。Lettuce
翻译为生菜,没错,就是吃的那种生菜,所以它的Logo
长这样:既然能被Spring
生态所认可,Lettuce
想必有过人之处,于是笔者花时间阅读她的官方文档,整理测试示例,写下这篇文章。编写本文时所使用的版本为Lettuce 5.1.8.RELEASE
,SpringBoot 2.1.8.RELEASE
,JDK [8,11]
。超长警告:这篇文章断断续续花了两周完成,超过4万字.....
小伙伴们有兴趣想了解内容和更多相关学习资料的请点赞收藏+评论转发+关注我,后面会有很多干货。我有一些面试题、架构、设计类资料可以说是程序员面试必备!所有资料都整理到网盘了,需要的话欢迎下载!私信我回复【000】即可免费获取
Lettuce
是一个高性能基于Java
编写的Redis
驱动框架,底层集成了Project Reactor
提供天然的反应式编程,通信框架集成了Netty
使用了非阻塞IO
,5.x
版本之后融合了JDK1.8
的异步编程特性,在保证高性能的同时提供了十分丰富易用的API
,5.1
版本的新特性如下:
Redis
的新增命令ZPOPMIN, ZPOPMAX, BZPOPMIN, BZPOPMAX
。Brave
模块跟踪Redis
命令执行。Redis Streams
。注意一点:Redis
的版本至少需要2.6
,当然越高越好,API
的兼容性比较强大。
只需要引入单个依赖就可以开始愉快地使用Lettuce
:
io.lettuce
lettuce-core
5.1.8.RELEASE
dependencies {
compile 'io.lettuce:lettuce-core:5.1.8.RELEASE'
}
单机、哨兵、集群模式下连接Redis
需要一个统一的标准去表示连接的细节信息,在Lettuce
中这个统一的标准是RedisURI
。可以通过三种方式构造一个RedisURI
实例:
URI
语法:RedisURI uri = RedisURI.create("redis://localhost/");
RedisURI.Builder
):RedisURI uri = RedisURI.builder().withHost("localhost").withPort(6379).build();
RedisURI uri = new RedisURI("localhost", 6379, 60, TimeUnit.SECONDS);
redis://
)格式:redis://[password@]host[:port][/databaseNumber][?[timeout=timeout[d|h|m|s|ms|us|ns]]
完整:redis://[email protected]:6379/0?timeout=10s
简单:redis://localhost
SSL
(前缀为rediss://
) <== 注意后面多了个s
格式:rediss://[password@]host[:port][/databaseNumber][?[timeout=timeout[d|h|m|s|ms|us|ns]]
完整:rediss://[email protected]:6379/0?timeout=10s
简单:rediss://localhost
Unix Domain Sockets
模式(前缀为redis-socket://
)格式:redis-socket://path[?[timeout=timeout[d|h|m|s|ms|us|ns]][&_database=database_]]
完整:redis-socket:///tmp/redis?timeout=10s&_database=0
redis-sentinel://
)格式:redis-sentinel://[password@]host[:port][,host2[:port2]][/databaseNumber][?[timeout=timeout[d|h|m|s|ms|us|ns]]#sentinelMasterId
完整:redis-sentinel://[email protected]:6379,127.0.0.1:6380/0?timeout=10s#mymaster
超时时间单位:
个人建议使用RedisURI
提供的建造器,毕竟定制的URI
虽然简洁,但是比较容易出现人为错误。鉴于笔者没有SSL
和Unix Domain Socket
的使用场景,下面不对这两种连接方式进行列举。
Lettuce
使用的时候依赖于四个主要组件:
RedisURI
:连接信息。RedisClient
:Redis
客户端,特殊地,集群连接有一个定制的RedisClusterClient
。Connection
:Redis
连接,主要是StatefulConnection
或者StatefulRedisConnection
的子类,连接的类型主要由连接的具体方式(单机、哨兵、集群、订阅发布等等)选定,比较重要。RedisCommands
:Redis
命令API
接口,基本上覆盖了Redis
发行版本的所有命令,提供了同步(sync
)、异步(async
)、反应式(reative
)的调用方式,对于使用者而言,会经常跟RedisCommands
系列接口打交道。一个基本使用例子如下:
@Test
public void testSetGet() throws Exception {
RedisURI redisUri = RedisURI.builder() // <1> 创建单机连接的连接信息
.withHost("localhost")
.withPort(6379)
.withTimeout(Duration.of(10, ChronoUnit.SECONDS))
.build();
RedisClient redisClient = RedisClient.create(redisUri); // <2> 创建客户端
StatefulRedisConnection connection = redisClient.connect(); // <3> 创建线程安全的连接
RedisCommands redisCommands = connection.sync(); // <4> 创建同步命令
SetArgs setArgs = SetArgs.Builder.nx().ex(5);
String result = redisCommands.set("name", "throwable", setArgs);
Assertions.assertThat(result).isEqualToIgnoringCase("OK");
result = redisCommands.get("name");
Assertions.assertThat(result).isEqualTo("throwable");
// ... 其他操作
connection.close(); // <5> 关闭连接
redisClient.shutdown(); // <6> 关闭客户端
}
注意:
Redis
驱动实例不需要太多的连接(一般情况下只需要一个连接实例就可以,如果有多个连接的需要可以考虑使用连接池,其实Redis
目前处理命令的模块是单线程,在客户端多个连接多线程调用理论上没有效果)。Lettuce
主要提供三种API
:
sync
):RedisCommands
。async
):RedisAsyncCommands
。reactive
):RedisReactiveCommands
。先准备好一个单机Redis
连接备用:
private static StatefulRedisConnection CONNECTION;
private static RedisClient CLIENT;
@BeforeClass
public static void beforeClass() {
RedisURI redisUri = RedisURI.builder()
.withHost("localhost")
.withPort(6379)
.withTimeout(Duration.of(10, ChronoUnit.SECONDS))
.build();
CLIENT = RedisClient.create(redisUri);
CONNECTION = CLIENT.connect();
}
@AfterClass
public static void afterClass() throws Exception {
CONNECTION.close();
CLIENT.shutdown();
}
Redis
命令API
的具体实现可以直接从StatefulRedisConnection
实例获取,见其接口定义:
public interface StatefulRedisConnection extends StatefulConnection {
boolean isMulti();
RedisCommands sync();
RedisAsyncCommands async();
RedisReactiveCommands reactive();
}
值得注意的是,在不指定编码解码器RedisCodec
的前提下,RedisClient
创建的StatefulRedisConnection
实例一般是泛型实例StatefulRedisConnection
,也就是所有命令API
的KEY
和VALUE
都是String
类型,这种使用方式能满足大部分的使用场景。当然,必要的时候可以定制编码解码器RedisCodec
。
先构建RedisCommands
实例:
private static RedisCommands COMMAND;
@BeforeClass
public static void beforeClass() {
COMMAND = CONNECTION.sync();
}
基本使用:
@Test
public void testSyncPing() throws Exception {
String pong = COMMAND.ping();
Assertions.assertThat(pong).isEqualToIgnoringCase("PONG");
}
@Test
public void testSyncSetAndGet() throws Exception {
SetArgs setArgs = SetArgs.Builder.nx().ex(5);
COMMAND.set("name", "throwable", setArgs);
String value = COMMAND.get("name");
log.info("Get value: {}", value);
}
// Get value: throwable
同步API
在所有命令调用之后会立即返回结果。如果熟悉Jedis
的话,RedisCommands
的用法其实和它相差不大。
先构建RedisAsyncCommands
实例:
private static RedisAsyncCommands ASYNC_COMMAND;
@BeforeClass
public static void beforeClass() {
ASYNC_COMMAND = CONNECTION.async();
}
基本使用:
@Test
public void testAsyncPing() throws Exception {
RedisFuture redisFuture = ASYNC_COMMAND.ping();
log.info("Ping result:{}", redisFuture.get());
}
// Ping result:PONG
RedisAsyncCommands
所有方法执行返回结果都是RedisFuture
实例,而RedisFuture
接口的定义如下:
public interface RedisFuture extends CompletionStage, Future {
String getError();
boolean await(long timeout, TimeUnit unit) throws InterruptedException;
}
也就是,RedisFuture
可以无缝使用Future
或者JDK
1.8中引入的CompletableFuture
提供的方法。举个例子:
@Test
public void testAsyncSetAndGet1() throws Exception {
SetArgs setArgs = SetArgs.Builder.nx().ex(5);
RedisFuture future = ASYNC_COMMAND.set("name", "throwable", setArgs);
// CompletableFuture#thenAccept()
future.thenAccept(value -> log.info("Set命令返回:{}", value));
// Future#get()
future.get();
}
// Set命令返回:OK
@Test
public void testAsyncSetAndGet2() throws Exception {
SetArgs setArgs = SetArgs.Builder.nx().ex(5);
CompletableFuture result =
(CompletableFuture) ASYNC_COMMAND.set("name", "throwable", setArgs)
.thenAcceptBoth(ASYNC_COMMAND.get("name"),
(s, g) -> {
log.info("Set命令返回:{}", s);
log.info("Get命令返回:{}", g);
});
result.get();
}
// Set命令返回:OK
// Get命令返回:throwable
如果能熟练使用CompletableFuture
和函数式编程技巧,可以组合多个RedisFuture
完成一些列复杂的操作。
Lettuce
引入的反应式编程框架是Project Reactor,如果没有反应式编程经验可以先自行了解一下Project Reactor
。
构建RedisReactiveCommands
实例:
private static RedisReactiveCommands REACTIVE_COMMAND;
@BeforeClass
public static void beforeClass() {
REACTIVE_COMMAND = CONNECTION.reactive();
}
根据Project Reactor
,RedisReactiveCommands
的方法如果返回的结果只包含0或1个元素,那么返回值类型是Mono
,如果返回的结果包含0到N(N大于0)个元素,那么返回值是Flux
。举个例子:
@Test
public void testReactivePing() throws Exception {
Mono ping = REACTIVE_COMMAND.ping();
ping.subscribe(v -> log.info("Ping result:{}", v));
Thread.sleep(1000);
}
// Ping result:PONG