Redis 6.0.0 GA is the largest Redis release in its history. Including Client Side Caching, ACL, Threaded I/O, Redis Cluster Proxy and many other updates.

Today we will talk about the necessity of client side cache, specific use, principle analysis and implementation.

Why do YOU need client caching?

As we all know, the main purpose of using Redis for data caching is to reduce the access to MySQL and other databases and provide faster access speed. After all, as mentioned in Redis in Action, the performance of Redis is roughly 10 ~ 100 times that of ordinary relational databases.

Therefore, as shown in the figure below, Redis is used to store hot data, Redis misses, and then accesses the database, which can meet the performance requirements in most cases.

However, Redis also has a performance ceiling, and accessing Redis inevitably has some network I/O and serialization deserialization costs. As a result, process caches are often introduced to store the hottest data locally, further speeding up access.

As shown in the figure above, process caches such as Guava Cache are used as level 1 caches and Redis as level 2 caches:

  1. Guava Cache is used to query data, and returns a hit.
  2. If no match is found in the Guava Cache, query the data in Redis. If a match is found, the data is returned and set in the Guava Cache.
  3. If Redis is not hit, you have to go to MySQL and set data to Redis and Guava Cache in sequence.

When only Redis distributed cache is used, when data is updated, the application program can invalidate the corresponding cache in Redis directly after updating the data in MySQL to maintain data consistency.

The data consistency of in-process cache is more challenging than that of distributed cache. When data is updated, how do you tell other processes to update their caches as well?

If we follow the distributed cache idea, we can set very short cache expiration times, so that we do not have to implement complex notification mechanism.

However, data in different processes will still face the problem of inconsistency, and different processes cache invalidation time is not uniform, the same request to different processes, may appear repeated illusory situation.

Ben gives a solution in RedisConf18 (video and PPT link at the end of the article), which can notify other process caches to remove this cache via Redis Pub/Sub. If Redis fails or the subscription mechanism is unreliable, it can still be covered by timeout.

Antirez(the author of Redis) listened to Ben’s proposal and decided to support client caching in Redis Server, because it is better to handle these problems with Server participation.

Function introduction and demonstration

Use Docker to install Redis 6.0.1, and Telnet to briefly demonstrate Redis 6.0 client caching capabilities. All related functions are shown in the following figure, which are normal mode and broadcast mode using RESP3 protocol version, and forwarding mode using RESP2 protocol version. Let’s start with the general pattern.

Normal mode

Run redis-cli to set the cache value test=111 and Telnet to Redis. Then send Hello. 3 Enable RESP3.

[root@VM_0_3_centos ~]# telnet 127.0.0.1 6379
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'Hello 3 // Telnet output format is standardized as follows, otherwise there are too many newlines and the format is RESP3, so you don't need to know the format. > HELLO 3 1# "server" => "redis"
2# "version" = > "the 6.0.1"
3# "proto" => (integer) 3
4# "id" => (integer) 10
5# "mode" => "standalone"
6# "role" => "master"
7# "modules" => (empty array)
Copy the code

Note that the Redis server will only track the key values of read-only commands that the client obtains during the lifetime of a connection. Track mode is not enabled on the Redis client by default. You need to use the command to enable track mode, and then you must obtain the value of test first, so that the Redis server will record it.

client tracking on
+OK
get test
$3
111
Copy the code

The Redis server notifies these clients when keys are modified or removed due to expire time and memory ceiling maxMemory policies. Here we simply update the value of test, and Telnet receives the following notification

>2 // The PUSH type in RESP3, marked by the > symbol$10
invalidate
*1
$4
test
Copy the code

If you update the test value again, Telnet will not receive the invalidate message this time. Re-tracking the corresponding key unless Telnet performs another GET operation.

That is to say, the client track information recorded by the Redis server only takes effect once and will be deleted after sending the invalid message. Only the next time the client executes the read-only command to be tracked again will the next message notification be made.

The command to cancel tracking is shown below.

client tracking off
+OK
Copy the code

Broadcasting mode

Redis also provides a broadcast mode (BCAST), which is another client-side caching implementation. In this way, the Redis server no longer consumes too much memory to store information, but sends more invalid messages to the client.

This is the tradeoff between the server storing too much data and consuming memory, and the client receiving too many messages and consuming network bandwidth.

Client tracking on Bcast +OK // Set key to a and receive the following message. > 2$10
invalidate
*1
The $1
a
Copy the code

If you don’t want all key invalid messages to be received, you can limit the prefix of the key. The following command will focus only on key invalid messages with the prefix test. Generally speaking, the cache key of a business has a uniform prefix based on the business, so this feature is very convenient.

