What is Lettuce?
In a technical discussion, I immediately shouted “Jedis, YES! “when everyone mentioned the advantages of Redis Java client.
“Jedis is the official client, so it’s easy to use, and it’s used in corporate middleware. Is there another one besides Jedis?” I’ll just throw the king fry.
“SpringDataRedis all use RedisTemplate! Jedis? It doesn’t exist.”
“Sit down Sule, SpringDataRedis is packaged based on Jedis.” Li Elder brother next to sip just open happy water, the corners of the mouth slightly up, showing a disdain.
“A lot of people are using Lettuce nowadays. You know that, right?” Lao Wang pushed the glasses lightly said, then slowly open the lens after the window of the heart, with caring eyes overlooking us a few vegetables chicken.
Lettuce? Lettuce? Confused, I quickly opened the Redis official website client list. It turns out that the Java language has three officially recommended implementations: Jedis, Lettuce, and Redission.
(screenshot: redis. IO /clients#jav…)
What client is Lettuce? Haven’t heard. But found its official introduction to be the longest:
Advanced Redis client for thread-safe sync, async, and reactive usage. Supports Cluster, Sentinel, Pipelining, and codecs.
Hurriedly looked up the dictionary to translate:
-
Advanced client
-
Thread safety
-
Supports synchronous, asynchronous, and reactive apis
-
Supports clustering, sentry, piping and codec
Lao Wang waved his hand and beckoned me to put the dictionary away and introduced it slowly.
1.1 Advanced Clients
“Master, you translate for the translator, what (beep -) called (beep -) advanced client?”
“Advanced client, Advanced client, Advanced client. You don’t have to worry about any implementation details, just pick up the business logic and spit it out.”
1.2 Thread Safety
This is one of the main differences with Jedis.
Jedis’s connection instances are thread-unsafe, so a connection pool needs to be maintained, and each thread takes the instance from the pool as needed and returns it when it is done or when it encounters an exception. As the number of connections increases with services, the consumption of physical connections also becomes a potential risk point for performance and stability.
The oracle uses Netty as a communication layer component, its connection instances are thread-safe and can access the operating system native calls epoll, kqueue etc. for performance improvement when conditions are right.
We know that although the Redis server instance can connect to multiple clients at the same time to send and receive commands, each instance is single-threaded when executing commands.
This means that if an application can operate Redis in multi-threaded + single-connection mode, the total number of connections to the Redis server can be reduced, and multiple applications sharing the same Redis server can also achieve better stability and performance. It also reduces the resource consumption of maintaining multiple connection instances for applications.
1.3 Supports synchronous, asynchronous and reactive apis
Lettuce is designed from the start with non-blocking IO and is a purely asynchronous client with comprehensive support for both asynchronous and reactive apis.
Even with synchronous commands, the underlying communication process is still an asynchronous model that simulates synchronization by blocking the calling thread.
1.4 Support cluster, sentry, pipeline and codec
“These features are standard, Lettuce is a premium client! Advanced, understand?” Lao Wang excitedly pointed his finger at the table, but it seemed that he did not want to introduce more, SO I silently wrote down the plan to learn.
Pipeling is a bit more abstract than Jedis when used in projects, and the potholes and workarounds are described below.
1.5 Usage in Spring
In addition to the official introduction of Redis, we can also find that Spring Data Redis updated the Database to 5.0 when upgrading to 2.0. In fact, the oracle has been officially integrated since SpringDataRedis 1.6. However, SpringSessionDataRedis directly uses the oracle as the default Redis client, indicating its maturity and stability.
Jedis is the widely known and even de facto standard Java client (de-facto Standard Driver), and its early launch (version 1.0.0 in September 2010, In addition, the API is straightforward to use and the fastest support for new Redis features.
However, the Lettuce is a late comer, its advantages and ease of use are also favored by Spring and other communities. The following is a summary of our experience in integrating Lettuce in a project for your reference.
What is the main difference between Jedis and Lettuce?
Having said that, what are the main differences between Lettuce and Jedis? Take a look at the comparison table in the Spring Data Redis help:
(Source: docs.spring.io)
Note: where X marks support.
After comparison, we can find that:
-
Jedis supports all Lettuce supports;
-
Jedis doesn’t support Lettuce!
It is not surprising then that Spring is increasingly using Lettuce.
Lettuce experience
In order to share the results of our attempt at Lettuce, especially in the batch command part, it takes a lot of time to step on holes.
3.1 Quick Start
If even the simplest examples are confusing, the library will not catch on. The fast start is really fast:
A. Introducing Maven dependencies (similar to other dependencies, see resources)
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>5.36..RELEASE</version>
</dependency>
Copy the code
B. Fill in the Redis address, connect, execute, and close. Perfect!
import io.lettuce.core.*;
// Syntax: redis://[password@]host[:port][/databaseNumber]
// Syntax: redis://[username:password@]host[:port][/databaseNumber]
RedisClient redisClient = RedisClient.create("redis://password@localhost:6379/0");
StatefulRedisConnection<String, String> connection = redisClient.connect();
RedisCommands<String, String> syncCommands = connection.sync();
syncCommands.set("key"."Hello, Redis!");
connection.close();
redisClient.shutdown();
Copy the code
3.2 Is cluster mode supported? Support!
Redis Cluster is the official Redis Sharding solution, you should be very familiar with no more introduction, the official document can refer to Redis Cluster 101.
Oracle link Redis cluster to the above client code line can be changed:
// Syntax: redis://[password@]host[:port]
// Syntax: redis://[username:password@]host[:port]
RedisClusterClient redisClient = RedisClusterClient.create("redis://password@localhost:7379");
Copy the code
3.3 Is high Reliability supported? Support!
Redis Sentinel is a highly reliable solution that can be automatically switched to slave nodes in the event of instance failure. Redis Sentinel Documentation is available for reference.
Instead of creating a client:
// Syntax: redis-sentinel://[password@]host[:port][,host2[:port2]][/databaseNumber]#sentinelMasterId
RedisClient redisClient = RedisClient.create("redis-sentinel://localhost:26379,localhost:26380/0#mymaster");
Copy the code
3.4 Support pipeline under cluster? Support!
Jedis does not support Redis Cluster, although it has pipeline commands. Generally, it is necessary to merge the slot and instance of each key before batch pipeline execution.
As of this writing (Feb 2021), Cluster Pipelining is still available four years later.
Pipeling supports pipeling, but pipeling does not support pipeling.
3.4.1 track implementation pipeline
Asynchronccommands and flushCommands are used to implement pipelines. after reading the official documentation, we can see that both synchronous and asynchronous commands share the same connection instance, and the underlying pipeline format is used to send/receive commands.
The difference is:
-
The sync command object obtained by the connection.sync() method. Each operation immediately sends the command over the TCP connection.
-
Connection.async () gets an asynchronous command object that returns RedisFuture<? >, batch send only when certain conditions are met.
Therefore, we can implement pipeline through asynchronous command + manual batch push, let’s look at the official example:
StatefulRedisConnection<String, String> connection = client.connect();
RedisAsyncCommands<String, String> commands = connection.async();
// disable auto-flushing
commands.setAutoFlushCommands(false);
// perform a series of independent callsList<RedisFuture<? >> futures = Lists.newArrayList();for (int i = 0; i < iterations; i++) {
futures.add(commands.set("key-" + i, "value-" + i));
futures.add(commands.expire("key-" + i, 3600));
}
// write all commands to the transport layer
commands.flushCommands();
// synchronization example: Wait until all futures complete
boolean result = LettuceFutures.awaitAll(5, TimeUnit.SECONDS,
futures.toArray(new RedisFuture[futures.size()]));
// later
connection.close();
Copy the code
3.4.2 Is there a problem with this?
SetAutoFlushCommands (false) ** Sets the sync() method to sync commands that do not return. ** Why? Let’s take a look at the official document:
Lettuce is a non-blocking and asynchronous client. It provides a synchronous API to achieve a blocking behavior on a per-Thread basis to create await (synchronize) a command response….. As soon as the first request returns, the first Thread’s program flow continues, while the second request is processed by Redis and comes back at a certain point in time
Sync and Async are identical in their underlying implementations, except that Sync simulates synchronization by blocking the calling thread. SetAutoFlushCommands works on connection objects, so it works on sync and async objects.
So, setting Auto Flush Commands to false on one thread affects all other threads that use that connection instance.
/**
* An asynchronous and thread-safe API for a Redis connection.
*
* @param <K> Key type.
* @param <V> Value type.
* @author Will Glozer
* @author Mark Paluch
*/
public abstract class AbstractRedisAsyncCommands<K.V> implements RedisHashAsyncCommands<K.V>, RedisKeyAsyncCommands<K.V>,
RedisStringAsyncCommands<K.V>, RedisListAsyncCommands<K.V>, RedisSetAsyncCommands<K.V>,
RedisSortedSetAsyncCommands<K.V>, RedisScriptingAsyncCommands<K.V>, RedisServerAsyncCommands<K.V>,
RedisHLLAsyncCommands<K.V>, BaseRedisAsyncCommands<K.V>, RedisTransactionalAsyncCommands<K.V>,
RedisGeoAsyncCommands<K.V>, RedisClusterAsyncCommands<K.V> {
@Override
public void setAutoFlushCommands(boolean autoFlush) { connection.setAutoFlushCommands(autoFlush); }}Copy the code
In contrast, if multiple threads call async() to fetch the asynchronous command set and call flushCommands() after their own business logic is complete, the asynchronous commands that are still being added by other threads will be forcibly flushed, and the commands that were logically part of the batch will be broken into multiple copies.
Although there is no effect on the correctness of the result, the performance improvement can be very unstable if commands are sent that break up each other because threads interact with each other.
It’s natural to think: each batch command creates a connection, and then… Doesn’t Jedis depend on connection pooling?
Recalling Lao Wang’s soul-piercing gaze behind the lens, I decided to dig again. Sure enough, a second reading of the document revealed another good thing: Batch Execution.
Rule 3.4.3 Batch Execution
Since flushCommands have a global effect on connection, why not limit flush to the thread level? I found examples in the documentation official examples.
Remember that the previous article is a high-level client, after reading the documentation, I found that it is really high-level, just need to define the interface (remind me of MyBatis Mapper interface), the following is an example to use in the project:
/
/** * defines the batch command */ to be used
@BatchSize(100)
public interface RedisBatchQuery extends Commands.BatchExecutor {
RedisFuture<byte[]> get(byte[] key);
RedisFuture<Set<byte[]>> smembers(byte[] key);
RedisFuture<List<byte[]>> lrange(byte[] key, long start, long end);
RedisFuture<Map<byte[].byte[]>> hgetall(byte[] key);
}
Copy the code
Call like this:
// Create a client
RedisClusterClient client = RedisClusterClient.create(DefaultClientResources.create(), "redis://" + address);
// Service holds the factory instance, which is created only once. The second argument indicates that the key and value are codeced using byte[]
RedisCommandFactory factory = new RedisCommandFactory(connect, Arrays.asList(ByteArrayCodec.INSTANCE, ByteArrayCodec.INSTANCE));
// create a query instance proxy class to invoke the command, and finally brush in the commandList<RedisFuture<? >> futures =new ArrayList<>();
RedisBatchQuery batchQuery = factory.getCommands(RedisBatchQuery.class);
for (RedisMetaGroup redisMetaGroup : redisMetaGroups) {
// Business logic, which loops through multiple keys and saves the results to futures results
appendCommand(redisMetaGroup, futures, batchQuery);
}
// After the asynchronous command is invoked, flush is executed in batches and the command is sent to the Redis server
batchQuery.flush();
Copy the code
It’s that simple.
Batch control is done at thread granularity, and batch operations are performed when flush is called or the number of cache commands configured at @batchsize is reached. For connection instances, there is no need to set Auto Flush Commands and keep the default true. This does not affect other threads.
If a single command takes a long time to execute or someone puts a command like BLPOP on it, it will definitely have an impact. This topic is also covered in the official documentation, so you can consider using connection pooling to handle it.
3.5 Can you do more?
Lettuce certainly supports more than the simple functions mentioned above, there are also some interesting things to try:
3.5.1 Read/write Separation
We know that Redis instances support master-slave deployment, where the slave instance asynchronously synchronizes data from the master instance and performs a master-slave switchover in the event of a primary instance failure with Redis Sentinel.
If the application is insensitive to data consistency and requires large throughput, the primary/secondary read/write separation mode is recommended. Lettuce can set StatefulRedisClusterConnection readFrom configuration to adjust:
3.5.2 Configuring automatic Cluster Topology Update
How to handle server capacity expansion when using Redis Cluster?
The oracle database is already in place – you can configure the parameters by passing in the ClusterClientOptions object using the redisClusterClientoptions method (see reference link at the end of this article for full configuration).
The common configuration of topologyRefreshOptions in ClusterClientOptions is as follows:
3.5.3 connection pool
Although the thread-safe single-connection instances have excellent performance, it is not ruled out that some large businesses need thread pools to improve throughput. In addition, exclusive connections are necessary for transactional operations.
The Apache common-pool2 component provides the ability to connect to a pool (see the RedisCluster client thread pool usage example below) :
RedisClusterClient clusterClient = RedisClusterClient.create(RedisURI.create(host, port));
GenericObjectPool<StatefulRedisClusterConnection<String, String>> pool = ConnectionPoolSupport
.createGenericObjectPool(() -> clusterClient.connect(), new GenericObjectPoolConfig());
// execute work
try (StatefulRedisClusterConnection<String, String> connection = pool.borrowObject()) {
connection.sync().set("key"."value");
connection.sync().blpop(10."list");
}
// terminating
pool.close();
clusterClient.shutdown();
Copy the code
CreateGenericObjectPool Creates a connection pool. The default setting for wrapConnections is true. The loaned object close method is overridden by a dynamic proxy to return the connection; If set to false, the close method closes the connection.
The database also supports asynchronous connection pooling (retrieving connections from a connection pool is an asynchronous operation). See the link in the end of this article for details. There are many more features that cannot be listed in detail, but explanations and examples can be found in the official documentation, which is well worth reading.
Use summary
Lettuce is faster and more abstract than Jedis. Moreover, thread-safe connection reduces the number of connections in the system and improves the stability of the system.
For the sophisticated gamer, The Database also provides many configurations and interfaces for performance optimization and deep business customization scenarios.
In addition, we have to say that the official document of Lettuce is very comprehensive and detailed, which is very rare. The community is active and Commiter will actively answer issues, which makes many questions self-resolvable.
In contrast, Jedis documentation, maintenance and update speed is relatively slow. The PR of JedisCluster Pipeline has not been closed for four years (February 2021).
The resources
Two of the GitHub issues are very valuable and highly recommended to read!
1.Lettuce Fast start: Lettuce. IO
2.Redis Java Clients
Lettuce official website: author. IO
4.SpringDataRedis Reference document
5.Question about pipelining
6.Why is Lettuce the default Redis client used in Spring Session Redis
7. Cluster – specific options: lettuce. IO
8. Lettuce connection pool
Weir. IO /core/releas…
10.SSL configuration: author. IO
Author: Li Haoxuan, Vivo Internet Data Intelligence Team