Redis is a high-performance, in-memory key-value database. According to official test reports, it can support around 100,000 QPS (queries per second) on a single machine. However, Redis uses a single-threaded architecture in its design.
Why does Redis still have such high performance with a single-threaded design? Wouldn't it be better to use multiple threads for concurrent request processing?
In this article, let's explore why Redis has a single-threaded architecture and still maintains its speed. The focus is on the following four aspects:
- Data storage in memory
- Efficient data structures
- Single-threaded architecture
- Non-blocking I/O
Let's analyze each one in detail.
Data storage in memory
Redis is completely based on memory, with data stored in memory. The vast majority of requests are pure memory operations, which are extremely fast. Compared with traditional disk file data storage, Redis avoids the overhead of reading data from disk into memory through disk I/O.
Efficient data structures
Redis has a total of 5 data types: String, List, Hash, Set, and SortedSet.
Different data types use one or more data structures at the bottom to support them, with the aim of achieving faster speeds.
Single-threaded architecture
Using a single thread saves a lot of time on context switching and CPU consumption, there are no race conditions, no need to consider various locking problems, and no locking and unlocking operations that could cause performance overhead due to deadlocks. Additionally, it allows the use of various "thread-unsafe" commands, such as Lpush
.
Note that when we emphasize single thread, we are referring to using one thread to handle network I/O and key-value pair read and write (file event dispatcher). In other words, one thread handles all network requests, but Redis's other functions, such as persistence, asynchronous deletion, and cluster data synchronization, are actually executed by additional threads.
So why use single thread? The official answer is that because the CPU is not Redis's bottleneck, it is most likely machine memory or network bandwidth. Since a single thread is easy to implement and the CPU will not become a bottleneck, it makes sense to adopt a single-threaded solution.
Although a multi-threaded architecture allows an application to process tasks concurrently through context switching, it provides only a slight performance boost for Redis because most threads will eventually be blocked by network I/O.
It is also important to note that because Redis uses a single thread, if a command takes too long to execute (such as the hgetall
command), it can cause blocking. Redis is a memory database designed for fast execution, so it is important to be cautious when using commands like lrange
, smembers
, hgetall
, and so on.
Non-blocking I/O
Using a thread model based on network I/O multiplexing (non-blocking I/O) allows for concurrent connections to be handled and helps alleviate the problem of slow network I/O speeds.
The multiplexing I/O model leverages the ability of select, poll, and epoll to simultaneously monitor I/O events of multiple streams. When idle, the current thread is blocked. When one or more streams have I/O events, the thread is awakened from its blocked state, and the program polls all streams (epoll only polls the streams that have generated events), then sequentially handles the ready streams. This approach avoids a large number of useless operations.
Here, "multiplexing" refers to multiple network connections, and "reuse" refers to reusing the same thread. The use of multiplexing I/O technology allows a single thread to efficiently handle multiple client network I/O connection requests (to minimize time spent on network I/O).
Redis's network event handler is based on the Reactor pattern, also known as the file event handler.
The file event handler uses I/O multiplexing to simultaneously listen to multiple sockets and associate tasks performed by the sockets with different event handlers.
The file event runs in a single-threaded manner, but by using I/O multiplexing programs to listen to multiple sockets, the file event handler implements a high-performance network communication model.
Redis handles client requests, including receiving (socket read), parsing, executing, and sending (socket write) by a single sequential main thread, which is the so-called single-threaded model.
Multiple sockets may generate different operations, and each operation corresponds to a different file event. However, the I/O multiplexing program listens to multiple sockets and queues events generated by the sockets. The event dispatcher retrieves an event from the queue each time, and passes the event to the corresponding event handler for processing.
Redis client calls to the server go through three processes: sending commands, executing commands, and returning results. During the command execution phase, since Redis is single-threaded in handling commands, each command arriving at the server is not executed immediately. All commands are entered into a queue and executed one by one. The execution order of commands sent by multiple clients is uncertain. However, it is certain that two commands will not be executed simultaneously, avoiding concurrency issues. This is Redis's basic single-threaded model.
Explanation of Redis 6.0 multi-threading
Why was multi-threading not used in Redis before version 6.0?
Redis used a single-threaded approach to achieve high maintainability. While multi-threading may perform well in certain aspects, it introduces uncertainty in the order of program execution, leading to a series of issues with concurrent reading and writing. This increases system complexity, and can potentially result in performance losses due to thread switching, locking and unlocking, and even deadlocks.
Why did Redis 6.0 introduce multi-threading?
Redis 6.0 introduced multi-threading because its bottleneck was not in memory, but in the network I/O module, which consumed CPU time. Therefore, multi-threading was introduced to handle network I/O and make full use of CPU resources, reducing the performance loss caused by network I/O blocking.
How to enable multi-threading in Redis 6.0?
By default, multi-threading is disabled in Redis, and it can be enabled in the conf file:
io-threads-do-reads yes
io-threads [number of threads]
The recommended number of threads according to the official guidelines is to set 2-3 threads for a 4-core machine, and 6 threads for an 8-core machine. The number of threads should be less than the number of machine cores and should not exceed 8 threads if possible.
Are there any thread concurrency issues in multi-threaded mode?
As shown in the diagram, a Redis request involves establishing a connection, getting the command to execute, executing the command, and finally writing the response to the socket.
In Redis' multi-threaded mode, receiving, sending, and parsing commands can be configured to be executed in multiple threads because they are the main time-consuming points we have identified. However, command execution, which involves memory operations, still runs in a single thread.
Therefore, Redis' multi-threaded part is only used to handle network data reading and writing and protocol parsing. Command execution is still executed in a single thread sequentially, so there are no concurrency safety issues.
Reference: 单线程的 Redis 为什么这么快?
????????