Understanding Redis I/O and Thread Models: From Blocking to Multi‑Reactor Designs
This article explains Redis's high‑performance I/O architecture by tracing the evolution from blocking, non‑blocking, and multiplexed network models to various Reactor‑based thread designs, illustrating each model with Java pseudo‑code and diagrams to clarify why Redis adopts a single‑threaded Reactor with optional multi‑threaded I/O extensions.
Redis is a high‑performance data store whose performance heavily depends on its I/O and threading architecture. This article reviews the evolution of network I/O models—blocking, non‑blocking, and I/O multiplexing—and connects them to Redis's thread design.
1. Overview
Redis relies on efficient I/O handling to serve high‑concurrency workloads. Understanding its I/O model and thread model is essential for system design.
2. Network I/O Model History
We first discuss blocking I/O (both single‑thread and multi‑thread), non‑blocking I/O, and I/O multiplexing (select/epoll). Code examples demonstrate each approach.
2.1 Blocking I/O
Single‑thread blocking example:
import java.net.Socket;
public class BioTest {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8081);
while (true) {
Socket socket = server.accept();
System.out.println("accept port:" + socket.getPort());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String inData = null;
try {
while ((inData = in.readLine()) != null) {
System.out.println("client port:" + socket.getPort());
System.out.println("input data:" + inData);
if ("close".equals(inData)) {
socket.close();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try { socket.close(); } catch (IOException e) { e.printStackTrace(); }
}
}
}
}Running two clients shows the server processes only one connection at a time because the accept/read calls block the main thread.
Multi‑thread blocking example (BIO multi‑thread):
package net.io.bio;
import java.io.*;
import java.net.*;
public class BioTest {
public static void main(String[] args) throws IOException {
final ServerSocket server = new ServerSocket(8081);
while (true) {
new Thread(() -> {
Socket socket = null;
try {
socket = server.accept();
System.out.println("accept port:" + socket.getPort());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String inData = null;
while ((inData = in.readLine()) != null) {
System.out.println("client port:" + socket.getPort());
System.out.println("input data:" + inData);
if ("close".equals(inData)) socket.close();
}
} catch (IOException e) { e.printStackTrace(); }
}).start();
}
}
}This resolves the single‑connection limitation but creates a thread per connection, which can exhaust resources under heavy load.
2.2 Non‑Blocking I/O
Non‑blocking code (pseudo‑code) demonstrates polling a list of sockets and processing data when ready:
package net.io.bio;
import java.io.*;
import java.net.*;
import java.util.*;
import org.apache.commons.collections4.CollectionUtils;
public class NioTest {
public static void main(String[] args) throws IOException {
final ServerSocket server = new ServerSocket(8082);
server.setSoTimeout(1000);
List
sockets = new ArrayList<>();
while (true) {
try {
Socket socket = server.accept();
socket.setSoTimeout(500);
sockets.add(socket);
System.out.println("accept client port:" + socket.getPort());
} catch (SocketTimeoutException e) {
System.out.println("accept timeout");
}
if (CollectionUtils.isNotEmpty(sockets)) {
for (Socket socketTemp : sockets) {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(socketTemp.getInputStream()));
String inData;
while ((inData = in.readLine()) != null) {
System.out.println("input data client port:" + socketTemp.getPort());
System.out.println("input data client port:" + socketTemp.getPort() + " data:" + inData);
if ("close".equals(inData)) socketTemp.close();
}
} catch (SocketTimeoutException e) {
System.out.println("input client loop" + socketTemp.getPort());
}
}
}
}
}
}While this avoids blocking, it incurs CPU spin‑waiting and frequent system calls.
2.3 I/O Multiplexing (Reactor)
The multiplexing model uses a selector to wait for events and dispatches them without dedicated polling threads.
import java.net.InetSocketAddress;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.*;
public class NioServer {
private static Charset charset = Charset.forName("UTF-8");
public static void main(String[] args) {
try {
Selector selector = Selector.open();
ServerSocketChannel channel = ServerSocketChannel.open();
channel.bind(new InetSocketAddress(8083));
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int select = selector.select();
if (select == 0) { System.out.println("select loop"); continue; }
System.out.println("os data ok");
Set
selectionKeys = selector.selectedKeys();
Iterator
iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
key.interestOps(SelectionKey.OP_ACCEPT);
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
StringBuilder content = new StringBuilder();
while (client.read(buffer) > 0) {
buffer.flip();
content.append(charset.decode(buffer));
}
System.out.println("client port:" + client.getRemoteAddress() + ",input data: " + content);
buffer.clear();
}
iterator.remove();
}
}
} catch (Exception e) { e.printStackTrace(); }
}
}This model reduces thread count and scales better under high concurrency.
3. NIO Thread Model Explanation
Redis uses the I/O multiplexing (Reactor) model. Various Reactor variants are described:
Single‑Reactor‑Single‑Thread: all events handled by one thread (simple but limited by CPU cores).
Single‑Reactor‑Multi‑Thread: business logic off‑loaded to a thread pool.
Multi‑Reactor‑Multi‑Thread: separate sub‑reactors handle socket read/write, main reactor handles accept.
Each variant’s advantages and drawbacks are discussed.
4. Redis Thread Model
Redis adopts a single‑threaded Reactor for event demultiplexing, with the following flow:
File (socket) events and timer events are distinguished.
Connection events are processed by the main thread, creating client objects and registering readable events.
Command execution (e.g., SET) is performed by the main thread after the read event is dispatched.
Responses are written back via the same thread.
The model offers high performance because command execution is memory‑bound and fast, but I/O latency can become a bottleneck as traffic grows.
Redis 6+ introduces a multi‑threaded I/O path while keeping command execution single‑threaded, effectively separating I/O from business logic similar to the Multi‑Reactor approach.
5. Summary
Understanding Redis's design choices—why it uses a single‑threaded Reactor, when multi‑threaded I/O is added, and how different Reactor patterns work—provides valuable guidance for building high‑performance server architectures.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.