Redis clients communicate with servers using TCP and pipelining technology has long been supported. In some high-concurrency scenarios, network overhead becomes a bottleneck for Redis speed, so pipes are needed to break through.
Before introducing pipelines, consider the execution steps of a single command:
- The client sends the command to the server, and then blocks the client, waiting for the result to be read from the socket
- The server processes the command and returns the result to the client
According to this description, the execution time of each command = client send time + server processing and return time + one network return time
The round-trip time of one network is not fixed, and it depends on many factors, such as the number of hops between the client and the server, whether the network is congested, and so on. But the magnitude of this time is also the largest, meaning that the length of time a command takes to complete depends largely on the network overhead. If our server can handle 100,000 requests per second and the network overhead is 250 milliseconds, we can actually handle only four requests per second. The most violent optimization method is to have the client and server on the same physical machine, which reduces the network overhead to less than 1ms. But in a real production environment we don’t do that. And even with this approach, when requests are very frequent, this is still a long time compared to the server processing time.
Redis Pipelining
To address this problem, Redis has long supported pipeline technology. That is to say, the client can send multiple commands at a time, and instead of waiting for the return value of each command, it can read the return result together at the end. In this way, the speed can be significantly improved with only one network overhead. Piping technology is already very mature and widely used, for example, the POP3 protocol significantly improves the speed of downloading mail from the server due to its support for piping.
In Redis, if the client pipes multiple commands, the server will put multiple commands into a queue. This operation consumes a certain amount of memory, so the number of commands in the pipeline is not better (too many commands will deplete memory), but should have a reasonable value.
In-depth understanding of Redis interaction processes
Pipes are not just a way to delay network overhead, they actually increase the total number of operations per second on the Redis server. Before explaining why, you need to take a closer look at the Redis command processing.
A complete interaction process is as follows:
- Client process call
write()
Writes messages to the send buffer assigned to the Socket by the operating system kernel - The operating system writes the contents of the Send buffer to the network adapter, which then sends the contents to the network adapter on the server through the gateway route
- The server nic writes the received message to the RECV buffer assigned to the Socket by the operating system
- Server process call
read()
The message is read and processed - Called after processing is complete
write()
Write the result to the server-side send buffer - The server operating system writes the contents of the send buffer to the network adapter and sends the contents to the client
- The client operating system reads the network adapter content into the RECV buffer
- Client process call
read()
Read the message from the RECV buffer and return it
Now let’s break down the command execution time further:
Command execution time = time for the client to invoke write and write the nic + time for a network overhead + time for the service to read the NIC and invoke read + time for the server to process data + time for the server to invoke write and write the NIC + time for the client to read and invoke the NIC
Besides the network overhead, the most time-consuming system calls to write() and read() require the operating system to switch from user mode to kernel mode, and the context switch involved can be a waste of time.
With pipes, multiple commands make only one read() and wrtie() system call, so using pipes increases the speed at which the Redis server processes commands. As the number of commands in pipes increases linearly, the number of requests per second the server processes approaches 10 times that without pipes.
And comparison of Scripting
For most application scenarios of pipes, using Redis scripts (Redis2.6 and later) results in better server-side performance. The biggest benefit of using scripts is that you can read and write data with minimal latency.
It is also possible to use EVAL and EVALSHA commands in pipes. Redis therefore provides the SCRIPT LOAD command to support this situation.
Seeing is believing
Seeing is believing. Here is a comparison of the speed difference between using pipes and not using pipes.
public class JedisDemo {
private static int COMMAND_NUM = 1000;
private static String REDIS_HOST = "Redis server IP";
public static void main(String[] args) {
Jedis jedis = new Jedis(REDIS_HOST, 6379);
withoutPipeline(jedis);
withPipeline(jedis);
}
private static void withoutPipeline(Jedis jedis) {
Long start = System.currentTimeMillis();
for (int i = 0; i < COMMAND_NUM; i++) {
jedis.set("no_pipe_" + String.valueOf(i), String.valueOf(i), SetParams.setParams().ex(60));
}
long end = System.currentTimeMillis();
long cost = end - start;
System.out.println("withoutPipeline cost : " + cost + " ms");
}
private static void withPipeline(Jedis jedis) {
Pipeline pipe = jedis.pipelined();
long start_pipe = System.currentTimeMillis();
for (int i = 0; i < COMMAND_NUM; i++) {
pipe.set("pipe_" + String.valueOf(i), String.valueOf(i), SetParams.setParams().ex(60));
}
pipe.sync(); // Get all the responses
long end_pipe = System.currentTimeMillis();
long cost_pipe = end_pipe - start_pipe;
System.out.println("withPipeline cost : " + cost_pipe + " ms"); }}Copy the code
The results were in line with our expectations:
withoutPipeline cost : 11791 ms
withPipeline cost : 55 ms
Copy the code
conclusion
- Using pipes can significantly speed up command processing in Redis by packaging multiple commands with only one network overhead, one on the server and one on the client
read()
andwrite()
System call to save time. - The number of commands in the pipeline should be appropriate, not more is better.
- After Redis2.6, scripts perform better than pipes in most scenarios.
extension
As mentioned earlier, to address the latency associated with network overhead, you can put the client and server on a single physical machine. But sometimes this is still slow when you use benchmark to do pressure measurements.
In this case, the client and server are actually on the same physical machine, all operations are in memory, there is no network latency, which should be very fast. Why can appear above the situation?
In fact, this is caused by kernel scheduling. For example, the Benchmark runtime reads the result returned by the server and writes a new command. This command is in the loopback interface’s send buffer. To execute this command, the kernel needs to wake up the Redis server process. So in some cases, local interfaces can also experience latency similar to network latency. In fact, the kernel is so busy that the Redis server process has not been scheduled.
reference
Redis official documentation
Redis source
Redis Deep Adventure: Core Principles and Practical Applications