client tracking on bcast prefix test
Copy the code

Different from the rule that a key must be obtained once in common mode, in broadcast mode, when a key is modified or deleted, the client that meets the rule receives an invalid message and can obtain the key multiple times

In this mode, less data is stored, but CPU resources are consumed because prefix rules need to be matched. Therefore, do not use long prefixes.

Forwarding mode

Redis provides the Redirect mode for compatibility with RESP2 protocol. Instead of using RESP3 to support PUSH messages, Redis sends messages to another client via Pub/Sub, as shown in the following figure.

Two Telleth are required, one of which needs to subscribe to the _redis_:invalidate channel. The other Telnet then turns on the Redirect mode and rules out sending invalidation messages to the first Telnet over the subscription channel.

# telent B
client id
:368
subscribe _redis_:invalidate

# Telnet A, enable track and specify forward to B
client tracking on bcast redirect 368

# telent B receives a message from the __redis__:invalidate channel
message
$20
__redis__:invalidate
*1
The $1
a
Copy the code

You can see that the forwarding pattern is similar to the update mechanism in the multi-level cache mentioned at the beginning of the article, except that in that scenario, the business system sends message notifications after changing the key, whereas in this case, the Redis server sends message notifications instead of the business system.

OPTIN and OPTOUT options

Tracking can optionally be turned on with OPTIN. Tracking will only be performed for the next read-only key after you send client Caching yes. Otherwise, other read-only keys will not be tracked.

Client tracking on optin client caching yes get a get B // If you change the values of A and B, only the invalid message of A is received >2$10
invalidate
*1
The $1
a
Copy the code

The OPTOUT parameter, by contrast, allows you to OPTOUT of tracking. After sending client Caching off, the key of the next read-only command will not be tracked. All other read-only commands will be tracked.

OPTIN and OPTOUT are for non-Bcast modes, where a key is tracked only after a read-only command is sent. In BCAST mode, no matter whether you send a read-only command for a key or not, only Redis changes the key, it will send the corresponding key expiration message (prefix matching).

NOLOOP options

By default, an invalid message is sent to all Redis clients that need it, but there are cases where the client that triggered the invalid message, that is, the client that updated the key, does not need to receive the message.

This can be avoided by setting NOLOOP, which works in both normal and broadcast mode.

Maximum tracking limit tracking_table_max_keys

Tracking keys and client data need to be stored in normal mode. Therefore, when a 10K client uses this mode to process millions of keys, it consumes a large amount of memory. So Redis introduces the tracking_table_max_keys configuration, which defaults to none and is unlimited.

When tracking a new key is tracked, if the number of tracking keys is greater than tracking_table_max_keys, the tracking key will be randomly deleted and an invalid message will be sent to the corresponding client.

Principle and source code implementation

Principle of common mode

We also explain the principle of common mode. The Redis server uses TrackingTable to store the client data of common mode, and its data type is radix Tree.

Cardinality tree is a multi-fork search tree for searching sparse long integer data. It can quickly and save space to complete the mapping. It is generally used to solve Hash conflicts and design problems of Hash table size.

Redis uses it to store the mapping between the pointer to the key and the client ID. Because the pointer to the key object is the memory address, which is long integer data. The client cache operation is to add, delete, alter, and query this data:

  • Redis is called when the track client obtains a key valueenableTrackingMethod Use cardinality tree to record the mapping between the key and clientId.
  • Redis is called when a key is modified or deletedtrackingInvalidateKeyMethod looks up all corresponding client ids from the TrackingTable based on the key and then callssendTrackingMessageMethod sends a failure message to these clients (checking whether the CLIENT_TRACKING flag is enabled and NOLOOP is enabled).
  • After sending the invalid message, the mapping is removed from the TrackingTable based on the pointer value of the key.
  • After the track function is disabled on the client, Redis only deletes the CLIENT_TRACKING flag of the client in the lazy deletion mode because a large number of operations are needed to delete the track function.

Principle of Broadcast Mode

Broadcast mode is similar to normal mode. Redis also uses PrefixTable to store the client data in broadcast mode. It stores the ** prefix string pointer and the mapping between the key to be notified and the client ID. The biggest difference between this mode and broadcast mode is when the invalid message is actually sent:

  • When the client is in broadcast mode, thePrefixTableThe client ID is added to the client list corresponding to the prefix of.
  • Redis is called when a key is modified or deletedtrackingInvalidateKeyMethod,trackingInvalidateKeyMethod if foundPrefixTableIs not nulltrackingRememberKeyToBroadcastIf the key matches the prefix rule, the key is recordedPrefixTableCorresponding position.
  • Called in the beforeSleep function of Redis’s event processing cycletrackingBroadcastInvalidationMessagesFunction to actually send the message.

