This article explores the application of Redis’ latest feature – client caching on SpringBoot.

Redis Tracking

The Redis client caching mechanism is based on the Redis Tracking mechanism. Let’s take a look at the Redis Tracking mechanism.

Why is Redis Tracking needed

Due to its high speed and high performance, Redis is often used as a cache database for traditional databases such as MySQL. However, because Redis is a remote service, Redis query needs to be requested through the network, which inevitably causes performance loss in high concurrent query scenarios. Therefore, high-concurrency applications usually introduce local caches to check whether there is data in the local cache before querying Redis.

If MySQL is used to store data, the data query process is shown below.

After the introduction of multi-cache, how to ensure data consistency among data caches when modifying data is a difficult problem. The usual practice is to modify MySQL data, and remove Redis cache, local cache. When the user finds that the cache does not exist, the user will query the MySQL data again and set the Redis cache and local cache.

In a distributed system, after a node modifies data, it not only deletes the local cache of the current node, but also sends a request to other nodes in the cluster to delete the local cache of the data, as shown in the following figure. If there are many nodes in a distributed system, this operation can cause a significant performance cost.

To this end, Redis 6 provides Redis Tracking, which optimizes the caching scheme. When Redis Tracking is enabled, the Redis server records all keys queried by the client and sends an invalid message notifying the client that the keys have changed. In this case, the client needs to remove the local cache of these keys. The Redis Tracking mechanism reduces system complexity and improves performance by eliminating the need to broadcast a “delete local cache” request across the cluster after a node changes data.

Redis Tracking app

The following table shows the basic use of Redis Tracking

(1) In order to support Redis server push message, Redis is extended on RESP2 protocol to implement RESP3 protocol. The HELLO 3 command indicates that the client communicates with the Redis server using RESP3 protocol.

Note: Redis 6.0 provides the Redis Tracking mechanism, but this version of Redis – CLI does not support the RESP3 protocol, so Redis 6.2 – CLI will be used to demonstrate this.

(2) The function of the CLIENT TRACKING on command is to enable the Redis TRACKING mechanism. After that, the Redis server will record the keys queried by the CLIENT and push the failure message to notify the CLIENT after the keys are changed. Invalid messages begin with invalidate, followed by an array of invalid keys.

Client client1 in the above table queries the key score, and client Client2 modifies the key. In this case, Redis server will immediately push the invalid message to client Client1, but Redis – CLI will not directly display the push message it receives. Instead, the message is displayed after the next request returns, so Client1 resends a PING request.

The non-broadcast mode used above, in addition, Redis Tracking also supports broadcast mode. In broadcast mode, when a changed key begins with a prefix that the client cares about, the Redis server sends a failure message to all clients that care about that prefix, regardless of whether the client has previously queried those keys. The following table shows how to use Redis Tracking’s broadcast mode.



Note the two parameters in the CLIENT TRACKING command:

BCAST parameter: Enables broadcast mode.

The PREFIX argument: specifies the PREFIX that the client cares about. That is, the client only cares about the key starting in cache.

To emphasize the differences between non-broadcast mode and broadcast mode: Non-broadcast mode: The Redis server records the keys queried by the client, and when these keys change, Redis sends an invalid message to the client. Broadcast mode: The Redis server does not record the keys queried by the client. When a changed key begins with a prefix that the client is interested in, Redis sends an invalid message to the client.

More on Redis Tracking is covered in detail in my new book, Redis Core Principles and Practices, and won’t be covered here.

Redis client cache

Since Redis provides Tracking, clients can implement client-side caching based on that mechanism.

Lettuce implementation

Oracle (version 6.1.5) already supports Redis client caching (in standalone mode), which can be implemented using the CacheFrontend class.

