Introduction to Lettuce – the Java Redis Client – Lettuce – Java Redis客户端简介

最后修改: 2018年 1月 27日

1. Overview


This article is an introduction to Lettuce, a Redis Java client.


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?


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.1. Dependency


Let’s start by declaring the only dependency we’ll need in the pom.xml:



The latest version of the library can be checked on the Github repository or on Maven Central.

库的最新版本可以在Github仓库Maven Central.上查看。

3.2. Redis Installation


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.1. Connecting to a Server


Connecting to Redis consists of four steps:

连接到 Redis 包括四个步骤。

  1. Creating a Redis URI
  2. Using the URI to connect to a RedisClient
  3. Opening a Redis Connection
  4. Generating a set of RedisCommands

Let’s see the implementation:


RedisClient redisClient = RedisClient
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.


4.2. Redis URIs

4.2.Redis URIs

We create a RedisClient by passing a URI to the static factory method.


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]]

There are four URI schemes:


  • 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.


In the example above, we’re using a String representation. Lettuce also has a RedisURI class for building connections. It offers the Builder pattern:


  .redis("localhost", 6379).auth("password")

And a constructor:


new RedisURI("localhost", 6379, 60, TimeUnit.SECONDS);

4.3. Synchronous Commands


Similar to Jedis, Lettuce provides a complete Redis command set in the form of methods.


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.


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.


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.


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.


4.4. Asynchronous Commands


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):


RedisFuture<String> result = asyncCommands.get("key");

A guide to working with a CompletableFuture can be found here.


4.5. Reactive API


Finally, let’s see how to work with non-blocking reactive API:


RedisStringReactiveCommands<String, String> reactiveCommands = connection.reactive();

These commands return results wrapped in a Mono or a Flux from Project Reactor.

这些命令从Project Reactor.返回包裹在MonoFlux中的结果。

A guide to working with Project Reactor can be found here.

使用Project Reactor的指南可以在这里找到。

5. Redis Data Structures


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.


5.1. Lists


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.


We can also pop elements from the other end:


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.


5.2. Sets


Redis Sets are unordered collections of Strings similar to Java Sets; there are no duplicate elements:


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


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.


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.


5.4. Sorted Sets


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.


We added “zero” with a rank of 4, so it will appear at the end of valuesForward and at the beginning of valuesReverse.


6. Transactions


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.


Let’s look at an example:


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.


In synchronous mode, the commands return null. In asynchronous mode, the commands return RedisFuture . Exec returns a TransactionResult which contains a list of responses.


Since the RedisFutures also receive their results, asynchronous API clients receive the transaction result in two places.


7. Batching


Under normal conditions, Lettuce executes commands as soon as they are called by an API client.


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:



List<RedisFuture<?>> futures = new ArrayList<>();
for (int i = 0; i < iterations; i++) {
    futures.add(commands.set("key-" + i, "value-" + i);

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.


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


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 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.


See the documentation here for more details.


8.1. Subscriber


A RedisPubSubListener receives pub/sub messages. This interface defines several methods, but we’ll just show the method for receiving messages here:


public class Listener implements RedisPubSubListener<String, String> {

    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:


StatefulRedisPubSubConnection<String, String> connection
 = client.connectPubSub();
connection.addListener(new Listener())

RedisPubSubAsyncCommands<String, String> async
 = connection.async();

With a listener installed, we retrieve a set of RedisPubSubAsyncCommands and subscribe to a channel.


8.2. Publisher


Publishing is just a matter of connecting a Pub/Sub channel and retrieving the commands:


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


Lettuce also offers a reactive interface for subscribing to pub/sub messages:


StatefulRedisPubSubConnection<String, String> connection = client

RedisPubSubAsyncCommands<String, String> reactive = connection

reactive.observeChannels().subscribe(message -> {
    log.debug("Got {} on channel {}",  message, channel);
    message = new String(s2);

The Flux returned by observeChannels receives messages for all channels, but since this is a stream, filtering is easy to do.


9. High Availability


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.


9.1. Master/Slave


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.


Lettuce can connect to Master/Slave systems, query them for the topology, and then select slaves for reading operations, which can improve throughput:


RedisClient redisClient = RedisClient.create();

StatefulRedisMasterSlaveConnection<String, String> connection
 = MasterSlave.connect(redisClient, 
   new Utf8StringCodec(), RedisURI.create("redis://localhost"));

9.2. Sentinel


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.


To do this, we build a different RedisURI and connect our RedisClient with it:


RedisURI redisUri = RedisURI.Builder
  .sentinel("sentinelhost1", "clustername")
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.


The complete documentation is available here.


9.3. Clusters


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:


RedisURI redisUri = RedisURI.Builder.redis("localhost")
RedisClusterClient clusterClient = RedisClusterClient
StatefulRedisClusterConnection<String, String> connection
 = clusterClient.connect();
RedisAdvancedClusterCommands<String, String> syncCommands = connection

RedisAdvancedClusterCommands holds the set of Redis commands supported by the cluster, routing them to the instance that holds the key.


A complete specification is available here.


10. Conclusion


In this tutorial, we looked at how to use Lettuce to connect and query a Redis server from within our application.


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.