1. Overview
1.概述
This article is an introduction to Lettuce, a Redis Java client.
本文是对Lettuce的介绍,这是一个RedisJava客户端。
Redis is an in-memory key-value store that can be used as a database, cache or message broker. Data is added, queried, modified, and deleted with commands that operate on keys in Redis’ in-memory data structure.
Redis 是一种内存键值存储,可用作数据库、缓存或消息代理。使用命令添加、查询、修改和删除数据,这些命令对Redis的内存数据结构中的键进行操作。
Lettuce supports both synchronous and asynchronous communication use of the complete Redis API, including its data structures, pub/sub messaging, and high-availability server connections.
Lettuce支持完整的Redis API的同步和异步通信使用,包括其数据结构、pub/sub消息传递和高可用性的服务器连接。
2. Why Lettuce?
2.为什么是生菜?
We’ve covered Jedis in one of the previous posts. What makes Lettuce different?
我们在之前的一篇文章中已经介绍了Jedis 。是什么让Lettuce与众不同?
The most significant difference is its asynchronous support via the Java 8’s CompletionStage interface and support for Reactive Streams. As we’ll see below, Lettuce offers a natural interface for making asynchronous requests from the Redis database server and for creating streams.
最重要的区别是它通过Java 8的CompletionStage接口提供的异步支持和对Reactive Streams的支持。正如我们将在下面看到的,Lettuce为从Redis数据库服务器发出异步请求和创建流提供了一个自然的接口。
It also uses Netty for communicating with the server. This makes for a “heavier” API, but also makes it better suited for sharing a connection with more than one thread.
它还使用Netty来与服务器进行通信。这使得API更加 “沉重”,但也使得它更适合于与多个线程共享连接。
3. Setup
3.设置
3.1. Dependency
3.1.依赖性
Let’s start by declaring the only dependency we’ll need in the pom.xml:
让我们先在pom.xml中声明我们唯一需要的依赖性。
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
The latest version of the library can be checked on the Github repository or on Maven Central.
库的最新版本可以在Github仓库或Maven Central.上查看。
3.2. Redis Installation
3.2.Redis安装
We’ll need to install and run at least one instance of Redis, two if we wish to test clustering or sentinel mode (although sentinel mode requires three servers to function correctly.) For this article, we’re using 4.0.x – the latest stable version at this moment.
我们需要安装并运行至少一个Redis实例,如果我们希望测试集群或哨兵模式,则需要两个实例(尽管哨兵模式需要三个服务器才能正常运行)。 对于本文,我们使用4.0.x–目前最新的稳定版本。
More information about getting started with Redis can be found here, including downloads for Linux and MacOS.
有关开始使用 Redis 的更多信息可在此处找到,包括用于 Linux 和 MacOS 的下载。
Redis doesn’t officially support Windows, but there’s a port of the server here. We can also run Redis in Docker which is a better alternative for Windows 10 and a fast way to get up and running.
Redis并不正式支持Windows,但有一个服务器的端口这里。我们还可以在Docker中运行Redis,这对Windows 10来说是一个更好的选择,也是一个快速启动和运行的方法。
4. Connections
4.连接
4.1. Connecting to a Server
4.1.连接到一个服务器
Connecting to Redis consists of four steps:
连接到 Redis 包括四个步骤。
- Creating a Redis URI
- Using the URI to connect to a RedisClient
- Opening a Redis Connection
- Generating a set of RedisCommands
Let’s see the implementation:
让我们看看实施情况。
RedisClient redisClient = RedisClient
.create("redis://password@localhost:6379/");
StatefulRedisConnection<String, String> connection
= redisClient.connect();
A StatefulRedisConnection is what it sounds like; a thread-safe connection to a Redis server that will maintain its connection to the server and reconnect if needed. Once we have a connection, we can use it to execute Redis commands either synchronously or asynchronously.
StatefulRedisConnection就是它听起来的样子;一个到Redis服务器的线程安全连接,它将保持与服务器的连接,并在需要时重新连接。一旦我们有了一个连接,我们就可以用它来同步或异步地执行 Redis 命令。
RedisClient uses substantial system resources, as it holds Netty resources for communicating with the Redis server. Applications that require multiple connections should use a single RedisClient.
RedisClient使用了大量的系统资源,因为它持有Netty资源用于与Redis服务器进行通信。需要多个连接的应用程序应使用一个RedisClient。
4.2. Redis URIs
4.2.Redis URIs
We create a RedisClient by passing a URI to the static factory method.
我们通过向静态工厂方法传递一个URI来创建一个RedisClient。
Lettuce leverages a custom syntax for Redis URIs. This is the schema:
Lettuce利用了Redis URI的自定义语法。这就是模式。
redis :// [password@] host [: port] [/ database]
[? [timeout=timeout[d|h|m|s|ms|us|ns]]
[&_database=database_]]
There are four URI schemes:
有四种URI方案。
- redis – a standalone Redis server
- rediss – a standalone Redis server via an SSL connection
- redis-socket – a standalone Redis server via a Unix domain socket
- redis-sentinel – a Redis Sentinel server
The Redis database instance can be specified as part of the URL path or as an additional parameter. If both are supplied, the parameter has higher precedence.
Redis数据库实例可以作为URL路径的一部分被指定,也可以作为一个额外的参数。如果两者都提供,参数具有更高的优先权。
In the example above, we’re using a String representation. Lettuce also has a RedisURI class for building connections. It offers the Builder pattern:
在上面的例子中,我们使用的是String表示。Lettuce也有一个RedisURI类用于建立连接。它提供了Builder模式。
RedisURI.Builder
.redis("localhost", 6379).auth("password")
.database(1).build();
And a constructor:
还有一个构造函数。
new RedisURI("localhost", 6379, 60, TimeUnit.SECONDS);
4.3. Synchronous Commands
4.3.同步命令
Similar to Jedis, Lettuce provides a complete Redis command set in the form of methods.
与Jedis类似,Lettuce以方法的形式提供了一个完整的Redis命令集。
However, Lettuce implements both synchronous and asynchronous versions. We’ll look at the synchronous version briefly, and then use the asynchronous implementation for the rest of the tutorial.
然而,Lettuce同时实现了同步和异步版本。我们将简要地看一下同步版本,然后在教程的其余部分使用异步实现。
After we create a connection, we use it to create a command set:
在我们创建一个连接后,我们用它来创建一个命令集。
RedisCommands<String, String> syncCommands = connection.sync();
Now we have an intuitive interface for communicating with Redis.
现在我们有了一个直观的界面来与Redis进行交流。
We can set and get String values:
我们可以设置和获取字符串值:。
syncCommands.set("key", "Hello, Redis!");
String value = syncommands.get(“key”);
We can work with hashes:
我们可以用哈希值工作。
syncCommands.hset("recordName", "FirstName", "John");
syncCommands.hset("recordName", "LastName", "Smith");
Map<String, String> record = syncCommands.hgetall("recordName");
We’ll cover more Redis later in the article.
我们将在文章后面介绍更多的Redis。
The Lettuce synchronous API uses the asynchronous API. Blocking is done for us at the command level. This means that more than one client can share a synchronous connection.
Lettuce的同步API使用异步API。阻断是在命令层面为我们完成的。这意味着一个以上的客户端可以共享一个同步连接。
4.4. Asynchronous Commands
4.4.异步命令
Let’s take a look at the asynchronous commands:
让我们来看看异步命令。
RedisAsyncCommands<String, String> asyncCommands = connection.async();
We retrieve a set of RedisAsyncCommands from the connection, similar to how we retrieved the synchronous set. These commands return a RedisFuture (which is a CompletableFuture internally):
我们从连接中检索一组RedisAsyncCommands,类似于我们检索同步集的方式。这些命令会返回一个RedisFuture(内部是一个CompletableFuture):。
RedisFuture<String> result = asyncCommands.get("key");
A guide to working with a CompletableFuture can be found here.
使用CompletableFuture的指南可以在这里找到。
4.5. Reactive API
4.5.反应式API
Finally, let’s see how to work with non-blocking reactive API:
最后,让我们看看如何使用非阻塞的反应式API。
RedisStringReactiveCommands<String, String> reactiveCommands = connection.reactive();
These commands return results wrapped in a Mono or a Flux from Project Reactor.
这些命令从Project Reactor.返回包裹在Mono或Flux中的结果。
A guide to working with Project Reactor can be found here.
使用Project Reactor的指南可以在这里找到。
5. Redis Data Structures
5.Redis数据结构
We briefly looked at strings and hashes above, let’s look at how Lettuce implements the rest of Redis’ data structures. As we’d expect, each Redis command has a similarly-named method.
上面我们简单看了一下字符串和哈希值,让我们看看Lettuce如何实现Redis的其他数据结构。正如我们所期望的,每个Redis命令都有一个名称相似的方法。
5.1. Lists
5.1.列表
Lists are lists of Strings with the order of insertion preserved. Values are inserted or retrieved from either end:
列表是保留了插入顺序的字符串的列表。值从两端插入或检索。
asyncCommands.lpush("tasks", "firstTask");
asyncCommands.lpush("tasks", "secondTask");
RedisFuture<String> redisFuture = asyncCommands.rpop("tasks");
String nextTask = redisFuture.get();
In this example, nextTask equals “firstTask“. Lpush pushes values to the head of the list, and then rpop pops values from the end of the list.
在这个例子中,nextTask等于”firstTask“。Lpush将值推到列表的头部,然后rpop从列表的末端弹出值。
We can also pop elements from the other end:
我们也可以从另一端弹出元素。
asyncCommands.del("tasks");
asyncCommands.lpush("tasks", "firstTask");
asyncCommands.lpush("tasks", "secondTask");
redisFuture = asyncCommands.lpop("tasks");
String nextTask = redisFuture.get();
We start the second example by removing the list with del. Then we insert the same values again, but we use lpop to pop the values from the head of the list, so the nextTask holds “secondTask” text.
在第二个例子中,我们先用del删除列表。然后我们再次插入相同的值,但我们使用lpop从列表的头部弹出这些值,所以nextTask持有”secondTask“文本。
5.2. Sets
5.2.套装
Redis Sets are unordered collections of Strings similar to Java Sets; there are no duplicate elements:
Redis集合是无序的字符串集合,类似于JavaSets;没有重复的元素:。
asyncCommands.sadd("pets", "dog");
asyncCommands.sadd("pets", "cat");
asyncCommands.sadd("pets", "cat");
RedisFuture<Set<String>> pets = asyncCommands.smembers("nicknames");
RedisFuture<Boolean> exists = asyncCommands.sismember("pets", "dog");
When we retrieve the Redis set as a Set, the size is two, since the duplicate “cat” was ignored. When we query Redis for the existence of “dog” with sismember, the response is true.
当我们将 Redis 集合作为 Set 检索时,其大小为 2,因为重复的 “cat” 被忽略了。当我们用sismember查询Redis是否存在“dog”时,的响应是true.。
5.3. Hashes
5.3.哈希值
We briefly looked at an example of hashes earlier. They are worth a quick explanation.
我们在前面简要地看了一个哈希值的例子。它们值得快速解释一下。
Redis Hashes are records with String fields and values. Each record also has a key in the primary index:
Redis Hashes是具有String字段和值的记录。每个记录在主索引中也有一个键。
asyncCommands.hset("recordName", "FirstName", "John");
asyncCommands.hset("recordName", "LastName", "Smith");
RedisFuture<String> lastName
= syncCommands.hget("recordName", "LastName");
RedisFuture<Map<String, String>> record
= syncCommands.hgetall("recordName");
We use hset to add fields to the hash, passing in the name of the hash, the name of the field, and a value.
我们使用hset向哈希添加字段,传入哈希的名称、字段的名称和一个值。
Then, we retrieve an individual value with hget, the name of the record and the field. Finally, we fetch the entire record as a hash with hgetall.
然后,我们用hget,记录的名称和字段来检索一个单独的值。最后,我们用hgetall.来获取整个记录的哈希值。
5.4. Sorted Sets
5.4.排序的集合
Sorted Sets contains values and a rank, by which they are sorted. The rank is 64-bit floating point value.
Sorted Sets包含值和一个等级,它们是按照这个等级排序的。等级是64位浮点值。
Items are added with a rank, and retrieved in a range:
项目被添加到一个等级,并在一个范围内被检索。
asyncCommands.zadd("sortedset", 1, "one");
asyncCommands.zadd("sortedset", 4, "zero");
asyncCommands.zadd("sortedset", 2, "two");
RedisFuture<List<String>> valuesForward = asyncCommands.zrange(key, 0, 3);
RedisFuture<List<String>> valuesReverse = asyncCommands.zrevrange(key, 0, 3);
The second argument to zadd is a rank. We retrieve a range by rank with zrange for ascending order and zrevrange for descending.
zadd的第二个参数是一个等级。我们用zrange来检索一个范围,用zrevrange来检索升序,用zrevrange来检索降序。
We added “zero” with a rank of 4, so it will appear at the end of valuesForward and at the beginning of valuesReverse.
我们添加了等级为4的”zero“,所以它将出现在valuesForward的末尾和valuesReverse.的开头。
6. Transactions
6.事务
Transactions allow the execution of a set of commands in a single atomic step. These commands are guaranteed to be executed in order and exclusively. Commands from another user won’t be executed until the transaction finishes.
事务允许在一个单一的原子步骤中执行一组命令。这些命令被保证按顺序完全执行。来自另一个用户的命令将不会被执行,直到事务完成。
Either all commands are executed, or none of them are. Redis will not perform a rollback if one of them fails. Once exec() is called, all commands are executed in the order specified.
要么所有的命令都被执行,要么都不被执行。如果其中一条命令失败,Redis不会执行回滚。一旦exec()被调用,所有命令将按照指定顺序执行。
Let’s look at an example:
我们来看看一个例子。
asyncCommands.multi();
RedisFuture<String> result1 = asyncCommands.set("key1", "value1");
RedisFuture<String> result2 = asyncCommands.set("key2", "value2");
RedisFuture<String> result3 = asyncCommands.set("key3", "value3");
RedisFuture<TransactionResult> execResult = asyncCommands.exec();
TransactionResult transactionResult = execResult.get();
String firstResult = transactionResult.get(0);
String secondResult = transactionResult.get(0);
String thirdResult = transactionResult.get(0);
The call to multi starts the transaction. When a transaction is started, the subsequent commands are not executed until exec() is called.
对multi的调用启动了事务。当一个事务被启动时,后续的命令不会被执行,直到exec()被调用。
In synchronous mode, the commands return null. In asynchronous mode, the commands return RedisFuture . Exec returns a TransactionResult which contains a list of responses.
在同步模式下,这些命令返回null。在异步模式下,这些命令返回RedisFuture。Exec返回一个TransactionResult,其中包含一个响应列表。
Since the RedisFutures also receive their results, asynchronous API clients receive the transaction result in two places.
由于RedisFutures也会收到它们的结果,所以异步API客户端会在两个地方收到交易结果。
7. Batching
7.配料
Under normal conditions, Lettuce executes commands as soon as they are called by an API client.
在正常情况下,Lettuce在被API客户端调用后会立即执行命令。
This is what most normal applications want, especially if they rely on receiving command results serially.
这是大多数普通应用程序想要的,特别是如果它们依赖于串行接收命令结果。
However, this behavior isn’t efficient if applications don’t need results immediately or if large amounts of data are being uploaded in bulk.
然而,如果应用程序不需要立即得到结果,或者大量的数据被上传,这种行为并不高效。
Asynchronous applications can override this behavior:
异步应用程序可以覆盖这一行为。
commands.setAutoFlushCommands(false);
List<RedisFuture<?>> futures = new ArrayList<>();
for (int i = 0; i < iterations; i++) {
futures.add(commands.set("key-" + i, "value-" + i);
}
commands.flushCommands();
boolean result = LettuceFutures.awaitAll(5, TimeUnit.SECONDS,
futures.toArray(new RedisFuture[0]));
With setAutoFlushCommands set to false, the application must call flushCommands manually. In this example, we queued multiple set command and then flushed the channel. AwaitAll waits for all of the RedisFutures to complete.
如果setAutoFlushCommands设置为false,应用程序必须手动调用flushCommands。在这个例子中,我们排队了多个set命令,然后刷新了通道。AwaitAll等待所有的RedisFutures完成。
This state is set on a per connection basis and effects all threads that use the connection. This feature isn’t applicable to synchronous commands.
该状态是在每个连接的基础上设置的,并影响所有使用该连接的线程。这个功能不适用于同步命令。。
8. Publish/Subscribe
8.发布/订阅
Redis offers a simple publish/subscribe messaging system. Subscribers consume messages from channels with the subscribe command. Messages aren’t persisted; they are only delivered to users when they are subscribed to a channel.
Redis提供了一个简单的发布/订阅消息系统。订阅者通过subscribe命令从通道中消费消息。消息不会被持久化;只有当用户订阅了一个通道时,它们才会被传递给用户。
Redis uses the pub/sub system for notifications about the Redis dataset, giving clients the ability to receive events about keys being set, deleted, expired, etc.
Redis使用pub/sub系统来通知Redis数据集,使客户能够接收关于键的设置、删除、过期等事件。
See the documentation here for more details.
更多细节请参见文档这里。
8.1. Subscriber
8.1.签约人
A RedisPubSubListener receives pub/sub messages. This interface defines several methods, but we’ll just show the method for receiving messages here:
一个RedisPubSubListener接收pub/sub消息。这个接口定义了几个方法,但我们在这里只展示接收消息的方法。
public class Listener implements RedisPubSubListener<String, String> {
@Override
public void message(String channel, String message) {
log.debug("Got {} on channel {}", message, channel);
message = new String(s2);
}
}
We use the RedisClient to connect a pub/sub channel and install the listener:
我们使用RedisClient来连接一个pub/sub通道并安装监听器。
StatefulRedisPubSubConnection<String, String> connection
= client.connectPubSub();
connection.addListener(new Listener())
RedisPubSubAsyncCommands<String, String> async
= connection.async();
async.subscribe("channel");
With a listener installed, we retrieve a set of RedisPubSubAsyncCommands and subscribe to a channel.
安装了监听器后,我们检索一组RedisPubSubAsyncCommands并订阅一个频道。
8.2. Publisher
8.2.出版商
Publishing is just a matter of connecting a Pub/Sub channel and retrieving the commands:
发布只是一个连接Pub/Sub通道和检索命令的问题。
StatefulRedisPubSubConnection<String, String> connection
= client.connectPubSub();
RedisPubSubAsyncCommands<String, String> async
= connection.async();
async.publish("channel", "Hello, Redis!");
Publishing requires a channel and a message.
发布需要一个渠道和一个信息。
8.3. Reactive Subscriptions
8.3.反应式订阅
Lettuce also offers a reactive interface for subscribing to pub/sub messages:
Lettuce还提供了一个反应式接口,用于订阅pub/sub消息。
StatefulRedisPubSubConnection<String, String> connection = client
.connectPubSub();
RedisPubSubAsyncCommands<String, String> reactive = connection
.reactive();
reactive.observeChannels().subscribe(message -> {
log.debug("Got {} on channel {}", message, channel);
message = new String(s2);
});
reactive.subscribe("channel").subscribe();
The Flux returned by observeChannels receives messages for all channels, but since this is a stream, filtering is easy to do.
由observeChannels返回的Flux接收所有通道的消息,但由于这是一个流,过滤很容易做到。
9. High Availability
9.高可用性
Redis offers several options for high availability and scalability. Complete understanding requires knowledge of Redis server configurations, but we’ll go over a brief overview of how Lettuce supports them.
Redis为高可用性和可扩展性提供了几种选择。完整的理解需要Redis服务器配置的知识,但我们将简要介绍Lettuce如何支持它们。
9.1. Master/Slave
9.1.主/从
Redis servers replicate themselves in a master/slave configuration. The master server sends the slave a stream of commands that replicate the master cache to the slave. Redis doesn’t support bi-directional replication, so slaves are read-only.
Redis服务器以主/从配置的方式进行自我复制。主服务器向从服务器发送命令流,将主缓存复制到从服务器上。Redis不支持双向复制,所以从机是只读的。
Lettuce can connect to Master/Slave systems, query them for the topology, and then select slaves for reading operations, which can improve throughput:
Lettuce可以连接到主/从系统,查询它们的拓扑结构,然后选择从机进行读取操作,这可以提高吞吐量。
RedisClient redisClient = RedisClient.create();
StatefulRedisMasterSlaveConnection<String, String> connection
= MasterSlave.connect(redisClient,
new Utf8StringCodec(), RedisURI.create("redis://localhost"));
connection.setReadFrom(ReadFrom.SLAVE);
9.2. Sentinel
9.2.哨兵
Redis Sentinel monitors master and slave instances and orchestrates failovers to slaves in the event of a master failover.
Redis Sentinel监控主站和从站实例,并在主站发生故障时协调对从站的故障恢复。
Lettuce can connect to the Sentinel, use it to discover the address of the current master, and then return a connection to it.
Lettuce可以连接到Sentinel,用它来发现当前主站的地址,然后返回一个连接给它。
To do this, we build a different RedisURI and connect our RedisClient with it:
为此,我们建立一个不同的RedisURI,并将我们的RedisClient与之连接。
RedisURI redisUri = RedisURI.Builder
.sentinel("sentinelhost1", "clustername")
.withSentinel("sentinelhost2").build();
RedisClient client = new RedisClient(redisUri);
RedisConnection<String, String> connection = client.connect();
We built the URI with the hostname (or address) of the first Sentinel and a cluster name, followed by a second sentinel address. When we connect to the Sentinel, Lettuce queries it about the topology and returns a connection to the current master server for us.
我们用第一个哨兵的主机名(或地址)和一个集群名称构建URI,后面是第二个哨兵地址。当我们连接到哨兵时,Lettuce会查询它的拓扑结构,并为我们返回一个与当前主服务器的连接。
The complete documentation is available here.
完整的文件可在这里获得。。
9.3. Clusters
9.3.集群
Redis Cluster uses a distributed configuration to provide high-availability and high-throughput.
Redis Cluster使用分布式配置来提供高可用性和高吞吐量。
Clusters shard keys across up to 1000 nodes, therefore transactions are not available in a cluster:
集群在多达1000个节点上的分片密钥,因此交易在集群中是不可用的:。
RedisURI redisUri = RedisURI.Builder.redis("localhost")
.withPassword("authentication").build();
RedisClusterClient clusterClient = RedisClusterClient
.create(rediUri);
StatefulRedisClusterConnection<String, String> connection
= clusterClient.connect();
RedisAdvancedClusterCommands<String, String> syncCommands = connection
.sync();
RedisAdvancedClusterCommands holds the set of Redis commands supported by the cluster, routing them to the instance that holds the key.
RedisAdvancedClusterCommands持有集群支持的Redis命令集,将它们路由到持有密钥的实例。
A complete specification is available here.
完整的规范可在这里获得。
10. Conclusion
10.结论
In this tutorial, we looked at how to use Lettuce to connect and query a Redis server from within our application.
在本教程中,我们研究了如何使用Lettuce从我们的应用程序中连接和查询Redis服务器。
Lettuce supports the complete set of Redis features, with the bonus of a completely thread-safe asynchronous interface. It also makes extensive use of Java 8’s CompletionStage interface to give applications fine-grained control over how they receive data.
Lettuce支持完整的Redis功能,还有一个完全线程安全的异步接口的好处。它还广泛使用了Java 8的CompletionStage接口,使应用程序能够精细地控制它们如何接收数据。
Code samples, as always, can be found over on GitHub.
像往常一样,可以在GitHub上找到代码样本。