public static void main(String[] args) throws InterruptedException { // [1] RedisURI redisUri = RedisURI.builder() WithHost (" 127.0.0.1). WithPort (6379). The build (); RedisClient redisClient = RedisClient.create(redisUri); // [2] StatefulRedisConnection<String, String> connect = redisClient.connect(); Map<String, String> clientCache = new ConcurrentHashMap<>(); CacheFrontend<String, String> frontend = ClientSideCaching.enable(CacheAccessor.forMap(clientCache), connect, TrackingArgs.Builder.enabled()); // [3] while (true) { String cachedValue = frontend.get("k1"); System.out.println("k1 ---> " + cachedValue); Thread.sleep(3000); }}Copy the code
  1. Build RedisClient.
  2. Build CacheFrontend.

ClientSideCaching. Enable Enable the CLIENT cache. That is, the CLIENT TRACKING command is sent to the Redis server to enable the TRACKING mechanism. The last parameter specifies the Redis Tracking mode, which is the simplest non-broadcast mode. As you can see, the contents of the client cache are saved using a Map. 3. Query the same value repeatedly to check whether the cache takes effect.

We can use the Monitor command of Redis to Monitor the commands received by the Redis service. Using this command, we can see that when client caching is enabled, the same key will not be queried repeatedly. And when we modify the key, the database will query the latest value of the key.

You can view the connection information by running the Client List command of Redis

> CLIENT LIST ID =4 addr=192.168.56.1:50402 fd=7 Name = age=23 IDLE =22 FLAGS =t...Copy the code

Flags =t indicates that Tracking is enabled for this connection.

SpringBoot application

So how do you use it on SpringBoot? Take a look at the following example

@Bean
public CacheFrontend<String, String> redisCacheFrontend(RedisConnectionFactory redisConnectionFactory) {
    StatefulRedisConnection connect = getRedisConnect(redisConnectionFactory);
    if (connect == null) {
        return null;
    }

    CacheFrontend<String, String> frontend = ClientSideCaching.enable(
            CacheAccessor.forMap(new ConcurrentHashMap<>()),
            connect,
            TrackingArgs.Builder.enabled());

    return frontend;
}

private StatefulRedisConnection getRedisConnect(RedisConnectionFactory redisConnectionFactory) {
    if(redisConnectionFactory instanceof LettuceConnectionFactory) {
        AbstractRedisClient absClient = ((LettuceConnectionFactory) redisConnectionFactory).getNativeClient();
        if (absClient instanceof RedisClient) {
            return ((RedisClient) absClient).connect();
        }
    }
    return null;
}
Copy the code

To create CacheFrontend, get a StatefulRedisConnection connection from the RedisConnectionFactory. Here the RedisClient#connect method creates a new connection, which distinguishes connections that use client caching from those that do not.

Combined with Guava caching

The StatefulRedisConnection class also provides the addListener method, which sets the callback method to handle messages pushed by Redis. Using this approach, we can combine Guava’s cache with Redis client cache

@Bean public LoadingCache<String, String> redisGuavaCache(RedisConnectionFactory redisConnectionFactory) { // [1] StatefulRedisConnection connect = getRedisConnect(redisConnectionFactory); if (connect ! = null) { // [2] LoadingCache<String, String> redisCache = CacheBuilder.newBuilder() .initialCapacity(5) .maximumSize(100) .expireAfterWrite(5, TimeUnit.MINUTES) .build(new CacheLoader<String, String>() { public String load(String key) { String val = (String)connect.sync().get(key); return val == null ? "" : val; }}); // [3] connect.sync().clientTracking(TrackingArgs.Builder.enabled()); // [4] connect.addListener(message -> { if (message.getType().equals("invalidate")) { List<Object> content = message.getContent(StringCodec.UTF8::decodeKey); List<String> keys = (List<String>) content.get(1); keys.forEach(key -> { redisCache.invalidate(key); }); }}); return redisCache; } return null; }Copy the code
  1. Get the Redis connection.
  2. Create a Guava cache class LoadingCache that queries Redis if the data is not present.
  3. Enable Redis client cache.
  4. Add a callback function to clear the Guava cache if an invalid message is received from Redis.

Redis Cluster mode

The applications mentioned above must be in Redis single-server mode (or master-slave, Sentinel mode), unfortunately, there is currently no support for client caching under Redis Cluster (version 6.1.5). A brief look at the source code shows the following causes: In Cluster mode, the Redis command needs to be redirected to the storage node based on the command key. The no key for the “CLIENT TRACKING” command, Lettuce didn’t send it to all the nodes in the Cluster, but it is sent to a fixed nodes by default (see ClusterDistributionChannelWriter class), So by StatefulRedisClusterConnection call RedisAdvancedClusterCommands. ClientTracking methods do not open the Redis service Tracking mechanism. In fact, this can also be modified, some time to study again.

Issues that need attention

Is there really no problem with the oracle client cache in standalone mode?

Considering the design of Redis Tracking, there are two points of concern with Redis client caching:

  1. After client cache is enabled, the Redis connection cannot be disconnected.

If a Redis connection is broken and the client automatically reconnects, Tracking is not enabled for the new connection, and the key queried by the connection will not receive a failure message, with serious consequences. The same connection must be used to enable Tracking and the same connection must be used to query the cache key. You cannot use A connection to enable Tracking and A connection to query the cache key (so clients cannot use connection pooling).

The Redis server can set the timeout configuration to automatically exceed connections that do not send requests under this configuration. And there is an automatic reconnection mechanism, the reconnection will not receive the invalid message. There are two solutions: (1) Implement the database heartbeat mechanism and send the PING command periodically to maintain the connection. In addition, the ReconnectionHandler class is also in use. In addition, the ReconnectionHandler class is also in use. In addition, the ReconnectionHandler class is in use. If Tracking is enabled for the connection originally, the Tracking mechanism needs to be enabled automatically after the connection is reconnected. Note that if you are using non-broadcast mode, you need to clear the old connection cache because the connection has changed and the Redis server will not send the old connection invalid message to the new connection.

  1. Connections with caching enabled should be distinguished from those without caching enabled.

This is relatively simple; in the previous example, we used the RedisClient#connect method to create a new connection, dedicated to the client cache.

Client caching is a powerful feature that needs to be used well. Unfortunately, currently there is no perfect Java client support, this book said some of my plans and ideas, welcome to discuss. I will keep an eye on continuing the Lettuce update and update this article if there is a decent Redis client caching support.

Redis Tracking on the detailed use and implementation of the principle, I in the new book “Redis core principles and practice” to do a detailed analysis, the last article, introduce the book: this book through in-depth analysis of Redis 6.0 source code, summarized the design and implementation of the core functions of Redis. Through reading this book, readers can deeply understand the internal mechanism and the latest features of Redis, and learn Redis related data structure and algorithm, Unix programming, storage system design, distributed system architecture and a series of knowledge. With the consent of the editor of the book, I will continue to publish some chapters in the book on binecy, as a preview of the book, you are welcome to check it out, thank you.

Jingdong link douban link