lettuce study notes

Article Directory

Lettuce is a Netty-based Redis client that can process all data using fewer unified management IO threads. Lettuce provides synchronous, asynchronous and reactive commands.
lettuce documentation: https://lettuce.io/core/release/reference/

Lettuce read and write command flow

Initialize connection information, netty parameters, retry mechanism, etc. through RedisClient

Get the connection StatefulRedisConnectionImpl through RedisClient.connect

In the process of creating the connection StatefulRedisConnectionImpl, important members such as BootStrap, Channel, and DefaultEndpoint are initialized. StatefulRedisConnectionImpl is bound to a Channel, and Channel is also bound to an IO thread (Bootstrap#connect).

Obtain a synchronous/asynchronous/responsive connection through StatefulRedisConnectionImpl, and then execute the Redis command.

The command is first encapsulated into an AsyncCommand , and then the command is sent through the DefaultEndpoint . The Channel is called internally by the DefaultEndpoint to send the command, and it is finally sent asynchronously by the Netty IO thread. At this time, the user thread can already obtain an AsyncCommand, but there is no response data.

The response of the command is first received by the Netty IO thread, and then forwarded to the corresponding channel, and then the corresponding AsyncCommand is retrieved from the CommandHandler, and the response is encapsulated to the output. At this time, the AsyncCommand obtained by the user thread can get the response.

Insert picture description here

Watchdog reconnection mechanism

Orderliness when the connection is normal

Lettuce operates on commands through CommandHandler. Every time CommandHandler sends a command to the server, it pushes a command to the bottom of the stack . TCP is in order , and Redis processes commands in a single thread , so it responds from the server. As a result, it can correspond to the command to go to the stack and then the CommandHandler only needs to pop out the top command to correspond.

Disorder when the connection is abnormal

But when there is a connection problem, if the channel is still the same, it is no longer in order. For example, when two commands are sent locally, the connection is disconnected when redis responds to the first command, and the connection is restored before the second command responds, which will cause the client to receive only the second command and will communicate with the first command. Commands are matched, which is out of order.

Watchdog reconnect

When the channel is unavailable, clear the stack and close the channel . Re-create a channel through io.lettuce.core.protocol.ReconnectionHandler#reconnect. Since the BootStrap of the Channel is the same, there is no difference between the Pipleline and the function is the same, because the DefaultEndpoint is the same object.

Watchdog configuration

io.lettuce.core.ClientOptions#autoReconnect

Precautions

PUBSUB

The PubSub command provided by Lettuce is implemented through a listener and will not block the current thread (Jedis' sub is monitored through an infinite loop). However, once the sub command has been executed, other commands other than the subscription can no longer be executed (cancelled), so you need to pay attention to the use of PUBSUB connections.

Reconnect OOM

OOM : Reconnection will cause the commandBuffer or disconnectedBuffer of DefaultEndpoint to continuously increase! It can be solved by changing io.lettuce.core.ClientOptions# disconnectedBehavior to REJECT_COMMANDS.

Because the size of the two buffers is equal to clientOptions.getRequestQueueSize(), and the default configuration of requestQueueSize is Integer.MAX_VALUE, the
following shows the process of sending a single command to explain the cause of the OOM problem.

public <K, V> Collection<RedisCommand<K, V, ?>> write(Collection<? extends RedisCommand<K, V, ?>> commands) {

        LettuceAssert.notNull(commands, "Commands must not be null");

        try {
            sharedLock.incrementWriters();
			//1. 如果开启了看门狗自动重连,但是连接不可用,且没有设置拒绝策略,这里的判断会通过,导致跳到3或者4,可能触发OOM
            validateWrite(commands.size());

            if (autoFlushCommands) {

                if (isConnected()) {
                    //2.直接发送命令
                    writeToChannelAndFlush(commands);
                } else {
                    //3.暂存命令到disconnectBuffer
                    writeToDisconnectedBuffer(commands);
                }

            } else {
                //4.写入commandBuffer,等到一次flush
                writeToBuffer(commands);
            }
        } finally {
            sharedLock.decrementWriters();
            if (debugEnabled) {
                logger.debug("{} write() done", logPrefix());
            }
        }

        return (Collection<RedisCommand<K, V, ?>>) commands;
    }

Assuming that the watchdog mechanism is turned off , the connection is completely unavailable, and no commands can be sent to the server .

Don't create too many connections

If the StatefulRedisConnectionImpl is frequently created, it will cause the frequent creation of BootStrap, Channel, IO Thread, etc., which will lead to waste of resources. Because the IO thread will be bound to a memory allocation pool, there will be a memory cache, if there is no fixed IO thread group, this will easily lead to OOM.

Remember to close RedisClient after use

Correctly release all resources under RedisClient through shutdown to avoid resource leakage.

Packet loss, timeout, will not directly remove the command from the stack

If the server is losing packets, when no data is returned to the client during the timeout period, the client will directly record the result as CancelException. Since the data stream between Redis and the client is transmitted, when the network is abnormal, this data stream is empty, so it is parsed as null, and the corresponding command result is also null, and the command should not be removed directly from the stack

The server goes offline and the stack will be cleared

It is equivalent to the server has closed the sokect

If the client happens to be processing in the handler, it will trigger the exceptionCaught method to call the handler, which may trigger the connectionWatchdog to reconnect;

If the client has no other operations, the client will eventually invoke channelInactive [the defaultEndPoint channel will be set to null. If the reconnection mechanism of lettuce is not used, the channel must be established by itself, otherwise the command will not be sent, and the io will be directly. lettuce.core.protocol.DefaultEndpoint#validateWrite throws Currently not connected. Commands are rejected.] and channelUnregistered method, then another command will be stored in the disconnectedBuffer of DefaultEndpoint.

CancelException

When the DefaultEndpoint is closed, for example, the outer StatefulConnection is closed, it will cause the endpoint to be closed, and both the incoming command and the previously stored command will be thrown CancelException