Handle the maximum tracking upper limit

TrackingLimitUsedSlots Redis calls trackingLimitUsedSlots after each execution (processCommand method) to determine whether a cleanup is needed:

  • Check whether the number of keys in TrackingTable is greater than tracking_table_max_keys;
  • Within a certain period of time (not too long, blocking the main process), randomly select a key from the TrackingTable and delete it until the number is less than or time runs out.

Specific source

TrackingInvalidateKey and sendTrackingMessage: trackingInvalidateKey; trackingMessage: trackingMessage; trackingInvalidateKey; Related functions such as broadcast mode and handling the maximum tracking cap are similar.

void trackingInvalidateKey(client *c, robj *keyobj) {
    if (TrackingTable == NULL) return;
    sds sdskey = keyobj->ptr;
    // omit, if the record cardinality tree of the broadcast mode is not empty, the broadcast mode is processed first
    // 1 go to the TrackingTable according to the key pointer
    rax *ids = raxFind(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey));
    if (ids == raxNotFound) return;
    // 2 use iterators to traverseraxIterator ri; raxStart(&ri,ids); raxSeek(&ri,"^".NULL.0);
    while(raxNext(&ri)) {
        // 3 Find the client instance based on clientId
        client *target = lookupClientByID(id);
        // 4 Skip if track or broadcast mode is not enabled.
        if (target == NULL| |! (target->flags & CLIENT_TRACKING)|| target->flags & CLIENT_TRACKING_BCAST) {continue;  }
        // 5 If NOLOOP is enabled and the client causes the key to change, skip.
        if (target->flags & CLIENT_TRACKING_NOLOOP &&
            target == c)
        {   continue;  }
        // 6 Send an invalid message
        sendTrackingMessage(target,sdskey,sdslen(sdskey),0);
    }
    // 7 Reduce statistics and delete corresponding records based on the SDSkey
    TrackingTableTotalItems -= raxSize(ids);
    raxFree(ids);
    raxRemove(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey),NULL);
}
Copy the code

The trackingInvalidateKey method does seven things:

  • Follow the key pointer to TrackingTable to find the list of client ids;
  • Iterators are used to traverse the list.
  • Find client instance based on clientId;
  • If track or broadcast mode is disabled, the client instance is skipped.
  • If the client instance has NOLOOP enabled and is the client that causes the key to change, skip.
  • Call the sendTrackingMessage method to send an invalid message;
  • Reduce data statistics and delete corresponding records based on the SDSkey

The sendTrackingMessage function that actually sends the message does six things:

  • If client_tracking_redirection is not empty, forwarding mode is enabled.
  • Find the forwarding client instance;
  • If the forwarding client is shut down, the original client must be notified.
  • If the client uses RESP3, it sends a PUSH message.
  • If it’s forward mode, go to TrackingChannelName_redis_:invalidateHeader information for sending invalid messages in the channel;
  • Send key and other information.
void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
    int using_redirection = 0;
    // 1 If client_tracking_redirection is not empty, the forwarding mode is enabled
    if (c->client_tracking_redirection) {
        // 2 Find the forwarding client instance
        client *redir = lookupClientByID(c->client_tracking_redirection);
        if(! redir) {// 3 If the forwarding client is shut down, the original client must be notified.return;
        }
        c = redir;
        using_redirection = 1;
    }

    if (c->resp > 2) {
        // 4 If RESP3, PUSH
        addReplyPushLen(c,2);
        addReplyBulkCBuffer(c,"invalidate".10);
    } else if (using_redirection && c->flags & CLIENT_PUBSUB) {
        // 5 Forwarding mode: sends messages to the TrackingChannelName channel
        addReplyPubsubMessage(c,TrackingChannelName,NULL);
    } else {
        return;
    }
    // 6 send key and other information, and the above operation 4 and 5 together.
    addReplyProto(c,keyname,keylen);
}
Copy the code

Afterword.

I believe all of you are a little tired, but please give us a lot of likes and comments. We will learn more about other Redis 6.0.0 features, so stay tuned.

Personal blog address, welcome to view

reference

  • Redis. IO/switchable viewer/clie…
  • Tech.meituan.com/2017/03/17/…
  • Cloud.tencent.com/developer/a…
  • Juejin. Cn/post / 684490…
  • www.kawabangga.com/posts/3590
  • PPT www.slideshare.net/RedisLabs/r…
  • Video www.bilibili.com/video/BV1qe…