As mentioned earlier, Redis takes great advantage of the non-blocking, multiplexing I/O model in its main processing single thread, although there are some circumstances where Redis spawns threads or child processes to perform certain tasks.
Redis contains a simple but powerful asynchronous event library called ae to wrap different operating system's polling facilities, such as epoll, kqueue, select, and so on.
So what's a polling facility of an operating system? Let's take a real-life scenario to illustrate it. Imagine you have ordered five dishes in a restaurant. You have to fetch your dishes by yourself at a waiting window, and you want to get them as soon as possible once the dishes get done because you are hungry. You may have three strategies for this scenario:
- You walk to the waiting window all by yourself from time to time in a short period to check whether each dish in the order list is ready.
- You hire five people. Each person walks to the waiting window for you to check whether one of the dishes you ordered is ready.
- You just sit at the table, waiting for the notification. The restaurant provides a dish-ready notification service for free which means that the waiter will tell you which dish is ready, once a dish gets done. When you get the notification, you fetch it by yourself.
Considering the time and efforts it takes, the third option is the best one, obviously.
The polling facility of an operating system works in a similarly way as the third option does. For the simplicity of this section, we only take the epoll API in Linux as an example. First, you can call epoll_create to tell the kernel that you would like to use epoll. Then, you call epoll_ctl to tell the kernel the file descriptors (FD) and what type of event you're interested in when an update occurs. After that, epoll_wait gets called to wait for certain events of the FDs you set in epoll_ctl. The kernel will send a notification to you when the FDs get updated. The only thing you have to do is to create handlers for certain events.
The whole multiplexing process is shown as follows:
I/O multiplexing model
The ae library in Redis basically follows the preceding procedure to process the requests. In the echo-server example, we create an event loop by calling aeCreateEventLoop firstly. Then a TCP server is built via anetTcpServer for network binding and listening. We call anetNonBlock to set the non-block I/O action for this socket FD. After that, we specify the acceptance event handler acceptProc for the socket FD using the event loop created in aeCreateEventLoop. Once a TCP connection is established, the server will trigger the action in acceptProc. In acceptProc, we use anetTcpAccept to accept the connection request and register readable events of the socket FD for readProc. Then readProc gets called, in which we read the data sent to the server and register the writable event of the socket FD. The event loop then receives the writable event to fire the writeProc to send back the data to the socket client.
Redis works in pretty much the same way as this echo-server does. In the main function of server.c, aeCreateEventLoop, anetTcpServer, and anetNonBlock get called in the initServer method to initialize the server:
The acceptance handler is also set in the initServer method, as the following screenshot shows:
Once the server is initialized, the aeMain method gets called:
In the aeMain method, asProcessEvents is called to process the events continuously: